MongoDB Testcontainers Setup for @DataMongoTest

Last Updated:  April 6, 2022 | Published: January 5, 2021

MongoDB is one of the NoSQL databases that Spring Boot offers great testing support for. Like all other test slice annotations from Spring Boot, when using @DataMongoTest, we'll get a Spring Test Context with just enough beans to test any MongoDB-related code. By default, Spring Boot tries to start and use an embedded MongoDB instance. We'll disable the default behavior of @DataMongoTest with this blog post to plug in a MongoDB Docker container instead and use Testcontainers for this purpose.

Spring Boot Project Setup for MongoDB and Testcontainers

For demonstration purposes, let's use a basic Spring Boot application. As we are about to test MongoDB-related code, we going to use the Spring Boot Starter for MongoDB. Apart from this starter, the project includes the Spring Boot Starter Web and Test (aka. the swiss-army knife for testing).

The only non-Spring Boot dependencies for this project are two test dependencies from Testcontainers. The junit-jupiter dependency contains the Testcontainers JUnit Jupiter extension, which we're going to use to manage our container's lifecycle. mongodb includes the MongoDB Testcontainers Module. To manage and align the version of both imports, we add the testcontainers-bom (Bill of Materials) to the dependencyManagement block.

Introduction To @DataMongoTest

Similar to @DataJpaTest, when writing tests that involve a relational database, with @DataMongoTest, we get a sliced Spring Context. This Spring Test Context contains all relevant MongoDB components of our Spring Boot application.

This includes:

  • a configured MongoTemplate (a utility for several basic MongoDB operations, similar to the JdbcTemplate)
  • a scan for all @Document objects (our collection entities)
  • all Spring Data MongoDB repositories (all our interfaces that extends the MongoRepository interface)

All other components (e.g. @Controller, @Service, @Component) are not part of this context.

When using this annotation, Spring Boot tries to start and auto-configure an embedded MongoDB database for us. The relevant logic is part of the EmbeddedMongoAutoConfiguration class.

This embedded database setup works for a lot of use cases. However, when we want to tweak the MongoDB server configuration, this mechanism might not be the best.

Furthermore, to use a pre-filled database with a production-like payload for testing, we might be better off with an already prepared Docker image. That's where Testcontainers comes into play. With Testcontainers we can manage the lifecycle of a Docker container for our tests.

Testcontainers already provides a MongoDB module (currently in incubating mode), making the setup even less complicated, especially when working with a MongoDB server version > 4.

Let's see how we can integrate Testcontainers with @DataMongoTest to use a dockerized MongoDB instance.

Accessing MongoDB with a Spring Data MongoRepository

As a first example, let's write a test for a custom repository for one of our documents. The relevant @Document class stores basic information about a customer:

By adding a CustomerRepository interface that extends the MongoRepository inteface we get access to all well-known repository methods like .save(), .delete(), .findAll().

We can enrich this repository with custom queries that might use the derived query feature of Spring Data:

Whether or not this query method is worth testing in isolation is not relevant for this blog post, as we are focussing on the required setup steps for using @DataMongoTest with Testcontainers. In general, try not to test the framework (e.g. simple derived queries) and instead focus on testing more advanced custom queries (e.g. JSON-based Queries with SpEL Expressions).

Test Setup For @DataMongoTest And Testcontainers

With this in mind, we can move on to writing tests for the CustomerRepository. We can break down the required setup into the following steps:

  1. Disable the auto-configuration for an embedded MongoDB
  2. Define our MongoDB Docker container and start it before executing the test
  3. Override the spring.data.mongodb.uri to point to the local database container (Testcontainers exposes a random ephemeral port)

We can accomplish the first step by excluding the EmbeddedMongoAutoConfiguration for our test. There are multiple ways to exclude a Spring Boot auto-configuration for a test. We're going to use the excludeAutoConfiguration attribute of the @DataMongoTest annotation.

Next comes the Docker container definition. With the help of the MongoDB module for Testcontainers, we can instantiate a MongoDBContainer and pass the Docker Image name of our choice. Make sure to specify the same MongoDB version that is used in production. We are not limited to use the official mongo Docker image here and can also provide a custom image that e.g. builds on top of the official image and tweaks the config or pre-populates data.

The Testcontainers JUnit Jupiter extension that we register with @Testcontainers takes care of the container lifecycle (starting/stopping).

What's left is to override the relevant Spring Boot property to connect to our local dockerized MongoDB instance. With @DynamicPropertySource we can solve this in an elegant way prior to starting the Spring Test Context.

Putting it all together, the full test for verifying our custom query looks like the following:

For projects that are using Spring Boot < 2.2.6 @DynamicPropertySource is not available yet. Refer to this article for a solution to configure the relevant Spring Boot property instead.

Verifying Code That Makes Use Of The MongoTemplate with @DataMongoTest

As an additional example, let's see how we can use this setup to test a service class that uses the MongoTemplate.

Our CustomerService class has one public method to find all customers that we consider as VIP:

We can use the exact same setup for testing the CustomerService:

The main difference here is that we have to instantiate the class under test on our own as it is not part of the Spring Test Context. In the previous example, the repository was already part of it, and hence we were able to inject it.

For this example, we could also write a plain unit test (if there would be more business logic) and use Mockito to mock the behavior of the MongoTemplate.

We can further tweak the Testcontainers setup to either reuse already started Docker containers or start one container for all our tests.

You can find additional Testcontainer recipes in the following articles:

The source code for this example is available on GitHub.

Have fun writing MongoDB tests with @DataMongoTest and Testcontainers,

Philip

>