A logo showing the text blog.marcnuri.com
Español
Home»Java»MockMvc – Spring MVC testing framework introduction: Testing Spring endpoints

Recent Posts

  • Fabric8 Kubernetes Client 7.2 is now available!
  • Connecting to an MCP Server from JavaScript using AI SDK
  • Connecting to an MCP Server from JavaScript using LangChain.js
  • The Future of Developer Tools: Adapting to Machine-Based Developers
  • Connecting to a Model Context Protocol (MCP) Server from Java using LangChain4j

Categories

  • Artificial Intelligence
  • Front-end
  • Go
  • Industry and business
  • Java
  • JavaScript
  • Legacy
  • Operations
  • Personal
  • Pet projects
  • Tools

Archives

  • May 2025
  • April 2025
  • March 2025
  • February 2025
  • January 2025
  • December 2024
  • November 2024
  • August 2024
  • June 2024
  • May 2024
  • April 2024
  • March 2024
  • February 2024
  • January 2024
  • December 2023
  • November 2023
  • October 2023
  • September 2023
  • August 2023
  • July 2023
  • June 2023
  • May 2023
  • April 2023
  • March 2023
  • February 2023
  • January 2023
  • December 2022
  • November 2022
  • October 2022
  • September 2022
  • August 2022
  • July 2022
  • June 2022
  • May 2022
  • March 2022
  • February 2022
  • January 2022
  • December 2021
  • November 2021
  • October 2021
  • September 2021
  • August 2021
  • July 2021
  • January 2021
  • December 2020
  • November 2020
  • October 2020
  • September 2020
  • August 2020
  • July 2020
  • June 2020
  • May 2020
  • February 2020
  • January 2020
  • December 2019
  • October 2019
  • September 2019
  • July 2019
  • March 2019
  • November 2018
  • July 2018
  • June 2018
  • May 2018
  • April 2018
  • March 2018
  • February 2018
  • December 2017
  • July 2017
  • January 2017
  • December 2015
  • November 2015
  • December 2014
  • March 2014
  • February 2011
  • November 2008
  • June 2008
  • May 2008
  • April 2008
  • January 2008
  • November 2007
  • September 2007
  • August 2007
  • July 2007
  • June 2007
  • May 2007
  • April 2007
  • March 2007

MockMvc – Spring MVC testing framework introduction: Testing Spring endpoints

2018-06-02 in Java tagged Agile / Automation / JUnit / Mockito / MockMVC / REST / Spring Framework / Spring Boot / Testing by Marc Nuri | Last updated: 2021-03-28
Versión en Español

Introduction

In this post we’ll see how to use MockMvc to test Spring endpoints. This is the first post of a series, this post will highlight the advantages of using Spring MVC test framework compared to other ways of testing controller classes and what are the different ways to setup your tests.

You can find the source code for this post at GitHub. The project contains a regular MVC controller that forwards requests to a static resource, and two rest controllers that return a list of languages and coffees.

MockMvc and the test pyramid

Test Pyramid
Test Pyramid

In his book, Succeeding with Agile: Software Development Using Scrum, Mike Cohn describes the test automation pyramid as a test automation strategy in terms of quantity and effort that must be spent in the different types of tests. The test pyramid is also used by other authors to describe a test portfolio in terms of cost and speed. The base of the pyramid are unit tests, meaning that unit tests are the foundation of the testing strategy and that there should be many more unit tests than high level end-to-end tests.

As you know, unit testing focuses on testing the smallest component of a software. In object oriented programming this is usually a single class or interface, or a method within this class. The test is performed in isolation of other units, in order to achieve this dependencies are usually mocked. One of the main traits of unit tests is that their execution is really fast.

Similarly, integration tests focus on testing a combination or group of the aforementioned componentes. Technically speaking, tests using MockMvc are in the boundaries between unit and integration tests. They aren’t unit tests because endpoints are tested in integration with a Mocked MVC container with mocked inputs and dependencies. But we might also don’t consider these tests as integration tests, if done right, with MockMvc we are only testing a single component with a mocked platform, but not a combination of components.

Technicalities aside, when preparing a test portfolio, tests using Spring MVC test framework should be treated as unit tests and be part of the foundation of the testing strategy.

Language Controller

For the purpose of this tutorial we are going to test a simple controller that returns a filtered list of programming languages.

