When you’re building Spring Boot applications that interact with MongoDB, ensuring the reliability of your data access layer is crucial. Unit tests play a vital role, but setting up and tearing down a real MongoDB instance for each test can be slow and cumbersome. This is where MongoDB Memory Server comes to the rescue, providing a fast and efficient in-memory alternative.

What is MongoDB Memory Server?

MongoDB Memory Server is a tool that spins up an actual MongoDB server process in memory. This gives you several key advantages:

  • Real MongoDB Behavior: Unlike mocking, you’re interacting with a real MongoDB instance, which significantly reduces the risk of tests passing but failing in production due to subtle differences in behavior.
  • Speed: In-memory operations are incredibly fast, leading to quicker test execution and faster development cycles.
  • Isolation: Each test can have its own isolated MongoDB instance, preventing data contamination and ensuring reliable results.

How to Use MongoDB Memory Server in Your Spring Boot Tests

Here’s a step-by-step guide to integrating MongoDB Memory Server into your Spring Boot unit testing workflow:

1. Dependencies

  • You’ll need the de.flapdoodle.embed.mongo dependency to manage the embedded MongoDB server. Add this to your pom.xml (Maven) or build.gradle (Gradle):
<dependency>
    <groupId>de.flapdoodle.embed</groupId>
    <artifactId>de.flapdoodle.embed.mongo</artifactId>
    <scope>test</scope>
</dependency>
// Gradle
testImplementation 'de.flapdoodle.embed:de.flapdoodle.embed.mongo'

2. Configuration (Spring Boot)

  • While MongoDB Memory Server handles the server itself, Spring Boot still needs database connection details. You can configure this in your application.properties or application.yml within src/test/resources.

  • Important: You won’t specify a fixed host and port here, as MongoDB Memory Server provides a dynamic connection URI.

  • Example (application.properties):

spring.data.mongodb.uri=${spring.mongodb.embedded.connection-string}
spring.data.mongodb.database=testdb
  • Example (application.yml):
spring:
  data:
    mongodb:
      uri: ${spring.mongodb.embedded.connection-string}
      database: testdb

3. Setting Up and Tearing Down the Embedded MongoDB

  • Here’s how you’d typically manage the MongoDB Memory Server lifecycle within your tests (using JUnit 5):
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import de.flapdoodle.embed.mongo.MongodExecutable;
import de.flapdoodle.embed.mongo.MongodStarter;
import de.flapdoodle.embed.mongo.config.ImmutableMongodConfig;
import de.flapdoodle.embed.mongo.config.Net;
import org.bson.Document;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest;
import org.springframework.context.annotation.Import;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;

import java.io.IOException;

import static org.junit.jupiter.api.Assertions.assertEquals;

@DataMongoTest
public class MyMongoDbTest {

    private static MongodExecutable mongodExecutable;
    private MongoClient mongoClient;
    private MongoCollection<Document> collection;

    @Autowired
    private MongoTemplate mongoTemplate;

    @Value("${spring.data.mongodb.database}")
    private String databaseName;

    @DynamicPropertySource
    static void setProperties(DynamicPropertyRegistry registry) throws IOException {
        MongodStarter starter = MongodStarter.getDefaultInstance();
        int port = 27018; // Choose a free port
        ImmutableMongodConfig mongodConfig = ImmutableMongodConfig.builder()
                .net(new Net("localhost", port, false))
                .build();
        mongodExecutable = starter.prepare(mongodConfig);
        mongodExecutable.start();
        registry.add("spring.mongodb.embedded.connection-string", () -> "mongodb://localhost:" + port);
    }

    @BeforeEach
    void setUp() {
        mongoClient = mongoTemplate.getMongoDatabaseFactory().getMongoClient();
        MongoDatabase database = mongoClient.getDatabase(databaseName);
        collection = database.getCollection("mycollection");
        collection.insertOne(new Document("name", "Test"));
    }

    @AfterEach
    void tearDown() {
        collection.deleteMany(new Document());
        mongoClient.close();
        mongodExecutable.stop();
    }

    @Test
    void myTest() {
        assertEquals(1, collection.countDocuments());
    }
}
  • Explanation
    • @DataMongoTest: This Spring Boot annotation sets up a test context focused on MongoDB, similar to @DataJpaTest for JPA.
    • @DynamicPropertySource: This annotation is used to dynamically set the spring.mongodb.embedded.connection-string property. The setProperties method starts the embedded MongoDB server and registers the connection string with Spring’s environment.
    • MongodStarter: This class from Flapdoodle is used to configure and start the MongoDB Memory Server.
    • @BeforeEach: This JUnit 5 annotation ensures that the setUp method is executed before each test method. Here, we get the MongoClient from Spring’s MongoTemplate, get the database and collection and insert a test document.
    • @AfterEach: This JUnit 5 annotation ensures that the tearDown method is executed after each test method. Here, we delete all documents from the collection, close the MongoClient and stop the embedded MongoDB server.
    • mongoTemplate: Spring Data MongoDB’s MongoTemplate is used to interact with the database.

4. Writing Your Tests

  • Now you can write your unit tests as you normally would, using Spring Data MongoDB’s repositories or the MongoTemplate to interact with the database.
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest;
import org.springframework.data.mongodb.core.MongoTemplate;

import static org.junit.jupiter.api.Assertions.assertEquals;

@DataMongoTest
public class MyServiceTest {

    @Autowired
    private MongoTemplate mongoTemplate;

    @Test
    void testMyServiceLogic() {
        // Arrange
        mongoTemplate.save(new MyDocument("testId", "testValue"), "mycollection");

        // Act
        MyService service = new MyService(mongoTemplate);
        MyDocument result = service.getDocument("testId");

        // Assert
        assertEquals("testValue", result.getValue());
    }
}

Benefits of Using MongoDB Memory Server

  • Fast and Reliable Tests: Significantly speeds up test execution and provides consistent results.
  • Reduced Setup Complexity: Automates the management of MongoDB instances for testing.
  • Improved Code Quality: Encourages more thorough testing of your data access logic.
  • Seamless Spring Boot Integration: Works well with Spring Data MongoDB and Spring Boot’s testing features.

Important Considerations

  • Resource Consumption: Running a MongoDB process in memory can consume significant RAM, especially for large datasets.
  • Platform Compatibility: Ensure that the embedded MongoDB binaries are available for your target platforms.
  • Test Isolation: While MongoDB Memory Server provides good isolation, always clean up your test data properly to avoid any potential side effects.

Conclusion

MongoDB Memory Server is a valuable tool for any Spring Boot developer working with MongoDB. By providing a fast, reliable, and in-memory database for unit testing, it helps you write more effective tests and build higher-quality applications. Remember to carefully manage the lifecycle of the embedded MongoDB instance and consider the potential resource consumption in your test environment.


Discover more from GhostProgrammer - Jeff Miller

Subscribe to get the latest posts sent to your email.

By Jeffery Miller

I am known for being able to quickly decipher difficult problems to assist development teams in producing a solution. I have been called upon to be the Team Lead for multiple large-scale projects. I have a keen interest in learning new technologies, always ready for a new challenge.