Unit Test Controllers with Spring MVC Test

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 3.2.

Getting Ready

Application Setup
These tests are built on the Spring Data JPA example in a previous post.

For this test we add a few dependencies to the pom.xml

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>3.2.3.RELEASE</version>
</dependency>

<!-- This is for mocking the service -->

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-all</artifactId>
    <version>1.9.5</version>
    <scope>test</scope>
</dependency>

<!-- Optional -->
<dependency>
    <groupId>org.hamcrest</groupId>
    <artifactId>hamcrest-core</artifactId>
    <version>1.3</version>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.hamcrest</groupId>
    <artifactId>hamcrest-library</artifactId>
    <version>1.3</version>
    <scope>test</scope>
</dependency>

Reference Class

Below is the Index Controller class we are testing:

@Controller
public class IndexController {

    public static final String PAGE_INDEX = "index";
    public static final String PAGE_SHOW = "show";

    @Autowired
    SampleService sampleService;


    @RequestMapping(value = "/", method = RequestMethod.GET)
    public ModelAndView index() {
        return new ModelAndView(PAGE_INDEX, "signupForm", new SignupForm());
    }

    @RequestMapping(value = "/create", method = RequestMethod.POST)
    public String create(Model model, @Valid SignupForm signupForm, BindingResult result) {

        String returnPage = PAGE_INDEX;

        if (!result.hasErrors()) {
            try {
                model.addAttribute("signupForm", sampleService.saveFrom(signupForm));
                returnPage = PAGE_SHOW;
            } catch (InvalidUserException e) {
                model.addAttribute("page_error", e.getMessage());
            }
        }
        return returnPage;
    }

    @RequestMapping(value = "/security-error", method = RequestMethod.GET)
    public String securityError(RedirectAttributes redirectAttributes) {
        redirectAttributes.addFlashAttribute("page_error", "You do have have permission to do that!");
        return "redirect:/";
    }
}

Create the Spring MVC Test class

Now it’s time to create the test class “com.luckyryan.sample.webapp.controller.IndexControllerTest”. The controller has a few methods, one to show the form, one to create it (handle the post) and one for security errors. This example will only focus on the create method since “index” has no logic and “securityError” is for HDIV (Post about HDIV).

Spring Test will Mock the MVC container. This will let us make get/post requests to the configured endpoints (/,/create) 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. If you want to know more about Mockito then check out this post.

public class IndexControllerTest {

    @Mock
    private SampleService sampleService;

    @InjectMocks
    private IndexController indexController;

    private MockMvc mockMvc;

    @Before
    public void setup() {

        // Process mock annotations
        MockitoAnnotations.initMocks(this);

        // Setup Spring test in standalone mode
        this.mockMvc = MockMvcBuilders.standaloneSetup(indexController).build();

    }
}

The above file sets up the foundation for each test. The IndexController depends on SampleService 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 IndexController class in isolation we use the “standaloneSetup” method.

  @Test
  public void testCreateSignupFormInvalidUser() throws Exception {

      when(sampleService.saveFrom(any(SignupForm.class)))
              .thenThrow(new InvalidUserException("For Testing"));

      this.mockMvc.perform(post("/create")
              .param("email", "mvcemail@test.com")
              .param("firstName", "mvcfirst")
              .param("lastName", "mvclastname"))
              .andExpect(status().isOk())
              .andExpect(forwardedUrl(IndexController.PAGE_INDEX))
              .andExpect(model().attributeExists("page_error"));

  }

Now we add the first test. This test will simulate a form post with an invalid user. The expected outcome is for the user to be directed to the index page with the “page_error” attribute set in the model.

The first “when” statement is a Mockito static method and instructs the mock service to throw and InvalidUserException when invoked by the controller. To build the test we use the mockMvc object created in the setup method. We call “perform” to make a mock call and then add Spring Excepts (assertions) to the result. You could just grab the result in a variable and use JUnit assertions but the built in matcher and Spring MVC helpers make it easy to follow their convention.

To test an invalid user we must construct a post call using post(). Post takes the URL destination as the method arg. You will want to use the same URL as what was set in the RequestMapping on the controller method. Post uses a builder pattern so we can add all the required params in a easy readable chain.

After the params we add our assertions. The first (.andExpect(status().isOk())) is a basic check for a 200 status. This is kind of a gimme since we don’t alter the status in any way in the create method but good to keep to catch potential future changes that might cause a bug.

Next .andExpect(forwardedUrl(IndexController.PAGE_INDEX)) validates that we are sending the user to the index page. The last assertion .andExpect(model().attributeExists(“page_error”)) looks into the model and validates that an attribute named “page_error” exists.

Now you can run the test and see it pass.

There is a more advanced assertion which I will cover in the Sprint MVC Integration Test post.

Testing Series
This post is one in a series about end to end testing of Spring MVC applications. If you are interested in other levels of application testing then check out the series index here.

12 Comments

  1. Will November 3, 2016
  2. Gary January 19, 2016
    • Ryan January 20, 2016
  3. binyam September 21, 2015
  4. Hussien June 20, 2014
    • Ryan June 20, 2014
  5. Hari Iyengar May 27, 2014
    • Ryan May 28, 2014
  6. Jakub March 22, 2014
    • Ryan March 22, 2014
  7. raghavendra March 18, 2014
    • Ryan March 18, 2014

Leave a Reply

Your email address will not be published. Required fields are marked *