知識庫 / Spring / Spring Boot RSS 訂閱

Spring Boot 測試中的嵌入式 PostgreSQL

Spring Boot,Testing
HongKong
7
11:12 AM · Dec 06 ,2025

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 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 測試上下文。 我們通過擴展測試類並將其與我們的 嵌入式Postgres配置 關聯到測試上下文。 隨後,我們成功地創建了一個 Person 實體並將其保存到數據庫中。

4. Flyway 集成

Flyway 是一款流行的遷移工具,用於管理模式變更。在使用時,務必將其包含在我們的集成測試中。在本節中,我們將通過使用嵌入式 Postgres 演示如何執行此操作。首先,請添加以下依賴項:

<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-core</artifactId>
</dependency>
<p>之後,我們將在 Flyway 遷移腳本中定義數據庫模式:</p>
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,通過使用 PreparedDbProviderFlywayPreparer,我們定義了遷移腳本的位置。 最終,這是我們的測試類:

@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

最新版本的嵌入式 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 Embedded Database)

Zonky 內嵌數據庫 是 Embedded 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());
    }
}

在這裏,我們使用了@AutoConfigureEmbeddedDatabase註解,並使用ZONKY提供者,從而能夠在不使用 Docker 的情況下使用嵌入式 Postgres 數據庫. 該庫還支持其他提供者,例如 Embedded 和 Docker。 最終,我們已成功地將 Person 實體保存到數據庫中。

6. 結論

在本文中,我們探討了如何使用嵌入式 Postgres 數據庫進行測試,並回顧了一些替代方案。 無論您使用 Docker 容器還是不使用 Docker 容器,都可以通過多種方式將 Postgres 數據庫集成到測試中。 最好的選擇取決於您的具體用例。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.