Fast Spring Boot API Tests

David Arques
Clarity AI Tech
Published in
4 min readSep 6, 2022

--

In this article, we explain the solution we have adopted at Clarity AI to streamline our API Tests using Spring Boot and Testcontainers.

Context

At Clarity AI, we like our code to be as well tested as possible, including good coverage. Most of the code base is dedicated to expose REST APIs. In addition to unit and integration tests, we also included tests that run the APIs in an environment as close to the production as possible, including: configuration, security, servlet container, etc. This article is focused on those API Tests.

Test Pyramid at Clarity AI

Our backend tech stack is based on Spring Boot, deploying more than 20 services using MongoDB, PostgreSQL, Redis, Rabbit, etc. So you can imagine that the API Test context creation might be complex and expensive.

We try to make the execution of our tests as fast as possible, including the aforementioned API Tests. We found a solution to speed them up that we are glad to share with you.

Spring Boot provides different tools to test your APIs:

  • Sliced Test with @WebMvcTest annotation: Spring Context will only include the required components of the WEB layer.
  • Entire App Test with @SpringBootTest annotation: The whole Spring Boot Application Context will be created and accessible. Additionally, an embedded servlet container can be started overriding the static webEnvironment attribute.

As the number of endpoints to test is very high, we can not afford starting a new app for each of them because it will end up making worse the feedback loop and the developer experience.

Solution

In order to minimize the impact of the creation of Spring Boot WEB contexts, we have identified two options:

  1. Make the test context as lightweight as possible. A solution could be defining a custom test context with @TestConfigurationand hand-picking the required beans, or even using mocks.
  2. Minimize the amount of WEB context creations.

Taking into account that our applications are stateless, the context of a test can be reused in others. And, since the first alternative does not really meet one of our requirements of replicating the production state, we’ve chosen the second option.

To learn about the Spring Context creation and caching we found very useful, the article Improve Build Times with Context Caching from Spring Test which we highly recommend.

Another point to highlight is the use of Testcontainers. Each application requires four different containers to be started, and we wanted them to be created just once per execution of the API Test suite. Launching containers only once forces us to use Shared Fixture pattern which may make tests erratic. This is a tradeoff we consciously chose.

We’ve created a sample application to illustrate the solution. Here’s the GitHub repo.

API Test Configuration

We have created an abstract class BaseApiTest that is in charge of configuring the API tests as well as easily reusable among different projects. This class includes:

  • The annotation @SpringBootTest setting the parameter webEnvironment to WebEnvironment.RANDOM_PORT so we get a web context with a real servlet container.
  • The @Testcontainers annotation to start up the required containers. For the sake of the example, we’ve defined two of them: PosgreSQLContainer and MongoDBContainer.
  • In order to inject the configuration for the defined containers, a static method annotated with @DynamicPropertySourceis used.
  • An extra test configuration has been added to define the beans needed to create the test setup. This way, the data sources used in the production and tests code are independent. Moreover, It provides us flexibility to use different users, configurations and permissions in the test setup stage. Each bean has been defined in a lazy mode so that they’re instantiated only if they are actually used.

Utility Annotations

To simplify the test development, we have created three annotations:

  • @TestSetupDataSource: Convenience annotation to make use of the test Data Source defined in BaseApiTest.
  • @TestSetupMongoClient: Annotation to make use of the Mongo test client defined in BaseApiTest.
  • @ApiBasePath: Exposes the base URL of the application, taking into consideration the dynamic port assignment.

API Test Suite

In this section, we are going to explain the class that extends BaseApiTest, called ApiTest. Which aims at defining the suite of API Tests we want to run in our application.

Since an application can expose APIs dedicated to different subdomains, including particular setups, a Testcase Class has been created for each one. Obtaining an elegant and legible API Test suite.

In order to execute each test file of the different subdomains, we’ve used the JUnit 5 @Nested, which allows nesting test files within the same class sharing the configuration defined in BaseApiTest.

API Testcase Classes

The API Testcase classes(CompaniesApiTest, EsgApiTest and ControversiesApiTest) are responsible for defining the actual tests to be executed within each API subdomain. They are extended by the ApiTest inner classes because we want them to reuse the same configuration provided by BaseApiTest.

These classes have been declared as abstract since they are not meant to be executed independently but only within the ApiTest suite.

Remember that if you want to use a property class, as those annotated with @TestSetupDataSource and @TestSetupMongoClient, within the @BeforeAll method, the test lifecycle must be marked as PER_CLASS. You can find an example in EsgApiTest.

Conclusions

Get your API tests running fast using a complex configuration in Spring Boot is a hard task. This article explained the solution that we’ve implemented at Clarity AI, achieving: faster feedback loop, higher quality tests and better developer experience.

We are still not totally satisfied with the idea of inheriting from an abstract class and sharing test fixtures. We’d like each test to be independent, but we couldn’t find a way to run them as fast as we’d like, so we keep looking for a better solution. Stay tuned!

And what about you? How do you implement your API Tests with Spring Boot? Any feedback is more than welcome.

Thanks to all Clarity AI Engineering Team who have participated in this project and article, especially to co-authors Jerónimo López and Jmiguel Rodriguez.

--

--