Exploring the Continuum of Test Doubles

image_pdf

Introduction
When implementing an application dealing with time conversion related logic, UTC-0 time to local time and vice versa, it became clear that testing was of the essence. The behavior around daylight saving time (DST) switches was something that required complex tests. At that moment I started reading about the different kind of unit tests I could use.

I required test objects that could stand in for the actual .NET DateTime class to simulate the time during DST switches, without having to change the server’s time. In testing theory these are called test doubles which resembles very closely the image of car tests-dummies. We ended up using Rhino Mocks.

During my literature research I found an article that was published in MSDN Magazine in 2007 by Mark Seemann describing the spectrum of test doubles going from absolute no implementation on the one end to fully implemented objects on the other end.

The article was very valuable to me and you can find the full version by going to http://msdn.microsoft.com/en-us/magazine/cc163358.aspx. Here you?l find a concise summary.

The spectrum
testspectrum
The following test doubles are identified in the spectrum, below we?l discuss them one by one:

  • Dummies
  • Stubs
  • Spies
  • Fakes
  • Mocks
    • Manual Mocks
    • Automatic Mocks

Dummies

Dummies are the most basic test doubles. Dummies just avoid compile time errors by complying with method signatures and interface implementations but are useless at run time.

This is the effect you get when you use the implement interface command in Visual Studio. For example:

public interface IShopDataAccess
{
    decimal GetProductPrice(int productId);
    void Save(int orderId, Order o);
}
public void Save(int orderId, Order o)
{
    throw new Exception("The method or operation is not implemented.");
}

It is very clear you should never call the Save method although the code compiles perfectly.

Stubs

Stubs are evolved versions of Dummies so that at least at runtime no exceptions are thrown.

In our example the Save method returns no result so avoiding run-time exceptions can be simply done by having an empty method implementation:

public void Save(int orderId, Order o) { }

If the method would return values we can just hard-code these values.

public decimal GetProductPrice(int productId)
{
	return 25
}

But very fast we end up with a tree of if-statements returning different values based on the Id that is passed as a parameter. Although it is useful, this is not a very flexible solution.

public decimal GetProductPrice(int productId)
{
    switch (productId)
    {
        case 1234:
            return 25;
        case 2345:
            return 10;
        default:
            throw new ArgumentException("Unexpected productId");
    }
}

Spies

The purpose of test dummies in general is to demonstrate that the test target works as intended. With Dummies and Stubs it is hard to argument they demonstrate anything.

Spies add the ability to see whether or not a certain method was called and the parameters it was called with.

internal class SpyShopDataAccess : IShopDataAccess
{
    private bool saveWasInvoked_;

    #region IShopDataAccess Members

    // Other IShopDataAccess members ommitted for brevity

    public void Save(int orderId, Order o)
    {
        this.saveWasInvoked_ = true;
    }

    #endregion

    internal bool SaveWasInvoked
    {
        get { return this.saveWasInvoked_; }
    }
}

Fakes

To avoid if-then-hierarchies to return values like with Stubs, we could create an in memory database based on collections. This allows for more realistic results to be returned with more flexibility then a fixed list in the method itself.

The Fake could be programmed as follows:

internal class FakeShopDataAccess : IShopDataAccess
{
    private ProductCollection products_;

    internal FakeShopDataAccess()
    {
        this.products_ = new ProductCollection();
    }

    #region IShopDataAccess Members

    public decimal GetProductPrice(int productId)
    {
        if (this.products_.Contains(productId))
        {
            return this.products_[productId].UnitPrice;
        }
        throw new ArgumentOutOfRangeException("productId");
    }

    public void Save(int orderId, Order o) { }

    #endregion

    internal IList<Product> Products
    {
        get { return this.products_; }
    }
}

The test data is populated through the test class that will be calling the Fake during the test. For example:

public void CalculateLineTotalsUsingFake()
{
    FakeShopDataAccess dataAccess = new FakeShopDataAccess();
    dataAccess.Products.Add(new Product(1234, 45));
    dataAccess.Products.Add(new Product(2345, 15));
    ...    // remainder of the  test code
    ...}

