はじめに
同僚から、APIのテストどうしてるん?と聞かれたので、個人的に思っていることを書いていこうと思います。
自分が考えている指針
API Routeに限った話ではないですが、、
- HowとWhatの分離をしてコーディングしていく
- 記述と実行の分離とも言う
- 「具体的な処理」と「どういう振る舞いをさせるか」を分けるということ
結果として、API Routeは以下のようなコード構成をしています
handler層
httpリクエストを受けつけて、リクエストに応じて処理を呼び出している層。1httpリクエストに対し、1関数作る。
Next.jsのAPI Routeでは、Next.js側でhttpメソッドを考慮したルーティングはされないので、httpメソッドのチェックもこの層で行う。
service層
ユーザーからのリクエストを受け付けて、データをユーザーに返すフォーマットに整形して返す層。ユーザーの一つの行動に対応して、1関数作る。
後述するRepository層の関数を複数回呼び出して、データを集計、変換し、handlerにデータを返す。
repository層
service層からの呼び出しを受けて、永続化層(≒SaaSやDBなど、外部のデータソース)へリクエストを代行する層。永続化層への1リクエストに対応して、1関数作る。
SaaSに対するリクエストの場合は、jsonでデータが返ってくるのでパース、オブジェクトへの変換もここで行う。
また、複数のリクエストを送ったり、処理の分岐はさせず、ただ単にリクエストしてオブジェクトに変換するという責務だけ持たせる。
API周りのテストをどう考えているか?
mockを使用して、各層毎に単体テストのみを行う。
すべての層を横断的にテストする、結合テストをした方が良いが、単体テストでそれなりに品質は担保できているであろうと判断。
また、網羅性の高いテストを行おうとすると n(handler層のテストケース) * m(service層のテストケース) * l(repository)層のテストケースが必要になり、実行にも時間がかかるし、テストケースの準備も大変なのでこのようにしている。
網羅性は単体テストで担保し、正常系だけでも結合テスト行えばいいのでは?という意見については同意するものの、今回は見送っている。
現時点でどうしているか。今後どうしたいか。
handler層
httpリクエスト、service層の関数のモックを作り、リクエストとserviceからの返り値を変更して、想定するレスポンスが返ってきているか確認するテストを作成している。
現状で満足している。
service層
repository層の関数をmock化し、mockの返り値、関数呼び出しの引数を変更して、想定するデータが返ってくるか確認するテストを作成している
現状で満足している。
repository層
DBへのテストは、インメモリデータベースをテスト起動時に起動してリクエストする方法が考えるが、数日取り組んだものの実現しなかった。(技術的には可能なので、もう一度チャレンジすれば多分いける)
SaaSへのリクエストのテストについて、実際にリクエストをするのは、テスト実行の排他制御も必要であったり、実行に準備がかかるので、テストを実施していない。
fetch関数をmock化する事もできるので、それによってテストができるが、SaaSのスキーマ変更により、テストは通っていても実際には動かないみたいなことも発生するので、個人的にはこのテストだけではあまり意味ないと判断。
また、ここのテストを実装しなくても、問題が起きづらくなるように、repostory層には単純にリクエストしてレスポンスをオブジェクト化するというだけの関数を作るようにした。
テストではないが、httpレスポンスに型がつけられる aspida を導入したいなと悶々と考えたりしている。