TestContainers太棒了! 它提供了一种非常方便的方式来启动和清除JUnit测试中的Docker容器。 此功能对于将应用程序与实际数据库以及可使用docker映像的任何其他资源进行集成测试非常有用。
我的目标是演示使用TestContainers对基于JPA的Spring Boot Application进行示例测试。 该示例基于TestContainer github repo上的示例 。
示例应用
基于Spring Boot的应用程序非常简单–它是基于Spring Data JPA的应用程序,其Web层使用Spring Web Flux编写。 完整的示例可在我的github存储库中找到 ,直接在此处直接遵循代码可能会更容易。
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