Integration testing with Mockito and Spring Boot

Within this post, I show you how to setup a Unit Test in Spring Boot performing HTTP requests with Mockito. In this case, we’re using Model in our controller, but we could follow similar instructions for a REST controller case.

Sometimes testing web access with Spring Boot can be tricky. There are some different annotations for different scenarios, and the configuration is also one of the most struggling parts.

Table of Contents

Maven configuration

First, you will need to include the corresponding dependency in your Spring Boot application. In my case and using Maven it will be something like this:

<project ...>
        [...]
    <dependencies>
                [...]
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>
Get the book Practical Software Architecture

This is the dependency provided by Spring Boot, containing Mockito in it. As usual, we want the dependency only for test purposes so we set the scope to test .

The test case

The scenario is a web page that is going to be shown when doing a specific request. We’re expecting some data in the model that later will be used and rendered in the HTML, in this case using Thymeleaf (irrelevant for the test).

We want to test the controller and view so we are going to mock the service to return some predefined data.

package es.macero.cqgame.controller;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import es.macero.cqgame.app.ApplicationTest;
import es.macero.cqgame.domain.stats.SonarStatsRow;
import es.macero.cqgame.service.SonarStatsService;

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@SpringApplicationConfiguration(classes = ApplicationTest.class)
public class SonarStatsControllerTest{

    private static final String TEAM_ONE = "Team One";
    private static final String JOHN_ONE = "John One";
    private static final String TEAM_TWO = "Team Two";
    private static final String JOHN_TWO = "John Two";
    private static final int INFO = 50;
    private static final int MINOR = 40;
    private static final int MAJOR = 30;
    private static final int CRITICAL = 20;
    private static final int BLOCKER = 10;
    private static final int TOTAL_PAID_DEBT = 100;
    private static final int TOTAL_POINTS = 25;

    @Autowired
    private WebApplicationContext context;

    @Autowired
    SonarStatsService sonarStatsService;

    private MockMvc mvc;

    @Before
    public void setUp() {
        this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build();
    }

    @Test
    public void testGetUsers() throws Exception {
        SonarStatsRow statsRow1 = new SonarStatsRow(JOHN_ONE, TEAM_ONE, TOTAL_POINTS, TOTAL_PAID_DEBT, BLOCKER,
                CRITICAL, MAJOR, MINOR, INFO, new ArrayList<>());
        SonarStatsRow statsRow2 = new SonarStatsRow(JOHN_TWO, TEAM_TWO, TOTAL_POINTS, TOTAL_PAID_DEBT, BLOCKER,
                CRITICAL, MAJOR, MINOR, INFO, new ArrayList<>());
        List<SonarStatsRow> expectedStatsRows = Arrays.asList(statsRow1, statsRow2);

        Mockito.when(sonarStatsService.getSortedStatsPerUser()).thenReturn(expectedStatsRows);
        
        this.mvc.perform(get("/legacykillers/users")).andExpect(status().isOk()).andExpect(view().name("sonarstats"))
                .andExpect(model().attribute("stats", expectedStatsRows));
    }

}

Pay attention here to the highlighted lines since they are the key for this to work:

  • @WebAppConfiguration  is a specific annotation for integration tests (check Javadoc here). It tells the framework to load a WebApplicationContext .
  • @SpringApplicationConfiguration  annotation is used to pass a specific configuration for the test. In this case, I have created a separated class for that but an inner class could also be used. This is really important because if you don´t use a different configuration for testing all the default beans in your Spring Boot app are going to be loaded.

So now let’s see what this specific configuration for testing contains:

package es.macero.cqgame.app;

import org.mockito.Mockito;
import org.springframework.context.annotation.Bean;

import es.macero.cqgame.controller.SonarStatsController;
import es.macero.cqgame.service.SonarStatsService;

public class ApplicationTest {

    @Bean
    public SonarStatsService service() {
        return Mockito.mock(SonarStatsService.class);
    }

    @Bean
    public SonarStatsController controller(SonarStatsService service) {
        return new SonarStatsController(service);
    }
    
}

Easy. Just injecting the controller with a service mock. It’s in the Unit Test where we use Mockito to pass the expected values, this way we can reuse this configuration class.

Another valid option would be creating profiles, this way the configuration would be taken automagically. But this is another story…

Want to know more about proper Unit Testing and Integration Testing with Spring Boot?

Moisés Macero's Picture

About Moisés Macero

Software Developer, Architect, and Author.
Are you interested in my workshops?

Amsterdam, The Netherlands https://thepracticaldeveloper.com

Comments