Five Unit Test Tips: #2: Change your code to make testing easier

It’s been a while since I promised to write top five unit testing tips, so I guess I should better start writing #2. As with my first example this one is based on a real-life story.

Your code is worth nothing if it can’t be tested. Change your code to make it more testable.

In my last project, we used a pipes and filter architecture, with messages being passed forward through a chain of services. Most services refined the messages or broke them up in smaller services and passed them on to the next service. When they refined the message, they often stored new messages to the data layer as a side effect. To test the results, we read the generated objects back from the data layer. I had code like this:

Before

@Test
public void fileShouldCreateTransactionObject() {
    Reader simulatedFile = new StringReader(createFileContents());
    FileRequest file = new FileRequest(filename, simulatedFile);
 
    TransactionRepository repo = new TestTransactionRepository();
 
    FileService fileService = new FileService();
    TransactionService transService = new TransactionService();
    fileService.setNextStep(transService);
    fileService.setTransactionRepo(repo);
 
    fileService.process(simulatedFile);
 
    Collection<Transaction> transactions = repo.findAll();
    assertEquals(1, transactions.size());
    Transaction transaction = transactions.iterator().next(); // Bleh!
 
    // Here is the real code I wanted
    assertEquals("foo", transaction.getTransactionType()); // Whatever
}

The solution was to change the design away from the pipes-and-filters design. Instead, the process() method returns the newly created Transaction object:

After

@Test
public void fileShouldCreateRequestObject() {
    Reader simulatedFile = new StringReader(createFileContents());
    FileRequest file = new FileRequest(filename, simulatedFile);
 
    FileService fileService = new FileService();
    Transaction transaction = fileService.transform(simulatedFile);
 
    // Here is the real code I wanted
    assertEquals("foo", transaction.getTransactionType()); // Whatever
}

This new design totally violated our architecture, but it’s clearer, it keeps the infrastructure away from the code, and it’s much less brittle. Our test transaction repository used to be vulnerable to being polluted with test data from a previous run. As we no longer use this sort of infrastructure objects, we don’t have to worry about interaction between tests.

If you don’t know how to test your architecture, the problem is not with the testing, but with the architecture.

Until next time: Happy testing.

Creative Commons License
This work is licensed under a Creative Commons Attribution 3.0 License.

Print This Post Print This Post
blog comments powered by Disqus
Creative Commons Attribution 3.0 Unported
This work is licensed under a Creative Commons Attribution 3.0 Unported.