Spring Boot中用嵌入式PostgreSQL測試

banq發表於2024-06-06

使用資料庫編寫整合測試提供了多種測試資料庫選項。一種有效的選項是使用真實資料庫,以確保我們的整合測試與生產行為緊密相關。

在本教程中,我們將演示如何使用嵌入式 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;
    <font>// getters and setters<i>
}

現在,讓我們為實體建立一個 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(<font>"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(<font>"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(<font>"db/migrations"))
          .createDataSource();
    }
}

我們指定了資料來源 bean,並使用PreparedDbProvider和FlywayPreparer定義了遷移指令碼的位置。最後,這是我們的測試類:

@DataJpaTest(properties = { <font>"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(
      <font>"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(<font>"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(<font>"New user");
        Person savedPerson = repository.save(person);
        assertNotNull(savedPerson.getId());
        assertEquals(person.getName(), savedPerson.getName());
    }
}

在這裡,我們使用ZONKY提供程式指定了@AutoConfigureEmbeddedDatabase註釋,使我們能夠在沒有 Docker 的情況下使用嵌入式 Postgres 資料庫。此庫還支援其他提供程式,例如 Embedded 和 Docker。最後,我們已成功將 Person 實體儲存在資料庫中。

相關文章