親愛なるジョンからの手紙

雑記です。ゲームとか漫画とかプログラミングとか。

Spring Bootでデータベースのテストやろうとして面倒臭いことになった備忘録

概要

基本的にデータベースを利用したテストってコードだけで完結しないし、適当な仮想環境をコードベースで用意できないならやるべきじゃないと思ってるんですが、それはそれとしてデータベースを使いたいテストっていうケースもあると思います。

例えばスパゲッティクエリを動的に組み立てなければいけないようなクラス。設計段階でなんとかしろって話なんですが、どうしてもやらざるを得ないところが出てくるし、やったらやったで実際にSQL通るかが不安になってきます。

今回はそんなテストをやってやろうと頑張ったときの備忘録になります。

環境

  • Spring Boot Test Starter 2.0.0
  • H2 Database Engine 1.4.197

状況整理

  • 基本的にはapplication.ymlの値を利用してDataSourceを組み立て。
  • テスト用のDBにはH2DBを利用。
  • マスタデータが膨大なので、各開発者ごとに仮想環境でデータベースを持っており、テストの種類によってはそちらを利用したいこともある(起動時に実行時引数を使ったりJVM引数を使ったりして調整)
    • profileでテスト内容と対象DBを切り替える手もあるけどできれば一気に全部テスト回したい。
  • 前提となるテストの回し方が悪いのは重々承知の上。

実装

やろうとしてダメだった方法

@ClassRule使ってテスト回す前にテーブルを組み立てる

@ClassRule
private static TestRule testRule;

@Autowired
private void setTestRule(CustomTestRule rule) {
  testRule = rule;
}

@Component
class CustomTestRule extends ExternalResource {
  @Autowired
  private DataSource dataSource;

  @Override
  protected void before() throws Throwable {
    // 必要ならデータベース設定したりなんだりする
  }

  @Override
  protected void after() {
    // 必要ならデータベースキレイにしたりなんだりする
  }
}

staticなメンバーだとField Injectionができないので裏技気味に設定。

これだとClassRuleが読まれた後にインジェクションが行われるため、Ruleがnullの状態でコードが走る = 特に何もせずテストが走る模様。 ライフサイクル的にも実装の残念さ加減的にもそらそうよな、といった感じであきらめました。

結局落ち着いた方法

@ClassRuleも@Ruleも使わないで力技で解決した。なのでJUnit5には互換不可能になりました。ふえぇ。

そもそもH2DB使ってるならクリーンアップ処理とかしないでいいし、JUnitならクラスごとに処理が行われるからクラス内の値は独立してるし、H2DB使ってるかどうかの判断処理を入れれば何も考えなくていいわ。ということで全力でクソコードのお出ましです。

private static boolean isSetupFinished;

@Before
public void setup() {
  if(isSetupFinished){
    return;
  }
  // データベースの設定処理
  isSetupFinished = true;
}

@Autowired
private void setTestRule(CustomTestRule rule) {
  testRule = rule;
}

データが入ってるかどうかの判断に関してはSQL内部でやることにした。うーんこの。

なんでこんなクソ記事書いたの?

もっと賢いやり方ありそうだからマサカリが欲しかった。