アプリケーションが成長するにつれて、自動テストはますます重要になります。この記事では、実際のデータベースにアクセスせずにデータベースインタラクションを含む関数をテストできるように、Prisma Client をモックする方法を学びます。
目次
- 目次
- はじめに
- 前提条件
- モックとは?
- Prisma プロジェクトのセットアップ
- Vitest のセットアップ
- Prisma Client をモックする理由
- Prisma Client のモック
- モックされたクライアントの使用
- メソッドのスパイ
- なぜ Vitest なのか?
- まとめと今後の展望
はじめに
テストは、開発者が書くコードに自信を持ち、製品をより効率的に反復できるようにするため、アプリケーションにおいてますます重要になっています。
自信を持って効率的に作業できることは、想像できるように、すべての開発者のワークフローにおいて重要な側面です。では… なぜすべての開発者がアプリケーションのテストを書かないのでしょうか?この質問に対する答えは多くの場合、「テストを書くこと、特にデータベースが関与する場合、トリッキーになる可能性がある!」ということです。

警告: 悪いアドバイス 👆🏻
このシリーズでは、データベースと対話するさまざまなアプリケーションに対して、さまざまな種類のテストを実行する方法を学びます。
この記事では特に、モックのトピックを掘り下げ、Prisma Client をモックする方法を順を追って説明します。次に、モックされたクライアントで何ができるかを見ていきます。
使用する技術
前提条件
前提知識
このシリーズに入るにあたって、以下の知識があると役立ちます。
- JavaScript または TypeScript の基本的な知識
- Prisma Client とその機能の基本的な知識
開発環境
提供されている例に従うには、以下が必要です。
モックとは?
このシリーズで最初に見る概念はモックです。この用語は、置き換える実際のオブジェクトと同様に動作する、オブジェクトの制御された代替物を作成する手法を指します。
モックの目的は通常、関数が必要とする可能性のある外部依存関係を開発者が置き換え、その関数に対して効果的にユニットテストを作成できるようにすることです。これにより、テストは直接関係のない外部モジュールの動作を気にすることなく、関数の動作に隔離できます。
注: このシリーズの次の記事で、ユニットテストを詳しく見ていきます。
これを説明するために、次の関数を考えてみましょう。
この関数は 3 つのことを行います。
- 有効なメールアドレスが提供されていることを確認します。
- 無効なアドレスが提供された場合はエラーをスローします。
- 仮想の
mailer
サービスを介してメールを送信します。
この関数が期待どおりに動作することを検証するテストを作成するには、まず、関数に無効なメールアドレスが提供されたシナリオをテストし、エラーがスローされることを検証することから始めるでしょう。
ただし、この関数は isValidEmail
と mailer
の 2 つの外部コードに依存しています。これらは別のコードであり、技術的にはテストしている関数とは無関係であるため、これらのインポートが適切に機能するかどうかを心配する必要はありません。代わりに、これらは機能的であり、個別にテストされるべきであると想定する必要があります。
また、mailer.send()
が呼び出されたときに、実際のメールがテスト中に送信されることも望ましくありません。これは、その機能がテストしている関数とは独立しているためです。
このような状況では、これらの依存関係をモックし、実際のインポートされたオブジェクトを制御された値を返す「偽物」に置き換えるのが一般的な方法です。そうすることで、別のモジュールの動作を考慮することなく、テストのターゲット関数で特定の状態をトリガーする機能を得ることができます。
これはモックがどのように役立つかを示すかなり基本的なシナリオですが、この記事の残りの部分では、モジュールをモックし、それらのモックを使用して特定のシナリオをテストするために使用できるさまざまなパターンとツールについて詳しく説明します。
Prisma プロジェクトのセットアップ
テストの作成に入る前に、実験するプロジェクトが必要です。セットアップするには、Prisma を使用したサンプルプロジェクトをすばやくセットアップできるツールである try-prisma
を使用します。
ターミナルで次のコマンドを実行します。
完了すると、スタータープロジェクトが現在の作業ディレクトリの mocking_playground
という名前のフォルダーにセットアップされているはずです。
また、ターミナルに追加の出力が表示され、次のステップに関する指示が表示されます。これらの指示に従ってプロジェクトに入り、最初の Prisma マイグレーションを実行します。
これで SQLite データベースが生成され、スキーマが適用され、Prisma Client が生成されました。プロジェクトでの作業を開始する準備ができました!
Vitest のセットアップ
テストとモックを作成するには、テストフレームワークが必要です。このシリーズでは、ますます人気が高まっている Vitest テストフレームワークを使用します。これは、テストを構築および実行したり、モジュールのモックを作成したりできるツールセットを提供します。
注: Vitest は、他にもたくさんの素晴らしいことを行います。興味があれば、ドキュメントをご覧ください。
プロジェクトでこのコマンドを実行して、Vitest フレームワークとその CLI ツールをインストールします。
次に、プロジェクトのルートに test
という名前の新しいフォルダーを作成します。これがすべてのテストの保存場所になります。
注: テストを
/test
フォルダーに入れることは、Vitest では必須ではありません。Vitest は、これらの 命名規則に基づいて、デフォルトでテストファイルを検出します。
最後に、package.json
で、vitest
コマンドを実行するだけの test
という名前の新しいスクリプトを追加します。
これで、npm run test
を使用してテストを実行できます。また、短縮形として npm t
を実行することもできます。現在、テストファイルがないため、テストは失敗します。
/test
ディレクトリ内に sample.test.ts
という名前の新しいファイルを作成します。
Vitest が正しくセットアップされていることを確認できるように、次のテストを追加します。
有効なテストができたので、npm t
を実行すると成功するはずです。Vitest がセットアップされ、使用できる状態になりました。
Prisma Client をモックする理由
ユニットテストで Prisma Client のモックがなぜ役立つのかを説明する最良の方法は、Prisma Client を使用する関数を作成し、モックされたクライアントを使用しないその関数のテストを作成することです。
プロジェクトのルートに、libs
という名前の新しいフォルダーを作成します。次に、そのフォルダー内に prisma.ts
という名前のファイルを作成します。
次のスニペットをその新しいファイルに追加します。
上記のコードは、Prisma Client をインスタンス化し、シングルトンインスタンスとしてエクスポートします。これは「本物」の Prisma Client インスタンスです。
使用可能な Prisma Client の使用可能なインスタンスができたので、それを使用する関数を作成します。
script.ts
の内容を次のように置き換えます。
createUser
関数は次のことを行います。
user
引数を受け取ります。user
をprisma.user.create
関数に渡します。- 応答を返します。これは新しいユーザーオブジェクトである必要があります。
次に、その新しい関数のテストを作成します。このテストでは、createUser
が有効なユーザー (新しいユーザー) を指定されたときに期待されるデータを返すことを確認します。
test/sample.test.ts
を更新して、以下のスニペットと一致するようにします。
注: 上記のテストは、モックされた Prisma Client を使用していません。実際のクライアントインスタンスを使用して、実際のデータベースに対してテストするときに発生する可能性のある問題を示しています。
データベースにまだユーザーレコードが含まれていないと仮定すると、このテストは最初に実行したときに合格するはずです。ただし、いくつかの問題があります。
- 次回このテストを実行すると、作成されたユーザーの
id
は1
にならず、テストが失敗します。 email
フィールドには、Prisma スキーマに@unique
属性があり、その列がデータベースに一意のインデックスを持っていることを示しています。これにより、テストを後続で実行するとエラーが発生します。- このテストでは、開発データベースに対して実行していることを前提としており、データベースが利用可能である必要があります。このテストを実行するたびに、レコードがデータベースに追加されます。
単一の関数に焦点を当てたユニットテストなどの状況では、データベース操作は正しく動作すると仮定し、代わりにクライアントまたはドライバーのモックバージョンを使用するのがベストプラクティスです。これにより、ターゲットとする関数の特定の動作のテストに集中できます。
注: データベースに対してテストし、実際に操作を実行したい場合があるシナリオがあります。結合テストとエンドツーエンドテストは、これらのケースの良い例です。これらのテストは、アプリケーションの複数の関数と領域にまたがって発生する複数のデータベース操作に依存する場合があります。
Prisma Client のモック
前のセクションで概説した理由から、Prisma Client を使用する関数を適切にユニットテストするには、クライアントのモックを作成するのがベストプラクティスと見なされています。このモックは、関数が通常使用するインポートされたモジュールを置き換えます。
これを実現するには、Vitest のモックツールと、vitest-mock-extended
という名前の外部ライブラリを使用します。
まず、vitest-mock-extended
をプロジェクトにインストールします。
次に、test/sample.test.ts
ファイルに移動し、Vitest に libs/prisma.ts
モジュールをモックする必要があることを知らせるために、次の変更を加えます。
vi
オブジェクトで使用可能な mock
関数は、Vitest に、指定されたファイルパスにあるモジュールをモックする必要があることを知らせます。ドキュメントで説明されているように、mock
関数がターゲットモジュールをモックする方法を決定できる方法はいくつかあります。
現在、Vitest は '../libs/prisma'
にあるモジュールをモックしようとしますが、prisma
オブジェクトの「深い」または「ネストされた」プロパティを自動的にモックすることはできません。たとえば、prisma.user.create()
は、Prisma Client インスタンスの深くネストされたプロパティであるため、適切にモックされません。これにより、関数が通常どおりに実際のデータベースに対して実行されるため、テストが失敗します。
この問題を解決するには、そのモジュールを正確にどのようにモックするかを Vitest に知らせ、モックされたモジュールがインポートされたときに返される値を指定する必要があります。これには、深くネストされたプロパティのモックバージョンを含める必要があります。
libs
ディレクトリ内に __mocks__
という名前の新しいフォルダーを作成します。
フォルダー名 __mocks__
は、モジュールの手動で作成されたモックを配置できるテストフレームワークでの一般的な規則です。__mocks__
フォルダーは、モックしているモジュールのすぐ隣にある必要があります。そのため、libs/prisma.ts
ファイルの隣にフォルダーを作成しました。
その新しいフォルダー内に、prisma.ts
という名前のファイルを作成します。
このファイルの名前が「本物」のファイルである prisma.ts
と同じであることに注意してください。この規則に従うことで、Vitest は vi.mock
を介してモジュールをモックするときに、そのファイルを使用してクライアントのモックバージョンを見つける必要があることを認識します。
その構造が整ったので、手動モックを作成します。
新しい libs/__mocks__/prisma.ts
ファイルに、次を追加します。
上記のスニペットは次のことを行います。
- モックされたクライアントを作成するために必要なすべてのツールをインポートします。
- 個々のテストごとに、モックを元の状態にリセットする必要があることを Vitest に知らせます。
vitest-mock-extended
ライブラリのmockDeep
関数を使用して、Prisma Client の「ディープモック」を作成およびエクスポートします。これにより、オブジェクトのすべてのプロパティ (深くネストされたプロパティも含む) がモックされることが保証されます。
注: 基本的に、
mockDeep
は、すべての Prisma Client 関数の値を Vitest ヘルパー関数であるvi.fn()
に設定します。
この時点で、npm t
でテストを再度実行すると、以前と同じエラーが表示されなくなっているはずです。しかし、まだ問題があります…

