Five Unit Test Tips: #1: Use the data store
I’ve looked over some of my code lately, and found ways that I often improve my tests. I’m planning on writing a blog post for each of my five favorites.
First out: Using the data storage. I upgraded our API for billing customers. I had a few compilation errors in my code, as the API had changed somewhat. After fixing these errors, I was left with a test that broke mysteriously with a MethodNotFoundException
. I located the test and found that it was close to unreadable. After examining it further, I found that it was trying to do was simple, but hidden in technical code.
I had the following test (the design is real, the details have been changed to protect the guilty):
@Test
public void shouldBillExtraForErrors() {
BillingService billingService = someService.getBillingService();
MethodCallLoggingInterceptor interceptor =
MethodCallLoggingInterceptor.for(billingService);
someService.setBillingService((BillingService)interceptor);
Request requestWithError = createCustomerRequestWithError(CUSTOMER\_ID);
someService.process(requestWithError);
Method method = BillingService.class.getMethod("sendBillingEvent", ....);
MethodCall[] callsToBilling = interceptor.getMethodCallsFor(method);
assertEquals(2, callsToBilling);
assertEquals(CUSTOMER\_ID, callsToBilling[0].getParams()[0]);
assertEquals(BillingCode.RECEIVED\_REQUEST, callsToBilling[0].getParams()[3]);
assertEquals(CUSTOMER\_ID, callsToBilling[1].getParams()[0]);
assertEquals(BillingCode.INVALID\_REQUEST, callsToBilling[1].getParams()[3]);
}
When the parameters to sendBillingEvent
changed and the test started throwing MethodNotFoundException
I changed it to the following:
@Test
public void shouldBillExtraForErrors() {
Request requestWithError = createCustomerRequestWithError(CUSTOMER\_ID);
someService.process(requestWithError);
BillingFinder finder = BillingFinder.forCustomer(CUSTOMER\_ID);
finder.setBillingCode(BillingCode.RECEIVED\_REQUEST);
assertEquals(1, billingRepository.count(finder));
finder.setBillingCode(BillingCode. INVALID\_REQUEST);
assertEquals(1, billingRepository.count(finder));
}
The lessons:
- Ask about the outcome, not about the method calls that get you there
- Avoid reflection in tests. It’s bound to be brittle
- Mocks and similar methods are overrated
(In tip #2, I will show how I preserve the data store metaphor without giving up execution speed)