Codementor Events

Writing effective unit tests in Java with Mockito

Published Aug 15, 2018Last updated Feb 11, 2019
Writing effective unit tests in Java with Mockito

Unit tests are an absolutely critical component of any serious software application. This is not a ground-breaking statement, and most developers would agree that effective unit tests are a useful way to verify the feature you're implementing or the bug you're fixing, and are especially useful as a safeguard against future regressions and bugs.

Part of my development process for any application I write, whether professional or as a hobby, includes writing at least one unit test for every feature I write or bug I fix, and I highly recommend other developers pursue the same goal.

Even if a developer writes unit tests though, sometimes those tests can be, well... not terribly useful. This can happen for a number of reasons:

  • Product deadlines
  • The developer finds writing tests boring
  • There are requirements for class/line coverage

What is your test verifying?

Every test needs to verify something. Ideally, that "something" is the "contract" of the method or process. The contract can be thought of as "what does this method do?" or possibly "what does this method definitely not do?"

Rudimentary stuff so far. And yet in my career I've come across plenty of unit tests that don't seem to be verifying anything useful. Let's go over a simple hypothetical example:

Say for example you're writing a unit test for a servlet controller that backs a JSP web page. When the page loads, you want to load a list of entities from the database into the session, so they will be available on the page (maybe not the best design but let's go with it for now).

// ClientPageController.java
public String loadPage(HttpSession session, String clientId) throws Exception {
    List<Customer> customers = customerService.findByClientId(clientId);
    session.setAttribute("customers", customers);
    return "myPage";
}

Pretty simple so far. But then you see this unit test:

@Test
public void testLoadPage() throws Exception {
  HttpSession session = mock(HttpSession.class);
  String result = clientPageController.loadPage(session, "my_client_id");
    assertEquals("myPage", result);
}

There's something missing here: part of this method's contract is setting the list of customers in the HttpSession, and yet there's nothing in the test verifying this. You're verifying the return value is correct, and you've gained some test coverage, but your test is missing an important verification. Let's say another developer wanders into this code base and mistakenly commits this change:

// ClientPageController.java
public String loadPage(HttpSession session, String clientId) throws Exception {
    // TODO: uncomment when compilation errors are fixed
    // List<Customer> customers = customerService.findByClientId(clientId);
    // session.setAttribute("customers", customers);
    return "myPage";
}

The unit test above still passes. That's a problem! So you decide to adjust your unit test with a verification:

@Test
public void testLoadPage() throws Exception {
  HttpSession session = mock(HttpSession.class);
  String result = clientPageController.loadPage(session, "my_client_id");
    assertEquals("myPage", result);
    verify(session).setAttribute("customers", anyListOf(Customer.class));
}

We're doing a little better, but there's still a problem.

Don't abuse Mockito's any() matchers

You'd be surprised how common this is. Proper verifications can be difficult or time consuming to write, so sometimes developers will take a shortcut by just verifying that a method gets called with any arguments, as above. But think back to the discussion about the method's "contract" earlier and what we're verifying. Is the loadPage() method just supposed to set the session attribute to any list? Or is it supposed to load a specific list into the session?

Verify verify verify

We want the loadPage() method to load the list of customers for the client into the session. So that's what we want to verify. Let's see a simple way to make this happen:

@Mock CustomerService customerService;

@Test
public void testLoadPage() throws Exception {
  List<Customer> fakeCustomers = new ArrayList<>();
    fakeCustomers.add(buildFakeCustomer());
    when(customerService.findByClientId("my_client_id"))
    	.thenReturn(fakeCustomers);

  HttpSession session = mock(HttpSession.class);
  String result = clientPageController.loadPage(session, "my_client_id");
    assertEquals("myPage", result);
    verify(session).setAttribute("customers", fakeCustomers);
}

Much better! Our unit test is more robust now. When we call verify(session).setAttribute("customers", fakeCustomers) we're effectively verifying that the HttpSession is set with the list of customers retrieved from the database.

The takeaway

Don't let your unit tests get sloppy just because you're chasing class/line coverage numbers, or because you think writing tests is boring. For every test you write, think carefully about what the method or process does, and how best to verify the method behaves as intended. When you see your test runs light up with green check marks, you'll feel better knowing each one is meaningful.

Discover and read more posts from Joe Cavazos
get started
post commentsBe the first to share your opinion
Show more replies