How we do unit testing with JUnit and Mockito

http://blog.iprofs.nl/2011/03/03/how-we-do-unit-testing-with-junit-and-mockito/

03/03/2011 Leave a comment Go to comments

Introduction

Recently iPROFS hired some new guys and I have had the pleasure of working closely with them in their first period with us.
It is always nice to get a lot of guestions like ‘How do you things like ……’ and ‘Is there a standard for ……..’
Some of the things we take for granted, required some explanation. One of the areas of our craftsmanship, where we have some de facto standards is Unit testing. Our company always had a focus on quality, so Unit testing is a required basic skill.

We use JUnit and Mockito for Unit testing in all our projects.

This post describes how we do unit testing based upon a basic and simple scenario in a web environment.

Scenario

A user selects a product in order to add them to the shopping cart.

A Unit test should specify a given scenario, invoke the class under test and verify if the class did what it had to do.
I start writing my tests with three lines of comment:

  1. //describe scenario
  2. //perform action
  3. //verify outcome


Similar to the Given, When, Then sentence from behaviour driven development, but changed due to a naming conflict on the word when with Mockito. The exacts words don’t matter as long as it helps set your mind in the right mood.

Writing the unit test

I’ll start by creating the Test-class with some basic components: An instance of the class to test and a test-method to invoke a single method of that instance.


public class ProductSelectorServletTest() {
// Instantiate the class under test.
private ProductSelectorServlet servlet = new ProductSelectorServlet();
private HttpServletRequest request;
private HttpServletResponse response;
public void testAddSelectedProductToShoppingCard() {
/* describe scenario */
// User selected a product and submitted it.
/* perform action */
servlet.doPost(request, response);
/* verify outcome */
// The selected product should have been added to the shopping cart.
}
From this point on it’s just some small steps to implement that in the test code.
Describe the scenario in code

We specify the behaviour of the HttpServletRequest in a set of rules how object should behave when they are invoked by the class under test. In order to specify those rules dynamically, those object should be a Mockito mock, so we need to add the @Mock-annotation to the fields, eg: @Mock private HttpServletRequest request
This alone will not get it working, because by default JUnit doesn’t know how to handle @Mock-annotations. In order to have Mockito initialize the annotated fields we need to the JUnit to run this Test-class with Mockito:


@RunWith(MockitoJUnitRunner.class)
public class ProductSelectorServletTest() {
…..
}

Now start translating the scenario in to actual java statements:


/* describe scenario */
when(request.getParameter("productId")).thenReturn("someId");

Oke, that looks oke, but probably the servlet uses some backend service to get the Product based upon it’s id. So we need to add another line:

when(backendService.getProduct("someId")).thenReturn(someProduct);

This adds two new object to be added as Mock’s

@Mock private BackendService backendService;
@Mock private Product someProduct;

This code assumes that the servlet has access to the backendService, so this requires some initialization of our test:

@Before
public void setUp() {
servlet.setBackendService(backendService);
when(request.getSession()).thenReturn(session);
when(session.getAttribute("shoppingCart")).thenReturn(shoppingCart);
}

Those last two line may have been a surprise, but we need to specify that behavior in order for the servlet to use the proper instances, so we can validate that changes made to it. And so we need two additional mocked fields.


@Mock private HttpSession session;
@Mock private ShoppingCart shoppingCart;
Verification in code
Again: Just translate the requirements into java statements:
// The selected product should have been added to the shopping cart.
verify(shoppingCart).addProduct(someProduct);

Adding this verify statement will instruct the mock shoppingCart to fail the test if the specified method is not invoked with the proper arguments. In those case we know exactly what the argument should be, so we can specify it.
In other cases that might not be true and we need to implement a different way to match the argument of the invoked method to something we expect. Mockito provided various Matchers for that purpose, with the ability to write your own.

Practical tips for Eclipse Users

For those of you, whom use Eclipse as their IDE, just like me, some practical tips:

  1. Add the following types to your favorites for code completion: (Java > Editor > Content Assist > Favorites in preferences)
    • org.junit.Assert
    • org.mockito.Matchers
    • org.mockito.Mockito
  2. Install EclEmma as a Java Code Coverage Plugin to determine if you need additional test scenarios for full test coverate.

Entire test class

Combining all code from this article should result in the following Test:

import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class ProductSelectorServletTest {
private ProductSelectorServlet servlet = new ProductSelectorServlet();
@Mock private HttpServletRequest request;
@Mock private HttpServletResponse response;
@Mock private HttpSession session;
@Mock private BackendService backendService;
@Mock private Product someProduct;
@Mock private ShoppingCart shoppingCart;
@Before
public void setUp() {
servlet.setBackendService(backendService);
when(request.getSession()).thenReturn(session);
when(session.getAttribute("shoppingCart")).thenReturn(shoppingCart);
}
@Test
public void testAddSelectedProductToShoppingCard() throws ServletException, IOException {
/* describe scenario */
// User selected a product and submitted it.
when(request.getParameter("productId")).thenReturn("someId");
when(backendService.getProduct("someId")).thenReturn(someProduct);
/* perform action */
servlet.service(request, response);
/* verify outcome */
// The selected product should have been added to the shopping cart.
verify(shoppingCart).addProduct(someProduct);
}
}