Monday, October 6, 2008

Don’t Mock Me

Michael and I came up with something pretty cool for mocking in unit tests.

So, you have a class:

public class Thing
{
    public int Do() { return 1; }
}

And you want to mock it because “return 1;” really means “call off to a web service that won’t be there during unit testing”.  Obviously, the traditional creating of that whole Interface/Class set thing so you can return a mock version of it sucks (maintenance).  Also, you don’t want to put code in your production code which is specifically test code.  So, how about we let our unit test project have its own derived version of the class?  Then it can override the methods that call out with some simple stubs.

public class Thing
{
    public virtual int Do() { return 1; }
}

public class MockThing : Thing
{
    public override int Do() { return 0; }
}

Getting closer, but now there’s still the problem of instantiating our class instead of the original.  How about we just get in the way of instantiating the class when in test mode?

public delegate Thing GetThingInstanceDelegate();
public class Thing
{
    protected Thing() { }
    public virtual int Do() { return 1; }
    protected static GetThingInstanceDelegate _getInst =
          () => new Thing();
    public static Thing GetInstance()
    {
        return _getInst();
    }
}
// located only in your unit test project
public class MockThing : Thing
{
    /// <summary>
    /// Temporarily used to force static constructor
    /// </summary>
    public static void Initialize() { }
    static MockThing()
    {
        _getInst = () => new MockThing();
    }
    protected MockThing() : base() { }
    public override int Do() { return 0; }
}

Now, to call it from your production code:

Thing thing = Thing.GetInstance();
System.Diagnostics.Trace.WriteLine(thing.Do().ToString(), "DoThing");

The cost you pay is a slight performance hit due to using delegation for instantiation (who cares?), a little more complexity and readability trouble, that Initialize method and opening up your design for extension where it probably shouldn’t be allowed.

These can be mitigated as such:

· The performance hit of a delegation is nothing compared to the hit of calling a service over the web.  Forget about it.

· Complexity can be commented, and the boilerplate stuff really can be in a base class of Thing if it requires no other base class.

· The initialize method can be skipped as long as there’s a way of triggering the static initialize… which can be done through a marker interface (or attribute) and forced in a method, using reflection, triggering all those classes.

· The opened up design can be mitigated somewhat using visible internals and some other trickery, but this is still a much smaller hit to the design than building all your objects as class/interface pairs.

Nifty?

No comments: