Five Unit Test Tips: #1: Use the data store

how-to
Sep 8, 20087 mins

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
<span style="color: #000000; font-weight: bold;">public</span> <span style="color: #993333;">void</span> shouldBillExtraForErrors<span style="color: #66cc66;">(</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span>
   BillingService billingService = someService.<span style="color: #006600;">getBillingService</span><span style="color: #66cc66;">(</span><span style="color: #66cc66;">)</span>;
   MethodCallLoggingInterceptor interceptor =
        MethodCallLoggingInterceptor.<span style="color: #b1b100;">for</span><span style="color: #66cc66;">(</span>billingService<span style="color: #66cc66;">)</span>;
   someService.<span style="color: #006600;">setBillingService</span><span style="color: #66cc66;">(</span><span style="color: #66cc66;">(</span>BillingService<span style="color: #66cc66;">)</span>interceptor<span style="color: #66cc66;">)</span>;
 
   <span style="color: #aaaadd; font-weight: bold;">Request</span> requestWithError = createCustomerRequestWithError<span style="color: #66cc66;">(</span>CUSTOMER_ID<span style="color: #66cc66;">)</span>;
   someService.<span style="color: #006600;">process</span><span style="color: #66cc66;">(</span>requestWithError<span style="color: #66cc66;">)</span>;
 
   <span style="color: #aaaadd; font-weight: bold;">Method</span> method = BillingService.<span style="color: #000000; font-weight: bold;">class</span>.<span style="color: #006600;">getMethod</span><span style="color: #66cc66;">(</span><span style="color: #ff0000;">"sendBillingEvent"</span>, ....<span style="color: #66cc66;">)</span>;
   MethodCall<span style="color: #66cc66;">[</span><span style="color: #66cc66;">]</span> callsToBilling = interceptor.<span style="color: #006600;">getMethodCallsFor</span><span style="color: #66cc66;">(</span>method<span style="color: #66cc66;">)</span>;
 
   assertEquals<span style="color: #66cc66;">(</span><span style="color: #cc66cc;">2</span>, callsToBilling<span style="color: #66cc66;">)</span>;
   assertEquals<span style="color: #66cc66;">(</span>CUSTOMER_ID, callsToBilling<span style="color: #66cc66;">[</span><span style="color: #cc66cc;">0</span><span style="color: #66cc66;">]</span>.<span style="color: #006600;">getParams</span><span style="color: #66cc66;">(</span><span style="color: #66cc66;">)</span><span style="color: #66cc66;">[</span><span style="color: #cc66cc;">0</span><span style="color: #66cc66;">]</span><span style="color: #66cc66;">)</span>;
   assertEquals<span style="color: #66cc66;">(</span>BillingCode.<span style="color: #006600;">RECEIVED_REQUEST</span>, callsToBilling<span style="color: #66cc66;">[</span><span style="color: #cc66cc;">0</span><span style="color: #66cc66;">]</span>.<span style="color: #006600;">getParams</span><span style="color: #66cc66;">(</span><span style="color: #66cc66;">)</span><span style="color: #66cc66;">[</span><span style="color: #cc66cc;">3</span><span style="color: #66cc66;">]</span><span style="color: #66cc66;">)</span>;
   assertEquals<span style="color: #66cc66;">(</span>CUSTOMER_ID, callsToBilling<span style="color: #66cc66;">[</span><span style="color: #cc66cc;">1</span><span style="color: #66cc66;">]</span>.<span style="color: #006600;">getParams</span><span style="color: #66cc66;">(</span><span style="color: #66cc66;">)</span><span style="color: #66cc66;">[</span><span style="color: #cc66cc;">0</span><span style="color: #66cc66;">]</span><span style="color: #66cc66;">)</span>;
   assertEquals<span style="color: #66cc66;">(</span>BillingCode.<span style="color: #006600;">INVALID_REQUEST</span>, callsToBilling<span style="color: #66cc66;">[</span><span style="color: #cc66cc;">1</span><span style="color: #66cc66;">]</span>.<span style="color: #006600;">getParams</span><span style="color: #66cc66;">(</span><span style="color: #66cc66;">)</span><span style="color: #66cc66;">[</span><span style="color: #cc66cc;">3</span><span style="color: #66cc66;">]</span><span style="color: #66cc66;">)</span>;
<span style="color: #66cc66;">}</span>

When the parameters to sendBillingEvent changed and the test started throwing MethodNotFoundException I changed it to the following:

@Test
<span style="color: #000000; font-weight: bold;">public</span> <span style="color: #993333;">void</span> shouldBillExtraForErrors<span style="color: #66cc66;">(</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span>
   <span style="color: #aaaadd; font-weight: bold;">Request</span> requestWithError = createCustomerRequestWithError<span style="color: #66cc66;">(</span>CUSTOMER_ID<span style="color: #66cc66;">)</span>;
   someService.<span style="color: #006600;">process</span><span style="color: #66cc66;">(</span>requestWithError<span style="color: #66cc66;">)</span>;
 
   BillingFinder finder = BillingFinder.<span style="color: #006600;">forCustomer</span><span style="color: #66cc66;">(</span>CUSTOMER_ID<span style="color: #66cc66;">)</span>;
   finder.<span style="color: #006600;">setBillingCode</span><span style="color: #66cc66;">(</span>BillingCode.<span style="color: #006600;">RECEIVED_REQUEST</span><span style="color: #66cc66;">)</span>;
   assertEquals<span style="color: #66cc66;">(</span><span style="color: #cc66cc;">1</span>, billingRepository.<span style="color: #006600;">count</span><span style="color: #66cc66;">(</span>finder<span style="color: #66cc66;">)</span><span style="color: #66cc66;">)</span>;
   finder.<span style="color: #006600;">setBillingCode</span><span style="color: #66cc66;">(</span>BillingCode. <span style="color: #006600;">INVALID_REQUEST</span><span style="color: #66cc66;">)</span>;
   assertEquals<span style="color: #66cc66;">(</span><span style="color: #cc66cc;">1</span>, billingRepository.<span style="color: #006600;">count</span><span style="color: #66cc66;">(</span>finder<span style="color: #66cc66;">)</span><span style="color: #66cc66;">)</span>;
<span style="color: #66cc66;">}</span>

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)