Поделиться через


Use shims to isolate your app for unit testing

Applies to: yesVisual Studio noVisual Studio for Mac

Note

This article applies to Visual Studio 2017. If you're looking for the latest Visual Studio documentation, see Visual Studio documentation. We recommend upgrading to the latest version of Visual Studio. Download it here

Shim types are one of two technologies that the Microsoft Fakes Framework uses to let you isolate components under test from the environment. Shims divert calls to specific methods to code that you write as part of your test. Many methods return different results dependent on external conditions, but a shim is under the control of your test and can return consistent results at every call. This makes it easier to write the tests.

Use shims to isolate your code from assemblies that are not part of your solution. To isolate components of your solution from each other, use stubs.

For an overview and "quick start" guidance, see Isolate code under test with Microsoft Fakes.

Requirements

  • Visual Studio Enterprise
  • A .NET Framework project

Example: The Y2K bug

Consider a method that throws an exception on January 1st of 2000:

// code under test
public static class Y2KChecker {
    public static void Check() {
        if (DateTime.Now == new DateTime(2000, 1, 1))
            throw new ApplicationException("y2kbug!");
    }
}

Testing this method is problematic because the program depends on DateTime.Now, a method that depends on the computer's clock, an environment-dependent, non-deterministic method. Furthermore, the DateTime.Now is a static property so a stub type can't be used here. This problem is symptomatic of the isolation issue in unit testing: programs that directly call into database APIs, communicate with web services, and so on, are hard to unit test because their logic depends on the environment.

This is where shim types should be used. Shim types provide a mechanism to detour any .NET method to a user-defined delegate. Shim types are code-generated by the Fakes generator, and they use delegates, which we call shim types, to specify the new method implementations.

The following test shows how to use the shim type, ShimDateTime, to provide a custom implementation of DateTime.Now:

//unit test code
// create a ShimsContext cleans up shims
using (ShimsContext.Create()) {
    // hook delegate to the shim method to redirect DateTime.Now
    // to return January 1st of 2000
    ShimDateTime.NowGet = () => new DateTime(2000, 1, 1);
    Y2KChecker.Check();
}

How to use shims

First, add a Fakes assembly:

  1. In Solution Explorer,

    • For an older .NET Framework Project (non-SDK style), expand your unit test project's References node.
    • If you're working in Visual Basic, select Show All Files in the Solution Explorer toolbar to see the References node.
  2. Select the assembly that contains the class definitions for which you want to create shims. For example, if you want to shim DateTime, select System.dll.

  3. On the shortcut menu, choose Add Fakes Assembly.

Use ShimsContext

When using shim types in a unit test framework, wrap the test code in a ShimsContext to control the lifetime of your shims. Otherwise, the shims would last until the AppDomain shut down. The easiest way to create a ShimsContext is by using the static Create() method as shown in the following code:

//unit test code
[Test]
public void Y2kCheckerTest() {
  using(ShimsContext.Create()) {
    ...
  } // clear all shims
}

It's critical to properly dispose each shim context. As a rule of thumb, call the ShimsContext.Create inside of a using statement to ensure proper clearing of the registered shims. For example, you might register a shim for a test method that replaces the DateTime.Now method with a delegate that always returns the first of January 2000. If you forget to clear the registered shim in the test method, the rest of the test run would always return the first of January 2000 as the DateTime.Now value. This might be surprising and confusing.

Write a test with shims

In your test code, insert a detour for the method you want to fake. For example:

[TestClass]
public class TestClass1
{
        [TestMethod]
        public void TestCurrentYear()
        {
            int fixedYear = 2000;

            using (ShimsContext.Create())
            {
              // Arrange:
                // Detour DateTime.Now to return a fixed date:
                System.Fakes.ShimDateTime.NowGet =
                () =>
                { return new DateTime(fixedYear, 1, 1); };

                // Instantiate the component under test:
                var componentUnderTest = new MyComponent();

              // Act:
                int year = componentUnderTest.GetTheCurrentYear();

              // Assert:
                // This will always be true if the component is working:
                Assert.AreEqual(fixedYear, year);
            }
        }
}
<TestClass()> _
Public Class TestClass1
    <TestMethod()> _
    Public Sub TestCurrentYear()
        Using s = Microsoft.QualityTools.Testing.Fakes.ShimsContext.Create()
            Dim fixedYear As Integer = 2000
            ' Arrange:
            ' Detour DateTime.Now to return a fixed date:
            System.Fakes.ShimDateTime.NowGet = _
                Function() As DateTime
                    Return New DateTime(fixedYear, 1, 1)
                End Function

            ' Instantiate the component under test:
            Dim componentUnderTest = New MyComponent()
            ' Act:
            Dim year As Integer = componentUnderTest.GetTheCurrentYear
            ' Assert:
            ' This will always be true if the component is working:
            Assert.AreEqual(fixedYear, year)
        End Using
    End Sub
