2023年2月14日

Prismaを使ったテストの究極ガイド:統合テスト

統合テストを行うことで、アプリケーションのさまざまなコンポーネントが連携して正しく動作することを確認できます。この記事では、テスト環境のセットアップ方法と統合テストの書き方について説明します。

The Ultimate Guide to Testing with Prisma: Integration Testing

目次

はじめに

このシリーズではこれまで、Prisma Clientのモック化と、そのモック化したPrisma Clientを使用してアプリケーションの小さな分離された領域に対して単体テストを作成する方法について説明してきました。

このシリーズのこのセクションでは、モックされたPrisma Clientに別れを告げ、実際のデータベースに対して統合テストを作成します!この記事の終わりまでに、Express API用の統合テスト環境をセットアップし、統合テストを作成できるようになります。

統合テストとは?

このシリーズの前回の記事では、アプリケーションの最小の構成要素が適切に機能することを確認するために、小さな分離されたコード単位のテストに焦点を当てて単体テストを作成する方法を学びました。これらのテストの目標は、基盤となるデータベース、外部モジュール、またはコンポーネント間の相互作用を気にすることなく、特定のシナリオと機能の一部をテストすることでした。

しかし、統合テストはまったく異なる考え方です。この種のテストでは、アプリケーションの関連する領域やコンポーネントを取り上げ、それらが連携して正しく機能することを確認します。

diagram of request

上記の図は、ユーザーの投稿を取得するシナリオの例を示しています。この場合、実際にデータを取得する前に、ユーザーがAPIまたは投稿にアクセスできることを確認するために、データベースに複数回アクセスする必要があるかもしれません。

上記で示されているように、アプリケーションの複数のコンポーネントが個々のリクエストやアクションの処理に関与する場合があります。これは、単一のリクエストまたは呼び出し中に、異なるコンポーネント間でデータベースとのやり取りが複数回発生することを意味します。このため、統合テストには、テスト対象のデータベースを含むテスト環境が含まれることがよくあります。

統合テストの簡単な概要を説明したところで、統合テストを実行するテスト環境の準備を始めます。

使用する技術

前提条件

前提知識

以下の手順を進める上で、以下の知識があると役立ちます

  • JavaScriptまたはTypeScriptの基本的な知識
  • Prisma Clientとその機能に関する基本的な知識
  • Dockerの基本的な理解
  • テストフレームワークの使用経験

開発環境

提供されている例に従うには、以下のものが必要です

  • Node.js がインストールされていること
  • 任意のコードエディタ (VSCodeを推奨します)
  • Git がインストールされていること
  • Docker がインストールされていること

このシリーズでは、このGitHubリポジトリを多用します。このリポジトリをクローンし、`unit-tests` ブランチをチェックアウトしてください。このブランチがこの記事の出発点となります。

リポジトリをクローンする

ターミナルでプロジェクトを保存しているディレクトリに移動します。そのディレクトリで以下のコマンドを実行します

上記のコマンドは、プロジェクトを`express_sample_app`という名前のフォルダにクローンします。このリポジトリのデフォルトブランチは`main`なので、`unit-tests`ブランチをチェックアウトする必要があります。

リポジトリをクローンしたら、プロジェクトをセットアップするためにいくつかの手順を実行します。

まず、プロジェクトに移動して`node_modules`をインストールします

次に、プロジェクトのルートに`.env`ファイルを作成します

このファイルには、`API_SECRET`という名前の変数を設定し、その値は任意の`string`に設定できます。また、`DATABASE_URL`という名前の変数も含まれるべきですが、これは現時点では空のままで構いません

`.env`ファイル内の`API_SECRET`変数は、認証サービスがパスワードを暗号化するために使用する秘密鍵を提供します。実際のアプリケーションでは、この値は数字とアルファベットの長いランダムな文字列に置き換える必要があります。

`DATABASE_URL`は、その名の通り、データベースへのURLを含みます。現在、実際のデータベースは持っておらず、必要もありません。

