Quick and Reliable Tests in Spring Using MongoDB

Introduction

If your Spring application uses MongoDB, there is one question you will have to answer at some point: how to set up a reliable and efficient database instance for your tests. Until early 2020, the default approach was to use 'Embedded Mongo' by Flapdoodle. However, many teams, including ours, found that this method had significant drawbacks that affected both development and continuous integration (CI) environments.


In this comprehensive guide, we will detail the issues associated with Flapdoodle Embedded Mongo, introduce you to the superior alternative—Testcontainers—and guide you through the setup and optimization processes. By the end of this article, you'll be well-equipped to improve your MongoDB-based tests in Spring applications.


Why Flapdoodle Embedded Mongo Became Problematic

The Initial Appeal

Initially, Flapdoodle Embedded Mongo seemed like a fantastic solution. It allowed developers to add a simple dependency without modifying the production code or requiring additional configuration. The setup was as easy as:


testRuntimeOnly('de.flapdoodle.embed:de.flapdoodle.embed.mongo:2.2.0')

Spring Boot would detect the presence of this library on the classpath and automatically start an embedded MongoDB instance during test execution. However, as appealing as this was, several critical issues began to surface.


Performance Bottlenecks and Resource Consumption

One of the most significant problems was the excessive resource consumption. When executing unit tests, developers noticed their computer fans roaring, and their machines becoming sluggish. Upon investigation, it became apparent that multiple MongoDB instances were running concurrently, consuming vast amounts of CPU and memory. This was particularly problematic when using Gradle's parallel build feature, which could result in up to 96 MongoDB instances for three subprojects.


Compatibility and Maintenance Issues

By early 2020, it was evident that Flapdoodle Embedded Mongo was no longer actively maintained. The library was stuck supporting only older versions of MongoDB, and any attempt to integrate modern versions or dependencies would cause tests to hang indefinitely or fail altogether. This made it nearly impossible to implement new business features or keep up with evolving technologies.


Why Testcontainers is the Better Alternative

Introduction to Testcontainers

Testcontainers is a Java library that allows you to start various services, such as databases and message queues, within Docker containers specifically for use in tests. Unlike Flapdoodle Embedded Mongo, Testcontainers provides a more stable and resource-efficient solution. It avoids creating zombie processes and ensures a cleaner, more reliable testing environment.


Setting Up MongoDB with Testcontainers

Testcontainers is a more generic tool compared to Flapdoodle. You'll need to write a bit of code to integrate it with your Spring tests, but the benefits far outweigh this small initial effort. Here’s a step-by-step guide to setting up MongoDB with Testcontainers:

1. Create a Context Initializer


class MongoInitializer : ApplicationContextInitializer {
override fun initialize(context: ConfigurableApplicationContext) {
val addedProperties = listOf(
"spring.data.mongodb.uri=${MongoContainerSingleton.instance.replicaSetUrl}"
)
TestPropertyValues.of(addedProperties).applyTo(context.environment)
}
}

2. Singleton for MongoDB Container


object MongoContainerSingleton {
val instance: MongoDBContainer by lazy { startMongoContainer() }
private fun startMongoContainer(): MongoDBContainer =
MongoDBContainer("mongo:4.2.11")
.withReuse(true)
.apply { start() }
}

3. Custom Annotation for Test Classes


@Target(CLASS)
@SpringBootTest
@ContextConfiguration(initializers = [MongoInitializer::class])
annotation class MongoSpringBootTest

4. Using the Annotation in Tests


@MongoSpringBootTest
class MyTest {
@Test
fun test1() {
// Test implementation
}
}


Optimizing Testcontainers Performance

Simply switching from Flapdoodle Embedded Mongo to Testcontainers already provides a significant performance boost. However, there are additional optimizations you can implement to make tests run even faster.


Enabling Container Reuse

By default, Testcontainers stops the Docker container once all tests have finished. This can slow down test execution during development, especially when running single tests frequently. To enable container reuse, set the following property:


testcontainers.reuse.enable=true

With container reuse enabled, the startup time for a single test becomes comparable to that of Flapdoodle Embedded Mongo.


Managing Parallel Builds

In a CI environment, parallel builds can lead to tests writing to the same MongoDB collections. To mitigate this, ensure each subproject uses a separate database within the same MongoDB container.


Measuring System Performance

Before fully committing to Testcontainers, it’s important to measure the impact on system performance. Our team used SAR and Gnuplot to gather and visualize data, ensuring the shifts were beneficial across different environments (Linux and MacOS).


Using SAR for Performance Metrics

SAR is a valuable tool available in the sysstat package for Linux. It collects detailed system performance statistics with minimal overhead. Here’s how you can use SAR to measure CPU and memory usage:



sar -o sar.binary 2 15 &

This command measures performance every 2 seconds, 15 times, and saves the results to a binary file. To analyze the data:



sar -f sar.binary -u
sar -f sar.binary -r | awk '{print $1 "\t" $4}'

Use Gnuplot to visualize the data:



cat cpu.dat | gnuplot -e "set yrange [0:100]; set terminal .webp size 800,600; set output 'cpu.webp'" base.gnuplot

Summary

Flapdoodle Embedded Mongo, while popular, presents various performance and compatibility problems that make it less suitable for modern Spring applications. Testcontainers offers a robust, actively maintained alternative that supports a wide range of services beyond MongoDB.


By adopting Testcontainers and following the optimizations outlined in this guide, you can improve the performance and stability of your tests, ensuring a smoother development and CI experience.


Interested in more MongoDB and Spring insights? Check out our article on MongoDB schema design for SQL developers and explore our GitHub repository comparing Flapdoodle Embedded Mongo and Testcontainers.