Look Mom, No Hands! Test driving Code Without a Mocking Framework

Mocking frameworks came quite a long way from when I first learned test driven development back in 2005. Working in .NET 1.1 at the time, most frameworks were built to use string-based reflection.

To mock a call to the SendEmail() method of an email client, we created a mock based on the interface, then expected that a method with the name “SendEmail” was called, checking the arguments against a index-based set of variables.

`[Test]
public void WelcomeEmailIsSent()
{
Mock mock = new DynamicMock(typeof(IEmailClient));
ClassUnderTest classUnderTest = new ClassUnderTest((IEmailClient)mock.MockInstance);
EmailEnvelope expectedEnvelope = new WelcomeEmail(“[email protected]”, “[email protected]”);

mock.Expect(“SendEmail”, new IsEqual(expectedEnvelope));

bool wasSent = classUnderTest.SendWelcomeEmail();

mock.Verify();
Assert.True(wasSent);
}`

This made the whole refactoring challenging. Method name changes could break tests easily, even though the the code was functionally correct, for example. Since refactoring is the third – and arguably most important – step in the red-green-refactor cycle of TDD, this was problematic.

 

train-your-python-part-14-more-advanced-lists-lambda-and-lambda-operators.1280x600

 

Lambda based reflection and expression trees in .NET changed this completely. Tools like RhinoMocks let us fluently configure mock setup and verification. I remember being blown away when I could do something like this:

`[Test]
public void WelcomeEmailIsSent()
{
IEmailClient emailClient = MockRepository.GenerateStub();
ClassUnderTest classUnderTest = new ClassUnderTest(emailClient);
EmailEnvelope expectedEnvelope = new WelcomeEmail(“[email protected]”, “[email protected]”);

bool wasSent = classUnderTest.SendWelcomeEmail();

emailClient.AssertWasCalled(ec => ec.SendEmail(expectedEnvelope)).Return(true);
Assert.True(wasSent);
}`

Type checking. Passing arguments naturally. No separate verification calls to an illusive mocking framework. Such a huge improvement in productivity, I thought. Refactoring became a bit better. Most tooling would pick up and accurately handle method name changes. Changing method signatures or object types typically caused compilation to fail, which is better than before because the feedback was immediate and obvious. Going back and updating all those tests to adjust the mock object setup or expectations. That was a pain.

This was what originally led me to try writing stub objects by hand. The difference between mocks and stubs can be confusing and nuanced, and much has been written about it already, but the core difference for me is in how much mocks need to know about implementation details.

Mock objects are describes as magnets for over-specification. Part of the reason is because mocked methods expect supplied arguments to match the actual arguments passed. By default, this matching is based on the object’s Equals implementation. If the argumentents don’t match then the test fails and any specified return value is not returned – leading to some confusing test results.

Sure, you can typically get into some fancy argument matchers: Arg.Is.Anything, for example. That’s a lot harder to type and it becomes redundant quickly if you are testing multiple paths through the same code in separate tests. As a software developer, I like to take the path of least resistance.

In contrast, writing my own EmailClientStub class gives me control over the level of specification that I want in my tests. If I rewrite the test, I may come up with something like this:

`[Test]
public void WelcomeEmailIsSent()
{
EmailClientStub emailClient = new EmailClientStub();
ClassUnderTest classUnderTest = new ClassUnderTest(emailClient);
EmailEnvelope expectedEnvelope = new WelcomeEmail(“[email protected]”, “[email protected]”);

bool wasSent = classUnderTest.SendWelcomeEmail();

Assert.AreEqual(expectedEnvelope, emailClient._LastEnvelope);
Assert.True(wasSent);
}`

There’s not a ton of difference syntactically, but there are some key changes for comprehension. We’ve made explicit what we’re asserting and we only need to assert what the test cares about. The stub object to support this test is maybe a dozen lines long and simple to understand.

 

 

It’s also reusable.

Test driving code with stub objects leads to an emergent library of reusable stand-in replacements for real objects. Test cases can then stay focused on specifying the test criteria and not worrying about the details of the collaborating objects.

Refactoring also becomes much easier because I have complete control over what goes in and comes out of each stub object.

Let’s say I have a test that drives the design of the code to need another parameter for the SendEmail() method of the IEmailClient. Most refactoring tools will automatically find and update all calls to that method, usually defaulting the value to null. The overspecification that I had to put in place to satisfy my mocking framework will lead to a failing test for every case that calls SendEmail().

My stub is fine with null. The test that forced the new argument will validate the argument value, if that’s important to specify, but no other tests change.

When I teach this to people who are used to mocking frameworks the first reaction is typically negative. It initially feels like there is even more test code to write and maintain. I agree. There is more code to write at first. But the ratio of stub code to test code does not grow proportionally.

When mocking, expectations grow 1:1 with test code. Because method invocations require matching arguments, mocks usually require that an expectation is written even if the test doesn’t have a need for it.

A reusable library of stub objects limits the number of times the same expectation must be written. If a test isn’t specifying some behavior for an object, it isn’t necessary to write that expectation.

 

I use test driven development because it helps me incrementally specify the behavior of an application. I want to stay focused on implementing the next behavior that does not yet exist. I do not want to specify what a framework asks me for. I want to specify as much, but no more, than the test asks me for.

About the Author

Nick Korbel

Find out more about @nick-korbel