1@Controller
2@RequestMapping(value = "/api", produces = MimeTypeUtils.APPLICATION_JSON_VALUE)
3public class LanguageApiController {
4
5  private final LanguageService languageService;
6
7  @Autowired
8  public LanguageApiController(
9      LanguageService languageService) {
10    this.languageService = languageService;
11  }
12
13  @RequestMapping(value = "/languages", method = GET)
14  public ResponseEntity<List<Language>> getLanguages(@RequestParam(value = "contains", required = false) String contains) {
15    return ResponseEntity.ok(languageService.getLanguages(contains));
16  }
17
18  @RequestMapping(value = "/languages/{name}", method = GET)
19  public ResponseEntity<Language> getLanguage(@PathVariable("name") String name) {
20    return ResponseEntity.ok(languageService.getLanguage(name).orElseThrow(() -> new SpringMockMvcException(
21        HttpStatus.NOT_FOUND, "Language was not found")));
22  }
23
24  @ExceptionHandler(SpringMockMvcException.class)
25  public ResponseEntity<String> onSpringMockMvcException(HttpServletRequest request, SpringMockMvcException ex) {
26    return ResponseEntity.status(ex.getHttpStatus()).body(String.format("%s - %s",
27        ex.getHttpStatus().value(), ex.getMessage()));
28  }
29
30}

The controller has a global @RequestMapping annotation specifying the output mime-type for the endpoints declared in the class as application/json. The class exposes two endpoints that rely on LanguageService to perform business operations.

The first endpoint (/api/languages) will retrieve a list of languages that can be optionally filtered with a parameter (contains).

An image showing the result of executing a curl request to api/languages filtered by contains parameter
An image showing the result of executing a curl request to api/languages filtered by contains parameter

The second endpoint (/api/language/{name}) will return a single language filtered by it’s unique name.

An image showing the result of executing a curl request to api/languages/java
An image showing the result of executing a curl request to api/languages/java

Finally there’s a an ExceptionHandler that will transform any controlled SpringMockMvcException into a ResponseEntity.

An image showing the result of executing a failed curl request to api/languages/Gibberrish with ExceptionHandler in action
An image showing the result of executing a failed curl request to api/languages/Gibberrish with ExceptionHandler in action

Spring MVC test framework advantages

As already stated, tests running with MockMvc lie somewhere in the boundaries between integration and unit tests. Following is a strict unit test that will test the LanguageApiController.

1@Test
2  public void getLanguages_null_shouldReturnListOfStrings() throws Exception {
3    // Given
4    final String mockedEsoteric = "Arnoldc";
5    final List<String> mockedLanguages = Stream.concat(
6        LanguageRepository.LANGUAGES.stream(),
7        Stream.of(mockedEsoteric)).collect(Collectors.toList());
8    doReturn(mockedLanguages).when(languageService).getLanguages(null);
9
10    // When
11    final ResponseEntity<List<String>> result = languageApiController.getLanguages(null);
12
13    // Then
14    assertThat(result.getBody(), hasSize(mockedLanguages.size()));
15    assertThat(result.getBody(), hasItem(mockedEsoteric));
16  }
17}

This test will validate that the method returns the expected list whenever a null argument is passed. It will add 100% coverage for the exposed controller method, even though many aspects of the implementation won’t be tested, more specifically:

  • Content negotiation headers: This specific controller is configured to produce only application/json content.
  • Response codes: With MockMvc we’re able to check if the response code matches the expected one, even if it’s produced outside the controller method or post processed by an annotation/aspect.
  • JSON serialization/deserialization: MockMvc will help us validate that JSON is deserialized as expected when performing a request with content, and correctly converted to the response body.
  • Availability of response headers
  • Many other scenarios and configurations that happen outside the method specific implementation but are part of the endpoint implementation (Validation, Security, Exception Handling…)

WebMvcTest annotation

The easiest way to run a test with MockMvc is to use the @WebMvcTest Spring boot annotation. This annotation will configure the SpringRunner JUnit test runner to deploy a partial webapplication by auto configuring only the required components for an MVC application.

In my opinion, this is not the best approach to test your application as for each test suite run a mocked application will be deployed. This may be an overkill if you only want to test units of a controller and not the integration of the controller with the rest of components (test pyramid).

The following test will use @WebMvcTest to autoconfigure the test environment and perform the test validations using an injected MockMvc instance:

1@RunWith(SpringRunner.class)
2@WebMvcTest(controllers = LanguageApiController.class)
3public class LanguageApiControllerWebMvcTest {
4
5  @Autowired
6  private MockMvc mockMvc;
7
8  @Test
9  public void getLanguages_null_shouldReturnOk() throws Exception {
10    // Given
11    // Real application context
12
13    // When
14    final ResultActions result = mockMvc.perform(
15        get("/api/languages")
16            .accept(MimeTypeUtils.APPLICATION_JSON_VALUE));
17
18    // Then
19    final int expectedSize = LANGUAGES.size();
20    final String[] expectedLanguageNames = LANGUAGES.stream().map(Language::getName)
21        .collect(Collectors.toList()).toArray(new String[LANGUAGES.size()]);
22    result.andExpect(status().isOk());
23    result.andExpect(jsonPath("$.length()").value(expectedSize));
24    result.andExpect(jsonPath("$[*].name", containsInAnyOrder(expectedLanguageNames)));
25  }
26}

Again, this is not the best approach if we want to have a test portfolio following the test pyramid strategy. An alternative methodology is to instantiate MockMvc programmatically is described in the following sections.

Web Application Context integration setup

There are several ways in which MockMvc can be setup programmatically, these settings will set the degree of integration your tests will perform. The highest level of integration with MockMvc will be achieved using .webAppContextSetup(...). In my opinion this is not the best approach to test your endpoints as many of the advantages of unit testing are lost (tests are slower, increased complexity to mock objects…).

The following test verifies the application behavior using this technique:

1@RunWith(SpringJUnit4ClassRunner.class)
2@WebAppConfiguration
3@ContextConfiguration(classes = {
4    SpringMockMvcConfiguration.class
5})
6public class LanguageApiControllerApplicationIntegrationTest {
7
8  @Autowired
9  private WebApplicationContext webApplicationContext;
10
11  private MockMvc mockMvc;
12
13  @Before
14  public void setUp() {
15    mockMvc = MockMvcBuilders
16        .webAppContextSetup(webApplicationContext)
17        .build();
18  }
19/*...*/
20  @Test
21  public void getLanguages_null_shouldReturnOk() throws Exception {
22    // Given
23    // Real application context
24
25    // When
26    final ResultActions result = mockMvc.perform(
27        get("/api/languages")
28            .accept(MimeTypeUtils.APPLICATION_JSON_VALUE));
29
30    // Then
31    final int expectedSize = LANGUAGES.size();
32    final String[] expectedLanguageNames = LANGUAGES.stream().map(Language::getName)
33        .collect(Collectors.toList()).toArray(new String[LANGUAGES.size()]);
34    result.andExpect(status().isOk());
35    result.andExpect(jsonPath("$.length()").value(expectedSize));
36    result.andExpect(jsonPath("$[*].name", containsInAnyOrder(expectedLanguageNames)));
37  }
38
39}

There are two important sections in this code. In the class annotations section we find @WebAppConfiguration which indicates that a WebApplicationContext should be loaded for the test using a default path to the web application root. Additionally we define a @ContextConfiguration to indicate which classes/beans should be loaded for the test. In this case we are reusing the same configuration bean used for the real application, so we’ll be able to completely test the application’s component integration.

The next important section in the code is the test setup. In this case we are building a MockMvc instance using the fully initialized WebApplicationContext. Spring will autowire this context as specified by the @WebAppConfiguration annotation.

The test validates that the endpoint is exposed in the expected path and that it sends the real repository’s languages when receiving a request with the appropriate headers. So on top of validating the controller implementation, we are validating that the aspect configuration works as expected.

As already stated, this test shares the real application WebMvc configuration (SpringMockMvcConfiguration), so a full fledged application context will be available. In order to restrict the amount of Beans in the test, (a) specific Test WebMvc configuration(s) could be used. This kind of configuration should only be considered when the desired behavior is to perform tests with a considerable degree of integration between your components. The next section describes standalone setup, which should be the preferred way when your tests focus only on the controller behavior.

MockMvc standalone setup

MockMvcBuilders offers an additional way to setup MockMvc, standalonSetup(...). This method allows us to register one or more Controllers without the need to use the full WebApplicationContext. Mocking controller dependencies using this procedure is really easy, this enables us to test a controller isolated from its dependencies same as we did in the strict unit test example. With MockMvc we are also able to test path, request method, headers, responses, etc. so we are getting the best of both worlds.

The following test shows this behavior:

1@RunWith(SpringJUnit4ClassRunner.class)
2@ContextConfiguration(classes = {
3    LanguageApiController.class
4})
5@Import(Config.class)
6public class LanguageApiControllerTest {
7
8  @Autowired
9  private LanguageService languageService;
10
11  @Autowired
12  private LanguageApiController languageApiController;
13
14  private MockMvc mockMvc;
15
16  @Before
17  public void setUp() {
18    mockMvc = MockMvcBuilders
19        .standaloneSetup(languageApiController)
20        .build();
21  }
22
23/* ... */
24
25  @TestConfiguration
26  protected static class Config {
27
28    @Bean
29    public LanguageService languageService() {
30      return Mockito.mock(LanguageService.class);
31    }
32
33  }
34
35}