End Class

Shim class names are made up by prefixing Fakes.Shim to the original type name.

Shims work by inserting detours into the code of the application under test. Wherever a call to the original method occurs, the Fakes system performs a detour, so that instead of calling the real method, your shim code is called.

Notice that detours are created and deleted at run time. You must always create a detour within the life of a ShimsContext. When it is disposed, any shims you created while it was active are removed. The best way to do this is inside a using statement.

You might see a build error stating that the Fakes namespace does not exist. This error sometimes appears when there are other compilation errors. Fix the other errors and it will vanish.

Shims for different kinds of methods

Shim types allow you to replace any .NET method, including static methods or non-virtual methods, with your own delegates.

Static methods

The properties to attach shims to static methods are placed in a shim type. Each property has only a setter that can be used to attach a delegate to the target method. For example, given a class MyClass with a static method MyMethod:

//code under test
public static class MyClass {
    public static int MyMethod() {
        ...
    }
}

We can attach a shim to MyMethod that always returns 5:

// unit test code
ShimMyClass.MyMethod = () => 5;

Instance methods (for all instances)

Similarly to static methods, instance methods can be shimmed for all instances. The properties to attach those shims are placed in a nested type named AllInstances to avoid confusion. For example, given a class MyClass with an instance method MyMethod:

// code under test
public class MyClass {
    public int MyMethod() {
        ...
    }
}

You can attach a shim to MyMethod that always returns 5, regardless of the instance:

// unit test code
ShimMyClass.AllInstances.MyMethod = () => 5;

The generated type structure of ShimMyClass looks like the following code:

// Fakes generated code
public class ShimMyClass : ShimBase<MyClass> {
    public static class AllInstances {
        public static Func<MyClass, int>MyMethod {
            set {
                ...
            }
        }
    }
}

Notice that Fakes passes the runtime instance as the first argument of the delegate in this case.

Instance methods (for one runtime instance)

Instance methods can also be shimmed by different delegates, based on the receiver of the call. This enables the same instance method to have different behaviors per instance of the type. The properties to set up those shims are instance methods of the shim type itself. Each instantiated shim type is also associated with a raw instance of a shimmed type.

For example, given a class MyClass with an instance method MyMethod:

// code under test
public class MyClass {
    public int MyMethod() {
        ...
    }
}

We can set up two shim types of MyMethod such that the first one always returns 5 and the second always returns 10:

// unit test code
var myClass1 = new ShimMyClass()
{
    MyMethod = () => 5
};
var myClass2 = new ShimMyClass { MyMethod = () => 10 };

The generated type structure of ShimMyClass looks like the following code:

// Fakes generated code
public class ShimMyClass : ShimBase<MyClass> {
    public Func<int> MyMethod {
        set {
            ...
        }
    }
    public MyClass Instance {
        get {
            ...
        }
    }
}

The actual shimmed type instance can be accessed through the Instance property:

// unit test code
var shim = new ShimMyClass();
var instance = shim.Instance;

The shim type also has an implicit conversion to the shimmed type, so you can usually simply use the shim type as is:

// unit test code
var shim = new ShimMyClass();
MyClass instance = shim; // implicit cast retrieves the runtime instance

Constructors

Constructors can also be shimmed in order to attach shim types to future objects. Each constructor is exposed as a static method Constructor in the shim type. For example, given a class MyClass with a constructor taking an integer:

// code under test
public class MyClass {
    public MyClass(int value) {
        this.Value = value;
    }
    ...
}

We set up the shim type of the constructor so that every future instance returns -5 when the Value getter is invoked, regardless of the value in the constructor:

// unit test code
ShimMyClass.ConstructorInt32 = (@this, value) => {
    var shim = new ShimMyClass(@this) {
        ValueGet = () => -5
    };
};

Each shim type exposes two constructors. The default constructor should be used when a fresh instance is needed, while the constructor taking a shimmed instance as argument should be used in constructor shims only:

// unit test code
public ShimMyClass() { }
public ShimMyClass(MyClass instance) : base(instance) { }

The generated type structure of ShimMyClass resembles the following code:

// Fakes generated code
public class ShimMyClass : ShimBase<MyClass>
{
    public static Action<MyClass, int> ConstructorInt32 {
        set {
            ...
        }
    }

    public ShimMyClass() { }
    public ShimMyClass(MyClass instance) : base(instance) { }
    ...
}

Base members

The shim properties of base members can be accessed by creating a shim for the base type and passing the child instance as a parameter to the constructor of the base shim class.

For example, given a class MyBase with an instance method MyMethod and a subtype MyChild:

public abstract class MyBase {
    public int MyMethod() {
        ...
    }
}

public class MyChild : MyBase {
}

We can set up a shim of MyBase by creating a new ShimMyBase shim:

// unit test code
var child = new ShimMyChild();
new ShimMyBase(child) { MyMethod = () => 5 };

