Course – LS – All
announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

Test containers help us spin up containers before running tests and stop them afterward by defining them in our code.

In this tutorial, we’ll take a look at configuring Testcontainers with MongoDB. Next, we’ll see how to create a base integration for our tests. Finally, we’ll learn how to use the library for the data access layer and application integration tests with MongoDB.

2. Configuration

To use Testcontainers with MongoDB in our tests, we need to add the following dependencies to our pom.xml file with test scope:

<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>testcontainers</artifactId>
    <version>1.18.3</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>1.18.3</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>mongodb</artifactId>
    <version>1.18.3</version>
    <scope>test</scope>
</dependency>

We have three dependencies. First is the core dependency that provides the main functionality of Testcontainers, such as starting and stopping containers. The next dependency is the JUnit 5 extension, and the last dependency is the MongoDB module.

We need Docker installed on our machine to run the MongoDB container.

2.1. Creating the Model

Let’s start by creating the entity corresponding to the Product table using the @Document annotation:

@Document(collection = "Product")
public class Product {

    @Id
    private String id;

    private String name;

    private String description;

    private double price;

    // standard constructor, getters, setters
}

2.2. Creating the Repository

Then, we’ll create our ProductRepository class extending from MongoRepository:

@Repository
public interface ProductRepository extends MongoRepository<Product, String> {

    Optional<Product> findByName(String name);
}

2.3. Creating the REST Controller

Finally, let’s expose a REST API by creating a controller to interact with the repository:

@RestController
@RequestMapping("/products")
public class ProductController {

    private final ProductRepository productRepository;

    public ProductController(ProductRepository productRepository) {
        this.productRepository = productRepository;
    }

    @PostMapping
    public String createProduct(@RequestBody Product product) {
        return productRepository.save(product)
          .getId();
  }

    @GetMapping("/{id}")
    public Product getProduct(@PathVariable String id) {
        return productRepository.findById(id)
          .orElseThrow(() -> new RuntimeException("Product not found"));
  }

}

3. Testcontainers MongoDB Integration Base

We’ll create an abstract base class that extends to all classes that need to start and stop the MongoDB container before and after running the tests:

@Testcontainers
@SpringBootTest(classes = MongoDbTestContainersApplication.class)
public abstract class AbstractBaseIntegrationTest {

    @Container
    static MongoDBContainer mongoDBContainer = new MongoDBContainer("mongo:7.0").withExposedPorts(27017);

    @DynamicPropertySource
    static void containersProperties(DynamicPropertyRegistry registry) {
        mongoDBContainer.start();
        registry.add("spring.data.mongodb.host", mongoDBContainer::getHost);
        registry.add("spring.data.mongodb.port", mongoDBContainer::getFirstMappedPort);
    }
}

We added the @Testcontainers annotation to enable Testcontainers support in our tests and the @SpringBootTest annotation to start the Spring Boot application context.

We also defined a MongoDB container field that starts the MongoDB container with the mongo:7.0 Docker image and exposes port 27017. The @Container annotation starts the MongoDB container before running the tests.

3.1. Data Access Layer Integration Tests

Data access layer integration tests the interaction between our application and the database. We’ll create a simple data access layer for a MongoDB database and write integration tests for it.

Let’s create our data access integration test class that extends the AbstractBaseIntegrationTest class:

public class ProductDataLayerAccessIntegrationTest extends AbstractBaseIntegrationTest {

    @Autowired
    private ProductRepository productRepository;

    // ..
    
}

Now, we can write integration tests for our data access layer:

@Test
public void givenProductRepository_whenSaveAndRetrieveProduct_thenOK() {
    Product product = new Product("Milk", "1L Milk", 10);

    Product createdProduct = productRepository.save(product);
    Optional<Product> optionalProduct = productRepository.findById(createdProduct.getId());

    assertThat(optionalProduct.isPresent()).isTrue();

    Product retrievedProduct = optionalProduct.get();
    assertThat(retrievedProduct.getId()).isEqualTo(product.getId());
}
@Test
public void givenProductRepository_whenFindByName_thenOK() {
    Product product = new Product("Apple", "Fruit", 10);

    Product createdProduct = productRepository.save(product);
    Optional<Product> optionalProduct = productRepository.findByName(createdProduct.getName());

    assertThat(optionalProduct.isPresent()).isTrue();

    Product retrievedProduct = optionalProduct.get();
    assertThat(retrievedProduct.getId()).isEqualTo(product.getId());
}

We created two scenarios: the first saves and retrieves a product and the second finds a product by name. Both tests interact with the MongoDB database spun up by Testcontainers.

3.2. Application Integration Tests

Application integration tests are used to test the interaction between different application components. We’ll create a simple application that uses the data access layer we created earlier and write integration tests for it.

Let’s create our application integration test class that extends the AbstractBaseIntegrationTest class:

@AutoConfigureMockMvc
public class ProductIntegrationTest extends AbstractBaseIntegrationTest {

    @Autowired
    private MockMvc mvc;
    private ObjectMapper objectMapper = new ObjectMapper();

    // ..

}

We need the @AutoConfigureMockMvc annotation to enable the MockMvc support in our tests and the MockMvc field to perform HTTP requests to our application.

Now, we can write integration tests for our application:

@Test
public void givenProduct_whenSave_thenGetProduct() throws Exception {
    MvcResult mvcResult = mvc.perform(post("/products").contentType("application/json")
      .content(objectMapper.writeValueAsString(new Product("Banana", "Fruit", 10))))
      .andExpect(status().isOk())
      .andReturn();

    String productId = mvcResult.getResponse()
      .getContentAsString();

    mvc.perform(get("/products/" + productId))
      .andExpect(status().isOk());
}

We developed a test to save a product and then retrieve it using HTTP. This process involves storing the data in a MongoDB database, which Testcontainers initializes.

4. Conclusion

In this article, we learned how to configure Testcontainers with MongoDB and write integration tests for a data access layer and an application that uses MongoDB.

We started with configuring the library with MongoDB to do the setup. Next, we created a base integration test for our tests.

Finally, we wrote data access and application integration tests that use the MongoDB database provisioned by Testcontainers.

As always, the full implementation of these examples can be found over on GitHub.

Course – LS – All
announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

res – REST with Spring (eBook) (everywhere)
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments