Hello, my name is Jeremy, and I sometimes write unit tests specifications that are uglier than sin. Specifically, when I started to look back at the interaction style tests I’ve been writing over the past couple years I’m finding a bunch of unit tests that are indecipherable to me, the author of those very tests. I came to the conclusion that I needed to start writing my interaction tests in a different fashion to promote readability — and hence better maintainability.
I was really concerned about readability more than anything else, but a quicker way to write integration tests would be nice to. To that end, I’ve been following the Eleutian guys’ posts on their AutoMockingContainer strategy. Honestly, I’ve been dubious about the value of the AutoMockingContainer idea. I get the mechanical savings, but worried that it was too much magic that would obfuscate the unit tests somewhat. I’ve got the StructureMap code open for its upcoming 2.5 release, so I decided to stick in some facility for “AutoMocking.”
The auto mocking stuff was easy enough to do, but I wanted to go farther in terms of readability. I’ve found that one of the factors related to integration tests being confusing to newbies and hard to read for anyone is simply the order in which things happen. In the traditional RhinoMocks record/replay model I call a bunch of expectations on the mock objects, then perform the actual action on the class under test, then call VerifyAll(). It’s Yoda syntax. Instead, what if we could express the tests in a more natural order of Given blah, when I execute blah, this stuff should happen.
Here’s what I came up with in my spike. This is working code, but I don’t want to talk about the mechanics of the underlying code. I’m mostly interested in what y’all think about the syntax and flow of the test. I’m purposely leaving out comments or trying to explain the grammar upfront because I want to see how “soluble” this syntax is if you’re looking at it cold.
This is a rewrite of an existing unit test in StoryTeller. Upon looking at the code, I think there was a simpler way to write the code, but that’s a refactoring for another day. The new and old code effectively express the exact same unit test. The auto mocking does do a lot to cut down on monotonous setup code. I definitely think this code is going to be smoother in C# 3.0, but I want to release a 2.0 version before I do that.
The New Code
[Test]
public void Execute_the_command()
{
string theFragmentName = "Fragment1";
Fragment theNewFragment = new Fragment(theFragmentName);
GivenThatThe<Suite>().Is(new Suite());
Given = delegate {
That.Calling(Service<IInputCommand>().PropertyValue).Return(theFragmentName);
};
WhenExecuting = delegate {
AddNamedFragmentCommand.Execute();
};
TheInteractionShouldBe = delegate {
Call<ISystemUnderTest>().Save(theNewFragment);
Call<ILeafNode>().RefreshNodes();
Call<IFragmentPresenter>().StartEditing(theNewFragment, Member<ISystemUnderTest>());
Call<IApplicationController>().OpenScreen(Member<IFragmentPresenter>());
};
Then = delegate {
Assert.IsNotNull(Member<Suite>().FindFragment(theFragmentName));
};
}
The Old Code for the Same Test
[SetUp]
public void SetUp()
{
_mocks = new MockRepository();
_node = _mocks.CreateMock<ILeafNode>();
_suite = new Suite();
_systemUnderTest = _mocks.CreateMock<ISystemUnderTest>();
_inputDialog = _mocks.CreateMock<IInputCommand>();
_applicationController = _mocks.CreateMock<IApplicationController>();
_command =
new AddNamedFragmentCommand(_node, _suite, _systemUnderTest, _inputDialog, _applicationController);
}
#endregion
private MockRepository _mocks;
private ILeafNode _node;
private Suite _suite;
private ISystemUnderTest _systemUnderTest;
private IInputCommand _inputDialog;
private AddNamedFragmentCommand _command;
private IApplicationController _applicationController;
[Test]
public void Execute()
{
string theFragmentName = "Fragment1";
And fragmentConstraint = new And(
new TypeOf(typeof (Fragment)),
new PropertyIs("Name", theFragmentName)
);
IFragmentPresenter fragmentPresenter = _mocks.CreateMock<IFragmentPresenter>();
ObjectFactory.InjectStub(typeof (IFragmentPresenter), fragmentPresenter);
Expect.Call(_inputDialog.PropertyValue).Return(theFragmentName);
// Save the fragment
_systemUnderTest.Save(null);
LastCall.Constraints(fragmentConstraint);
_node.RefreshNodes();
// Open the screen
fragmentPresenter.StartEditing(null, _systemUnderTest);
LastCall.Constraints(fragmentConstraint, new Equal(_systemUnderTest));
_applicationController.OpenScreen(fragmentPresenter);
_mocks.ReplayAll();
_command.Execute();
_mocks.VerifyAll();
Assert.IsNotNull(_suite.FindFragment(theFragmentName));
}