Mockobjects are the classic solution to the even more classic problem of how to test code in isolation, when that code needs other objects to perform its tasks. The problem is we want to test classes individually, without introducing too many other objects that may make our tests too complex.
In this article I hope to wet your appetite for this technique. First I will illustrate the problem with a simple example. Then I will demonstrate a solution, using EasyMock to create mockobjects with minimal coding effort.
The problem
First consider this typical service interface:
public interface Service { /** Executes the requested service. */ public ServiceResult execute(ServiceRequest request); /** Commits changes, after the service executed successfully. */ public void commit(); /** Rolls back changes, after a failure occurred. */ public void rollback(); }
This service processes a request and produces a result. After successful processing the commit() method should be called to consolidate the results. When a failure occurs, rollback() should be called instead.
The actual service that implements the interface could be a sophisticated system communicating with back-end systems like a database.
Now we want to create a facade for the service, a ServiceFacade, that wraps the interaction with the service interface in a single call.
This is provided for by the ServiceFacade method service():
/** * Tries to execute the requested service. * * @param request The service request. * @return The service result. * @throws RuntimeExeption when a failure occurs. */ public ServiceResult service(ServiceRequest request) { ... }
ServiceFacade needs a Service to perform its duty, so a Service is injected upon creation of a ServiceFacade:
/** Constructor. */ public ServiceFacade(Service service) { ... }
Now here is the problem: we want to create a JUnit test for the ServiceFacade’s service() method. In order to instantiate a ServiceFacade object to test, we need to inject it with a Service. If we use the actual Service implementation for this, chances are our test is going to be too complicated, as we need to access the back-end system to perform the test. In addition, we may need to rely on assumptions about the back-end system like the presence of certain testdata in the database. Moreover, we are not testing our method in isolation, and the test may fail for many reasons external to the code of our method.
The solution
Let’s restate the purpose of our test. The service() method should call the Service with the proper arguments, and, when the call is successful, commit to the service and return the result. Alternatively, when a failure occurs, it should perform a rollback on the service and throw a RuntimeException.
So, in order to test the method in isolation, all we need to test is that the Service methods are called according to this prescription. But how?
This is where mockobjects come in. In short: we create a mockobject that implements the Service interface and use it to create a ServiceFacade instance for our test. Next we tell the mockobject how it’s methods are expected be called, and how to react. Then we call the ServiceFacade’s service() method. Finally we verify that the result of the call is as expected and that the mockobject was called as expected.
Sounds awkward? Let’s how to do this in actual code, using EasyMock!
The code of our test starts with the creation of a so called MockControl. We hand it the interface to be implemented, and will need it later on to, ahem, control the mockobject.
MockControl control = MockControl.createStrictControl(Service.class);
Now we can create the mockobject. Note that it can be cast to Service, as the control creates an implementation of the interface it was handed in the previous line of code.
Service mockService = (Service) control.getMock();
Now that we have the mock Service, we can use it to create the ServiceFacade instance under test.
ServiceFacade facade = new ServiceFacade(mockService);
Next, we create dummy request and response objects that we will need for our tests.
ServiceRequest testRequest = new ServiceRequest(); ServiceResult testResult = new ServiceResult();
The next step is to tell the mockobject how it is expected to be called, and how it should react. Which is as follows:
- it’s execute() method is called with parameter testRequest
- it responds by returning testResult
- it’s commit() method is called
- and that’s all…
Here’s how to do this:
te(testRequest); control.setReturnValue(testResult); mockService.commit(); control.replay();
This may look puzzling at first. Imagine we are recording calls to the mockobject, and we finalize the recording by calling control.replay().
Now everything is in place to perform the actual test:
ServiceResult result = facade.service(testRequest); assertEquals(testResult, result); control.verify();
What happens here is this: we call the method under test. If the resulting calls to the mockobject are as expected, it will react as prescribed in the previous lines. If the mockobject is called in an unexpected way, it will throw an exception and the test fails. Next we test that the result of the call is same as the result returned by the mockobject. Finally, the call to control.verify() will throw an exception when the mockobject has not yet received all the expected calls.
Were done!
Once you can get your head around the previous code snippets, I’ll show you the remaining test without detail comments. This code tests the behaviour of the code when the call to the service fails:
// Record the expected calls to the service. control.reset(); mockService.execute(testRequest); control.setThrowable(new RuntimeException("Service failure!")); mockService.rollback(); control.replay(); // Execute the text. try { facade.service(testRequest); fail("Should throw exception."); } catch (Throwable e) { assertTrue(e instanceof RuntimeException); } control.verify();
By now I hope you are as much impressed as I was upon learning how easy it can be to use mockobjects with EasyMock. It puts the fun right back in JUnit testing!
Considerations
The sample code above uses EasyMock v1.2. There’s a v2.0 as well, but I it requires Java 5.
Upon closer inspection of the sample code, you may observe that the class under test needs to fulfill a few requirements in order to lend itself to this technique.
First of all, the class under test must provide methods for the mockobject to be injected, using either the constructor or a setter method. In other words, it must use the dependency injection pattern.
Secondly, the injection refers to an interface rather than a concrete class, since an interface is required to create a mockobject. (Actually, there’s an extension avaible that allows to create mockobjects to extend concrete classes, but it relies on EasyMock v2.0 and Java 5.)
Meeting these requirements shouldn’t be a problem as long as testability of the code is given proper attention from the outset. This is just one of the area’s where Test Driven Development (TDD) has its benefits. Mockobjects and TDD are a natural marriage, as the mockobjects can just as well be created for interfaces that haven’t been implemented yet. In short: TDD rules!
Hi,
A good solution!
But how can solve this problem that the testing one class has extend and one more dependency?