DockerコンテナでPostgresをセットアップ

テスト環境を準備するために最初に行うことは、Postgresサーバーを提供するDocker Composeを使用してDockerコンテナを構築することです。これは、統合テストの実行中にアプリケーションが使用するデータベースになります。

しかし、次に進む前に、お使いのマシンにDockerがインストールされ、実行されていることを確認してください。Dockerをマシンにセットアップする手順はこちらを参照してください。

Dockerコンテナの設定を開始するには、プロジェクトのルートに`docker-compose.yml`という名前の新しいファイルを作成します

このファイルでは、コンテナを設定し、データベースサーバーのセットアップ方法、使用するイメージ(Dockerイメージはコンテナを構築する方法を詳述する一連の命令です)、およびコンテナのデータの保存方法をDockerに指示します。

: `docker-compose.yml`ファイル内で設定できる項目は非常にたくさんあります。ドキュメントはこちらで確認できます。

コンテナはPostgresサーバーを作成して公開する必要があります。

これを達成するために、まず使用するComposeのファイル形式のバージョンを指定します

このバージョン番号は、使用するDocker Engineのバージョンも決定します。この記事の執筆時点では、`3.8`が最新バージョンです。

次に、データベースサーバーを実行する`service`が必要です。以下の設定で`db`という名前の新しい`service`を作成します

上記で追加された設定は、`db`という名前のサービスで、以下の構成を定義しています

  • `image`: このサービスを構築する際に使用するDockerイメージを定義します
  • `restart`: `always`オプションは、障害が発生した場合やDockerが再起動された場合に、このサービスを再起動するようDockerに指示します
  • `environment`: コンテナ内で公開する環境変数を設定します
  • `ports`: Dockerがマシンの`5432`ポートを、Postgresサーバーが実行されるコンテナの`5432`ポートにマッピングするように指定します
  • `volumes`: ボリュームの名前と、コンテナがデータを永続化するローカルマシン上の場所を指定します

サービスの構成を完了するには、`volumes`構成で定義されたボリュームをどのように構成し、ネットワーク化するかをDockerに知らせる必要があります。

`docker-compose.yml`ファイルに以下を追加して、ボリュームがローカルのDockerホストマシン(ファイルシステム内)に保存されるべきであることをDockerに知らせます

これでターミナルに移動し、プロジェクトのルートディレクトリに移動すると、以下のコマンドを実行してPostgresを実行しているコンテナを起動できます

Docker Compose running

データベースサーバーが利用可能になり、`postgres://postgres:postgres@localhost:5432`のURLからアクセスできるようになりました。

`.env`ファイルの`DATABASE_URL`変数を更新し、そのURLを指すようにし、データベース名として`quotes`を指定します

統合テスト用のVitest設定ファイルを追加

前回の記事では、Vitestの設定ファイルを作成しました。その設定ファイル`vitest.config.unit.ts`は、プロジェクト内の単体テストに特化したものでした。

次に、`vitest.config.integration.ts`という名前の2つ目の設定ファイルを作成し、統合テストを実行する際のVitestの実行方法を設定します。

: このシリーズではこれらのファイルは非常に似ています。プロジェクトの複雑さによっては、このように設定を分割する方が明らかに有益になります。

プロジェクトのルートに`vitest.config.integration.ts`という名前の新しいファイルを作成します

新しいファイルに以下を貼り付けます

上記のコードスニペットは、`test.include`が単体テストの設定のように`src`内の任意のファイルではなく、`src/tests`内の任意の`.ts`ファイルを指している点を除けば、基本的に`vitest.config.unit.ts`の内容と同じです。これは、すべての統合テストが`src`内の`tests`という新しいフォルダに配置されるべきであることを意味します。

次に、この新しい設定ファイルに、Vitestが異なるスレッドで同時に複数のテストを実行しないように指示する別のキーを追加します

