TypeMock Isolator and matching faked method’s arguments – part 3

Part 1

Part 2

I promised to address matching reference and output arguments using lambda-based syntax that I proposed for TypeMock Isolator. Before getting there I would like to retract my original comment that faked method’s arguments should be matched by default. I still think that, like strong typing, strong matching is a best practice to follow. However I should not forget the main goal of my proposal: increase code simplicity and compactnes. My first examples used very simple method calls consisting of one-two arguments, so an extra effort of typing “(int x, int y)” went unnoticed. Using real API methods immediately showed how inconvenient it can become to faked methods with large number of arguments if developer is forced to list argument types even when no argument match is required. Here how it might look:

Isolate.WhenCalled((string a, Evidence b, byte[] c, AssemblyHashAlgorithm d) => 
        Assembly.LoadFrom(a, b, c, d)).WillReturn(null);

Note where it gets really terrible: you have to type the whole argument list of LoadFrom: “string a, Evidence b, byte[] c, AssemblyHashAlgorithm d”. All this without help from Intellisense! The argument list comes before the method name.

Now compare it with current use of TypeMock API:

Isolate.WhenCalled(() => Assembly.LoadFrom(null, null, null, 0)).WillReturn(null);

What would you choose? With all respect to strong matching the choice is clear.

Eli suggested defining a different method for faking a method a method when arguments matter, and now I came to the same conclusion. So I will leave WhenCalled alone and use a different method name, let it be called WhenCalledArgs (I am not sure about using word “Exact” in such method name, because this method will support partial match, and in case you only match one argument out of five, the word “exact” can be confusing).

Now back to agenda: matching reference and output arguments. In my previous post for each size of argument list I defined two overloads (I will be using new name WhenCalledArgs):

public static IPublicNonVoidMethodHandler WhenCalledArgs(Func func);
public static IPublicNonVoidMethodHandler WhenCalledArgs(Func func, Func predicate);

The definitions above cover the case of a method with single argument. The first overload corresponds to a call with no argument match and no checker. The second overload can be used to define a custom checker for an argument. What is missing here is support for reference and output arguments, when the passed value is assigned to an argument marked as “ref” or “out” on method return. Fortunately, this is easy to achive using the same notation. It just requires new oveloads.

To assign reference and output value we need to extend WhenCalledArgs with a new parameter: an Action delegate:

public static IPublicNonVoidMethodHandler WhenCalledArgs(Func func, Action output);

And to enable specifying both custom checker and output delegate, we will have an overload that combines them all:

public static IPublicNonVoidMethodHandler WhenCalledArgs(Func func, Func predicate, Action output);

Similar overloads we will need to provide for void methods (in such case WhenCalledArgs will be returning IVoidActionHandler instead of IPublicNonVoidMethodHandler). Now let’s look at the test code. For the sake of simplicity we assume to be dealing with a static class called MyClass, and the method that we are going to fake has the following signature:

public static void LookupCustomer(int customerId, out string firstName, out string lastName);

First we will fake its call but assign on return the values “John” and Smith” to the firstName and lastName respectively. Here how the code will look:

Isolate.WhenCalledArgs((int x, string y, string z) => 
        MyClass.LookupCustomer(x, out y, out z), (x, y, z) => { y = "John"; z = "Smith"; }).IgnoreCall();

Note that the customerId argument will not be matched. If we want to match it we slightly change the code:

Isolate.WhenCalledArgs((string x, string y) => 
        MyClass.LookupCustomer(123, out x, out y), (x, y) => { x = "John"; y = "Smith"; }).IgnoreCall();

What happens now is that the method LookupCustomer will only be faked for customerId equals 123, and upon return firstName and lastName arguments will be assigned values “John” and “Smith”.

Using “ref” instead of “out” does not really change much. It just gives an opportunity to assign (and match) a value to an argument and in addition specify another one on return.

And of course we can combine assignment of ret/out values with custom argument checker. Here’s an example:

Isolate.WhenCalledArgs((int x, string y, string z) => 
        MyClass.LookupCustomer(x, out y, out z), (x, y, z) => x > 100, 
        (x, y, z) => { y = "John"; z = "Smith"; }).IgnoreCall();

This is quite compact notation, however it does various things:

  • It fakes calls to MyClass.LookupCustomer without argument match
  • Even though arguments are not matched, only calls with customerId greater than 100 will be faked, as speficied in custom checker predicate
  • On return output arguments firstName and lastName will be assigned values “John” and “Smith” respectively.

Conclusion

In these blogs posts I tried to demonstrate how we can take advantage of C# 3.0 language features and provide compact notation for TypeMock Isolator AAA syntax. I am pretty sure there is someting uncovered, and I haven’t even thought about how this approach can be applied to VB (I am afraid it can’t, at least not until Visual Basic gets action-based lambda expression support). However I believe this notation can be successfully applied to Isolator AAA syntax, as it is based on the same principles and can further improve compactness and readability of the code.

Note: This post was originally published at my old blog host and may contain old comments.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s