Unit Testing of Spring MVC Controllers

After all the services, DAO’s and support classes are tested then it’s time for the controller. Generally this is hard to test and most developers (based on observation) would rather just test it via Selenium or worse, by hand. That can work but it makes testing logic branches difficult and not to mention it’s time consuming. Plus no active developer would be willing to wait for browser tests to run before checking in code. Luckily the Spring MVC Test project can do full controller testing via unit tests, it was such a success that it’s now in Spring MVC core as of version 4.0.5.

Getting Ready

We can get the required testing dependencies by adding the following dependency declarations to the POM file of our example application:

  • JUnit: Unit testing framework for Java
  • Mockito: Mock lower layer
  • Jackson: Json support for your application
  • JsonPath: Assert json response in unit testing case

The relevant part of our pom.xml file looks as follows:

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.11</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>${spring.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-all</artifactId>
    <version>1.9.5</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>com.jayway.jsonpath</groupId>
    <artifactId>json-path</artifactId>
    <version>0.9.1</version>
    <scope>test</scope>
</dependency>

Let’s move on and find out how we can write unit tests for Spring MVC controllers by using the Spring MVC Test framework.

How to Write Test Cases for Controller

Every unit test which we write to test the behavior of a controller method consists of these steps:

  1. We send a request to the tested controller method.
  2. We verify that we received the expected response.

The Spring MVC Test framework has a few “core” classes which we can use for implementing these steps in our tests. These classes are described in the following:

  • We can build our requests by using the static methods of the MockMvcRequestBuilders class. Or to be more specific, we can create request builders which are then passed as a method parameter to the method which executes the actual request.
  • The MockMvc class is the main entry point of our tests. We can execute requests by calling its perform(RequestBuilder requestBuilder) method.
  • We can write assertions for the received response by using the static methods of the MockMvcResultMatchers class.

The Controller to be Tested

@Controller
@RequestMapping(value = "/accounts")
public class AccountsController {
    @RequestMapping(value = "/login")
    public ModelAndView loginView(
            @RequestParam(value="logout", required=false, defaultValue="false") boolean isLogout,
            HttpServletRequest request,
            HttpSession session) {
        if ( isLogout ) {
            destroySession(request, session);
        }
        return new ModelAndView("accounts/login");
    }

    ...

    @RequestMapping(value = "/login.action", method = RequestMethod.POST)
    public @ResponseBody HashMap<String, Boolean> loginAction(
            @RequestParam(value="username", required=true) String username,
            @RequestParam(value="password", required=true) String password,
            HttpServletRequest request,
            HttpSession session,
            Model model) {
        String ipAddress = request.getRemoteAddr();
        HashMap<String, Boolean> result = getLoginResult(username, md5(password));
        if ( result.get("isSuccessful") ) {
            getSession(request, session, this.user);
        }
        return result;
    }

    ...

    private HashMap<String, Boolean> getLoginResult(String username, String password) {
        HashMap<String, Boolean> result = new HashMap<String, Boolean>();
        result.put("isUsernameEmpty", username.isEmpty());
        result.put("isPasswordEmpty", password.isEmpty());
        result.put("isAccountValid", false);
        result.put("isSuccessful", false);
        
        if ( !result.get("isUsernameEmpty") && !result.get("isPasswordEmpty") ) {
            this.user = userService.isAccountValid(username, password);
            if ( user != null ) {
                result.put("isAccountValid", true);
                result.put("isSuccessful", true);
            }
        }
        return result;
    }

    ...
}

Create the Spring MVC Test class

Now it’s time to create the test class. The AccountsControllers has a few methods, one to display the login page, one to verify the effectiveness of the account. Spring Test will Mock the MVC container. This will let us make get/post requests to the configured endpoints (/,/accounts) and evaluate the Spring response. Even though we get the container mocked for us, the service/dao or any other dependencies are not. For a proper unit test we will use Mockito to mock the service.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:test-spring-context.xml"})
public class AccountControllerTest {
    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        mockMvc = MockMvcBuilders.standaloneSetup(accountsController).build();
    }

    private MockMvc mockMvc;
    
    @InjectMocks
    private AccountsController accountsController;
    
    @Mock
    private UserService userService;
}

The above file sets up the foundation for each test. The AccountsController depends on UserService so we set that as a Mock and tell mockito to inject it for us to the controller. MockMvc is from Spring Test and sets up the Mock container. Since we just want to test the AccountsController class in isolation we use the “standaloneSetup” method.

Writing Unit Tests for Controller Methods

Rendering The Login Page

Let’s start by taking a look at the implementation of the controller method which is used to render the login page.

Expected Behavior
  1. It processes GET requests send to url ‘/accounts/login’.
  2. It returns the name of the rendered view.

The relevant part of the AccountsController class looks as follows:

@Test
public void testLoginView() throws Exception {
    MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get("/accounts/login"))
        .andExpect(MockMvcResultMatchers.status().isOk())
        .andExpect(MockMvcResultMatchers.view().name("accounts/login"))
        .andExpect(MockMvcResultMatchers.forwardedUrl("accounts/login"))
        .andReturn();
    Assert.assertNotNull(result.getModelAndView());
}

Verify the Effectiveness of the Account

Before we can write the actual unit tests for our controller method, we have to take a closer look at the implementation of that method.

Expected Behavior

The implementation of the controller method which is used to verify the effectiveness of the account which has the following steps:

  1. Post the username and password
  2. Use UserService to verify if the account is valid
  3. Return a JSON object
@Test
public void testLoginActionSuccessful() throws Exception {
    String username = "20116524";
    String password = DigestUtils.md5Hex("Password");
    
    Mockito.when(userService.isAccountValid(username, password))
            .thenReturn(new User(username, password));
    
    MvcResult result = mockMvc.perform(
            MockMvcRequestBuilders.post("/accounts/login.action")
                .param("username", "20116524")
                .param("password", "Password"))
            .andExpect(MockMvcResultMatchers.status().isOk())
            .andExpect(MockMvcResultMatchers.content().contentType("application/json;charset=UTF-8"))
            .andExpect(MockMvcResultMatchers.jsonPath("$.isSuccessful").value(new Boolean(true)))
            .andExpect(MockMvcResultMatchers.jsonPath("$.isUsernameEmpty").value(new Boolean(false)))
            .andExpect(MockMvcResultMatchers.jsonPath("$.isPasswordEmpty").value(new Boolean(false)))
            .andExpect(MockMvcResultMatchers.jsonPath("$.isAccountValid").value(new Boolean(true)))
            .andReturn();
    Assert.assertNotNull(result);
}

Summary

We have now written some unit tests for controller methods by using the Spring MVC Test framework. This tutorial has taught has four things:

  • We learned to create requests which are processed by the tested controller methods.
  • We learned to write assertions for the responses returned by the tested controller methods.
  • We learned how we can write unit tests for controller methods which render a view.
  • We learned to write unit tests for controller methods which handle form submissions.
Contact Us
  • Room 614, Zonghe Building, Harbin Institute of Technology
  • cshzxie [at] gmail.com