The configuration differs a little from the previous. @WebAppConfiguration annotation is no longer used, so Spring won’t load a full application context (less overhead). The @ContextConfiguration annotation includes a reference to the real controller we want to test, Spring will create a Bean for this controller and will autowire it to the test. Finally there is an inner configuration where we declare mock beans for the dependencies of the controller. Spring will inject this mock instances to our real controller. These dependencies will also be autowired to our test, so preparing mock data will be really easy.

Languages Happy Path

The following test uses the standalone MockMvc configuration and describes the happy path of the /api/languages endpoint:

1@Test
2public void getLanguages_null_shouldReturnOk() throws Exception {
3  // Given
4  final Language mockedEsoteric = new Language("Arnoldc", "Lauri Hartikka");
5  final List<Language> mockedLanguages = Stream.concat(
6      LanguageRepository.LANGUAGES.stream(),
7      Stream.of(mockedEsoteric)).collect(Collectors.toList());
8  doReturn(mockedLanguages).when(languageService).getLanguages(null);
9
10  // When
11  final ResultActions result = mockMvc.perform(
12      get("/api/languages")
13          .accept(MimeTypeUtils.APPLICATION_JSON_VALUE));
14
15  // Then
16  result.andExpect(status().isOk());
17  result.andExpect(jsonPath("$.length()").value(mockedLanguages.size()));
18  result.andExpect(jsonPath("$[?(@.name === 'Arnoldc')]").exists());
19}

In the Given section we prepare a list of languages that will be returned by the mocked LanguageService Bean when a null parameter is received by the getLanguages function.

Next we perform the GET request to our Mock Mvc environment using an Accept: application/json header. Finally we assert that the request returns with the expected 200 status code and that languages include all of our mocked data in the expected JSON path.

As mentioned earlier, not only we are testing the controller implementation, but also content negotiation headers, JSON serialization and http response status code.

Invalid Accept header

The following test will highlight how using MockMvc will assert that endpoint annotations work as expected validating that the endpoint is only exposed when receiving the expected Accept: application/json request header.

1@Test
2public void getLanguages_invalidAcceptHeader_shouldReturnNotAcceptable() throws Exception {
3  // Given
4  final String invalidAcceptMimeType = MimeTypeUtils.APPLICATION_XML_VALUE;
5  doReturn(LanguageRepository.LANGUAGES).when(languageService).getLanguages(null);
6
7  // When
8  final ResultActions result = mockMvc.perform(
9      get("/api/languages")
10          .accept(invalidAcceptMimeType));
11
12  // Then
13  result.andExpect(status().isNotAcceptable());
14}

In this case, the request is performed using an Accept: application/xml request header. The endpoint is configured to serve only JSON data, so Spring MVC will never invoke the endpoint and will return a 406 not acceptable response status code.

Testing the ExceptionHandler

The LanguageApiController includes an @ExceptionHandler that will handle controlled exceptions thrown anywhere in the Controller. When invoking the /api/languages/{name}, if no language is found matching the path variable name a controlled SpringMockMvc exception is thrown.

The following test will validate that the @ExceptionHandler works as expected and that the controlled exception is serialized to a ResponseEntity accordingly.

1@Test
2public void getLanguage_nonExistingLanguage_shouldReturnNotFound() throws Exception {
3  // Given
4  final String mockedLanguageName = "Arnoldc";
5  doReturn(Optional.empty()).when(languageService).getLanguage(mockedLanguageName);
6
7  // When
8  final ResultActions result = mockMvc.perform(
9      get("/api/languages/".concat(mockedLanguageName))
10          .accept(MimeTypeUtils.APPLICATION_JSON_VALUE));
11
12  // Then
13  result.andExpect(status().isNotFound());
14}

In the Given section we configure the mocked LanguageService to return an empty Optional when a language with the name “Arnoldc” is requested.

Next, a GET request to /api/languages/Arnoldc is performed. Finally the test asserts that a 404 not found response status code is yielded according to the ...ResponseEntity.status(ex.getHttpStatus())... statement in the ExceptionHandler.

Conclusion

This post is an introduction to Spring MVC testing framework. It highlights the advantages of using MockMvc instead of regular unit tests. We see three ways of setting up MockMvc and how using standaloneSetup has the advantages of unit and integration testing. The post focuses only on testing a basic spring REST controller, although MockMvc can be used to test the complete suite of components from Spring MVC framework.

The full source code for this post can be found at Github.

Twitter iconFacebook iconLinkedIn iconPinterest iconEmail icon

Post navigation
Linux: How to list installed RPM packagesSpring Boot: How to change the application port?
© 2007 - 2025 Marc Nuri