用於 Spring Boot 測試的嵌入式 PostgreSQL
1. 概述
使用資料庫編寫整合測試為測試資料庫提供了多種選項。一個有效的選擇是使用真實的資料庫,確保我們的整合測試密切模仿生產行為。
在本教程中,我們將示範如何使用嵌入式 PostgreSQL進行 Spring Boot 測試並查看一些替代方案。
2. 依賴和配置
我們將首先新增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並啟用了模式重新建立。
3. 使用方法
首先,讓我們建立將在測試中使用的Person實體:
@Entity
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column
private String name;
// getters and setters
}
現在,讓我們為我們的實體建立一個 Spring 資料儲存庫:
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實體並將其保存到資料庫中。
4. 飛行路線整合
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實體保存在資料庫中並成功檢索它。
5.替代方案
5.1.測試容器
嵌入式 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 資料庫中。
5.2. 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 實體保存在資料庫中。
六,結論
在本文中,我們探討如何使用嵌入式 Postgres 資料庫進行測試,並回顧了一些替代方案。有多種方法可以將 Postgres 資料庫合併到測試中,無論是否使用 Docker 容器。最佳選擇取決於您的特定用例。
像往常一樣,完整的源代碼可以在 GitHub 上找到。