Thursday, May 10, 2012

UnitTest : Mock Framework

One way of thinking about an object-oriented program is that each object is an entity that interacts with other objects (which we will call 'collaborators') in order to do its work. This is illustrated in the following diagram:
MockObjects1
The idea behind Mock Objects is this: when unit testing an object, you replace its collaborators with 'Mock' collaborators. This is illustrated below:
MockObjects2
So how is this different from stubs? Well, whilst there are some similarities, there are also two important distinctions:
  1. Mock Objects test themselves - ie, they check that they have been called in the right manner at the right time by the object being tested. Stubs generally just return stubbed data, which can be configured to change depending on how they are called.
  2. Mock Objects are generally extremely lightweight. There are often totally different instances of them set up for every test. Stubs, on the otherhand, are relatively heavyweight and are often reused between tests.

Design Implications

It's important to realise that, depending on the degree to which you use them, Mock Objects have two major implications for your software design. These are:

Everything by Interfaces

Most inter-object interaction must be done via interfaces. This is so that either real or mock implementations can be passed to objects being tested. Another way of thinking about this is that you essentially have to introduce an extra layer of indirection between an object and any collaborator that you want to mock. This extra indirection can make the code difficult to follow at first.

Dependency Injection

Generally, the collaborators of an object should be given to the object - the object should not get the collaborators itself. This is an existing design strategy known as 'dependency injection', or 'inversion of control' - perhaps it's most well-known use is in the Spring framework. I won't go into this pattern in too much detail - Martin Fowler has written a useful article about it. I found that whilst its usage could generally lead to better design, when used for testing at a very low level it can have quite major and disturbing implications on your code. I will discuss this further in the section Experiences with Mock Objects.

When Are Mock Objects Useful?

Perhaps the main benefit of using Mock Objects is that because you are replacing the collaborators of an object, you don't have to set up all of the complex state that may be required to run those collaborators.
For example, I was recently working on some business logic that took data from an XML file, processed it, and inserted it in a database. When it came to testing the intricacies of the business logic, I didn't want to have to write an automated test to painstakingly set up XML files, trigger the business logic and then carefully check the contents of the database tables. All I cared about was the the business logic interacted correctly with the XML Parser and the database layer - ie, that it made the right calls with the right parameters at the right time. So I carefully defined my interfaces with the XML parser and the database, and then plugged 'Mock' implementations of these interfaces into the business logic when I was testing it.
An important thing to emphasis about Mock Objects is that they are really just a tool that helps you to test details of an object that would have otherwise been inaccessible or impractical to test.
For example, by mocking up a JDBC driver used by a Data Access Object, you can easily simulate the driver throwing SQLExceptions, and thus test how your DAO deals with them. Simulating such conditions with a real JDBC driver in an automated test would be much more difficult. And whilst mocking up parts of a JDBC driver can seem like pain, I believe that in the long run it's less painful than trying to set up a database to induce a particular behaviour that you are trying to test.
Overall I found that when developing with Mock Objects, I spent most of my time writing lots of small, focused tests, instead of spending lots of time thinking about how I could contrive a single big test that would exercise a deeply embedded piece of functionality.
The flipside of this is that using Mock Objects does not test the integration of your units. As you would know, sometimes defects can occur during the integration of components - there can be mismatches between what collaborators expect from one another.
For example, at some stage it would be a good idea to try your unit with a real JDBC driver just in case the query that you're passing to it is garbage. Consequently, higher-level tests of your unit are still a good idea. It's just that if it gets to a point where you're spending more time writing code that is 'non-core' to the test - ie, code for setting up or tearing down the particular state required - it might be worth using Mock Objects instead.

There are many Framework available for the same.
 
 This is just a quicky, doesn't mean much, but allows a look at how each framework's syntax allows to setup a expectation & return value for a method:
  • NMock:
    IMock mock = new DynamicMock(typeof(IRequest));
    IRequest request = (IRequest)mock.MockInstance;
    mock.ExpectAndReturn("Username","Ayende");
    testedObject.HandleRequest(request);
    mock.Verify();
    EasyMock.Net:
    MockControl control = MockControl.CreateControl(typeof(IRequest));
    IRequest request = (IRequest)control.GetMock();
    control.ExpectAndReturn(request.Username,"Ayende");
    control.Replay();
    testedObject.HandleRequest(mock);
    control.Verify();
    TypeMock.Net: [Update: This is the correct syntax]
    Mock requestMock = MockManager.Mock(typeof(ConcreteRequest));
    requestMock.ExpectAndReturn("Username","Ayende");
    //TypeMock.Net should take care of intercepting the calls for Username
    testedObject.HandleRequest(new ConcreteRequest());
    MockManager.Verify();
    Rhino Mocks Version 1.0:
    MockControl mockRequest = MockControl.CreateControl(typeof(IRequest));
    IRequest request = (IRequest)mockRequest.MockInstance;
    mockRequest.ExpectAndReturn(request.Username,"Ayende");
    mockRequest.Replay();
    testedObject.HandleRequest(request);
    mockRequest.Verify();
    NMock2:
    using(Mockery mocks = new Mockery())
    {
    IRequest request = (IRequest)mocks.NewMock(typeof(IRequest),"IRequest");
    Expect.Once.On(request).GetProperty("Username").Will(Return.Value("Ayende"));
    testedObject.HandleRequest(request);
    }
    Rhino Mocks Version 2.0:
    using(MockRepository mocks = new MocksRepository)
    {
    IRequest request = (IRequest)mocks.CreateMock(typeof(IRequest));
    Expect.On(request).Call(request.Username).Return("Ayende");
    mocks.ReplayAll();
    testedObject.HandleRequest(request);
    }
     

No comments:

Post a Comment