The trouble with this approach is that creating Fakes requires almost as much work as writing the code we are testing.

If the code for the test doubles is becoming more and more complex you?e probably overdoing it. If you start thinking about testing your test doubles you simply went too far.

Mocks

Mocks are libraries that support the creation of Dummies, Stubs, Spies and Fakes. Libraries can be reused to avoid repeating tedious programming.

Manual Mocks

Creating your own mocking libraries, i.e. Manual Mocks, isn’t a walk in the park.

A Manual Mock could look like this:

internal class MockShopDataAccess : IShopDataAccess
{
    private ImplementationCallback implement_;

    internal MockShopDataAccess(ImplementationCallback callback)
    {
        this.implement_ = callback;
    }

    #region IShopDataAccess Members

    public decimal GetProductPrice(int productId)
    {
        MemberData member = new MemberData("GetProductPrice");
        member.Parameters.Add(new ParameterData("productId", productId));

        this.implement_(member);

        return (decimal)member.ReturnValue;
    }

    public void Save(int orderId, Order o)
    {
        MemberData member = new MemberData("Save");
        member.Parameters.Add(new ParameterData("orderId", orderId));
        member.Parameters.Add(new ParameterData("o", o));

        this.implement_(member);
    }

    #endregion
}

The important part is situated in the GetProductPrice method were a delegates is called to retrieve a value. This delegate must be implemented in the test class and will be called back from the Manual Mock. In order for the Manual Mock to be aware what delegate to call, it is passed in the constructor.

The test class needs an implementation for the delegate. We could use an inline delegate definition for this:

public void CalculateLineTotalsUsingManualMock()
{
    MockShopDataAccess dataAccess =
        new MockShopDataAccess(delegate(MemberData member)
    {
        if (member.Name == "GetProductPrice")
        {
            int productId = (int)member.Parameters["productId"].Value;
            switch (productId)
            {
                case 1234:
                    member.ReturnValue = 45m;
                    break;
                case 2345:
                    member.ReturnValue = 15m;
                    break;
                default:
                    throw new ArgumentOutOfRangeException(
                        "productId");
            }
        }
        else
        {
            throw new InvalidOperationException("Unexpected member");
        }
    });
    ...
}

You see a repeating pattern … Very good, this calls for automation!

Automatic Mocks

With Automatic Mocks the approach is completely different. Instead of creating class instances manually, a framework will instantiate objects automatically at runtime.

For example using Rhino Mocks the code to mock an object implementing a certain interface looks like:

public void SaveOrderAndVerifyExpectations()
{
    MockRepository mocks = new MockRepository();
    IShopDataAccess dataAccess = mocks.CreateMock<IShopDataAccess>();

    ...
    // Start replay of recorded expectations
    mocks.ReplayAll();

    ...

    mocks.VerifyAll();
}

We have two modes: first the recording mode where expected results are defined and secondly a replay mode where the client accesses the mock objects.

Summary

Dummy
The simplest, most primitive type of test double. Dummies contain no implementation and are mostly used when required as parameter values, but not otherwise utilized. Nulls can be considered dummies, but real dummies are derivations of interfaces or base classes without any implementation at all.
Stub
A step up from dummies, stubs are minimal implementations of interfaces or base classes. Methods returning void will typically contain no implementation at all, while methods returning values will typically return hard-coded values.
Spy
A test spy is similar to a stub, but besides giving clients an instance on which to invoke members, a spy will also record which members were invoked so that unit tests can verify that members were invoked as expected.
Fake
A fake contains more complex implementations, typically handling interactions between different members of the type it’s inheriting. While not a complete production implementation, a fake may resemble a production implementation, albeit with some shortcuts.
Mock
A mock is dynamically created by a mock library (the others are typically produced by a test developer using code). The test developer never sees the actual code implementing the interface or base class, but can configure the mock to provide return values, expect particular members to be invoked, and so on. Depending on its configuration, a mock can behave like a dummy, a stub, or a spy.