JustMock is a small product. It is around 10,000 lines of code. Despite its small size there are a lot of decisions that were made and many others that have to be made. In this post I would like to shed some light on our decision making process and I’ll try to answer the question why JustMock is built the way it is. The topic is quite large for a single blog post so I am going to focus on small part from JustMock – namely design choices for private method resolution.
First, I would like to emphasize that JustMock was built around C#. Every time when we had to decide how particular feature should be implemented we designed it from C# perspective. As a result JustMock works with concepts that are better expressed in C# rather than VB.NET for example. The reason for this decision is that it seems C# developers tend to use more often mocking tools/frameworks than VB.NET ones. So far, it seems we did the right choice.
As a consequence JustMock is more or less tightly coupled to the C# compiler. So are other mocking libraries. Lets consider the following example with Moq:
public interface IFoo { int Bar(string s); int Bar(StringBuilder s); }
Often, it makes sense to define overloading methods in your code. The way IFoo is defined is perfectly legal. So, lets see a corner case with this interface. Suppose we want to mock Bar(string s) method when the argument is null:
var mock = new Mock<IFoo>(); mock.Setup(foo => foo.Bar(null)).Returns(1);
You will quickly find that the this code fragment does not compile. Here goes the error:
error CS0121: The call is ambiguous between the following methods or properties: 'IFoo.Bar(string)' and 'IFoo.Bar(System.Text.StringBuilder)'
This error has nothing to do with Moq. It is related to the C# compiler which in this case does not have enough information how to do method resolution. So, it is our fault and we have to fix it.
Here comes the second guide line we follow when design JustMock – there are no “right” or “wrong” choices. It is not white or black; rather it is like different gray shades. We could employ C# syntax in JustMock API to provide single method resolution (in most cases) but we didn’t. The reason is that different C# developers have different strategies to correct this issue. Here is a sample list with the most common fixes:
// option 1 mock.Setup(foo => foo.Bar((string)null)).Returns(1); // option 2 mock.Setup(foo => foo.Bar(null as string)).Returns(1); // option 3 mock.Setup(foo => foo.Bar(default(string))).Returns(1); // option 4 string s = null; mock.Setup(foo => foo.Bar(s)).Returns(1);
After all, it’s matter of taste. Please notice that while I provided the sample above with Moq the issue is valid for JustMock. Lets make the things more interesting. Suppose we have the following concrete type:
public sealed class Foo { public Foo() { ... } // ... private int Bar(string s) { ... } private int Bar(StringBuilder s) { ... } }
This time Bar(string s) and Bar(StringBuilder s) are private methods in a sealed type. Now we have to design an API for mocking private methods.
Note: Mocking private methods is a controversial thing. There are two equally large camps and a hot discussion between them. For good or bad, JustMock offers this feature.
We can provide the following method:
public static void Arrange(object instance, string methodName, params object[] args) { ... }
And then use it as follows:
var foo = Mock.Create<Foo>(); Mock.Arrange(foo, "Bar", null); string s = null; int actual = foo.Bar(s);
The problem with this approach is that the last argument passed to Arrange method is null and thus we cannot resolve the correct Bar(…) method. This time options 1 to 4 provided above don’t solve the issue. Lets see what can we do.
Approach 1: no method resolution
The problem is all about method resolution so if we don’t have to do it then we are good. Lets provide the API:
public static void Arrange(object instance, MethodInfo method, params object[] args) { ... }
We have to change our test code:
Type[] argTypes = { typeof(string) }; var bar = typeof(Foo).GetMethod("Bar", argTypes); var foo = Mock.Create<Foo>(); Mock.Arrange(foo, bar, null);
The test code is more messy. Now we have to deal with System.Reflection stuff which is not related to our code. Some developers are fine with this approach though. While this approach is suitable for API-to-API scenarios a lot of people prefer something more “friendly”.
Approach 2: pass method parameter types
We can pass the actual parameter types along with the parameter values. Lets change the Arrange method signature:
public static void Arrange(object instance, string methodName, Type[] types, params object[] args) { ... }
We added Type[] argument to Arrange method so we have to change our test code:
var foo = Mock.Create<Foo>(); Type[] argTypes = { typeof(string) }; Mock.Arrange(foo, "Bar", argTypes, null); // use foo instance
This solution is easy to understand. It can also handle ref and out parameters and other more complex scenarios.
Approach 3: pass method parameter types as generic parameters
In case we decide to support private methods with simple signatures (no ref and out parameters and other fancy stuff) we can modify our previous approach by providing generic version of Arrange method (we can use T4 template for code generation; for the sake of simplicity we add a single generic parameter):
public static void Arrange<T>(object instance, string methodName, params object[] args) { ... }
We change our test code accordingly to:
var foo = Mock.Create<Foo>(); Mock.Arrange<string>(foo, "Bar", null); // use foo instance
This all looks good and nice but now we have to provide generic parameter types even when we don’t need them.
var foo = Mock.Create<Foo>(); Mock.Arrange<string>(foo, "Bar", "test"); // use foo instance
It is not that bad but this can be annoying if Bar(…) method accepts, lets say, 5 integer parameters.
Approach 4: use typed references
The CLR and .NET framework already provide support for typed references but for our need we can come up with something much simpler. We have to provide support for our corner case when values are null. So we can provide a simple Null<T> type as follows:
public sealed class Null<T> where T : class { }
Every time when we have to pass null value we are going to replace it with an instance of Null<T> type:
var foo = Mock.Create<Foo>(); Mock.Arrange(foo, "Bar", new Null<string>()); // use foo instance
As the previous approach this one does not support methods with ref and out parameters. We can try to fix it by providing another constructor for Null<T> type:
public sealed class Null<T> where T : class { public Null() {} public Null(System.Reflection.ParameterAttributes attrs) { this.Attrs = attrs; } public ParameterAttributes Attrs { get; private set; } }
Suppose we add the following method to Foo:
private int Bar(ref string s) { ... }
If we want to mock it then our test code should like something like:
var foo = Mock.Create<Foo>(); Mock.Arrange(foo, "Bar", new Null<string>(ParameterAttributes.Out)); // use foo instance
It doesn’t look nice to me. The syntax becomes more verbose and doesn’t flow naturally. We can mitigate it by adding new helper type as follows:
public static class Arg { public static Null<T> Null<T>() where T : class { return new Null<T>(); } }
Now we can compare the two options
var foo = Mock.Create<Foo>(); //option 1 Mock.Arrange(foo, "Bar", new Null<string>()); //option 2 Mock.Arrange(foo, "Bar", Arg.Null<string>()); // use foo instance
The readability seems a little bit better but many developers find the syntax verbose.
Approach 5: using dynamic (C# 4 and higher)
This is a good approach for a lot of developers. There is a caveat though. There are a plenty of customers that are still using .NET 3.5 and earlier. Shall we abandon them? A tough question.
Which one to use?
First, it is a wrong question. There is no silver bullet and the best thing we can do is to decide on a case-by-case basis. So far, we saw a few possible options; there are other alternatives as well. The options are not mutually exclusive which makes it even harder. If you are curious how we implemented it you can download a trial JustMock version.