クエリが `undefined` を返す
このエラーは実際には、モックが正しく配置されたために発生します。script.ts
の prisma.user.create
呼び出しは、データベースにアクセスしなくなりました。現在、その関数は本質的に何もせず、undefined
を返します。
Vitest に prisma.user.create
が何をする必要があるかをモックすることで伝える必要があります。適切なモックバージョンの Prisma Client ができたので、テストに簡単な変更を加える必要があります。
test/sample.test.ts
ファイルで、次のコードを追加して、その個々のテストの過程で関数がどのように動作するかを Vitest に伝えます。
上記では、「偽」のクライアントは、Prisma Client のディープモックをエクスポートするためインポートされました。
このオブジェクトでは、各 Prisma Client プロパティと関数に新しい関数セットがアタッチされていることに気付くでしょう。

上記のスニペットで使用されている mockResolvedValue
は、通常の prisma.user.create
関数を、指定された値を返す関数に置き換えます。その単一のテストの過程では、その関数は次の代入を実行した場合と同じように動作します。
注: この記事の後半では、モックされた Prisma Client で使用できる役立つ関数とその使用方法について詳しく説明します。
これで、Prisma Client の動作を事前にモックして目的の結果を保証することで、Prisma Client を使用する関数を実行できるようになりました。これにより、個々のクエリを心配するのではなく、関数の実際のビジネスロジックに集中できます。
ここでテストを再度実行すると、すべてのテストが合格したことが最終的に表示されるはずです! ✅
モックされたクライアントの使用
モックされた Prisma Client インスタンスを取得し、クライアントを操作して、関数で特定のシナリオをテストするために必要なクエリ結果を生成する機能を手に入れたとしましょう… 次は何でしょうか?
この記事の残りの部分では、モックされたクライアントと Vitest が使用できる多くの関数と、それらがさまざまなシナリオでどのように使用されてテストエクスペリエンスを有効にするかについて詳しく説明します。
注: 以下の例は、実行可能な本格的なユニットテストではありません。むしろ、モックされたクライアントを介して使用可能なツールの機能サンプルになります。このシリーズの次の記事では、ユニットテストについて詳しく説明します。
クエリ応答のモック
モックされたクライアントを使用する最も一般的なことの 1 つは、クエリの応答をモックすることです。この記事の前半で、create
メソッドの応答をすでにモックしましたが、これを行う方法は複数あり、それぞれに独自のユースケースがあります。
たとえば、このシナリオを見てみましょう。
注: ここでの
toStrictEqual
の使用法は重要です。オブジェクトを比較する場合、toStrictEqual
はオブジェクトが同じ構造と型を持っていることを保証します。
このテストは正常に合格しますが、あまり意味がありません。prisma.post.findMany.mockResolvedValue
が呼び出されると、その関数に提供された値は、テストの残りの部分の prisma.post.findMany
の応答として使用されます。より具体的には、libs/__mocks__/prisma.ts
で mockReset
関数が呼び出されるまでです。
その結果、unpublished
配列と published
配列には、published
プロパティの true
値を含め、まったく同じ値が含まれます。
このシナリオでより現実的な応答を生成するために、別の関数 (mockResolvedValueOnce
) を使用できます。この関数は、関数の応答と後続の呼び出しの応答をモックするために複数回呼び出すことができます。
getPosts
関数では、mockResolvedValueOnce
を使用して、その関数が返す最初の応答と 2 番目の応答をモックできます。
注: Vitest を介して使用可能な多くの関数には、
mockXValue
とともにmockXValueOnce
メソッドがあります。詳細については、ドキュメントを参照してください。
エラーのトリガーとキャプチャ
テストしたい別のシナリオは、クエリが失敗してエラーを返すかスローする場合です。これが役立つ可能性のある優れた例は、Prisma Client の findUniqueOrThrow
関数です。
この関数は一意のレコードを検索しますが、レコードが見つからない場合はエラーをスローします。ただし、Prisma Client の関数はモックされているため、findUniqueOrThrow
関数はそのように動作しなくなりました。エラー状態を手動でトリガーする必要があります。この動作をテストする方法の例を以下に示します。
mockImplementation
を使用すると、モックされた関数の動作を置き換える関数を提供できます。上記の例では、置換関数は単にエラーをスローします。
最初は少し面倒に見えるかもしれませんが、この場合に関数の動作を手動で定義する必要があることは、実際には追加の利点です。これにより、エラー状態であっても、さまざまな状態での関数の出力を細かく制御できます。
上記と同様に、テストしているメソッドがエラーに関連するメッセージを返すのではなく、実際のエラーをスローすることを意図している場合も、それをテストできます!
expect
関数の応答で rejects
キーワードを使用すると、Vitest は expect
に指定された Promise
を解決し、エラー応答を探すことを認識します。Promise
が解決されると、toThrow
関数と toThrowError
関数を使用して、エラーに関する特定の詳細を確認できます。
トランザクションのモック
モックする必要がある可能性のある Prisma Client のもう 1 つの要素は、$transaction
です。
トランザクションには、シーケンシャル操作と インタラクティブトランザクションの 2 種類があります。これらのモックの方法は、テストの目標と $transaction
を使用しているコンテキストによって大きく異なります。ただし、この関数をモックする一般的な方法は 2 つあります。
シーケンシャル操作とインタラクティブトランザクションの両方で、完了したトランザクションの結果は最終的に $transaction
関数から返されます。テストがトランザクションの結果のみを気にする場合、テストは関数の応答をモックした上記のテストと非常によく似ています。
例は次のようになります。
上記のテストでは、
- 作成する予定の投稿のデータをモックしました。
$transaction
からの応答がどのように見えるかをモックしました。- Prisma Client メソッドがモックされた後に関数を呼び出しました。
- 関数から返された値が、期待どおりの値と一致することを確認しました。
$transaction
関数自体の応答をモックすることにより、トランザクションのシーケンシャルアクション (またはインタラクティブトランザクションが該当する場合) 内で何が起こったかを心配する必要はありませんでした。
検証する必要がある重要なビジネスロジックを持つインタラクティブトランザクションをテストする場合はどうでしょうか?この方法では、トランザクションの内部動作を完全に無視するため、機能しません。
重要なビジネスロジックを持つインタラクティブトランザクションをテストするには、次のようなテストを作成できます。
このテストは、考慮すべきさまざまな可動部品が多いため、少し複雑です。
これが起こることです。
- 投稿オブジェクトと応答オブジェクトがモックされます。
create
メソッドとcount
メソッドの応答がモックされます。$transaction
関数の実装がモックされ、実際のクライアントインスタンスではなく、モックされた Prisma Client をインタラクティブトランザクション関数に提供できるようにします。addPost
メソッドが呼び出されます。- 応答の値が検証され、インタラクティブトランザクション内のビジネスロジックが機能することを確認します。より具体的には、新しい投稿の
published
フラグがtrue
に設定されていることを確認します。
メソッドのスパイ
最後に探求する概念は、スパイです。Vitest は、TinySpy という名前のパッケージを介して、関数をスパイする機能を提供します。スパイを使用すると、コードの実行中に関数を監視し、呼び出された回数、渡されたパラメーター、返された値などを判断できます。
注: 関数をスパイすると、ターゲット関数またはその動作を変更せずに、コードの実行中に関数の詳細を観察できます。
vi.spyOn()
を使用してモックされていない関数をスパイできますが、vi.fn()
を使用したモックされた関数は、デフォルトですべてのスパイ機能を使用できます。Prisma Client はモックされているため、すべての関数をスパイできるはずです。