これは極めて重要です。統合テストはデータベースと連携し、特定のデータセットを期待するためです。複数のテストが同時に実行され、データベースとやり取りすると、予期せぬデータによってテストに問題が生じる可能性があります。

同様に、テスト間でデータベースをリセットする方法も必要になります。このアプリケーションでは、各テストの間にデータベースを完全にクリアし、各テストをゼロから開始できるようにします。

`src`内に`tests`という新しいフォルダを作成し、その`tests`フォルダ内に`helpers`という新しいフォルダを作成します

その新しいディレクトリ内に、`prisma.ts`という名前のファイルを作成します

このファイルは、単にPrisma Clientをインスタンス化してエクスポートするヘルパーです。

そのファイルに以下を追加します

次に、`src/tests/helpers`に`reset-db.ts`という名前の別のファイルを作成します

このファイルには、データベースをリセットする関数を記述してエクスポートします。

データベースには`Tag`、`Quote`、`User`の3つのテーブルしかありません。これらの各テーブルに対して`deleteMany`をトランザクション内で実行する関数を作成し、エクスポートします

上記で作成したファイルにより、データベースをクリアする方法ができました。ここでの最後の作業は、各統合テストの間にその関数を実際に呼び出すことです。

これを実現する良い方法は、セットアップファイルを使用することです。これは、Vitestを構成して、テストを実行する前に処理させるファイルです。ここでVitestのライフサイクルフックを使用して、その動作をカスタマイズできます。

`src/tests/helpers`に`setup.ts`という別のファイルを作成します。

目標は、すべてのテストの前にデータベースをリセットして、クリーンな状態を確保することです。これは、Vitestが提供する`beforeEach`ライフサイクル関数内で、`reset-db.ts`によってエクスポートされた関数を実行することで実現できます。

`setup.ts`内で、`beforeEach`を使用して、各テストの間にリセット関数を実行します

これでテストスイートを実行すると、`src/tests`内のすべてのファイルに含まれる個々のテストがクリーンな状態から開始されます。

: 特定のテストコンテキストで何らかのデータから始めたいシナリオについて疑問に思っているかもしれません。作成する各テストファイル内で、これらのライフサイクル関数にフックし、ファイルごとに動作をカスタマイズすることもできます。これの例は後で示されます。

最後に、このセットアップファイルについてVitestに知らせ、テストを実行するたびにこのファイルを実行するように指示する必要があります。

`vitest.config.integration.ts`を以下で更新します

単体テスト設定の更新

現在、単体テストの設定ファイルは、`src`内にある`.ts`ファイルを検索するため、統合テストも実行してしまいます。

`vitest.config.unit.ts`の設定を更新し、`src/tests`内のファイルを無視するようにします

これで、単体テストと統合テストが完全に分離され、それぞれの個別のコマンドを使用してのみ実行できるようになりました。

テスト環境を起動するスクリプトを作成

これまでは、以下の方法を構築してきました

  • Dockerコンテナ内でデータベースサーバーを起動する
  • 特定のテスト設定で統合テストを実行する
  • 単体テストを統合テストとは別に実行する

不足しているのは、Dockerコンテナの作成と統合テストの実行を、データベースがテスト環境で実行され利用可能であることを保証する方法で実際にオーケストレーションする方法です。

これを機能させるために、Dockerコンテナを起動し、サーバーの準備が整うのを待ち、その後テストを実行する一連のカスタムbashスクリプトを作成します。

プロジェクトのルートに`scripts`という名前の新しいディレクトリを作成します

そのディレクトリ内に、`run-integration.sh`という名前のファイルを作成します

このファイル内では、以下の手順を実行する必要があります

  1. データベースURLにアクセスできるように、`.env`ファイルから環境変数を読み込みます。
  2. Dockerコンテナをデタッチモードで起動します。
  3. データベースサーバーが利用可能になるまで待ちます。
  4. Prismaマイグレーションを実行して、Prismaスキーマをデータベースに適用します。
  5. 統合テストを実行します。おまけとして、`--ui`フラグを付けてこのファイルを実行すると、VitestのGUIインターフェースも実行できるようになります

