1. 概述
编写数据库集成测试时,测试数据库的选择至关重要。使用真实数据库是最佳实践之一,能确保集成测试最大程度模拟生产环境行为。
本文将演示如何在Spring Boot测试中使用嵌入式PostgreSQL,并对比几种替代方案。
2. 依赖和配置
首先添加Spring Data JPA依赖,用于创建Repository:
<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>
最后添加嵌入式PostgreSQL依赖:
<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
✅ 指定了PostgreSQL方言
✅ 启用测试执行前自动重建数据库结构
3. 使用
先创建测试用的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();
}
}
}
⚠️ 关键点:
- 指定了Repository和实体类路径
- 使用
EmbeddedPostgres
构建器创建数据源,可指定PostgreSQL版本 - 添加
EmbeddedPostgresExtension
确保测试类执行后关闭连接
最后创建测试类:
@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
注解初始化测试上下文
✅ 通过EmbeddedPostgresExtension
扩展测试类
✅ 成功创建并保存Person
实体到数据库
4. Flyway集成
Flyway是流行的数据库迁移工具,集成测试中需要包含它。本节演示如何结合嵌入式PostgreSQL使用。
添加依赖:
<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();
}
}
✅ 使用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
实体
5. 替代方案
5.1. Testcontainers
最新版嵌入式PostgreSQL项目底层使用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();
}
}
✅ 实现ApplicationContextInitializer
设置测试上下文属性
✅ 实现AfterAllCallback
测试后关闭容器连接
测试类实现:
@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());
}
}
✅ 使用@ContextConfiguration
指定初始化器
✅ 成功在测试容器中保存实体
5.2. Zonky嵌入式数据库
Zonky Embedded Database作为嵌入式PostgreSQL的分支,继续支持无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());
}
}
✅ 使用@AutoConfigureEmbeddedDatabase
注解指定ZONKY提供者
✅ 无需Docker即可使用嵌入式PostgreSQL
✅ 同时支持Embedded和Docker等其他提供者
6. 结论
本文探讨了在测试中使用嵌入式PostgreSQL的方法及替代方案。PostgreSQL测试集成方案多样,无论是否使用Docker容器都能实现。最佳选择取决于具体场景:
- ✅ 简单场景:直接使用嵌入式PostgreSQL
- ✅ 需要容器化:Testcontainers
- ✅ 无Docker环境:Zonky方案
完整源代码可在GitHub获取。