How I would match faked method’s arguments if I was TypeMock Isolator

I enjoy compactness and clarity of TypeMock Isolator AAA syntax. But it still lacks certain features in comparison with the original RecordExpectation-based syntax. One of such features is support for exact match of method’s attributes. Here’s an example (I assume that MyClass is a static class):

Isolate.WhenCalled(() => MyClass.GetSum(1, 2)).WillReturn(10);
Isolate.WhenCalled(() => MyClass.GetSum(3, 4)).WillReturn(20);

At the time of writing (TypeMock Isolator version 5.2.2) this will not trigger return value of 10 on all calls to GetSum with arguments 1 and 2. Argument values are currently ignored. However, recently Isolator was enhanced with exact argument match for indexers:

Isolate.WhenCalled(() => MyClass.Elements[1]).WillReturn(10);
Isolate.WhenCalled(() => MyClass.Elements[2]).WillReturn(20);

In the case above any subsequent call to MyClass.Elements with an index value of 2 will return 20.

Needless to say that support for exact argument match is one of the most requested features for AAA syntax. And the wait is soon over – there will be new methods that will make it possible to require exact argument matching. But is it really necessary to define new methods for this purpose? And what about partial argument match? I think there is another way to go.

Before I move on to my proposal, let me express first what does not make me fully satisfied with current TypeMock approach.

1. Indexers’ and methods’ arguments should not be handled differently.

I understand what was the motivation for exact match of arguments on indexers. Indexers are mostly used with arrays, so when you set different expectations on ar[1] and ar[2], you will most likely want Isolator to map indexes to corresponding values and always return values depending on the passed index. The problem however is that C# makes it very easy to implement indexers as alternatives for getters and setters. So calls MyClass.GetValue(1) and MyClass.Values[1] can mean the same, but when you set expectations on GetValue and Values, the first one will ignore the argument value, while the second one will memorize it.

2. All or nothing is not enough.

Alright, we are soon getting support for exact match of arguments. All arguments. But will it cover all needs? If you can come up with scenario when you need to fake a method with exact argument match, and you can come up with scenario when you need to fake a method with no argument match, then why can’t you have a scenario when you need to match just certain method arguments? I can clearly see some typical scenarios: for example, you may fake a method that receives as part of its input current time, generated GUIDs etc. We can of course ask if this is a good practice to make tests dependent on non-deterministic data, but don’t forget that Isolator can be successfully used not just for pure unit tests. Smoke tests and even integration tests can also be simplified by isolating some aspects, and once there is support for both exact match and no match of method arguments, there is nothing wrong with requesting a partial match.

But how can it be provided? Extend ExactMatch or whatever the new method will be called with an overload that would take an array of argument ordinals? Cumbersome and hard to read and maintain.

Let’s use lambda expressions at its full strength!

One thing that makes TypeMock Isolator AAA syntax charming (although if I am not mistaken it was Moq that introduced it first) is its use of lambda expressions:

Isolate(() => SomeCall(...)).WillReturn(...);

First time I saw this syntax, I spent some time trying to understand what I can use instead of “()” to the left from “=>”. Maybe depending on the faked method’s signature I could sometimes write (x)? Or even (x,y)? Then I realized that this was not an option: static class Isolate supports two overloads of WhenCalled:

public static IVoidActionHandler WhenCalled(Action action);
public static IPublicNonVoidMethodHandler WhenCalled(Func func)

So no matter whether you fake a void method or a method that returns some value, in both cases you pass a parameterless delegate. You can’t have unbound variables: all method’s arguments must be resolved.

But what if WhenCalled supported overload for each form of Action and Func? Like this:

public static IPublicNonVoidMethodHandler WhenCalled(Func func) {...}
public static IPublicNonVoidMethodHandler WhenCalled(Func func)  {...}
public static IPublicNonVoidMethodHandler WhenCalled(Func func) {...}
public static IPublicNonVoidMethodHandler WhenCalled(Func func) {...}
public static IPublicNonVoidMethodHandler WhenCalled(Func func) {...}

This would open quite interesting way of setting expectations with exact match of only selected arguments! Because then we could write Isolate.WhenCalled(() => …, Isolate.WhenCalled((x) => …, Isolate.WhenCalled((x, y) => …. And use these variables in a method call to indicate the unspecified arguments that will not be matched exactly.

Let me illustrate this with an example. Let’s say we have a static class MyClass with a method GetSum(int x, int y). In TypeMock Isolator the following code will work fine:

Isolate.WhenCalled(() => MyClass.GetSum(1, 2)).WillReturn(4);
Assert.AreEqual(4, MyClass.GetSum(1, 2));
Assert.AreEqual(4, MyClass.GetSum(10, 20));

The code above uses currently supported syntax, except that I believe it’s semantics should be identical to the one used by indexers: method arguments should be matched exactly.

And here how we can disregard argument values:

Isolate.WhenCalled((int x, int y) => MyClass.GetSum(x, y)).WillReturn(6);
Assert.AreEqual(6, MyClass.GetSum(1, 2));
Assert.AreEqual(6, MyClass.GetSum(2, 1));

In the code above it does not matter what arguments are passed to GetSum: we used a new overload of WhenCalled (unfortunately only supported in this blog entry) that let us speficy what arguments should be treated as unbound.

And what about partial argument match? Now it’s easy:

Isolate.WhenCalled((int y) => MyClass.GetSum(1, y)).WillReturn(7);
Assert.AreEqual(7, MyClass.GetSum(1, 2));
Assert.AreEqual(4, MyClass.GetSum(2, 2));

Here Isolator is instructed to set expectations only on calls with first argument equal to 1. So GetSum(2,2) will not be faked.

Finally setting expectations on an indexer:

Isolate.WhenCalled(() => MyClass.Elements[0]).WillReturn(8);
Assert.AreEqual(8, MyClass.Elements[0]);
Assert.AreNotEqual(8, MyClass.Elements[123]);
Isolate.WhenCalled((int x) => MyClass.Elements[x]).WillReturn(9);
Assert.AreEqual(9, MyClass.Elements[123]);

What do we gain by this?

1. Consistent semantics for argument match on indexers and methods.

2. Support for partial argument match.

3. No need to introduce new method names – just overloads to WhenCalled.

4. And isn’t such use of labmda expressions cool?

To demonstrate the proposed syntax, I wrote a simple example with unit tests that include the code I showed above. Of course I could not use real TypeMock Isolator in these tests: it does not support (yet!) proposed WhenCalled overloads. So I cheated a little and implemented a toy MyIsolate class with just enough overloaded methods to make these example work. The test project can be downloaded from this post. You can compile and run the tests – they will pass as if they were real. I wish they were.

Part 2

Part 3

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