Spring WebTestClient for Efficient REST API Testing

Last Updated:  August 27, 2021 | Published: December 8, 2019

Alongside the WebClient, Spring provides a WebTestClient for testing purposes. The API of this class is similar to the WebClient and allows the assertion of all parts of the HTTP response. With this blog post, I'll demonstrate how to use the WebTestClient to write integration tests for a Spring Boot REST API.

TL;DR:

  • Spring Boot autoconfigures a WebTestClient once you use @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
  • Easy-to-use assertions for the response body, status code, and headers of your REST API
  • If you already know the WebClient, using the WebTestClient will be straightforward

Spring Boot Application Setup

To provide a reasonable example to showcase the capabilities of the WebTestClient, we're developing and testing a Java 11 and Spring Boot 2.5 application.

We include the Spring Boot Starter Web (to auto-configure Tomcat), WebFlux (includes the WebClient and WebTestClient), and Validation (was recently removed from Web) alongside the Spring Boot Starter Test (aka. testing swiss-army knife):

When using both the Spring Boot Starter Web and WebFlux, Spring Boot assumes we want a blocking servlet stack and auto-configures the embedded Tomcat for us. We can still use WebFlux features like the WebClient (preferred over the RestTemplate) for fetching data from remote APIs or use the WebTestClient for testing purposes.

Spring Boot Application Walkthrough

Next, let's have a look at the REST API of the sample application. This API is all about serving data about the User resource in a RESTful manner.

It allows creating, querying, and deleting users:

For the sake of simplicity, the UserService stores the available users in-memory:

We'll now test some of the corner cases of this API (e.g., creating a user with an already existing id) and also the happy-path in the following section using the WebTestClient.

Using the WebTestClient for Integration Tests

With the Spring WebTestClient, we can fire HTTP requests against our running application during integration tests. Spring Boot autoconfigures a WebTestClient bean for us, once our test uses: @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) (further configuration options of @SpringBootTest).

The client is already autoconfigured, and the base URL points to our locally running Spring Boot. There's no need to inject the random port and configure the client.

In addition to this, we can bootstrap the WebTestClient with its builder manually.  This allows to, e.g., bind the client to a specific controller or route:

We'll use the autoconfigured version of the WebTestClient in the following examples. We can inject this bean with @Autowired within our test class:

Given this bean, let's write the first test to verify our three default users are returned:

As the base URL with the random port of our Spring application is already configured, we just have to pass the URI we want to create a request for. If you are familiar with the WebClient, you'll notice that the API of this WebTestClient is quite similar.

The main difference is the capability of asserting different things after we call .exchange() (recall exchange vs. retrieve). We can expect different parts of the response in a fluent manner: the status code, response headers, and the body.

We are not limited only to write jsonPath expressions for verifying the body. We can also use xpath or parse the result to a POJO, as we'll see later on.

In the same way, we can write tests to verify corner cases like requesting an unknown user or asking for XML as a content type:

Advanced Usage of the WebTestClient

Next, let's write a more advanced integration test and work with the response of the WebTestClient. For this, we'll write a test to verify the flow of creating a new user, querying for it, and then deleting the user again, works:

With .returnResult() you can return the response to a local variable and work with it. We need this to access the Location header of the response.

Similar to this, we can expect the body of the response to match an object. We see this within the second WebTestClient call, where we parse the response to a User object and expect it to be equal with the newUser object. Make sure to implement .equals() and .hashCode() for this to work properly.

Further Resources on Spring's WebClient

To learn more about the WebClient, have a look at the following blog posts:

The source code for this sample REST API application, including the tests, is available on GitHub.

For further tips & tricks when it comes to testing Spring Boot Applications, consider enrolling in the Spring Boot Testing Applications Masterclass. The Masterclass is a deep-dive online course with practical hands-on advice for testing real-world (AWS, React, PostgreSQL, OIDC with Keycloak, etc.) Spring Boot applications.

Have fun using the Spring WebTestClient,

Phil

>