DrizzleでHonoのDBテスト用のフィクスチャ作成
注意
ただ作成といっても以下のものをDrizzle用に移植したものになります。
はじめに
バックエンドのテストを書く際、あらかじめデータベースの状態をフィクスチャとして準備し、テストを行うというのをよくやります。
Rails
やDjango
などのフレームワークには必ずと言って良いほど入っており、非常に助かります。
ですがHonoでのDBフィクスチャのテストの方法を探しても(観測した範囲では)見つからず、だったら作ってしまおうかとなり作成しました。
備忘録として記事にしています。
コードは以下のリポジトリにおいています。
前提
利用するものは以下の通りです。
- ライブラリ&フレームワーク
- Hono
- Drizzle
- Next.js
- jest
- postgres
- データベース
- PostgreSQL
Postgresで以下のテーブルを利用したTodoアプリを例として話します。
drizzleでのスキーマ定義
import { integer, serial } from 'drizzle-orm/pg-core';
import {
timestamp,
pgTable,
text,
boolean,
} from 'drizzle-orm/pg-core';
export const todos = pgTable('todos', {
id: serial().primaryKey().notNull(),
name: text().notNull(),
done: boolean().notNull(),
created_at: timestamp(),
updated_at: timestamp(),
});
export const assignees = pgTable('assignees', {
todoId: integer().primaryKey().references(() => todos.id),
name: text().notNull(),
created_at: timestamp(),
updated_at: timestamp(),
});
テーブル
postgres=# \d+ public.todos
テーブル"public.todos"
列 | タイプ | 照合順序 | Null 値を許容 | デフォルト | ストレージ | 圧縮 | 統計目標 | 説明
------------+-----------------------------+----------+---------------+-----------------------------------+------------+------+----------+------
id | integer | | not null | nextval('todos_id_seq'::regclass) | plain | | |
name | text | | not null | | extended | | |
done | boolean | | not null | | plain | | |
created_at | timestamp without time zone | | | | plain | | |
updated_at | timestamp without time zone | | | | plain | | |
インデックス:
"todos_pkey" PRIMARY KEY, btree (id)
参照元:
TABLE "assignees" CONSTRAINT "assignees_todoId_todos_id_fk" FOREIGN KEY ("todoId") REFERENCES todos(id)
アクセスメソッド: heap
postgres=# \d+ public.assignees
テーブル"public.assignees"
列 | タイプ | 照合順序 | Null 値を許容 | デフォルト | ストレージ | 圧縮 | 統計目標 | 説明
------------+-----------------------------+----------+---------------+------------+------------+------+----------+------
todoId | integer | | not null | | plain | | |
name | text | | not null | | extended | | |
created_at | timestamp without time zone | | | | plain | | |
updated_at | timestamp without time zone | | | | plain | | |
インデックス:
"assignees_pkey" PRIMARY KEY, btree ("todoId")
外部キー制約:
"assignees_todoId_todos_id_fk" FOREIGN KEY ("todoId") REFERENCES todos(id)
アクセスメソッド: heap
postgres=#
MySQLでも問題なく動くはずです。
フィクスチャとは?
CakePHPのドキュメントがわかりやすい表現をしていたので引用します。
テストコードの挙動がデータベースやモデルに依存するとき、テストに使うためのテーブルを生成し、 一時的なデータをロードするために フィクスチャー を使うことができます。 フィクスチャーを使うことにより、 実際のアプリケーションに使われているデータを破壊することなく テストができるというメリットがあります。 また、アプリケーションのためのコンテンツを実際に用意するより 先にコードをテストすることができます。
これによりデータがある場合のAPIレスポンスを確認することや、データがない場合の挙動をテストで再現することができます。
フィクスチャの流れ
fixtures.ts
とそれぞれのテーブルに対応するassignee.ts
とtodo.ts
を作成します。
.
├── fixture
│ ├── assignee.ts
│ ├── fixtures.ts
│ └── todo.ts
assignee.ts
todo.ts
例としてtodoモデルの流れを見ていきます。
- テーブルに対応するモデルを作成。
- フィクスチャ作成の関数を作成。
- デフォルト値を宣言。
- 後述のModelConnectorクラスに起こす。
fixtures.ts
ModelConnectorクラスでモデルの管理や、データベースに挿入を行います。
以下のように、テーブルに対応するモデルをフィクスチャに定義します。
それぞれの引数は以下のような役割を持っています。
model
- モデルオブジェクト。テーブルに対応するモデルを入れます。
- 上記の場合、todoのモデルが入ります。
setter
- モデルの初期値を設定します。
- 引数として
model
オブジェクトを持ってきます。 - ここのオブジェクトを書き換えることで、用意したいデータを挿入することができます。
- 引数として
- また、メソッドチェーンを使って複数のモデルやデータの書き込みができます。
fixture = await fixtureBuild(fixture,
newTodo(
(todo) => {
todo.name = 'todo';
todo.done = false;
}
),
newTodo(
(todo) => {
todo.name = 'todo2';
todo.done = false;
}
)
);
addToFixture
-
setter
で設定したモデルをフィクスチャに追加します。
connect
- 後述の
connectWith
で他のモデルと接続する際に結合するモデルを入れます。
insertTable
- モデルをデータベースに挿入します。
connectWith
- 結合するモデルの初期値を設定します。
-
assignees
のtodo_id
はtodos
の主キーにあたり、assignees
単体で挿入ができません。-
connect
を使うことでモデルの関連を表現しやすくなり、外部キーを勝手に挿入するようになります。
newTodo( (todo) => { todo.name = 'todo'; todo.done = false; } ).connectWith( newAssignee( (assignee) => { assignee.name = 'user'; } ) )
-
-
addToFixtureAndConnect
- 上記の
connectWith
のモデルをフィクスチャに追加し、データベースに挿入を行います。
以上をfixtureBuild
にまとめて、データベースに挿入したデータをモデルとして呼び出せるようにします。
フィクスチャの使い方
todo一覧を取得し、jsonを返すAPIがあります。
このtodoの取得のテストをしてみます。
- トランザクションを張り、フィクスチャを作成します。
- フィクスチャにモデルを追加し、テーブルに挿入します。
- requestメソッドでGetリクエストを呼び出します。
- 200のステータスコードと期待したレスポンスが帰ってくるか確認します。
- ちゃんとテストがpassするはずです。
テストが終わり次第、トランザクションをロールバックさせます。
しかしDrizzle
はロールバックをするとエラーを起こすらしく、以下のような例外処理を毎回挟む必要があります。
まとめ
いかがでしたでしょうか?
フィクスチャは実DBを利用してテストを行うので、外部キー制約などに気づきやすくなります。
TypeScript初心者なので間違いや修正点などがあれば指摘していただけると幸いです。
Discussion