環境変数をロードする

この最初のステップでは、`.env`ファイルを読み込み、それらの変数をスクリプトのコンテキスト内で利用できるようにします。

`scripts`に`setenv.sh`という名前の別のファイルを作成します

このファイル内に、以下のスニペットを追加します

これにより、`.env`ファイルが読み込まれ、各変数がエクスポートされ、スクリプト内で利用できるようになります。

`scripts/run-integration.sh`に戻って、`source`コマンドを使用してこのファイルから環境変数にアクセスできるようになりました

上記では、`DIR`変数が`setenv.sh`への相対パスを見つけるために使用され、そのパスがスクリプトを実行するために使用されます。

Dockerコンテナをデタッチモードで起動

次のステップは、Dockerコンテナを起動することです。デタッチモードでコンテナを起動する必要があることに注意することが重要です。

通常、`docker-compose up`を実行すると、ターミナルはコンテナの出力に接続され、何が起こっているかを確認できます。しかし、これはDockerコンテナを停止するまでターミナルが他のアクションを実行するのを妨げます。

コンテナをデタッチモードで実行すると、バックグラウンドで実行できるため、ターミナルが他のコマンド(統合テストを実行するコマンドなど)を継続して実行できるようになります。

`run-integration.sh`に以下を追加します

ここで、`-d`フラグはコンテナがデタッチモードで実行されるべきであることを示します。

データベースサーバーが準備できるまでスクリプトを待機させる

Prismaマイグレーションとテストを実行する前に、データベースがリクエストを受け入れる準備ができていることを確認する必要があります。

これを行うには、`wait-for-it.sh`と呼ばれるよく知られたスクリプトを使用します。このスクリプトを使用すると、URLといくつかのタイミング設定を提供でき、提供されたURLのリソースが利用可能になるまでスクリプトが待機してから次に進むようになります。

以下のコマンドを実行して、そのスクリプトの内容を`scripts/wait-for-it.sh`という名前のファイルにダウンロードします

警告: `wait-for-it.sh`スクリプトが機能しない場合は、データベースへの接続の別の方法とテストが正常に実行されることを確認する方法については、GitHubディスカッションを参照してください。

次に、`run-integration.sh`に戻って以下で更新します

これで、スクリプトは`DATABASE_URL`環境変数で指定された場所にあるデータベースが利用可能になるまで待機し、その後続行します。

Macをご利用の場合、`wait-for-it.sh`スクリプト内で使用されるコマンドをインストールしてエイリアスを設定するために、以下のコマンドも実行する必要があります

データベースを準備してテストを実行する

最後の2つのステップは安全に実行できるようになりました。

`wait-for-it`スクリプトが実行された後、Prismaマイグレーションを実行してデータベースに新しい変更を適用します

最後に、統合テストを実行するために以下のステートメントを追加します

使用された`if`/`else`文に注目してください。これにより、スクリプトに渡されたフラグを探すことができます。フラグが見つかった場合、それは`--ui`であると仮定され、Vitestのユーザーインターフェースでテストが実行されます。

スクリプトを実行可能にする

テストを実行するために必要なスクリプトはすべて完成しましたが、どれかを実行しようとすると権限エラーが発生します。

これらのスクリプトを実行可能にするには、現在のユーザーがそれらを実行できるようにする以下のコマンドを実行する必要があります

npmスクリプトの設定

スクリプトが実行可能になりました。次のステップは、これらのカスタムスクリプトを呼び出し、テストを起動する`scripts`レコードを`package.json`内に作成することです。

`package.json`の`scripts`セクションに以下を追加します

これで、以下のいずれかのスクリプトを実行すると、Dockerコンテナが起動し、Prismaマイグレーションが実行され、最後にテストが実行されるのが確認できるはずです

tests running and failing

: 現時点ではテストは失敗します。これはVitestが実行すべきテストを含むファイルを見つけられなかったためです。

統合テストの作成

さあ、テスト環境を活用してテストを書きましょう!

アプリケーションのどの部分に統合テストが必要かを考える際には、コンポーネント間の重要な相互作用と、それらの相互作用がどのように呼び出されるかを考えることが重要です。

作業しているExpress APIの場合、重要な相互作用のグループ化は、ルートコントローラ、およびサービスの間で発生します。ユーザーがAPIのエンドポイントにアクセスすると、ルートハンドラはリクエストをコントローラに渡し、コントローラはサービス関数を呼び出してデータベースとやり取りする場合があります。

この点を踏まえ、統合テストでは各ルートを個別にテストすることに焦点を当て、それぞれがHTTPリクエストに適切に応答することを確認します。これには、APIへの有効なリクエストと無効なリクエストの両方が含まれます。目標は、APIの利用者がAPIとやり取りする際の体験をテストが模倣することです。

: 統合テストが何をカバーすべきかについては、さまざまな意見があります。場合によっては、開発者は、より小さなコンポーネント(コントローラサービスなど)が連携して正しく機能することを確認するための専用の統合テストと、APIルート全体が正しく機能することを確認するテストを作成したいと考えるかもしれません。テストで何をカバーすべきかについての決定は、アプリケーションのニーズと、開発者として何がテストされるべきだと感じるかに完全に依存します。

このシリーズの以前の記事と同様に、このチュートリアルの情報量を管理しやすい長さに保つために、APIルートの`/auth/signin`と`/auth/signup`のテストを作成することに焦点を当てます。

: `/quotes/*`ルートのテストがどのようなものか興味がある場合は、完全なテストセットがGitHubリポジトリの`integration-tests`ブランチで利用可能です。

`/auth/signup` のテストを作成

`src/tests`に`auth.test.ts`という名前の新しいファイルを作成します

ここには、APIの`/auth`ルートに関連するすべてのテストが配置されます。

このファイル内で、Vitestから`describe`、`expect`、`it`関数をインポートし、`describe`を使用してこのテストスイートを定義します

最初にテストするエンドポイントは、`POST /auth/signup`ルートです。

テストスイートのコンテキスト内で、この特定のルートに関連するテストスイートを記述するために、別の`describe`ブロックを追加します

このルートでは、ユーザー名とパスワードを提供して新しいユーザーを作成できます。`src/auth/auth.controller.ts`のロジックと`src/auth/auth.routes.ts`のルート定義を確認することで、テストする上で以下の動作が重要であることがわかります

  • `200`ステータスコードとユーザー詳細で応答するべきです
  • 成功した場合は有効なセッショントークンで応答するべきです
  • 指定されたユーザー名を持つユーザーが存在する場合、`400`ステータスコードで応答するべきです
  • 無効なリクエストボディが提供された場合、`400`ステータスコードで応答するべきです

: テストには、APIから期待されるあらゆる種類の応答(成功およびエラー)を含めるべきです。APIのルート定義とコントローラを読み解くことで、テストすべき異なるシナリオを判断するには通常十分です。

次の4つのセクションでは、これらのシナリオに対するテストの書き方について詳しく説明します。

`200`ステータスコードとユーザー詳細で応答するべきです

このシナリオのテストを書き始めるには、Vitestからインポートした`it`関数を使用して、「それが」何をするべきかを記述します。

`/auth/signup`ルートの`describe`ブロック内に以下を追加します

アプリケーションのエンドポイントに実際に`POST`データを送信するには、`supertest`という名前のライブラリを使用します。このライブラリを使用すると、HTTPサーバーを提供し、シンプルなAPIを介してそのサーバーにリクエストを送信できます。

`supertest`を開発依存関係としてインストールします

次に、`src/tests/auth.test.ts`の先頭で`supertest`を`request`という名前でインポートします。また、`app`オブジェクトを提供する`src/lib/createServer`からのデフォルトエクスポートもインポートします

これで、`request`関数を使用してExpress APIにリクエストを送信できます

