Spring BootおよびTestcontainersを使用したDB統合テスト

1. 概要

Spring Data JPAは、データベースクエリを作成し、組み込みH2データベースでテストする簡単な方法を提供します。
しかし、場合によっては、特にプロバイダーに依存するクエリを使用する場合、実際のデータベースでのテストのほうがはるかに有益です*。
このチュートリアルでは、Spring Data JPAおよびPostgreSQLデータベースとの統合テストにlink:/docker-test-containers[Testcontainers]を使用する方法を示します。*
前のチュートリアルでは、いくつかのデータベースlink:/spring-data-jpa-query [主に_ @ Query_アノテーションを使用したクエリ]を作成しました。これをテストします。

2. 構成

テストでPostgreSQLデータベースを使用するには、https://search.maven.org/classic/#search%7Cga%7C1%7Cg%3A%22org.testcontainers%22%20AND%20a%3A%を追加する必要があります22postgresql%22 [Testcontainersdependency] _test_ scope *およびscopehttps://search.maven.org/classic/#search%7Cga%7C1%7Cg%3A%22org.postgresql%22%20AND%20a%3A%22postgresql% 22 [PostgreSQLドライバー]を_pom.xml_に:
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>postgresql</artifactId>
    <version>1.10.6</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.2.5</version>
</dependency>
また、適切なドライバークラスを使用し、各テスト実行でスキームを作成および削除するようSpringに指示するテストリソースディレクトリの下に_application.properties_ファイルを作成しましょう。
spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.hibernate.ddl-auto=create-drop

3. 単一のテスト使用

単一のテストクラスでPostgreSQLインスタンスの使用を開始するには、最初にコンテナ定義を作成し、次にそのパラメーターを使用して接続を確立する必要があります。
@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(initializers = {UserRepositoryTCIntegrationTest.Initializer.class})
public class UserRepositoryTCIntegrationTest extends UserRepositoryCommonIntegrationTests {

    @ClassRule
    public static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer("postgres:11.1")
      .withDatabaseName("integration-tests-db")
      .withUsername("sa")
      .withPassword("sa");

    static class Initializer
      implements ApplicationContextInitializer<ConfigurableApplicationContext> {
        public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
            TestPropertyValues.of(
              "spring.datasource.url=" + postgreSQLContainer.getJdbcUrl(),
              "spring.datasource.username=" + postgreSQLContainer.getUsername(),
              "spring.datasource.password=" + postgreSQLContainer.getPassword()
            ).applyTo(configurableApplicationContext.getEnvironment());
        }
    }
}
上記の例では、JUnitの_ @ ClassRule_を使用して、テストメソッドを実行する前にデータベースコンテナーをセットアップしました。 また、_ApplicationContextInitializerを実装する静的な内部クラスを作成しました。
*これら3つのアクションを実行することにより、Springコンテキストが公開される前に接続プロパティを設定できます。*
前の記事の2つのUPDATEクエリを使用してみましょう。
@Modifying
@Query("update User u set u.status = :status where u.name = :name")
int updateUserSetStatusForName(@Param("status") Integer status,
  @Param("name") String name);

@Modifying
@Query(value = "UPDATE Users u SET u.status = ? WHERE u.name = ?",
  nativeQuery = true)
int updateUserSetStatusForNameNative(Integer status, String name);
そして、構成された環境でそれらをテストします。
@Test
@Transactional
public void givenUsersInDB_WhenUpdateStatusForNameModifyingQueryAnnotationJPQL_ThenModifyMatchingUsers(){
    insertUsers();
    int updatedUsersSize = userRepository.updateUserSetStatusForName(0, "SAMPLE");
    assertThat(updatedUsersSize).isEqualTo(2);
}

@Test
@Transactional
public void givenUsersInDB_WhenUpdateStatusForNameModifyingQueryAnnotationNative_ThenModifyMatchingUsers(){
    insertUsers();
    int updatedUsersSize = userRepository.updateUserSetStatusForNameNative(0, "SAMPLE");
    assertThat(updatedUsersSize).isEqualTo(2);
}

