Spring Boot Functional Tests With Selenium and Testcontainers

Last Updated:  April 6, 2022 | Published: January 31, 2020

Ensuring your application is working properly is a critical part of continuous integration and delivery. Unit tests help you to test your methods' business logic for both normal & edge cases. When it comes to guaranteeing that your users can correctly work with your application, we need something different. If your application exposes a frontend with user interaction, we can write functional tests to ensure different use cases are working. With this blog post, I'll provide an example of writing functional tests (also called end-to-end tests) for Spring Boot applications using Selenium and Testcontainers.

Spring Boot Selenium with Testcontainers Project Setup

The sample Maven application uses the following dependencies:

The application to test for this example is rather simple. We're using Thymeleaf and Spring MVC and want to write a test for the following view:

As a counterpart to the view above, the following controller provides the model:

While this is a rather basic application setup that doesn't reflect real-world applications, the following test setup works for any Spring Boot application which serves the frontend (e.g., Spring MVC or bundling SPA within the application).

Selenium Test Setup With Testcontainers

To use Selenium to access the frontend of our application and write functional tests, we need a WebDriver. We can either manually download such a driver for our browser of choice (e.g., Chrome or Firefox) or use Testcontainers.

The Testcontainers project provides a module to launch container-based WebDriver to avoid any manual setup efforts using the BrowserWebDriverContainer class.

As we'll write an end-to-end test, we have to bootstrap the whole Spring Boot application (including Tomcat) with @SpringBootTest. Once everything is up and running, we can request the RemoteWebDriver instance from the container and perform any action with Selenium.

As the web driver runs within a Docker container and its own network,  accessing the Spring Boot application using localhost doesn't work. Instead, we have to first expose the host port we want to access before starting the WebDriverContainer.

Timing-wise this has to happen after we start our Tomcat server but before we start the WebDriverContainer with Testcontainers. As we're using a random port, we have to make this dynamic and can use the @BeforeAll lifecycle of JUnit Jupiter:

Once the port is exposed, we start the Docker container.

Custom JUnit Jupiter Screenshot Extension

When running functional tests (and especially when the browser runs inside a container), it's important to make screenshots of test failures. As the functional tests are usually executed within a CI/CD pipeline, we need a visual output once a test fails to understand the root cause of the failure.

This is a perfect use case for a custom JUnit Jupiter extension. We can write an extension that implements the AfterEachCallback to perform actions after each test. The ExtensionContext contains the information on whether an exception was thrown during test execution.

We can use this information and request a screenshot from the RemoteWebDriver once an exception occurred.

We register this custom extension with @ExtendWith:

The screenshots are stored within target/selenium-screenshots and we can archive this folder within our CI/CD pipeline to investigate failed builds.

The source code for this Selenium, Testcontainers, and Spring Boot example is available on GitHub.

For more content on writing integration tests for your Spring Boot application, have a look at the following blog posts:

Have fun testing your application with Testcontainers, Selenium and Spring Boot,

Phil

>