上記では、`app`インスタンスが`request`関数に渡されました。その関数の応答は、`request`に渡されたHTTPサーバーとやり取りできる一連の関数です。

次に、`post`関数を使用して、やり取りする対象のHTTPメソッドとルートを定義しました。最後に`send`を呼び出して、リクエストボディとともに`POST`リクエストを送信しました。

その戻り値にはリクエストの応答のすべての詳細が含まれていますが、特に`status`と`body`の値が応答から取り出されました。

これで応答ステータスとボディにアクセスできるようになったので、ルートが意図されたアクションを実行し、正しい値で応答したことを検証できます。

これらのケースを検証するために、このテストに以下を追加します

上記の変更は以下を実行します

  1. `prisma`をインポートして、データベースをクエリし、データが正しく作成されたことを再確認できます
  2. `prisma`を使用して新しく作成されたユーザーを取得します
  3. リクエストが`200`ステータスコードで応答したことを確認します
  4. ユーザーレコードが見つかったことを確認します
  5. 応答ボディにユーザーの`username`と`id`を含む`user`オブジェクトが含まれていることを確認します

ターミナルで`npm run test:int:ui`を実行すると、Vitest GUIが開き、成功したテストメッセージが表示されるはずです。

: このコマンドをまだ実行していなかった場合、`@vitest/ui`パッケージのインストールを促され、コマンドを再実行する必要があるかもしれません。

Successful test run

: このテストでは、Prisma Clientを含むモジュールはモックされていません!テストは実際のデータベースに対して実行され、このルートでのデータインタラクションが正しく機能することを確認しました。

成功した場合は有効なセッショントークンで応答するべきです

この次のテストでは、ユーザーが作成された際に、APIへのそのユーザーのリクエストを検証するために使用できるセッショントークンが応答に含まれることを確認します。

前のテストの下に、このシナリオのための新しいテストを作成します

このテストは以前よりも少しシンプルです。必要なのは、有効なサインアップリクエストを送信し、応答を調べて有効なトークンが返されたことを確認することだけです。

`supertest`を使用して、`POST`リクエストを`/auth/signup`エンドポイントに送信し、応答ボディを取得します

応答ボディには、セッショントークン文字列を含む`token`という名前のフィールドが含まれているはずです。

応答に`token`フィールドが存在することを確認する一連の期待を追加し、`jwt`ライブラリを使用してトークンが有効なセッショントークンであることを確認します

指定されたユーザー名を持つユーザーが存在する場合、`400`ステータスコードで応答するべきです

これまでは、`/auth/signup`への有効なリクエストが期待通りに応答することを確認してきました。次に、無効なリクエストをアプリが適切に処理することを確認します。

このシナリオのために別のテストを追加します

既存のユーザー名でサインアップリクエストが行われたときに発生すべき400エラーをトリガーするには、データベースにユーザーが既に存在している必要があります。

このテストに、任意のパスワードで`'testusername'`という名前のユーザーを作成するクエリを追加します

これで、そのユーザーと同じユーザー名でサインアップリクエストを送信することでエラーをトリガーできるはずです。

: このユーザーレコード(およびサインアップテストの結果として作成された他のレコード)は、各個別のテスト間で削除されることを忘れないでください。

`/auth/signup`にリクエストを送信し、上記で作成したユーザーと同じユーザー名(`'testusername'`)を提供します

そのエンドポイントにリクエストが送信されたので、このシナリオで何が起こることを期待するかを考える時が来ました。あなたは以下を期待するでしょう

  • リクエストが`400`ステータスコードで応答すること
  • 応答ボディに`user`オブジェクトが含まれていないこと
  • データベース内のユーザー数が`1`であること

これらの点がすべて満たされていることを確認するために、テストに以下の期待を追加します

無効なリクエストボディが提供された場合、`400`ステータスコードで応答するべきです