以下は、スパイを使用したテストの例です。
これらのスパイ関数は、さまざまな入力に基づいて特定のシナリオがトリガーされることを確認しようとする場合に特に役立ちます。
なぜ Vitest なのか?
この記事が、Jest のような、より確立された人気のあるフレームワークではなく、Vitest をテストフレームワークとして重点を置いている理由について疑問に思うかもしれません。
この決定の背景にある理由は、異なるツールと Node.js との互換性、特に Error
オブジェクトの取り扱いに関連しています。 Matteo Collina 氏は、Node.js Technical Steering Committee のメンバーであり、その他にも素晴らしい実績を持つ人物ですが、最近の彼のライブストリームでこの問題について非常に分かりやすく説明しています。
問題を簡単に言うと、Jest はエラーが Error
クラスのインスタンスであるかどうかを標準で判別できません。
これは、アプリケーションのさまざまなケースに対するテストを作成する際に、予期しないさまざまな問題を引き起こす可能性があります。
どのように違うのですか?
幸いなことに、ほとんどのテストフレームワークは非常によく似ており、概念は比較的スムーズに移行できます。たとえば、Jest の使用に慣れていて、Vitest や node-tap
(別のテストフレームワーク) のようなものへの移行を検討している場合、すでに持っている知識は新しいテクノロジーに非常に転用可能です。
非常に小さな調整が必要になります。たとえば、関数の命名規則や設定などです。
Jest を使うべき時はありますか?
はい! Jest は非常に有能な人々によって書かれた素晴らしいツールです。Vitest が Node.js でバックエンドアプリケーションをテストする際の「最適なツール」であるかもしれませんが、Jest はフロントエンドの JavaScript アプリケーションをテストするのに十分な能力を持っています。
まとめ & 次のステップ
この記事では、アプリケーションのユニットテストで重要な役割を果たす モッキング と スパイ の概念に焦点を当てました。具体的には、以下を探求しました。
- モッキングとは何か、そしてなぜそれが役立つのか
- Vitest と Prisma を設定したプロジェクトをセットアップする方法
- Prisma Client をモックする方法
- モックされた Prisma Client インスタンスを使用する方法
テストの世界に関するこの知識と背景があれば、アプリケーションのユニットテストに必要なツールセットが揃いました。このシリーズの次の記事では、まさにそれを行います!
Prisma Client を使用するアプリケーションをテストするさまざまな方法を探求するこのシリーズの次のパートにもぜひご参加ください。
次の投稿をお見逃しなく!
Prisma ニュースレターに登録する