TestContainers和Spring Boot

徐瑞
2023-12-01

TestContainers太棒了! 它提供了一种非常方便的方式来启动和清除JUnit测试中的Docker容器。 此功能对于将应用程序与实际数据库以及可使用docker映像的任何其他资源进行集成测试非常有用。

我的目标是演示使用TestContainers对基于JPA的Spring Boot Application进行示例测试。 该示例基于TestContainer github repo的示例

示例应用

基于Spring Boot的应用程序非常简单–它是基于Spring Data JPA的应用程序,其Web层使用Spring Web Flux编写。 完整的示例可在我的github存储库中找到 ,直接在此处直接遵循代码可能会更容易。

保留的City实体如下所示(使用Kotlin ):

import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.Id

@Entity
data class City(
        @Id @GeneratedValue var id: Long? = null,
        val name: String,
        val country: String,
        val pop: Long
) {
    constructor() : this(id = null, name = "", country = "", pop = 0L)
}

由于出色的Spring Data JPA项目,提供一个用于管理该实体的存储库所需的就是以下接口:

import org.springframework.data.jpa.repository.JpaRepository
import samples.geo.domain.City

interface CityRepo: JpaRepository<City, Long>

我不会在此处介绍Web层,因为它与讨论无关。

测试存储库

Spring Boot提供了一种称为切片测试的功能,这是一种测试应用程序不同水平切片的好方法。 CityRepo存储库的测试如下所示:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.junit4.SpringRunner;
import samples.geo.domain.City;
import samples.geo.repo.CityRepo;

import static org.assertj.core.api.Assertions.assertThat;

@RunWith(SpringRunner.class)
@DataJpaTest
public class CitiesWithEmbeddedDbTest {

    @Autowired
    private CityRepo cityRepo;

    @Test
    public void testWithDb() {
        City city1 = cityRepo.save(new City(null, "city1", "USA", 20000L));
        City city2 = cityRepo.save(new City(null, "city2", "USA", 40000L));

        assertThat(city1)
                .matches(c -> c.getId() != null && c.getName() == "city1" && c.getPop() == 20000L);

        assertThat(city2)
                .matches(c -> c.getId() != null && c.getName() == "city2" && c.getPop() == 40000L);

        assertThat(cityRepo.findAll()).containsExactly(city1, city2);
    }

}

“ @DataJpaTest”注释将启动嵌入式h2数据库,配置JPA并加载任何Spring Data JPA存储库(在此示例中为CityRepo)。

考虑到JPA提供了数据库抽象,并且如果正确使用JPA,则该代码应可跨任何受支持的数据库移植,因此这种测试效果很好。 但是,假设此应用程序有望在生产环境中针对PostgreSQL运行,那么理想情况下,将针对数据库进行某种级别的集成测试,这正是TestContainer所适合的。它提供了一种以docker方式启动PostgreSQL的方法。容器。

测试容器

使用TestContainers的相同存储库测试如下所示:

import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.testcontainers.containers.PostgreSQLContainer;
import samples.geo.domain.City;
import samples.geo.repo.CityRepo;

import java.time.Duration;

import static org.assertj.core.api.Assertions.assertThat;

@RunWith(SpringRunner.class)
@DataJpaTest
@ContextConfiguration(initializers = {CitiesWithPostgresContainerTest.Initializer.class})
public class CitiesWithPostgresContainerTest {

    @ClassRule
    public static PostgreSQLContainer postgreSQLContainer =
            (PostgreSQLContainer) new PostgreSQLContainer("postgres:10.4")
                    .withDatabaseName("sampledb")
                    .withUsername("sampleuser")
                    .withPassword("samplepwd")
                    .withStartupTimeout(Duration.ofSeconds(600));

    @Autowired
    private CityRepo cityRepo;

    @Test
    public void testWithDb() {
        City city1 = cityRepo.save(new City(null, "city1", "USA", 20000L));
        City city2 = cityRepo.save(new City(null, "city2", "USA", 40000L));

        assertThat(city1)
                .matches(c -> c.getId() != null && c.getName() == "city1" && c.getPop() == 20000L);

        assertThat(city2)
                .matches(c -> c.getId() != null && c.getName() == "city2" && c.getPop() == 40000L);

        assertThat(cityRepo.findAll()).containsExactly(city1, city2);
    }

    static class Initializer
            implements ApplicationContextInitializer<ConfigurableApplicationContext> {
        public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
            TestPropertyValues.of(
                    "spring.datasource.url=" + postgreSQLContainer.getJdbcUrl(),
                    "spring.datasource.username=" + postgreSQLContainer.getUsername(),
                    "spring.datasource.password=" + postgreSQLContainer.getPassword()
            ).applyTo(configurableApplicationContext.getEnvironment());
        }
    }
}

代码的核心与先前的测试相同,但是此处的存储库正在针对此处的真实PostgreSQL数据库进行测试。 更详细一点-

正在使用JUnit类规则来启动PostgreSQL容器,该规则会在运行任何测试之前触发。 使用以下类型的gradle依赖项来拉入此依赖项:

testCompile("org.testcontainers:postgresql:1.7.3")

类规则将启动PostgreSQL docker容器(postgres:10.4)并配置数据库和数据库凭据。 现在从Spring Boot的角度来看,这些细节需要在属性开始传递给应用程序之前,即Spring开始为要运行的测试创建测试上下文之前,这是使用ApplicationContextInitializer为测试完成的,Spring在很早的时候就调用了它。 Spring Context的生命周期。

使用以下代码将用于设置数据库名称,URL和用户凭据的自定义ApplicationContextInitializer连接到测试:

...
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
...

@RunWith(SpringRunner.class)
@DataJpaTest
@ContextConfiguration(initializers = {CitiesWithPostgresContainerTest.Initializer.class})
public class CitiesWithPostgresContainerTest {
...

设置好样板后,TestContainer和Spring Boot slice测试将接管测试的运行。 更重要的是,TestContainers还负责拆卸,JUnit类规则可确保测试完成后立即停止并删除容器。

结论

这是一次TestContainers的旋风之旅,TestContainers的功能远不止我在这里介绍的内容,但我希望这为使用此优秀库以及如何使用Spring Boot进行配置提供了可能。 这个示例可以在我的github仓库中找到

翻译自: https://www.javacodegeeks.com/2018/05/testcontainers-and-spring-boot.html

 类似资料: