Spring Boot Testing: MockMvc vs. WebTestClient vs. TestRestTemplate

Last Updated:  July 18, 2022 | Published: July 18, 2022

Mega Spring Boot Bundle Sale: Unleash the Ultimate Mega Bundle Packed with 10 Game-Changing Digital Products (Online Courses & eBooks) on Spring Boot, Java, Testing, AWS, and Architecture. Available until the 27th of February.

Spring offers various tools for testing our controller endpoints: MockMvc, WebTestClient, and the TestRestTemplate. While all three candidates serve a similar goal – invoking our HTTP endpoints and verifying the response – there's still a subtitle difference among them. This article will give an overview of where and how those three classes are intended to be used, including what's their differences.

TL;DR: That's the short version of the comparison:

MockMvc WebTestClient TestRestTemplate Comparison

Spring Boot Project Setup for Testing Purposes

To showcase the usage of these three different testing tools, we're going to test a basic Spring Web MVC application. We enrich this Spring Boot application with the Starter for Spring WebFlux. However, we won't mix non-blocking and blocking controller endpoints and stick to the blocking Tomcat servlet container.

The idea behind this additional dependency (Spring WebFlux) is to get access to the WebClient and the WebTestClient.

To demonstrate testing a controller that returns a server-side rendered view, we additionally include the Spring Boot Starter for Thymeleaf:

Having both the Starter for Spring Web MVC and WebFlux on the classpath, Spring Boot assumes we're going to write a blocking application and autoconfigures Tomcat for us. Nevertheless, we can use the WebClient (the future proof version of the RestTemplate) for making HTTP requests and the WebTestClient for testing purposes.

Throughout this article, we're going to write tests for the following sample REST API:

The CustomerController allows us to create customers and return all existing customers. For the sake of simplicity, we're storing all customers inside an in-memory List. As this article is all about testing our Spring Boot application via our controller endpoints, the upcoming explanations hold true independent of what's our actual data store.

Next to this @RestController, our sample project exposes a server-side rendered view using Thymeleaf:

This endpoint renders a view and returns HTML instead of a JSON payload.

With these two controller classes in place, let's explore the differences of MockMvc, the WebTestClient and the TestRestTemplate.

Using MockMvc for Testing Spring Boot Applications

At its core, MockMvc is a mocked servlet environment. As the name (MockMvc) applies, we use it for servlet-based Spring Web MVC controller endpoints. But not for Spring WebFlux endpoints.

In comparison to a real servlet environment, with MockMvc, we don't need to start our embedded servlet container (e.g., Tomcat). Hence we don't occupy any port. We use the MockMvc instance to interact with this mocked environment and don't initiate real HTTP communication.

This mocked servlet environment still follows HTTP semantics as we can modify the headers and body of the request (MockMvcRequest) and can inspect the response's header, status, and body.

We can either bootstrap this environment on our own (see the different bootstrap variants) or use the autoconfigured version from Spring Boot (@AutoConfigureMockMvc).

Most of the time, we'll see this class is in combination with @WebMvcTest – a sliced Spring Boot test annotation – for our Spring Web MVC endpoints. The @WebMvcTest meta-annotation contains the @AutoConfigureMockMvc annotation, and hence we get the MockMvc instance autoconfigured for such tests.

Let's start with a first test for our CustomerController where we want to verify the @PostMapping endpoint using MockMvc:

First, we inject the autoconfigured version of MockMvc to our test as we're using @WebMvcTest. This test creates a minimal subset of our Spring ApplicationContext containing only Spring Web MVC-related beans and our CustomerController.

Next, we use the injected MockMvc instance to perform a POST request against our mocked servlet environment. We prepare the request's body to contain a valid JSON payload. As a basic verification, we expect our endpoint to return 201.

We get access to a fluent API to perform various operations against our mocked servlet environment and can simulate incoming HTTP requests (GET, POST, PUT, etc.). Furthermore, we can chain verifications to our request to verify the body, header, etc. of the MockMvc response.

However, we have to keep in mind that we're not making any requests against a running servlet container. We operate in a mocked environment that is less close to our production runtime. No actual HTTP request hit our endpoint.

MockMvc also comes with support for testing server-side rendered views:

In the example above, we get rather low-level access to the response and Spring's internals as we can verify both the returned view and its model. This allows testing if the Thymeleaf engine can successfully render our view indicating that we set all the mandatory model attributes.