このエンドポイントについて書く最後のテストは、無効なリクエストボディがAPIに送信された場合に、リクエストが`400`ステータスコードで応答することを確認するテストです。

`src/auth/auth.router.ts`で示されているように、このエンドポイントは`zod`を使用して、`src/lib/middlewares.ts`で定義された`validate`という名前のミドルウェアを介して、リクエストボディに有効な`username`フィールドと`password`フィールドが含まれていることを検証します。

このテストは特に、`validate`ミドルウェアと`zod`定義が期待通りに機能していることを確認します。

このシナリオのために新しいテストを追加します

このテストは非常に単純です。単に`POST`リクエストを`/auth/signup`エンドポイントに送信し、無効なリクエストボディを提供するだけです。

`supertest`を使用して、`POST`リクエストを`/auth/signup`に送信します。ただし、`username`フィールドの代わりに`email`フィールドを送信します

このリクエストボディにより、バリデーションミドルウェアはコントローラに処理を続行する前に、リクエストに対して`400`エラーコードで応答するはずです。

この動作を検証するために、以下の一連の期待を使用します

これで、`/auth/signup`エンドポイントのテストスイートは完了です!Vitest GUIをもう一度見ると、すべてのテストが成功しているはずです

Full suite of signup tests complete

`/auth/signin` のテストを作成

次にテストを作成するこのエンドポイントは、以前のものと多くの類似点がありますが、新しいユーザーを作成するのではなく、既存のユーザーを検証します。

`/auth/signin`エンドポイントは、`username`と`password`を受け取り、提供されたデータでユーザーが存在することを確認し、セッショントークンを生成し、セッショントークンとユーザーの詳細を含む応答を返します。

: この機能の実装は、`src/auth/auth.controller.ts`と`src/auth/auth.router.ts`で確認できます。

テストスイートでは、このエンドポイントについて以下のことが真であることを確認します

  • 有効な資格情報が提供された場合、`200`ステータスコードで応答するべきです
  • 成功した場合はユーザー詳細で応答するべきです
  • 成功した場合は有効なセッショントークンで応答するべきです
  • 無効な資格情報が与えられた場合、`400`ステータスコードで応答するべきです
  • ユーザーが見つからない場合、`400`ステータスコードで応答するべきです
  • 無効なリクエストボディが与えられた場合、`400`ステータスコードで応答するべきです

各シナリオをテストする前に、このエンドポイントに関連するすべてのテストをグループ化するために、別のテストスイートを定義する必要があります。

`/auth/signup`テストスイートを定義した閉じタグの下に、`/auth/signin`ルート用の別の`describe`を追加します

このスイートで記述するテストでも、サインイン機能をテストするため、データベースにユーザーが存在する必要があります。

追加した`describe`ブロック内で、Vitestの`beforeEach`関数を使用して、各テストの前にユーザーをデータベースに追加できます。

新しいテストスイートに以下を追加します

: ここでのパスワードの暗号化方法は、`src/auth/auth.service.ts`で使用されている暗号化方法と完全に一致する必要があることに注意することが重要です。

このテストスイートの初期セットアップが完了したので、テストの作成に進むことができます。

これまでと同様に、次の6つのセクションでは、これらのシナリオをそれぞれ個別にカバーし、テストがどのように機能するかを説明します。

有効な資格情報が提供された場合、`200`ステータスコードで応答するべきです

この最初のテストでは、正しい資格情報を持つ有効なサインインリクエストがAPIから`200`応答コードを返すことを単に確認します。

開始するには、`beforeEach`関数のすぐ下にあるこのテストスイートの`describe`ブロック内に新しいテストを追加します

目的の動作をテストするには、テストユーザーを作成したのと同じユーザー名とパスワードで`/auth/signin`エンドポイントに`POST`リクエストを送信します。次に、応答のステータスコードが`200`であることを確認します

成功した場合はユーザー詳細で応答するべきです

この次のテストは前のテストと非常に似ていますが、`200`応答ステータスをチェックする代わりに、応答ボディ内の`user`オブジェクトをチェックし、その内容を検証します。

