Practical .NET

Faking it Well: Effective Mocking with the Fakes Framework

Using shims in Visual Studio 2012 Ultimate lets you easily bypass code—no matter how deeply buried—to test just the parts of your application that you want to test.

In my last column I discussed the need for mocking in a TDD environment. Mocking lets you bypass code you're not interested in testing to focus your efforts on the code you actually care about. I also introduced a simple way of handling mocking, by using shims in the Microsoft Fakes Framework (provided you're using Visual Studio 2012 Ultimate).

By the end of that previous column, I'd assembled a test project that would test a method called AddTwoIntegers; I isolated that method from a separate object that the method used internally with a shim. With my shim in place, the AddTwoIntegers method, instead of calling a method named ConvertAnythingToInteger on an object called Conversion, would call an anonymous method I created as part of test:

Using sc = Microsoft.QualityTools.Testing.Fakes.ShimsContext.Create()
  MathStuff.Fakes.ShimConversion.AllInstances.ConvertAnythingToIntegerObject =
    Function()
      Return 4
    End Function
  Dim mh As New MathStuff.MathFuncs
  Dim res As Integer = mh.AddTwoIntegers(5, 7)
  Assert.AreEqual(8, res, "Unable to add two integers")
End Using

This sample creates a shim that always returns the number 4 (see my previous column for the origin of the bizarre names -- ShimConversion, AddTwoIntegersObject -- that appear in this code).

Accepting Parameters
Microsoft refers to these redirections as "detours" and, as you can see, this detour isn't very good. While a mock method should be brain-dead simple (so that you don't have to test the mock method), this version is too simple. The real ConvertAnythingToInteger method accepts a parameter and returns a value based on that parameter; it would be better if my mock method did the same.

Your shim method will always have a signature similar to the method it mocks; the only difference is that the shim method will have one extra parameter that points to the original class you're mocking. Since the real ConvertAnythingToInteger method accepts a single integer as its only parameter, any shim for this method will accept two parameters with the first parameter being the reference to the real class. To accept and then simply return the parameter passed to it, I can set up my detour like this:

MathStuff.Fakes.ShimConversion.AllInstances.ConvertAnythingToIntegerObject =
    Function(mockedClass, parm)
      Return parm
    End Function
Reusing Shims
This is a useful detour that I might want to use in other tests. Rather than recreating it for each test, I can store it in a Func variable. Before doing that, however, I'll save myself some typing by adding the following three Imports directives to my test class. The first two statements reference namespaces in the Fakes Framework, while the last one references the namespace in the project's class that I'm testing that the Fakes Framework added:

Imports Microsoft.QualityTools.Testing.Fakes.Shims
Imports Microsoft.QualityTools.Testing.Fakes
Imports MathStuff.Fakes

Now I can declare a variable to hold my shim method and initialize it with the shim:

<TestClass()>
Public Class MathFuncsTest
  Dim EchoConvert As FakesDelegates.Func(Of MathStuff.Conversion, Object, Integer) =
         Function(mockedClass, parm)
             Return parm
         End Function

Taking advantage of this stored shim (and the Imports statement), I can rewrite my detour like this:

Using sc = ShimsContext.Create()
  ShimConversion.AllInstances.ConvertAnythingToIntegerObject = EchoConvert
Avoiding Shims
As you might expect, if you don't detour a method and your code under test calls that method, the real method is called. I haven't provided a detour for ConvertDatesToIntegers on my Conversion class, so if the code under test calls ConvertDatesToIntegers method, the real method is executed. You can change that behavior if you want.

If, for instance, you want to find out what methods are being called on a shimmed class by the code under test, you can tell the shim version of your class to raise a Not Implement exception if any member that isn't detoured is called (including constructors). All you need to do is set the shim class's Behavior property to the enumerated value ShimBehaviors.NotImplemented:

Using sc = ShimsContext.Create()
  ShimConversion.Behavior = ShimBehaviors.NotImplemented

More usefully, you can also tell your shims to just return the default value for the data type. This can sharply reduce the number of detours you need to write. If, for instance, you don't bother to provide a detour for a method that returns integers, calling the method will return a zero. You do that by setting the shim class's Behavior property to ShimBehaviors.BehaveAsDefault:

ShimConversion.Behavior = ShimBehaviors.BehaveAsDefault

Of course, classes consist of more than just methods. In my next column, I'll look at detouring properties and working with constructors (among other things).

In the meantime: I mentioned in my previous post that using stubs (another option in the Fakes Framework) instead of shims gives you better performance. To try and determine the difference, I created a separate version of my MathStuff project, written in a way that supported using stubs. I also created a version of this test that used stubs. The shim version was, in fact, slower than the stubs version: by 8 milliseconds. I suppose, if I was running thousands of tests at a time, I'd worry about that. Since I'm not, and given the limitations of stubs, I don't think I'll worry about those 8 milliseconds.

About the Author

Peter Vogel is a principal in PH&V Information Services, specializing in Web development with expertise in SOA, client-side development, and user interface design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His most recent book ("rtfm*") is on writing effective user manuals, and his blog posts on communicating effectively can be found at http://blog.learningtree.com/category/communication-2/.

comments powered by Disqus
Upcoming Events

.NET Insight

Sign up for our newsletter.

I agree to this site's Privacy Policy.