Spring Boot Integration Testing with Embedded MongoDB

1. Overview

In this tutorial, we’ll learn how to use Flapdoodle’s embedded MongoDB solution together with Spring Boot to run MongoDB integration tests smoothly.

MongoDB is a popular NoSQL document database. Thanks to the high scalability, built-in sharding and excellent community support it’s often considered “the NoSQL storage” by many developers.

As with any other persistence technology, it’s critical to be able to test database integration with the rest of our application easily. Thankfully, Spring Boot allows us to write that kind of tests easily.

2. Maven Dependencies

First, let’s set up the Maven parent for our Boot project.

Thanks to the parent we don’t need to define version for each Maven dependency manually.

We’re naturally going to be using Spring Boot:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.5.1</version>
    <relativePath /> <!-- lookup parent from repository -->
</parent>

You can find the latest Boot version here.

Since we added Spring Boot parent, we can add required dependencies without specifying their versions:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

spring-boot-starter-data-mongodb will enable Spring support for MongoDB:

<dependency>
    <groupId>de.flapdoodle.embed</groupId>
    <artifactId>de.flapdoodle.embed.mongo</artifactId>
    <scope>test</scope>
</dependency>

de.flapdoodle.embed.mongo provides embedded MongoDB for integration tests.

3. Test Using Embedded MongoDB

This section covers two scenarios: Spring Boot test and manual test.

3.1. Spring Boot Test

After adding de.flapdoodle.embed.mongo dependency Spring Boot will automatically try to download and start the embedded MongoDB when running tests.

The package will be downloaded only once for each version so that subsequent tests run much faster.

At this stage we should be able to start and pass the sample JUnit 5 integration test:

@DataMongoTest
@ExtendWith(SpringExtension.class)
public class MongoDbSpringIntegrationTest {
    @DisplayName("given object to save"
        + " when save object using MongoDB template"
        + " then object is saved")
    @Test
    public void test(@Autowired MongoTemplate mongoTemplate) {
        // given
        DBObject objectToSave = BasicDBObjectBuilder.start()
            .add("key", "value")
            .get();

        // when
        mongoTemplate.save(objectToSave, "collection");

        // then
        assertThat(mongoTemplate.findAll(DBObject.class, "collection")).extracting("key")
            .containsOnly("value");
    }
}

As we can see, the embedded database was automatically started by Spring, which should also be logged in the console:

...Starting MongodbExampleApplicationTests on arroyo with PID 10413...

3.2. Manual Configuration Test

Spring Boot will automatically start and configure the embedded database and then inject MongoTemplate instance for us. However, sometimes we might need to configure embedded Mongo database manually (e.g., when testing a specific DB version).

The following snippet shows how we can configure the embedded MongoDB instance manually. This is roughly the equivalent of the previous Spring test:

class ManualEmbeddedMongoDbIntegrationTest {
    private static final String CONNECTION_STRING = "mongodb://%s:%d";

    private MongodExecutable mongodExecutable;
    private MongoTemplate mongoTemplate;

    @AfterEach
    void clean() {
        mongodExecutable.stop();
    }

    @BeforeEach
    void setup() throws Exception {
        String ip = "localhost";
        int port = 27017;

        ImmutableMongodConfig mongodConfig = MongodConfig
            .builder()
            .version(Version.Main.PRODUCTION)
            .net(new Net(ip, port, Network.localhostIsIPv6()))
            .build();

        MongodStarter starter = MongodStarter.getDefaultInstance();
        mongodExecutable = starter.prepare(mongodConfig);
        mongodExecutable.start();
        mongoTemplate = new MongoTemplate(MongoClients.create(String.format(CONNECTION_STRING, ip, port)), "test");
    }

    @DisplayName("given object to save"
        + " when save object using MongoDB template"
        + " then object is saved")
    @Test
    void test() throws Exception {
        // given
        DBObject objectToSave = BasicDBObjectBuilder.start()
            .add("key", "value")
            .get();

        // when
        mongoTemplate.save(objectToSave, "collection");

        // then
        assertThat(mongoTemplate.findAll(DBObject.class, "collection")).extracting("key")
            .containsOnly("value");
    }
}

