Lesson 1: End-To-End API Tests - WebTestClient

1. Goal

In this lesson, we’ll learn about API tests and how to implement them in our project.

We’ll also look at library alternatives, and develop both Live and Integration End-to-End API tests using Spring’s WebTestClient.


2. Lesson Notes

The relevant module you need to import when you're starting with this lesson is: end-to-end-api-tests-webtestclient-start.

If you want to have a look at the fully implemented lesson as a reference, feel free to import: end-to-end-api-tests-webtestclient-end.


2.1. What Are API Tests

As we’ve seen, API design is a crucial aspect of a RESTful service, since it defines the elements that will allow fluent communication with other systems.

Therefore, it’s essential to provide testing support and validate that it fulfills its expected functionality, reliability, performance, and security.

A common practice is to use End-to-End tests to test these points effectively and reliably simulate the API's final functionality.

These special Integration tests require a running service to consume and assert its API without bypassing or mocking any layer.

Now let’s look at the available testing libraries we can use for this.


2.2. API Testing Alternatives

The most popular libraries and mechanisms we can use to this end are:

  • REST Assured, a library that offers simplicity to test REST APIs
  • The Spring TestRestTemplate, a synchronous client to perform HTTP-based REST APIs request
  • The Spring WebTestClient, a non-blocking, reactive client for testing web servers

In this lesson, we’ll be using Spring’s WebTestClient feature. 

At first, this might not be the most intuitive decision because our project is based on a Servlet synchronous stack. However, Spring encourages using both the WebClient and WebTestClient features, since they can be used in a blocking manner, making them perfectly suitable for consuming and testing any kind of application.


2.3. Introducing WebTestClient

Let’s have a look at what WebTestClient is, what its functionalities are, and how to use it.

First, we’ll start by opening the pom.xml file and analyzing the dependencies we need to include in our service to use this approach.

The WebTestClient is already available in our project since it belongs to the spring-test library, which the spring-boot-starter-test library pulls in. Still, we also need to include support for the Reactive Webflux functionality to make use of this mechanism:

Note that we’re adding the Reactive WebFlux dependency with a test scope, since we only need it for the WebTestClient, not our main application logic.

Now we can open the com.baeldung.rwsb.endtoend.CampaignEndToEndApiTest class we have in the test source folder, and start by declaring a WebTestClient variable:

Let’s take a quick peek at the Javadoc describing this interface:

As we can see, WebTestClient uses WebClient internally to carry out the requests and provides different convenience methods to instantiate WebTestClient variables according to the various testing requirements we might have.

Let’s move forward and analyze which one is most suitable for our case.


2.4. Live API Tests

As we said, to test the API of our RESTful service from end to end, we want to make requests to a fully functional instance of the application. The most straightforward way of doing this is simply launching the application locally and creating tests that consume from it.

We call these “Live Tests” because we’re assessing a live instance independent of the lifecycle of the test (no matter if it’s live just locally or in production). 

So let’s start our application by running the RwsbApp class from the IDE, or using the “mvn spring-boot:run” command in the console.

With this, we can go back to the CampaignEndToEndApiTest class and create our test case:

The code is simple and self-explanatory, but shows how powerful and valuable the WebTestClient functionality is.

First, we’re declaring and initializing a WebTestClient variable using the bindToServer method in order to indicate that we’ll connect to a live server and provide the base URL for it. We’re pointing to localhost:8080 because that’s the port our application is listening to locally.

Then, in the test method, we’re simply instructing the request that should be carried out and the expected response aspects that we want to check.

For this, the testing framework provides a fluent API to indicate the following:

  • the HTTP Method of the request we want to execute (in this case, GET)
  • the endpoint of the service we want to call (“//3”)
  • to perform the request afterwards with the exchange() method
  • to verify that the status of the response is “200 OK

Let’s run the test and see what happens:

As we can see, the test is successful, indicating the endpoint is valid and healthy.

Next, let’s analyze how we can improve this test case by changing the approach we use to test the REST API.


2.5. End-to-End Integration API Tests

As we can see, this test requires manually launching the application before running the tests, which can be very inconvenient, especially for a CI environment.

Ideally, we’d like to avoid performing any manual setup to test the service API, and for this, we’ll rely on some handy Spring Boot features. 

Let’s update the test accordingly:

What’s happening here is:

  • The @SpringBootTest annotation tells the framework to start a Spring application context.
  • The “webEnvironment=RANDOM_PORT” argument instructs to start the server in a random port.
  • To point to this running service, we let the framework inject the assigned random port into a variable via the @LocalServerPort annotation.
  • Use this port variable to form the baseUrl when initializing the WebTestClient instance dynamically
  • For this, we have to move the WebTestClient initialization logic inside the test case (or alternatively in a @BeforeEach method), since we need to wait for Boot to inject the port value first

We can now stop the service we previously launched, and execute the test again:

As we can see, the test passed successfully. Even though we’re still technically running the validations against a Live instance (which is started ad hoc), we can consider this a simple Integration Test because it doesn’t test a pre-existing Live instance of a service.

It’s worth noting that, as we can see in the image above, this test took a significant amount of time to get executed compared to our previous run, as it has to go over the whole application initialization process before finally executing the test case.


2.6. Spring Boot WebTestClient Autoconfiguration

There’s one final step to further simplify our test case by using a helpful Spring Boot feature.

Simply put, when we use @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT), Spring Boot automatically sets up a WebTestClient instance in the context pointing to the assigned port, in order to avoid the overhead of having to do this ourselves.

So, knowing this, we can simplify the test by directly autowiring the variable

We can execute the test one last time to confirm it works as expected:

2.7. Conclusion

In summary, in this lesson, we learned how to use the WebTestClient to validate our service’s API with End-to-End tests. 

We also explored the practical support Spring Boot provides for this, but not without analyzing in detail what the different Boot mechanisms bring to the table, and how they fit together to simplify the test class as much as possible.

In a future lesson, we’ll go further into detail on the functional capabilities offered by WebTestClient.


3. Resources

- Spring 5 WebClient - Working with the WebTestClient

- Spring - WebTestClient

- REST Assure

Complete and Continue