Fun with jMock
November 22nd, 2009 |
Here at Palantir, a lot of our automatic tests are full-chain tests. A backend server is fired up, client code runs against it, and everything runs much like a production environment. This makes intuitive sense because it’s a faithful approximation of how the system will run in the field.
However, there are some disadvantages to this:
- Full-pass tests don’t always localize the problem. Tests on a client class might fail even if it was the service that behaved incorrectly.
- These full-pass tests are relatively slow. Client code is running against an actual remote service. If a client is being tested, the server code still has to do work — sometimes a lot of work — even if that isn’t the focus of the test.
- The constraints of the test are loose. Full-chain tests can mostly only see whether the operation finished correctly. It’s much harder to figure out whether the operation was done efficiently and without making unnecessary service calls.
- They’re very little setup flexibility. If you want an RPC to return a specific value, you have little choice but to have your test get the service into a state where it can return that value. This is easy in some cases, but prohibitively difficult in others.
- Client tests are forced to share any non-determinism leaked from the service. For example, under real conditions, a request to call A might respond before call B, and sometimes the other way around. This can result in flaky tests or tests that don’t always simulate the conditions you want to exercise.
What’s to be done? Fortunately, there’s an option that handles these cases elegantly. We also test with jMock, a library that dynamically generates mock objects from arbitrary interfaces. These mock objects can be configured to check that particular methods are called with particular inputs a particular number of times, and then give prescribed responses.
Hit the link to see a concrete example of jMock in action.
jMock in action
Let’s say I want to test my object viewer page in Palantir Web, but I don’t want to fire up a dispatch server at all. First, I create my mock service object.
Mockery context = new Mockery(); final PalantirService service = context.mock(PalantirService.class);
Then, I set the expectations of my mock object. In this case, I want to tell my mock object to expect a call to PalantirService.getObject() and PalantirService.getDataSources(). getObject() will return a specific object. Any call made to the service apart from these will make the test fail.
context.checking(new Expectations() {{
oneOf(service).getObject(realm.getId(), myObject.getId());
will(returnValue(myObject));
oneOf(service).getDataSources(myObject.getDataSources());
}});
Now, I create the object I want to test and inject the service.
ObjectViewController controller = new ObjectViewController(); controller.setService(service);
And then we fire away.
ModelMap model = new ModelMap(); controller.doGet(myObject.getId(), model);
Now that the controller (the class we’re exercising) has gone off and populated the model, we check to see that the model is populated correctly. Just like we would in any other test.
assertEquals(myObject.getName(), model.get("objectName"));
assertEquals(myObject, model.get("object"));
But in addition, we also assert that the expectations specified above were satisfied.
context.assertIsSatisfied();
Not only can we be sure that the right calls were made with the right parameters, but we can also be sure that no calls besides the expected calls were made. So the next time you want more speed or control over your tests, take a look at jMock or another framework like it. It’s a powerful tool in the effort to test your best!







Thanks for the reference. There are a few extra points to note:
Adding @RunWith(JMock.class) to the JUnit class will call assertIsSatisfied() automatically for each test
When we specify a query, such as:
oneOf(service).getObject(realm.getId(), myObject.getId()); will(returnValue(myObject));
we usually make that
allowing(service).getObject(realmId, objectId); will(returnValue(myObject));
Queries don’t change the world outside the object, so we stub them to express that relationships and to make the tests more resilient as the code changes.
November 24th, 2009 at 4:20 am