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:
The idea behind Mock Objects is this: when unit testing an object,
you replace its collaborators with 'Mock' collaborators. This is
illustrated below:
So how is this different from stubs? Well, whilst there are some similarities, there are also two important distinctions:
- 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.
- 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.
- NMock
- EasyMock.NET
- TypeMock Isolator Commercial / Paid
- Rhino Mocks
- Moq
- NSubstitute
- JustMock Commercial / Paid
- FakeItEasy
- Microsoft Fakes (formerly Moles) Commercial (included with VS 11 Premium/Ultimate)
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));EasyMock.Net:
IRequest request = (IRequest)mock.MockInstance;
mock.ExpectAndReturn("Username","Ayende");
testedObject.HandleRequest(request);
mock.Verify();
MockControl control = MockControl.CreateControl(typeof(IRequest));TypeMock.Net: [Update: This is the correct syntax]
IRequest request = (IRequest)control.GetMock();
control.ExpectAndReturn(request.Username,"Ayende");
control.Replay();
testedObject.HandleRequest(mock);
control.Verify();
Mock requestMock = MockManager.Mock(typeof(ConcreteRequest));Rhino Mocks Version 1.0:
requestMock.ExpectAndReturn("Username","Ayende");
//TypeMock.Net should take care of intercepting the calls for UsernametestedObject.HandleRequest(new ConcreteRequest());
MockManager.Verify();
MockControl mockRequest = MockControl.CreateControl(typeof(IRequest));NMock2:
IRequest request = (IRequest)mockRequest.MockInstance;
mockRequest.ExpectAndReturn(request.Username,"Ayende");
mockRequest.Replay();
testedObject.HandleRequest(request);
mockRequest.Verify();
using(Mockery mocks = new Mockery())Rhino Mocks Version 2.0:
{
IRequest request = (IRequest)mocks.NewMock(typeof(IRequest),"IRequest");
Expect.Once.On(request).GetProperty("Username").Will(Return.Value("Ayende"));
testedObject.HandleRequest(request);
}
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