使用数据库编写集成测试提供了多种测试数据库选项。一种有效的选项是使用真实数据库,以确保我们的集成测试与生产行为紧密相关。
在本教程中,我们将演示如何使用嵌入式 PostgreSQL进行 Spring Boot 测试并回顾一些替代方案。
我们首先添加Spring Data JPA 依赖项,因为我们将使用它来创建我们的存储库:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>
|
要为 Spring Boot 应用程序编写集成测试,我们需要包含Spring Test 依赖项:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
|
最后,我们需要包含嵌入式 Postgres 依赖项:
<dependency> <groupId>com.opentable.components</groupId> <artifactId>otj-pg-embedded</artifactId> <version>1.0.3</version> <scope>test</scope> </dependency>
|
另外,让我们为国际测试设置基本配置:
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect spring.jpa.hibernate.ddl-auto=create-drop
|
我们在执行测试之前指定了PostgreSQLDialect并启用了模式重新创建。
首先,让我们创建在测试中使用的Person实体:
@Entity public class Person { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @Column private String name; // getters and setters }
|
现在,让我们为实体创建一个 Spring Data Repository:
public interface PersonRepository extends JpaRepository<Person, Long> { }
|
之后我们来创建一个测试配置类:
@Configuration @EnableJpaRepositories(basePackageClasses = PersonRepository.class) @EntityScan(basePackageClasses = Person.class) public class EmbeddedPostgresConfiguration { private static EmbeddedPostgres embeddedPostgres; @Bean public DataSource dataSource() throws IOException { embeddedPostgres = EmbeddedPostgres.builder() .setImage(DockerImageName.parse("postgres:14.1")) .start(); return embeddedPostgres.getPostgresDatabase(); } public static class EmbeddedPostgresExtension implements AfterAllCallback { @Override public void afterAll(ExtensionContext context) throws Exception { if (embeddedPostgres == null) { return; } embeddedPostgres.close(); } } }
|
在这里,我们指定了存储库和实体的路径。我们使用EmbeddedPostgres构建器创建了数据源,并选择了测试期间要使用的 Postgres 数据库的版本。此外,我们添加了EmbeddedPostgresExtension以确保在执行测试类后关闭嵌入式 Postgres 连接。最后,让我们创建测试类:
@DataJpaTest @ExtendWith(EmbeddedPostgresExtension.class) @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @ContextConfiguration(classes = {EmbeddedPostgresConfiguration.class}) public class EmbeddedPostgresIntegrationTest { @Autowired private PersonRepository repository; @Test void givenEmbeddedPostgres_whenSavePerson_thenSavedEntityShouldBeReturnedWithExpectedFields(){ Person person = new Person(); person.setName("New user"); Person savedPerson = repository.save(person); assertNotNull(savedPerson.getId()); assertEquals(person.getName(), savedPerson.getName()); } }
|
我们使用@DataJpaTest注释来设置基本的 Spring 测试上下文。我们使用EmbeddedPostgresExtension扩展了测试类,并将EmbeddedPostgresConfiguration附加到测试上下文。之后,我们成功创建了一个Person实体并将其保存在数据库中。
Flyway 集成
Flyway是一种流行的迁移工具,可帮助管理架构更改。当我们使用它时,将其包含在我们的国际测试中非常重要。在本节中,我们将了解如何使用嵌入式 Postgres 来完成此操作。让我们从依赖项开始:
<dependency> <groupId>org.flywaydb</groupId> <artifactId>flyway-core</artifactId> </dependency>
|
之后,让我们在 flyway 迁移脚本中指定数据库模式:
CREATE SEQUENCE IF NOT EXISTS person_seq INCREMENT 50; ; CREATE TABLE IF NOT EXISTS person( id bigint NOT NULL, name character varying(255) ) ;
|
现在我们可以创建测试配置:
@Configuration @EnableJpaRepositories(basePackageClasses = PersonRepository.class) @EntityScan(basePackageClasses = Person.class) public class EmbeddedPostgresWithFlywayConfiguration { @Bean public DataSource dataSource() throws SQLException { return PreparedDbProvider .forPreparer(FlywayPreparer.forClasspathLocation("db/migrations")) .createDataSource(); } }
|
我们指定了数据源 bean,并使用PreparedDbProvider和FlywayPreparer定义了迁移脚本的位置。最后,这是我们的测试类:
@DataJpaTest(properties = { "spring.jpa.hibernate.ddl-auto=none" }) @ExtendWith(EmbeddedPostgresExtension.class) @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @ContextConfiguration(classes = {EmbeddedPostgresWithFlywayConfiguration.class}) public class EmbeddedPostgresWithFlywayIntegrationTest { @Autowired private PersonRepository repository; @Test void givenEmbeddedPostgres_whenSavePerson_thenSavedEntityShouldBeReturnedWithExpectedFields(){ Person person = new Person(); person.setName("New user"); Person savedPerson = repository.save(person); assertNotNull(savedPerson.getId()); assertEquals(person.getName(), savedPerson.getName()); List<Person> allPersons = repository.findAll(); Assertions.assertThat(allPersons).contains(person); } }
|
我们禁用了spring.jpa.hibernate.ddl-auto属性,以允许 Flyway 处理架构更改。之后,我们将Person实体保存在数据库中并成功检索它。
TestContainer
嵌入式 Postgres 项目的最新版本在后台使用TestContainers 。因此,一种替代方法是直接使用 TestContainers 库。让我们从添加必要的依赖项开始:
<dependency> <groupId>org.testcontainers</groupId> <artifactId>postgresql</artifactId> <version>1.19.8</version> <scope>test</scope> </dependency>
|
现在我们将创建初始化类,在其中为我们的测试配置PostgreSQLContainer :
public class TestContainersInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext>, AfterAllCallback { private static final PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer( "postgres:14.1") .withDatabaseName("postgres") .withUsername("postgres") .withPassword("postgres"); @Override public void initialize(ConfigurableApplicationContext applicationContext) { postgreSQLContainer.start(); TestPropertyValues.of( "spring.datasource.url=" + postgreSQLContainer.getJdbcUrl(), "spring.datasource.username=" + postgreSQLContainer.getUsername(), "spring.datasource.password=" + postgreSQLContainer.getPassword() ).applyTo(applicationContext.getEnvironment()); } @Override public void afterAll(ExtensionContext context) throws Exception { if (postgreSQLContainer == null) { return; } postgreSQLContainer.close(); } }
|
我们创建了PostgreSQLContainer实例并实现了ApplicationContextInitializer接口来设置测试上下文的配置属性。此外,我们还实现了 AfterAllCallback以在测试后关闭 Postgres 容器连接。现在,让我们创建测试类:
@DataJpaTest @ExtendWith(TestContainersInitializer.class) @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @ContextConfiguration(initializers = TestContainersInitializer.class) public class TestContainersPostgresIntegrationTest { @Autowired private PersonRepository repository; @Test void givenTestcontainersPostgres_whenSavePerson_thenSavedEntityShouldBeReturnedWithExpectedFields() { Person person = new Person(); person.setName("New user"); Person savedPerson = repository.save(person); assertNotNull(savedPerson.getId()); assertEquals(person.getName(), savedPerson.getName()); } }
|
在这里,我们使用TestContainersInitializer扩展了测试,并使用@ContextConfiguration注释指定了测试配置的初始化程序。我们创建了与上一节相同的测试用例,并成功地将Person实体保存在测试容器中运行的 Postgres 数据库中。
Zonky 嵌入式数据库
Zonky 嵌入式数据库是作为嵌入式 Postgres 的一个分支创建的,并继续支持没有 Docker 的测试数据库选项。让我们添加使用此库所需的依赖项:
<dependency> <groupId>io.zonky.test</groupId> <artifactId>embedded-postgres</artifactId> <version>2.0.7</version> <scope>test</scope> </dependency> <dependency> <groupId>io.zonky.test</groupId> <artifactId>embedded-database-spring-test</artifactId> <version>2.5.1</version> <scope>test</scope> </dependency>
|
之后我们就可以编写测试类了:
@DataJpaTest @AutoConfigureEmbeddedDatabase(provider = ZONKY) public class ZonkyEmbeddedPostgresIntegrationTest { @Autowired private PersonRepository repository; @Test void givenZonkyEmbeddedPostgres_whenSavePerson_thenSavedEntityShouldBeReturnedWithExpectedFields(){ Person person = new Person(); person.setName("New user"); Person savedPerson = repository.save(person); assertNotNull(savedPerson.getId()); assertEquals(person.getName(), savedPerson.getName()); } }
|
在这里,我们使用ZONKY提供程序指定了@AutoConfigureEmbeddedDatabase注释,使我们能够在没有 Docker 的情况下使用嵌入式 Postgres 数据库。此库还支持其他提供程序,例如 Embedded 和 Docker。最后,我们已成功将 Person 实体保存在数据库中。