[Spring] ユニットテスト実行前の 1 回だけ SQL を実行したい

JavaJUnit5,SpringBoot,SQL,UnitTest

Spring Boot + JUnit5 のユニットテストで、テストファイル実行時に最初の 1 回だけ SQL を実行する方法。複数のテストメソッドがあるテストファイルで @Sql を使うと、テストメソッドごとに @Sql で指定した SQL が実行される。 1 テストファイルにつき 1 回だけテストデータを流す方法を調べたのでメモ。

ResourceDatabasePopulator を使う

@BeforeAllResourceDatabasePopulator を使って、スクリプトを実行する。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import javax.sql.DataSource;

@SpringBootTest
public class ResourceDatabasePopulatorTest {

  @BeforeAll
  public static void initDb(@Autowired DataSource dataSource) {
    ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
    // テスト用の SQL ファイルを指定する
    populator.addScript(new ClassPathResource("xxx.sql"));
    populator.addScript(new ClassPathResource("xxxx.sql"));
    populator.execute(dataSource);
  }

}

@BeforeAllstatic だからフィールドインジェクションはできない。だから、メソッドの引数のとこで @Autowired した。

@BeforeAll で SQL 実行しているので、テストクラス内で 1 番最初に 1 回だけ実行される。テスト終了後はロールバックされ、 DB データ投入前の状態に戻る。

@Sql はテストメソッドごとに実行される

テストクラス 1 つにつき  1 回だけ SQL を実行したかった理由。

  • ID 指定した同じデータを各メソッドで使いたいときがあったから ( DELETE できるできないとかトランザクションの確認とかで )
  • 同じ ID のデータは一意制約違反になったから

例えば以下のように、テストクラス前に @Sql を付けると、各テストメソッド実行前に同じ SQL が実行される。

@SpringBootTest
@Sql("classpath:unit/test.sql")
public class ResourceDatabasePopulatorTest {

}

各メソッド前に @Sql を付けると、各メソッド後にデータは消えるが、同じ ID で SQL 流すと一意制約違反になる。

@SpringBootTest
public class ResourceDatabasePopulatorTest {

    @Test
    @Sql("classpath:unit/test.sql")
    public void testXxx() {

    }

    @Test
    @Sql("classpath:unit/test.sql")
    public void testXyz() {

    }
}

推測だが、これはロールバックはされる (データは消える) が、コミットされない、または同一トランザクション内だから auto_increment はリセットされていない (今回使用していたのは MySQL )。実際、 MySQL はデータを削除しても、 auto_incrementID は削除データ ID + 1 で次のデータが登録される。

Posted by Agopeanuts