Note that the child shim type is implicitly converted to the child instance when passed as a parameter to the base shim constructor.

The generated type structure of ShimMyChild and ShimMyBase resembles the following code:

// Fakes generated code
public class ShimMyChild : ShimBase<MyChild> {
    public ShimMyChild() { }
    public ShimMyChild(Child child)
        : base(child) { }
}
public class ShimMyBase : ShimBase<MyBase> {
    public ShimMyBase(Base target) { }
    public Func<int> MyMethod
    { set { ... } }
}

Static constructors

Shim types expose a static method StaticConstructor to shim the static constructor of a type. Since static constructors are executed once only, you need to make sure that the shim is configured before any member of the type is accessed.

Finalizers

Finalizers are not supported in Fakes.

Private methods

The Fakes code generator creates shim properties for private methods that only have visible types in the signature, that is, parameter types and return type visible.

Binding interfaces

When a shimmed type implements an interface, the code generator emits a method that allows it to bind all the members from that interface at once.

For example, given a class MyClass that implements IEnumerable<int>:

public class MyClass : IEnumerable<int> {
    public IEnumerator<int> GetEnumerator() {
        ...
    }
    ...
}

You can shim the implementations of IEnumerable<int> in MyClass by calling the Bind method:

// unit test code
var shimMyClass = new ShimMyClass();
shimMyClass.Bind(new List<int> { 1, 2, 3 });

The generated type structure of ShimMyClass resembles the following code:

// Fakes generated code
public class ShimMyClass : ShimBase<MyClass> {
    public ShimMyClass Bind(IEnumerable<int> target) {
        ...
    }
}

Change the default behavior

Each generated shim type holds an instance of the IShimBehavior interface, through the ShimBase<T>.InstanceBehavior property. The behavior is used whenever a client calls an instance member that was not explicitly shimmed.

If the behavior has not been explicitly set, it uses the instance returned by the static ShimBehaviors.Current property. By default, this property returns a behavior that throws a NotImplementedException exception.

This behavior can be changed at any time by setting the InstanceBehavior property on any shim instance. For example, the following snippet changes the shim to a behavior that does nothing or returns the default value of the return type—that is, default(T):

// unit test code
var shim = new ShimMyClass();
//return default(T) or do nothing
shim.InstanceBehavior = ShimBehaviors.DefaultValue;

The behavior can also be changed globally for all shimmed instances for which the InstanceBehavior property was not explicitly set by setting the static ShimBehaviors.Current property:

// unit test code
// change default shim for all shim instances
// where the behavior has not been set
ShimBehaviors.Current = ShimBehaviors.DefaultValue;

Detect environment accesses

It is possible to attach a behavior to all the members, including static methods, of a particular type by assigning the ShimBehaviors.NotImplemented behavior to the static property Behavior of the corresponding shim type:

// unit test code
// assigning the not implemented behavior
ShimMyClass.Behavior = ShimBehaviors.NotImplemented;
// shorthand
ShimMyClass.BehaveAsNotImplemented();

Concurrency

Shim types apply to all threads in the AppDomain and don't have thread affinity. This is an important fact if you plan to use a test runner that supports concurrency. Tests involving shim types cannot run concurrently. This property is not enforced by the Fakes runtime.

Call the original method from the shim method

Imagine that you want to write the text to the file system after validating the file name passed to the method. In that case, you'd call the original method in the middle of the shim method.

The first approach to solve this problem is to wrap a call to the original method using a delegate and ShimsContext.ExecuteWithoutShims(), as in the following code:

// unit test code
ShimFile.WriteAllTextStringString = (fileName, content) => {
  ShimsContext.ExecuteWithoutShims(() => {

      Console.WriteLine("enter");
      File.WriteAllText(fileName, content);
      Console.WriteLine("leave");
  });
};

Another approach is to set the shim to null, call the original method, and restore the shim.

// unit test code
ShimsDelegates.Action<string, string> shim = null;
shim = (fileName, content) => {
  try {
    Console.WriteLine("enter");
    // remove shim in order to call original method
    ShimFile.WriteAllTextStringString = null;
    File.WriteAllText(fileName, content);
  }
  finally
  {
    // restore shim
    ShimFile.WriteAllTextStringString = shim;
    Console.WriteLine("leave");
  }
};
// initialize the shim
ShimFile.WriteAllTextStringString = shim;

System.Environment

To shim System.Environment, add the following content to the mscorlib.fakes file after the Assembly element:

<ShimGeneration>
    <Add FullName="System.Environment"/>
</ShimGeneration>

After you rebuild the solution, the methods and properties in the System.Environment class are available to be shimmed, for example:

System.Fakes.ShimEnvironment.GetCommandLineArgsGet = ...

Limitations

Shims cannot be used on all types from the .NET base class library mscorlib and System in .NET Framework, and in System.Runtime in .NET Core or .NET 5.0 and later.

See also