UPDATE: This post was later revisited, but it’s still worth reading for an example of what mocking shouldn’t be :).

I was recently placed on my first project at my new job (I’ll post about that later) and they’re having me integrate a bunch of great things into their process. These include automated unit testing, continuous integration and refactoring to patterns. Anyway, I’ve been writing some of my unit tests using isolation with RhinoMocks and came up with a way to achieve full path coverage without duplicating my Expects between test cases.

Below is a fairly standard MVP example. A presenter method is invoked and makes decisions based on information obtained from the view. Say you are testing the following method using isolation and assume the dependencies on view and model are injected via the presenter’s constructor:


public class Presenter
{
IView view;
IModel model;

//...constructors, properties

public void Foo()
{
if (view.val1)
{
//...
model.F1();
//...
if (view.val2)
{
//...
model.F2();
//...
}
else
{
//...
model.F3();
//...
}
}
else
{
//...
model.F4();
//...
}
}
}

Now obviously there are 3 possible paths through this method:

val1 = true & val2 = true
val1 = true & val2 = false
val1 = false & val2 = don’t care

Now, my approach to this sort of problem in the past had been to duplicate a lot of test code only to change a single case return:

[Test]
public void CanFooVal1TrueVal2True()
{
Expect.Call(view.val1).Return(true);
//...some expects
model.F1();
LastCall.IgnoreArguments();
//...some more expects
Expect.Call(view.val2).Return(true);
//...some expects
model.F2();
LastCall.IgnoreArguments();
//...some more expects

mockery.ReplayAll();

presenter.Foo();
//Assertions
}

[Test]
public void CanFooVal1TrueVal2False()
{
Expect.Call(view.val1).Return(true);
//...some expects
model.F1();
LastCall.IgnoreArguments();
//...some expects
Expect.Call(view.val2).Return(false);
//...some expects
model.F3();
LastCall.IgnoreArguments();

mockery.ReplayAll();

presenter.Foo();
//Assertions
}

[Test]
public void CanFooVal1False()
{
Expect.Call(view.val1).Return(false);
//...some expects
model.F4();
LastCall.IgnoreArguments();

mockery.ReplayAll();

presenter.Foo();
//Assertions
}

The approach I’ve come up with to solve this problem is to have each test case call a single method which uses its parameter list to take a path through the method being tested (granted this is one of the simplest mocking cases):


[Test]
public void CanFooVal1TrueVal2True()
{
FooPaths(true, true);

mockery.ReplayAll();

presenter.Foo();
//Assertions
}

[Test]
public void CanFooVal1TrueVal2False()
{
FooPaths(true, false);

mockery.ReplayAll();

presenter.Foo();
//Assertions
}

[Test]
public void CanFooVal1False()
{
FooPaths(false, true); //keep in mind val2 doesn't matter in this case

mockery.ReplayAll();

presenter.Foo();
//Assertions
}

private void FooPaths(bool val1, bool val2)
{
Expect.Call(view.val1).Return(val1);
if (val1)
{
//...
model.F1();
LastCall.IgnoreArguments();
Expect.Call(view.val2).Return(val2);
if (val2)
{
//...
model.F2();
LastCall.IgnoreArguments();
}
else
{
//...
model.F3();
LastCall.IgnoreArguments();
}
}
else
{
//...
model.F4();
LastCall.IgnoreArguments();
}
}

This makes it so that you only need to change Expects and other information relative to the code you are testing in once place (FooPaths) when the code you are testing changes.

Does anyone else out there have any other solutions to this problem?