Java中MongoDB +TestContainer

如何使用 MongoDB 配置 TestContainer,并为数据访问层和使用 MongoDB 的应用程序编写集成测试。

TestContainer帮助我们在运行测试之前启动容器,并通过在代码中定义它们来停止它们。

在本教程中,我们将了解如何使用 MongoDB配置TestContainers。接下来,我们将了解如何为测试创建基础集成。最后,我们将学习如何使用 TestContainers 进行数据访问层和应用程序与 MongoDB 的集成测试。

配置
为了在我们的测试中使用带有 MongoDB 的 TestContainer,我们需要在具有测试范围的pom.xml文件中添加以下依赖项:

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

我们有三个依赖项。第一个是核心依赖项,它提供 TestContainers 的主要功能,例如启动和停止容器。下一个依赖项是 TestContainers 的 JUnit 5 扩展。最后一个依赖项是 TestContainers 的 MongoDB 模块。

我们需要在我们的机器上安装Docker来运行 MongoDB 容器。

创建模型
让我们首先使用@Document注释创建与Product表对应的实体:

@Document(collection = "Product")
public class Product {
    @Id
    private String id;
    private String name;
    private String description;
    private double price;
   
// standard constructor, getters, setters
}

创建存储库
然后,我们将创建 从 MongoRepository扩展的ProductRepository 类:

@Repository
public interface ProductRepository extends MongoRepository<Product, String> {
    Optional<Product> findByName(String name);
}

创建 REST 控制器
最后,让我们通过创建一个控制器来与存储库交互,从而公开 REST API :

@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"));
  }
}

TestContainers MongoDB集成基础
我们将创建一个抽象基类,该基类扩展到需要在运行测试之前和之后启动和停止 MongoDB 容器的所有类:

@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);
    }
}

我们添加了 @Testcontainers 注释以在我们的测试中启用TestContainers支持,并添加了@SpringBootTest注释以启动Spring Boot应用程序上下文。

我们还定义了一个 MongoDB 容器字段,该字段使用mongo:7.0 Docker 映像启动 MongoDB 容器并公开端口27017。@Container注释在运行测试之前启动 MongoDB 容器。

1. 数据访问层集成测试
数据访问层集成测试我们的应用程序与数据库之间的交互。我们将为 MongoDB 数据库创建一个简单的数据访问层并为其编写集成测试。

让我们创建扩展AbstractBaseIntegrationTest类的数据访问集成测试类:

public class ProductDataLayerAccessIntegrationTest extends AbstractBaseIntegrationTest {
    @Autowired
    private ProductRepository productRepository;
    // ..
    
}

现在,我们可以为数据访问层编写集成测试:

@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());
}

我们创建了两个场景:第一个场景保存并检索产品,第二个场景按名称查找产品。两个测试都与 TestContainers 启动的 MongoDB 数据库进行交互。

2. 应用程序集成测试
应用程序集成测试用于测试不同应用程序组件之间的交互。我们将创建一个使用我们之前创建的数据访问层的简单应用程序,并为其编写集成测试。

让我们创建扩展AbstractBaseIntegrationTest类的应用程序集成测试类:

@AutoConfigureMockMvc
public class ProductIntegrationTest extends AbstractBaseIntegrationTest {
    @Autowired
    private MockMvc mvc;
    private ObjectMapper objectMapper = new ObjectMapper();
    // ..
}

我们需要@AutoConfigureMockMvc注释来在我们的测试中启用MockMvc支持,并需要MockMvc字段对我们的应用程序执行 HTTP 请求。

现在,我们可以为我们的应用程序编写集成测试:

@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());
}

我们开发了一个测试来保存产品,然后使用 HTTP 检索它。此过程涉及将数据存储在 MongoDB 数据库中,该数据库由 TestContainers 初始化。