Note, that we can quickly create MongoTemplate bean configured to use our manually configured embedded database and register it inside the Spring container by merely creating, e.g., a @TestConfiguration with @Bean method that will return new MongoTemplate(MongoClients.create(connectionString, “test”).

More examples can be found on the official Flapdoodle’s GitHub repository.

3.3. Logging

We can configure logging messages for MongoDB when running integration tests by adding these two properties to src/test/resources/application.propertes file:

logging.level.org.springframework.boot.autoconfigure.mongo.embedded
logging.level.org.mongodb

For example, to disable logging, we simply set the values to off:

logging.level.org.springframework.boot.autoconfigure.mongo.embedded=off
logging.level.org.mongodb=off

3.4. Using a Real Database on Production

Since we added de.flapdoodle.embed.mongo dependency using <scope>test</scope> there’s no need to disable embedded database when running on production. All we have to do is to specify MongoDB connection details (e.g., host and port) and we are good to go.

To use an embedded DB outside of tests, we can use Spring profiles that will register the right MongoClient (embedded or production) depending on the active profile.

We’ll also need to change the scope of the production dependency to <scope>runtime</scope>.

4. Embedded Testing Controversy

Using embedded database might look like a great idea at the beginning. Indeed, it’s a good approach when we want to test if our application behaves correctly in areas such as:

  • Object<->Document mapping configuration
  • Custom persistence lifecycle event listeners (refer to AbstractMongoEventListener)
  • The logic of any code working directly with the persistence layer

Unfortunately, using an embedded server cannot be considered as “full integration testing”. Flapdoodle’s embedded MongoDB isn’t an official MongoDB product. Therefore, we cannot be sure that it behaves exactly as in the production environment.

If we want to run communication tests in the environment as close to the production as possible, a better solution is to use an environment container such as Docker.

5. Conclusion

Spring Boot makes it extremely simple to run tests that verify proper document mapping and database integration. By adding the right Maven dependency, we are immediately able to use MongoDB components in Spring Boot integration tests.

We need to remember that embedded MongoDB server cannot be considered a replacement for a “real” server.

The full source code of all the examples is available over on GitHub.

Related posts:

Giới thiệu luồng vào ra (I/O) trong Java
Java Program to Implement Sparse Matrix
Hướng dẫn Java Design Pattern – Flyweight
Inheritance with Jackson
Guide to Mustache with Spring Boot
How To Serialize and Deserialize Enums with Jackson
Spring REST API with Protocol Buffers
Java Program to Implement LinkedHashMap API
Java Program to Implement Counting Sort
Implementing a Binary Tree in Java
A Quick Guide to Spring MVC Matrix Variables
Java Program to Find Nearest Neighbor for Dynamic Data Set
Java Program to Find Minimum Element in an Array using Linear Search
Java Program to implement Priority Queue
Java Program to Check Whether Topological Sorting can be Performed in a Graph
Finding Max/Min of a List or Collection
Thao tác với tập tin và thư mục trong Java
Loại bỏ các phần tử trùng trong một ArrayList như thế nào?
New Features in Java 13
Giới thiệu Google Guice – Dependency injection (DI) framework
The “final” Keyword in Java
Guava Collections Cookbook
Java Program to Compute Cross Product of Two Vectors
Find the Registered Spring Security Filters
A Guide to the ResourceBundle
Java Program to Check if any Graph is Possible to be Constructed for a Given Degree Sequence
Java Program to Find Basis and Dimension of a Matrix
Join and Split Arrays and Collections in Java
Summing Numbers with Java Streams
Java Program to Find Median of Elements where Elements are Stored in 2 Different Arrays
Spring Boot: Customize Whitelabel Error Page
Reversing a Linked List in Java