private void insertUsers() {
    userRepository.save(new User("SAMPLE", "[email protected]", 1));
    userRepository.save(new User("SAMPLE1", "[email protected]", 1));
    userRepository.save(new User("SAMPLE", "[email protected]", 1));
    userRepository.save(new User("SAMPLE3", "[email protected]", 1));
    userRepository.flush();
}
上記のシナリオでは、最初のテストは成功して終了しますが、2番目のテストは次のメッセージで_InvalidDataAccessResourceUsageException_をスローします。
Caused by: org.postgresql.util.PSQLException: ERROR: column "u" of relation "users" does not exist
H2埋め込みデータベースを使用して同じテストを実行した場合、両方のテストは正常に完了しますが、PostgreSQLはSET句でエイリアスを受け入れません。 問題のあるエイリアスを削除することで、クエリをすばやく修正できます。
@Modifying
@Query(value = "UPDATE Users u SET status = ? WHERE u.name = ?",
  nativeQuery = true)
int updateUserSetStatusForNameNative(Integer status, String name);
今回は、両方のテストが正常に完了します。 この例では、* Testcontainersを使用してネイティブクエリの問題を特定します。これは、実稼働環境で実際のデータベースに切り替えると明らかになります。*一般に、_JPQL_クエリは、使用されるデータベースプロバイダー。

4. 共有データベースインスタンス

前の段落では、単一のテストでTestcontainerを使用する方法について説明しました。 実際のシナリオでは、起動時間が比較的長いため、同じデータベースコンテナを複数のテストで再利用したいと考えています。
_PostgreSQLContainer_を拡張し、_start()_メソッドと_stop()_メソッドをオーバーライドして、データベースコンテナ作成用の共通クラスを作成しましょう。
public class BaeldungPostgresqlContainer extends PostgreSQLContainer<BaeldungPostgresqlContainer> {
    private static final String IMAGE_VERSION = "postgres:11.1";
    private static BaeldungPostgresqlContainer container;

    private BaeldungPostgresqlContainer() {
        super(IMAGE_VERSION);
    }

    public static BaeldungPostgresqlContainer getInstance() {
        if (container == null) {
            container = new BaeldungPostgresqlContainer();
        }
        return container;
    }

    @Override
    public void start() {
        super.start();
        System.setProperty("DB_URL", container.getJdbcUrl());
        System.setProperty("DB_USERNAME", container.getUsername());
        System.setProperty("DB_PASSWORD", container.getPassword());
    }

    @Override
    public void stop() {
        //do nothing, JVM handles shut down
    }
}
_stop()_メソッドを空のままにしておくと、JVMがコンテナーのシャットダウンを処理できるようになります。 また、単純なシングルトンパターンを実装します。このパターンでは、最初のテストのみがコンテナーの起動をトリガーし、後続の各テストは既存のインスタンスを使用します。 _start()_メソッドでは、__System#setProperty __を使用して、接続パラメーターを環境変数として設定します。
これらを__application.properties __fileに配置できます。
spring.datasource.url=${DB_URL}
spring.datasource.username=${DB_USERNAME}
spring.datasource.password=${DB_PASSWORD}
テスト定義でユーティリティクラスを使用してみましょう。
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserRepositoryTCAutoIntegrationTest {

    @ClassRule
    public static PostgreSQLContainer postgreSQLContainer = BaeldungPostgresqlContainer.getInstance();

    // tests
}
前の例のように、コンテナー定義を保持するフィールドに@ClassRule __annotationを適用しました。 これにより、Springコンテキストの作成前に、_DataSource_接続プロパティに正しい値が入力されます。
  • database_BaeldungPostgresqlContainer_ utilityクラスでインスタンス化された_ @​​ ClassRule_アノテーション付きフィールドを定義するだけで、同じデータベースインスタンスを使用して複数のテストを実装できるようになりました。

5. 結論

この記事では、Testcontainersを使用して実際のデータベースインスタンスでテストを実行する方法を説明しました。
Springの_ApplicationContextInitializer_メカニズムを使用し、再利用可能なデータベースのインスタンス化のためのクラスを実装する、単一のテストの使用例を見ました。
また、Testcontainersが複数のデータベースプロバイダー間の互換性の問題、特にネイティブクエリの特定にどのように役立つかを示しました。
いつものように、この記事で使用される完全なコードはhttps://github.com/eugenp/tutorials/tree/master/persistence-modules/spring-data-jpa[GitHubで]から入手できます。