1. Overview

2. Configuration

<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>

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

@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

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

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