以下の内容で別のテストを追加します

上記のテストの内容は以下を実行します

  1. テストユーザーのユーザー名とパスワードを含むリクエストボディで、`/auth/signin`に`POST`リクエストを送信します
  2. 応答ボディの`user`オブジェクトのキーを抽出します
  3. 応答に`id`と`username`の2つのキーが存在し、`user.username`の値がテストユーザーのユーザー名と一致することを確認します

成功した場合は有効なセッショントークンで応答するべきです

このテストでも、以前の2つのテストと非常に似たプロセスに従いますが、このテストでは応答ボディ内の有効なセッショントークンの存在を確認します。

前のテストの下に、以下のテストを追加します

上記のように、ターゲットエンドポイントにリクエストが送信され、その結果から応答ボディが抽象化されました。

`toHaveProperty`関数を使用して、応答ボディに`token`キーが存在することを確認しました。次に、`jwt.verify`関数を使用してセッショントークンが検証されました。

: パスワードの暗号化と同様に、セッショントークンが`src/auth/auth.service.ts`で使用されているのと同じ関数を使用して検証されることが重要であることに注意することが重要です。

無効な資格情報が与えられた場合、`400`ステータスコードで応答するべきです

これで、無効な資格情報を含むリクエストボディを送信した結果として、正しいエラー応答が返されることを検証します。

このシナリオを再現するには、テストユーザーの正しいユーザー名と間違ったパスワードを使用して、`/auth/signin`に`POST`リクエストを送信するだけです。

以下のテストを追加します

上記のように、応答のステータスは`400`であることが期待されます。

また、無効なログインリクエストではセッショントークンが生成されるべきではないため、応答ボディに`token`プロパティが含まれていないことに対する期待も追加されました。

: このテストの2番目の期待は厳密には必要ありません。`400`ステータスコードだけで、コントローラ内の条件が満たされてリクエストをショートサーキットし、エラーで応答したことがわかるためです。

ユーザーが見つからない場合、`400`ステータスコードで応答するべきです

ここでは、指定されたユーザー名で見つからないシナリオをテストします。これは、以前のテストと同様に、リクエストをショートサーキットし、エラーのステータスコードで早期に応答させるべきです。

テストに以下を追加します

無効なリクエストボディが与えられた場合、`400`ステータスコードで応答するべきです

この最後のテストでは、無効なリクエストボディを送信するとエラー応答が発生することを確認します。

`src/auth/auth.router.ts`で使用されている`validate`ミドルウェアは、無効なリクエストボディを捕捉し、認証コントローラ全体をショートサーキットするはずです。

このテストスイートを完了するために、以下のテストを追加します

上記のように、以前この記事のテストで行ったように、`username`フィールドが`email`フィールドに置き換えられました。その結果、リクエストボディがリクエストボディの`zod`定義と一致せず、エラーがトリガーされます。

Vitest GUIに移動すると、両方のエンドポイントに対するテストスイート全体がすべてのチェックを正常にパスしているはずです。

Screen showing a full set of passing integration tests

まとめと今後の展望

この記事を最後まで読んでいただき、おめでとうございます!テストシリーズのこのセクションには情報が満載だったので、振り返ってみましょう。この記事では、以下のことを行いました

  • 統合テストとは何かを学びました
  • テスト環境でPostgresデータベースを実行するためのDockerコンテナをセットアップしました
  • 単体テストと統合テストを独立して実行できるようにVitestを設定しました
  • テスト環境を起動し、統合テストスイートを実行するための起動シェルスクリプト一式を作成しました
  • Express APIの主要な2つのエンドポイントについてテストを作成しました

このシリーズの次のセクションでは、これらの記事で取り上げる最後の種類のテストであるエンドツーエンドテストについて見ていきます。

このシリーズの残りの部分もぜひお付き合いください!

次回の投稿をお見逃しなく!

Prismaニュースレターに登録する

© . All rights reserved.