概要
組み込みMySQL(wix-embedded-mysql) をSpringBootTest、Spockと組み合わせて使う際に、少しハマったのでその対応方法。
ハマりどころ
サンプルで動かしてみるところまではうまく行ったが、複数のテストケースを動かそうとするとうまくいかない。
元々やろうとしてたのは下記の通り。
- 各テストクラスの実行前にMySQLを起動(setupSpec)
- テスト終了時に停止(cleanupSpec)
ところがこれをやろうとするとクラスが増えるとうまくいかない。
どうも下記のようなところで不都合が出る模様。
- SpringBoot(Test) 起動と組み込みDBの起動停止の兼ね合い
- 組み込みDBをテスト実行の中でいつ起動するか停止するかで組み込みDBのプロセス管理を考える必要が出て来る
- 例えばSpringBootの起動前にDBは立ち上がっている状態にしておかないと、dataSourceやflywayなどのDBマイグレーションが動かない
setupSpecじゃなくてDIコンテナに登録しておくとか、色々試してみたものの、最終的に下記の対応に落ち着いた。
対応
- 組み込みMySQLインスタンスはテストフェーズの中でひとつにする
- 全てのテストの中で最初に実行されるテストで起動し、テスト中はそのインスタンスを使い続ける
- 具体的にはSingleton(staticファクトリーメソッド方式)で組み込みMySQLインスタンス提供するUtilityを用意
- setupSpecで、そのインスタンスを明示的にロード
- 最初にロードされた時点で組み込みMySQLが起動し、以降は使いまわされる
- どのsetupSpecが最初に呼ばれるかは保証されないので全テストクラスに必要
- setupSpecはSpringBootの起動より前に呼ばれる
サンプル
MySQLインスタンス提供ユーティリティ
public final class EmbeddedTestMysql {
private EmbeddedTestMysql() {
}
// プロジェクトに合わせた組み込みMySQLの設定
private static final Version VERSION = Version.v5_5_40;
private static final Charset CHARSET = Charset.UTF8;
private static final int PORT = 13306;
private static final String SCHEMA_NAME = "sample";
private static final MysqldConfig MYSQLD_CONFIG = MysqldConfig.aMysqldConfig(VERSION)
.withCharset(CHARSET)
.withPort(PORT)
.build();
private static final SchemaConfig SCHEMA_CONFIG = SchemaConfig.aSchemaConfig(SCHEMA_NAME)
.withCharset(CHARSET)
.build();
private static final EmbeddedMysql SINGLETON_INSTANCE = EmbeddedMysql.anEmbeddedMysql(MYSQLD_CONFIG)
.addSchema(SCHEMA_CONFIG).start();
// staticファクトリーメソッドによるシングルトンインスタンスを提供
public static EmbeddedMysql getInstance() {
return SINGLETON_INSTANCE;
}
実際のテストクラス
@Unroll
@SpringBootTest
class SampleRepositorySpec extends Specification {
@Shared
EmbeddedMysql mysql;
@Autowired
DataSource dataSource
@Autowired
SampleRepository sampleRepository
def setupSpec() {
// Bootの起動より前に呼ばれる
// 初回にどこのテストが呼ばれるか分からないので、必ず書く
mysql = EmbeddedTestMysql.getInstance()
}
def cleanupSpec() {
// インスタンスを使いまわすので、mysql.stop()はしない
}
def setup() {
// この時点でDBは起動している
// Bootも起動している
// 初期データの登録などが可能
}
def "何かしらのテスト"() {
// データベースが必要なテストを実施
}
}