Are You a Mockaholic?

Mockaholic: (noun) – a person who compulsively takes mocking frameworks as the answer to every test-isolation.

In my current project, I use Specification pattern for my data-access query, and while it is testable, they were anything but fun.

Here’s some SUT of my MVC Customer controller:

public ActionResult Search(string name, DateTime? dob, bool? isTerminated)
	ISpecification spec = null;
		spec &= whereCustomer.NameContains(name);
	if(dob != null)
		spec &= whereCustomer.WasBornInDay(dob.Value);
	if(isTerminated != null)
		spec &= isTerminated? whereCustomer.IsTerminated(): !whereCustomer.IsTerminated();

	searchResult = repository.Query(spec);
	return View("search", searchResult);

I stripped out all PTO related stuffs for clarity. Using Rhino-Mock, here is the unit-test:
(PS: &, |, and ! operators call And(), Or(), and Not() instance methods respectively behind the scene)


IWhereCustomer whereCustomer = MockRepository.CreateStub<IWhereCustomer>();

ISpecification<Customer> nameSpec = StubSpec();
ISpecification<Customer> dobSpec = StubSpec();
ISpecification<Customer> notTerminatedSpec = StubSpec();
ISpecification<Customer> finalSpec = StubSpec();

whereCustomer.Expect(x => whereCustomer.NameContains("Hendry")).Return(nameSpec);
whereCustomer.Expect(x => whereCustomer.WasBornInDay(dob)).Return(dobSpec);
whereCustomer.Expect(x=> whereCustomer.IsTerminated().Not()).Return(notTerminatedSpec); // Recursive Mock

nameSpec.Expect(x=> x.And(dobSpec).And(notTerminatedSpec)).Return(finalSpec); // Recursive Mock

repository.Expect(x=> x.Query(finalSpec)).Return(stubCustomers);

controller.Search("Hendry", dob, false).ShouldRenderView("search").WithModel(stubCustomers);

Although the code bloat has been reduced significantly by the new recursive mock feature in Rhino-Mock 3.5, the test is still far than neat. It’s tedious to write, difficult to read. From reader’s perspective, I can’t immediately figure out what this unit-test is trying to express. If this is how I should write unit-test for anything dealing with ISpecification, I definitely need to find something better.

So I scrap the whole Rhino-Mock stuffs from my unit-test, and write my own hand-coded stub for ISpecification class myself, and I’m able to refactor my test-case into:

IWhereCustomer whereCustomer = StubSpecificationFactory<IWhereCustomer>();

repository.Expect(x=> x.Query(
	whereCustomer.NameContains("Hendry") & whereCustomer.WasBornInDay(dob) & !whereCustomer.IsTerminated()

controller.Search("Hendry", dob, false).ShouldRenderView("search").WithModel(stubCustomers);

We managed to reduce the test code by more than a half! And even better, you can use AAA style test that wasn’t quite possible previously.

controller.Search("Hendry", dob, false).ShouldRenderView("search").WithModel(stubCustomers);

repository.AssertWasCalled(x=> x.Query(
	whereCustomer.NameContains("Hendry") & whereCustomer.WasBornInDay(dob) & !whereCustomer.IsTerminated());

How was that possible? Here’s the stubbed ISpecification that I wrote.

public class SpecificationMock<T>: ISpecification<T>
	private MethodInfo methodInfo;
	private object[] args;

	public override bool Equals(object other)
		SpecificationMock<T> spec = other as SpecificationMock<T>;
		if(spec== null)
			return false;
		return methodInfo.Equals(spec.methodInfo) && args.SequenceEquals(spec.args);

So this stub is only responsible in comparing equality between expectated and actual ISpecification.
Mocked IWhereCustomer will forward all MethodInfos (NameContains() and WasBornInDay() in this case) and its arguments down to the our SpeficiationMock. I use Castle DynamicProxy here to automate that (hence StubSpecificationFactory utility).

By unleashing yourself from full blown mocking-framework and instead going back to basic using handcrafted mock objects, you can actually produce a much simpler test design, using a carefully established “test-oriented vocabulary” that suits your specific situation.


One thought on “Are You a Mockaholic?

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your 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