Test Your Spring MVC Controller with the WebTestClient and MockMvc

Last Updated:  August 27, 2021 | Published: November 17, 2020

Starting with Spring Framework version 5.3 (part of Spring Boot since version 2.4.0) you can perform requests with the WebTestClient against MockMvc. This allows unifying the way you invoke your controller endpoints during tests. For both test scenarios (real HTTP communication or mocked Servlet environment), you now have the possibility to use the WebTestClient.

WebTestClient and MockMvc before Spring 5.3

In the past (before Spring Framework 5.3), you either tested your MVC controller endpoints with MockMvc or WebTestClient (or the TestRestTemplate).

Using MockMvc you could (and still can) test your MVC components with a mocked Servlet environment.  You get an auto-configured MockMvc instance when using @WebMvcTest. With such tests you can verify that e.g. an endpoint is properly protected by Spring Security, the correct HTTP status code is returned on failure or the Model contains all required attributes.

All of the verification above is happening without any underlying HTTP communication. This is great when it comes to performance, as these tests only use a sliced context and don't have to start the embedded Servlet container. On the other side, such tests don't exactly mirror how your application is invoked in production.

This brings us to the WebTestClient.

Spring Boot autoconfigures an instance of the WebTestClient whenever you select WebEnvironment.RANDOM_PORT or WebEnvironment.DEFINED_PORT for @SpringBootTest. This ensures to start the embedded Servlet container (e.g. Tomcat) on a local port and hence you can communicate with your application over HTTP.

The auto-configured WebTestClient is bound (correct base URL + port) to your application and you can start writing tests without further configuration:

While you used the WebTestClient and MockMvc separately in the past, you can now (with Spring Framework 5.3) perform requests with the WebTestClient against a MockMvc instance.

Perform requests with WebTestClient against MockMvc

Let's see how we can configure the WebTestClient to target MockMvc.

As of now, there is no auto-configuration for this test setup and you have to create the WebTestClient instance on your own. The MockMvcTestClient interface provides static factory methods to create a client that is bound to either specific controllers, an application context, or an MockMvc instance.

While creating the client, you can apply any customizations e.g. default headers, filter functions, etc.

What's left is to perform your requests using the WebTestClient and write expectations on the result:

As you see in the test above, the usage of the WebTestClient is similar to writing a test against a running Spring Boot application and invoke a controller over HTTP. But with the test above there is no HTTP communication happening as we target MockMvc.

Testing Spring @Controller endpoints with this setup

You might wonder: “What about testing @Controller endpoints? I thought the WebTestClient only offers expectations for the body, header, or status of a real HTTP response.”

Fortunately, the Spring Team also added support for this and you can use this setup for testing your Thymeleaf Spring Boot application. If you use the WebTestClient and write tests against a MockMvc instance, you are now able to write assertions for e.g. the Model or RedirectAttributes.

The WebTestClient doesn't offer methods for such assertions itself. Once you have access to the EntityExchangeResult (calling .returnResult()), you can use MockMvcWebTestClient.resultActionsFor() and write expectations similar to MockMvc:

Summary

With this small enhancement, you are now able to unify the way you invoke your controller endpoints during tests. This might help to standardize and reuse existing WebTestClient snippets for tests using @WebMvcTest and MockMvc.

IMHO this is not (yet) a game-changer feature, especially if you are using Kotlin and are familiar with the MockMvc DSL.  When it comes to mocking security concerns, the MockMvc integration with Spring Security is quite powerful due to the SecurityMockMvcRequestPostProcessors. I couldn't find any similar feature for the WebTestClient (yet), as webTestClient.mutateWith(mockUser()) only works (AFAIK) when targeting a reactive WebFlux environment.

You can find the source code with further WebTestClient and MockMvc examples on GitHub.

Have fun performing requests with the WebTestClient against MockMvc,

Philip

>