Faking the Rest: Detouring Properties, Shimming Parameters and More
Peter finishes up his discussion of using the Fakes Framework with TDD in Visual Studio 2012 Ultimate by looking at mocking properties and why you'd want to pass a shim as parameters.
This is the third column in a series on Microsoft's Fakes Framework for mocking objects to support Test Driven Development. In my first column I discussed the need for mocking in a TDD environment: Mocking lets you isolate the code you want to test. That column also introduced shims, part of Microsoft's Fakes Framework.
In my last column I showed how to manage mocking with shims. However, in both I just concentrated on mocking methods -- and, at the very least, classes also have properties that you might want to mock. And while I've looked at using shims to mock objects by creating detours, there are times when you'll use the shim version of a class as an object.
The story so far: I have a method (called AddTwoIntegers) in a class call MathStuff. I also have a Test project with a test for the AddTwoIntegers method. However, the code inside the AddTwoIntegers method calls a method named ConvertAnythingToInteger, which belongs to a class called Conversion. I don't want to test the Conversion class' code so, in my Test project, I've been using the Fakes Framework's shims to redirect any calls made to the Conversion class' AddTwoIntegers method to an anonymous function (which I have complete control over), that I've set up in my test code.
Up until now I've only worried about the methods on the Conversion class that AddTwoIntegers calls. But what if the AddTwoIntegers' code reads or sets some property on the Conversion class?
The Fakes Framework has that covered: The shim class created by the Fakes Framework has two members for every property on the real class. One of those members has the same name as the property, with "Get" appended to the end; the other member has the name of the property with "Set" and the name of the property's datatype appended. For instance, the Conversion class used inside of my AddTwoIntegers method has a Boolean property called RejectZeroes, so the ShimConversion class generated by Fakes has two corresponding members: one called RejectZerosGet and RejectZerosSetBoolean. You can set these members to anonymous functions that will be called when code the code you're testing either reads or sets the property.
With most properties, all you want your mock to do is provide a function to return a fixed value when the property's read. The following example, for instance, creates an anonymous method to have the RejectZeros property always return True (this function mocks the property's get). As with detouring methods, the method used to detour a property can accept a parameter that points to the real class (the Conversion class, in this case). This example accepts that parameter (called instance), but doesn't use it:
If you do want to provide a function to detour the property's set, you'll probably want to store the value that's passed to the set so that you can use the value later on (probably, to return the value in the function that detours the property's get). That's what the following example does. Since the get detour doesn't use the instance parameter, I've skipped it. However, in the set detour, the value that the property is being set to is passed to the function's second parameter. As a result, I have to specify the first parameter in the set detour function so that I can accept the value in the second parameter:
Dim _value As Boolean
_value = value
Passing instances of the Shim Class
I've been assuming, so far, that the reason you're creating a detour is because, inside the class you're testing, a call is being made to some member on of the class you're mocking (as AddTwoIntegers calls methods on the Conversion class). However, it's possible that the class you want to mock is actually being passed to the method you're testing. You can also use shims in this scenario by instantiating the shim class and passing it to the method you're testing.
In both C# and Visual Basic, .NET will do an implicit conversion from a shim version of a class to the real version of the class. That means I can pass an instance of ShimConversion to the MathFuncs class's constructor. The following code creates a context for shims, instantiates a ShimConversion class, sets its ConvertAnythingToIntegerObject method to a detour function and then passes the configured ShimConversion object to the MathFuncs's constructor.
Using sc = ShimsContext.Create()
Dim shConvert As New ShimConversion
Dim mh As New MathStuff.MathFuncs(shConvert)
Accessing the Actual Instance
I've mentioned the parameter passed to these detour functions so often that it makes sense to finish up by describing how to use it. The following example rewrites a detour for the ConvertAnythingToInteger method created in the first column in this series. That detour always returned the number 4. This version, however, instead of directly returning the number 4, passes the number 4 to the real ConvertAnythingToInteger method and returns that value (no, I don't know why you'd want to do this -- I just made it up to demonstrate the technique).
To make that happen, I have my anonymous method accept a single parameter which will be set by the Fakes Framework to point to an instance of the Conversion class (the class that this shim is based on). I then use that parameter (after recasting it to a Conversion object) to call the real ConvertAnythingToInteger method:
Dim conv As MathStuff.Conversion
conv = instance
There's more to the Fakes Framework, of course, but if you need to isolate the class you're testing from other code in your universe, these three columns have probably covered everything you'll need.
Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.