Saturday, May 28, 2005

EasyMockNET vs NMock

Recently I have been convinced to try easy mock instead of NMock (my mocking framework of preference). Here is how I went...

I started using NMock when all that was available was a dll you could download from sourceforge. There was absolutely no documentation and no source code. I ended up using Reflector (great tool) to work out how to use it. NMock soon became an indispensable tool.

There are a choice of mocking frameworks now (a good list is available on testdriven.net). Most are basically the same. I have been happy with NMock but it does have one problem though. Here is an example of an NMock Test.

[Test]
public void LoadingScriptWithSingleKnownAction()
{
DynamicMock mockActionLoader = new DynamicMock(typeof(IDbActionLoader));
IDbActionLoader actionLoader = (IDbActionLoader)mockActionLoader.MockInstance;

DynamicMock mockAction = new DynamicMock(typeof(IDbAction));
IDbAction action = (IDbAction)mockAction.MockInstance;

DBScriptLoader loader = new DBScriptLoader();
loader.RegisterActionLoader(actionLoader);

string scriptTxt = @"CreateTable
Name: newTable";

mockActionLoader.ExpectAndReturn("HasName",true,"CreateTable");
mockActionLoader.ExpectAndReturn("LoadAction",action,scriptTxt);

IDbScript script = loader.LoadScript(scriptTxt);
Assert.AreEqual(1,script.Length);
Assert.AreEqual(action,script[0]);
mockActionLoader.Verify();
}


Notice the "ExpectAndReturn" calls require you to specify the name of the method you are expecting as a string. The problem with this is that refactoring tools (i.e. Resharper) doesn't know these are method names. If you rename one of these methods you will have to manually rename the string to match. This sort of test maintenance increases the cost of having tests. Anything that increases the cost of tests is bad as it pushes you towards writing less tests and that is where the map is marked 'here be monsters'.

EasyMock has another way of writing tests. The mock object that is created has two modes. It starts in recording mode. To set expectations you just call methods on the object. When you have set all your expectations you then switch all the objects to replay mode. To set the return value of a method call you have to call 'SetReturnValue' just after the method has been called. This is ugly, but luckily there is some syntactic sugar to wrap this up in most cases. Here is the same test again in EasyMock (using the ExpectAndReturn syntax).

[Test]
public void LoadingScriptWithSingleKnownAction()
{
MockControl mockActionLoader = MockControl.CreateStrictControl(typeof(IDbActionLoader));
IDbActionLoader actionLoader = (IDbActionLoader)mockActionLoader.GetMock();

MockControl mockAction = MockControl.CreateStrictControl(typeof(IDbAction));
IDbAction action = (IDbAction)mockAction.GetMock();

DBScriptLoader loader = new DBScriptLoader();
loader.RegisterActionLoader(actionLoader);

string scriptTxt = @"CreateTable
Name: newTable";

mockActionLoader.ExpectAndReturn(actionLoader.HasName("CreateTable"),true);
mockActionLoader.ExpectAndReturn(actionLoader.LoadAction(scriptTxt),action);

mockActionLoader.Replay();
mockAction.Replay();

IDbScript script = loader.LoadScript(scriptTxt);
Assert.AreEqual(1,script.Length);
Assert.AreEqual(action,script[0]);

mockActionLoader.Verify();
mockAction.Verify();
}


ExpectAndReturn in Easymock is the syntactic sugar to make the interface look like NMocks. The following are equivalent...

mockActionLoader.ExpectAndReturn(actionLoader.HasName("CreateTable"),true);

or

actionLoader.HasName("CreateTable");
mockActionLoader.SetReturnValue(true);


In the ExpectAndReturn methods the first parameter is ignored. It is just a nice place to put your method call to the mock object.

I started using EasyMockNET on a new project and quickly found that a simple helper class makes using EasyMock even easier.

public class MockManager
{
private ArrayList mocks = new ArrayList();
public MockControl CreateMock(Type typeToMock)
{
MockControl strictControl = MockControl.CreateStrictControl(typeToMock);
mocks.Add(strictControl);
return strictControl;
}

public void StopRecordingAndStartPlayback()
{
foreach (MockControl mock in mocks)
{
mock.Replay();
}
}

public void VerifyAllExpectations()
{
foreach (MockControl mock in mocks)
{
mock.Verify();
}
}

public void ResetAllExpectations()
{
foreach (MockControl mock in mocks)
{
mock.Reset();
}
}
}


By creating an instance of this class in the SetUp method of the test fixture, and using CreateMock instead of MockControl.CreateStrictControl it allows you to verify, reset or replay all the mocks in one go.

One problem I have with mock objects (in general) is how easy it is to make your test dependent on the implementation details of the class under test. A good unit test should verify the external behavior of the class, not the method it uses. The very nature of mock objects forces you to set expectations on how a class is used. I don't care if the calling class uses my "Has Items" method as long as it doesn't try to access the items when there aren't any. If the calling class decided to call this method two or three times I don't really care either. So... I want to be able to say "method, don't expect to be called, but if you are with these parameters then return this value". I have added such a method to my local version of NMock. I guess I will have to add something similar to EasyMockNET.

When I am writing a test with mock objects I find that I use them in three ways. One is to set up data so I can put the class under test into a known state before I begin my real test. Another is as a simple data object that I verify is passed around correctly (no expectations are set). The other is the actual expectations I wish to validate for the test. Again the first set of expectations I don't actually want to verify for this test (they should be tested elsewhere). Thoughtfully EasyMock provides a Reset method which throws away all the expectations set so far. You can therefore set up mocks to return values, call the init methods on your class under test so its state is set, then call Reset on all mocks. You are not ready to set your real expectations.

So... My conclusion? I'm using EasyMockNET on all my projects from now on. The fact that refactorings tools can keep my tests up to date is a big win for me. If I change my mind later, I'll let you know ;)

No comments:

GitHub Projects