Database RiderでのCSVテストデータの外部キー制約エラー: 原因と解決策
背景
私たちはSpring Bootを用いたサービス開発をしており、その統合テストでDatabase Riderを活用してCSV形式のテストデータをDBにインポートしています。テストデータの管理には、アプリケーション用の権限が制限されたDBユーザーと、テストデータの管理に幅広い権限を持つDBユーザーの二種類を使用しています。詳細なセットアップについては以前の記事で触れています:
今回の投稿では、このセットアップを基に進める中で発生した、テストデータのインポートエラーとその解決策について詳述します。
課題:外部キー制約のあるテーブルにテストデータをインポートする際のエラー
統合テストのケースが増えるにつれて、テストデータのインポート時に以下のようなエラーが頻発しました。
ERROR: update or delete on table "product" violates foreign key constraint "fk_product" on...DETAIL: Key is still referenced from table "product_metadata".
このエラーは、productテーブルを更新または削除しようとした際に、product_metadataテーブルが外部キー制約でproductテーブルを参照しているため、操作を完了できないことを示しています。
原因: テストデータセットのtable-ordering.txtの内容に不整合あり
原因は、@DataSet
で指定しているテストデータセットの、table-ordering.txt
の対象テーブルがテスト毎にばらつきがあったことでした。
これは、@DataSet
アノテーションが何をしているかに関係しています。
@DataSet
アノテーションでcsvファイルを利用してテストデータを流し込む際、table-ordering.txt
というファイルをテストデータと同じ階層においておくと、cleanBefore
オプションがその順番にテーブルのデータを消し、その後テストデータを流し込んでくれます。
しかし、このtable-ordering.txt
に含まれるテーブルの一覧が、他のテストデータのtable-ordering.txt
と内容が違うと、削除するテーブルにも差分が出ることがわかりました。
例えば、 testdata-1
と testdata-2
の二つのテストデータセットが以下のように異なる場合、エラーが発生します。
// testdata-1/table-ordering.txt
user
product
product_metadata
// testdata-2/table-ordering.txt
user
product
product_metadataはproductテーブルへの外部キー制約が存在するため、testdata-1を使用後にtestdata-2を使用すると、外部キー制約によるエラーが発生します。
解決策
解決策として、table-ordering.txt
の内容に左右されずに、テストごとにDBの内容を一貫して初期化することが有効です。
採用している解決策: 初期化用のデータセットをテスト前にインポート
DBRiderを用いてデータ初期化を実施する方法をチームメンバーのJagaさんがひらめき、現在はこちらの解決策を採用しています。
その方法は、初期化用のテストデータセットをテスト実行前に流し込む、というものです。
具体的には、以下のようなテストデータセットを用意します。
- ヘッダのみでデータは含まない、全テーブルのテストデータCSVファイル群
- 全てのテーブル名が記載された
table-ordering.txt
このテストデータセットを、@BeforeEach
にて流し込むことでDBを初期化させます。
このデータセットを流し込む手順では、以下のようなことが起こっています。
-
table-ordering.txt
に全てのテーブル名が記載されているので、最初に全てのテーブルに対してDELETEが走ります。 - その後、全てのテーブルのデータを、CSVファイルをもとに流し込もうとします。ただし、CSVファイルのレコード内容は空なので、特にレコードが追加されることはありません。
以下のようなコード例で実現しています。
@SpringBootTest
@DBRider(name = "dbRiderDataSource")
public DummyApiTest {
@BeforeEach
@DataSet(value = "empty/user.csv", cleanBefore = true)
public void beforeEach() {
}
@Test
@DataSet(value = "testdata-1/user.csv")
void test_ユーザー一覧が表示できること() {
}
...
}
@Transactional
アノテーション
トライしたが採用していない解決策:統合テスト時のDBの初期化には@Transactional
アノテーションが利用できることを、プロになるためのSpring入門で知りました。
以下の記事を参考に、DBテーブルの初期化に @Transactional
アノテーションを利用する方法を試しました。
Database RiderをSpring Framework(Spring Boot)と合わせて使う時に、Rider JUnit 5とRider Springのどちらを使うか? - CLOVER🍀
dbrider-junit5
ライブラリを利用していると@Transactional
アノテーションがうまく動作せずdbrider-spring
ライブラリを利用するとよい点が大変参考になりました。
しかし、背景で記載した、複数のDBユーザーを用いる私達の設定の場合、@Transactional
アノテーションは想定どおり機能しないことがわかり、この方法を取ることは断念しました。
まとめ
DBユーザーに権限制約がある状況下でDatabase Riderを利用する際に、CSVファイルからのデータインポート時に発生するエラーの解決法を書きました。
Nstockではエンジニアを募集しています!
👨👩👧👦カジュアル面談から気軽にお話しましょう🤞
Discussion