For further hands-on examples, including how to verify protected endpoints by Spring Security, head over to this @WebMvcTest and MockMvc introduction article.

Using the WebTestClient for Testing Spring Boot Applications

As the name applies, the WebTestClient is the testing counterpart of the Spring Webflux WebClient.

Although its non-blocking origin, we can still use it for blocking applications. The primary purpose is to verify controller endpoints of Spring WebFlux applications in combination with @WebFluxTest.

However, the Spring team has extended the use cases for the WebTestClient. We can use it for both tests that work with a mocked-servlet environment (MockMvc) and for integration tests against a running servlet container.

Let's see this in action by writing an integration test for our CustomerController endpoint:

As soon as we're starting our embedded servlet container for our @SpringBootTest and have Spring WebFlux on the classpath, Spring Boot autoconfigures a WebTestClient for us. This autoconfigured instance targets our locally running Spring Boot application.

What follows is a test for our HTTP POST API endpoint to verify our customer creation use case. We can chain both the request setup and the verification thanks to the fluent API of the WebTestClient.

In this example, we initiate a real HTTP request that reaches our locally running Spring Boot application.

Starting with Spring Boot 2.4 (Spring Framework 5.3), we can now also use the  WebTestClient to perform requests and verify their response when targeting a MockMvc environment. This gives us the possibility to potentially reuse a request specification for our MockMvc test and integration tests against a running servlet container.

Using the TestRestTemplate for Testing Spring Boot Applications

The last candidate for our comparison is the TestRestTemplate. It's the testing counterpart of the RestTemplate.

Similar to the WebTestClient, Spring Boot autoconfigures the TestRestTemplate as soon as we're starting an embedded servlet container (see @SpringBootTest configuration options).

Let's repeat the test setup for the HTTP POST endpoint of our CustomerController using the TestRestTemplate:

We're posting a valid payload to our API endpoint and expect an HTTP status of 201 Created. Similar to the previous WebTestClient example, we invoke our controller with an HTTP request.

Compared to the WebTestClient, the usage of the TestRestTemplate is a bit less fluent. Constructing the HttpHeaders and HttpEntity object before passing them to the exchange method, adds some boilerplate code to our test. Furthermore, we can't fluently write assertions for the response headers, body, and status code.

It achieves the same purpose as the reactive test HTTP client and lets us target our locally running Tomcat server (usually on a random port) for testing purposes.

Unlike the WebTestClient, we can use the  TestRestTemplate only for blocking Spring Web MVC endpoints. We can't use this for testing Spring WebFlux endpoints that return Flux or Mono data types.

Furthermore, we can't use the TestRestTemplate for interacting with a mocked servlet environment.

Summary: MockMvc vs. WebTestClient vs. TestRestTemplate

All three tools help us invoke and test our Spring Boot application's endpoint. The main difference lies in whether we can perform requests against a mocked servlet environment and/or our servlet container runtime. Furthermore, not all tools are designed to work with both Spring Web MVC (blocking) and Spring WebFlux (non-blocking).

In short, these three technologies serve the following purpose:

  • MockMvc: Fluent API to interact with a mocked servlet environment. No real HTTP communication. The perfect solution to verify blocking Spring WebMVC controller endpoints. We either bootstrap the MockMvc instance on our own, use @WebMvcTest or @SpringBootTest (without a port configuration). Includes API support for verifying the model or view name of a server-side rendered view endpoint.
  • WebTestClient: Originally the testing tool for invoking and verifying Spring WebFlux endpoints. However, we can also use it to write tests for a running servlet container or MockMvc. Fluent API that allows chaining the request and verification. There's no API support for verifying the model or view name of a server-side rendered view endpoint.
  • TestRestTemplate: Test and verify controller endpoints for a running servlet container over HTTP. Less fluent API. If our team is still familiar with the RestTemplate and hasn't made the transition to the WebTestClient (yet), we may favor this for our integration tests. We can't use the TestRestTemplate to interact with mocked servlet environment or Spring WebFlux endpoints. There's no API support for verifying the model or view name of a server-side rendered view endpoint.

When in doubt or when starting a new project, I prefer to use MockMvc for the @WebMvcTest tests and the WebTestClient for integration tests.

You can find related articles here:

The source code for this example is available on GitHub.

Joyful testing,

Philip

{"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}
>