PostgreSQLを使ったユニットテストをするときに、外部キーを一時的に無効化する

概要

RDBを使ったユニットテストをするときに、前提条件となるテストデータを用意することがある。

しかし外部キーの制約上、テストしたいことと直接関係ないデータの作成が必要になる状況が発生する。

例えば、テーブルが「親-子-孫」という階層になっているときに、孫に関するテストを書きたいのだが子だけでなく親のデータも必要になるといった感じ。

そういったときには、外部キーを一時的に無効化するという選択肢がある。

ALTER TABLEでTRIGGERを無効化する方法

TRIGGERを無効化することで、制約チェックを走らなくする。

テストデータの準備ができたら、再度有効化すればよい。

-- 無効化
ALTER TABLE child_table DISABLE TRIGGER ALL;
-- 有効化
ALTER TABLE child_table ENABLE TRIGGER ALL

JavaのDbSetupを使うなら下記のような感じになる。

これをユニットテストのsetupで行うイメージ。

Operation operation = Operations.sequenceOf(
  // 制約チェック無効化
  Operations.sql("ALTER TABLE child_table DISABLE TRIGGER ALL"),
  Operations.insertInto("child_table")
          .columns("id", "parent_id", "name")
          .values(11, 1, "1の子どもです")
          .build()
  , Operations.sql("ALTER TABLE child_table ENABLE TRIGGER ALL")
)
DbSetup(DataSourceDestination(dataSource), operation).launch()

dbsetup.ninja-squad.com

制約チェックをDEFERREDにする方法

PostgreSQLの制約チェックはデフォルトだとIMMEDIATE(即時)なのだが、これをDEFERRED(遅延)にする。

DEFERREDの場合、制約チェックはトランザクション終了時(コミット時)に走るようになる。

ただしDEFFERREDを使うにはそもそも制約をDEFERRABLEとして定義する必要がある。

-- 制約はDEFERRABLEで定義する
CREATE TABLE child_table (
  id        SERIAL        PRIMARY KEY,
  parent_id INTEGER       NOT NULL REFERENCES parent_table(id) DEFERRABLE,
  name      VARCHAR(128)  NOT NULL
);
-- 無効化(チェックを遅延)
SET CONSTRAINTS ALL DEFERRED;
-- 有効化(チェックを即時)
SET CONSTRAINTS ALL IMMEDIATE

無効化したいタイミングがトランザクション単位になるようであればこちらの方法もとれる。

しかしテストデータの作成とテストの実行はトランザクション単位を分けたほうがいいと思うので、ユニットテストで使うには微妙かも。

参考

stackoverflow.com qiita.com