MockMvc – Spring MVC testing framework introduction: Testing Spring endpoints


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

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.

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

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

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

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.

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:

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:

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:

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:

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.

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.

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.

Leave a comment

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