# The Ultimate Guide to Testing with Prisma: Mocking Prisma Client URL: https://www.prisma.io/blog/testing-series-1-8eRB5p0Y8o Published: 2022-12-22 Updated: 2026-07-09 Authors: Sabin Adams Tags: education Series: The Ultimate Guide to Testing with Prisma Description: How to mock Prisma Client with Vitest and vitest-mock-extended so you can unit test database code without a real database. Verified on Prisma ORM 7.8. > Blog posts describe Prisma as it was on the date shown and are not kept current. Before implementing from a post, check the documentation index at https://www.prisma.io/docs/llms.txt and the changelog at https://www.prisma.io/changelog.md. > For an index of every published post, fetch https://www.prisma.io/blog/llms.txt. To unit test code that uses Prisma ORM, you mock Prisma Client so the function under test runs against a fake client instead of a real database. This article shows how to build that mock with Vitest and the `vitest-mock-extended` library, then use it to control query responses, trigger errors, mock transactions, and spy on methods. This is part 1 of a five-part series on testing with Prisma ORM. > **Updated (July 2026):** This article was rewritten for Prisma ORM 7 and the current JavaScript testing stack. Every command and code sample here was executed against Prisma ORM 7.8.0, `@prisma/client` 7.8.0, `@prisma/adapter-pg` 7.8.0, Vitest 4.1.10, and `vitest-mock-extended` 4.0.0 on Node.js 22. The project uses the `prisma-client` generator, a driver adapter, a `prisma.config.ts` file for the connection URL, and a local [Prisma Postgres](https://www.prisma.io/docs/postgres) database. The mocking approach itself is unchanged; the setup and a few Vitest matcher names have moved on since the original 2022 article. ## Introduction [#introduction] Testing lets developers work confidently and iterate on their products efficiently. So why doesn't everyone write tests? A common answer: writing tests, especially when a database is involved, can be tricky. ![Testing meme](https://www.prisma.io/blog/testing-series-1-8eRB5p0Y8o/imgs/testing_meme.png) *Warning: bad advice 👆🏻* In this series, you will learn how to perform different types of tests against applications that interact with a database. This article dives into *mocking* and walks through how to mock Prisma Client, then looks at what you can do with the mocked client. ### Technologies you will use [#technologies-you-will-use] * [Vitest](https://vitest.dev/) 4 * Prisma ORM 7 with the `prisma-client` generator and the `@prisma/adapter-pg` driver adapter * Prisma Postgres running locally * [Node.js](https://nodejs.org/en/) 20 or later ### Assumed knowledge [#assumed-knowledge] The following will help you follow along: * Basic knowledge of JavaScript or TypeScript * Basic knowledge of Prisma Client and its queries * [Node.js](https://nodejs.org) installed ## What is a mock? [#what-is-a-mock] The first concept in this series is *mocking*. This term refers to creating a controlled replacement for an object that behaves like the real object it replaces. The goal of mocking is to replace any external dependencies a function relies on so you can write unit tests against that function in isolation. Tests then focus on the function's own behavior without depending on external modules. > **Note**: You will look at unit tests in depth in [part 2](https://www.prisma.io/blog/testing-series-2-xPhjjmIEsM) of this series. Consider the following function: ```ts import { isValidEmail } from './validators' import mailer from 'mail-service' async function sendEmail(to: string, message: string) { // 1 if (!isValidEmail(to)) { // 2 throw new Error('Please provide a valid email address') } // 3 mailer.send({ to, message }) } ``` This function does three things: 1. Checks that a valid email address is provided 2. Throws an error if the address is invalid 3. Sends an email via an imaginary `mailer` service To test that this function behaves as expected, you would start by passing an invalid email address and verifying an error is thrown. The function relies on two external pieces of code: `isValidEmail` and `mailer`. Because these are separate and unrelated to the function under test, you do not want your test to depend on them, and you certainly do not want a real email sent when `mailer.send()` is called. In situations like this, you *mock* those dependencies, replacing the real object with a fake that returns a controlled value. This lets you trigger specific states in the target function without considering the behavior of another module. The rest of this article digs into the patterns and tools you can use to mock modules and use those mocks to test specific scenarios. ## Set up a Prisma project [#set-up-a-prisma-project] Before writing tests, you need a project. Create a new directory, initialize it, and install the dependencies: ```sh mkdir prisma-testing && cd prisma-testing npm init -y npm install -D prisma@7.9.1 typescript tsx @types/node vitest vitest-mock-extended npm install @prisma/client@7.9.1 @prisma/adapter-pg dotenv ``` The `prisma-client` generator emits modern ES modules, so mark the project as ESM in `package.json`: ```json { "name": "prisma-testing", "type": "module" } ``` Create a `prisma/schema.prisma` file. On Prisma ORM 7, the generator is `prisma-client` (not the legacy `prisma-client-js`), and it writes the client to an output path you own. The connection URL no longer lives in the `datasource` block; only the provider does: ```prisma // prisma/schema.prisma generator client { provider = "prisma-client" output = "../src/generated/prisma" } datasource db { provider = "postgresql" } model User { id Int @id @default(autoincrement()) email String? @unique name String? posts Post[] } model Post { id Int @id @default(autoincrement()) title String content String? published Boolean @default(false) author User @relation(fields: [authorId], references: [id]) authorId Int } ``` The connection URL is supplied through a `prisma.config.ts` file at the project root. Prisma 7 does not load `.env` automatically, so import `dotenv/config` here: ```ts // prisma.config.ts import 'dotenv/config' import { defineConfig } from 'prisma/config' export default defineConfig({ schema: 'prisma/schema.prisma', datasource: { url: process.env.DATABASE_URL! } }) ``` Now start a local Prisma Postgres database. This command runs a local server (powered by PGlite) and prints a connection string: ```sh npx prisma dev -n testing ``` ```text ✔ Your local Prisma Postgres server testing is now running 👍 🔌 To connect with Prisma ORM use the following connection strings: DATABASE_URL="postgres://postgres:postgres@localhost:51254/template1?sslmode=disable&connection_limit=10&connect_timeout=0&max_idle_connection_lifetime=0&pool_timeout=0&socket_timeout=0" ``` Leave that process running. In a second terminal, create a `.env` file with the `DATABASE_URL` value it printed: ```shell # .env DATABASE_URL="postgres://postgres:postgres@localhost:51254/template1?sslmode=disable&connection_limit=10&connect_timeout=0&max_idle_connection_lifetime=0&pool_timeout=0&socket_timeout=0" ``` Push the schema to the database, then generate the client: ```sh npx prisma db push npx prisma generate ``` ```text 🚀 Your database is now in sync with your Prisma schema. Done in 71ms ✔ Generated Prisma Client (7.8.0) to ./src/generated/prisma in 43ms ``` > **Note**: On Prisma 7, `prisma db push` and `prisma migrate dev` no longer regenerate the client. After any schema change, run `npx prisma generate` yourself. This was verified: adding a model and running `db push` left the generated client unchanged until `generate` was run. ## Set up Vitest [#set-up-vitest] You installed [Vitest](https://vitest.dev/) above. Add a `test` script to `package.json`: ```json { "name": "prisma-testing", "type": "module", "scripts": { "test": "vitest" } } ``` Create a `test` folder for your tests: ```sh mkdir test ``` > **Note**: Vitest does not require a `test` folder. It detects test files by [naming convention](https://vitest.dev/config/#include). Add a `test/sample.test.ts` file to confirm Vitest works: ```ts // test/sample.test.ts import { expect, test } from 'vitest' test('1 === 1', () => { expect(1).toBe(1) }) ``` Run `npm test`. The test passes, so Vitest is ready. ## Why mock Prisma Client? [#why-mock-prisma-client] The clearest way to show why mocking Prisma Client is useful is to write a function that uses it and test that function without a mock. Create `src/lib/prisma.ts`. On Prisma 7 you instantiate the client with a driver adapter, and you import the client from the path the generator wrote to (not from `@prisma/client`). Because env is read here, import `dotenv/config`: ```ts // src/lib/prisma.ts import 'dotenv/config' import { PrismaPg } from '@prisma/adapter-pg' import { PrismaClient } from '../generated/prisma/client' const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL! }) const prisma = new PrismaClient({ adapter }) export default prisma ``` Now write a function that uses that client. Create `src/script.ts`: ```ts // src/script.ts import { Prisma } from './generated/prisma/client' import prisma from './lib/prisma' // 1 export const createUser = async (user: Prisma.UserCreateInput) => { // 2 & 3 return await prisma.user.create({ data: user }) } ``` The `createUser` function: 1. Takes a `user` argument 2. Passes `user` to `prisma.user.create` 3. Returns the response, which is the new user object Write a test for it that checks `createUser` returns the created user. Update `test/sample.test.ts`: ```ts // test/sample.test.ts import { expect, test } from 'vitest' import { createUser } from '../src/script' test('createUser should return the generated user', async () => { const newUser = { email: 'user@prisma.io', name: 'Prisma Fan' } const user = await createUser(newUser) expect(user).toStrictEqual({ ...newUser, id: 1 }) }) ``` > **Note**: This test is **not** using a mocked Prisma Client. It uses the real client to demonstrate the problem. If your database has no users yet, this test passes the first time. There are a few problems: * The next run creates a user with `id` `2`, so the assertion on `id: 1` fails. * The `email` field has a `@unique` attribute, so a second run throws a unique constraint error. * The test requires a running database and writes a record on every run. For unit testing, which focuses on a single function, the best practice is to assume your database operations behave correctly and use a mocked client instead. That lets you focus on the behavior of the function you are targeting. > **Note**: There are cases where you want to test against a real database, such as integration and end-to-end tests. Those are covered in [part 3](https://www.prisma.io/blog/testing-series-3-aBUyF8nxAn) and [part 4](https://www.prisma.io/blog/testing-series-4-OVXtDis201). ## Mock Prisma Client [#mock-prisma-client] To properly unit test functions that use Prisma Client, create a mock of the client. This mock replaces the imported module the function normally uses. You will use Vitest's mocking tools and a library called [`vitest-mock-extended`](https://github.com/eratio08/vitest-mock-extended), which provides a deep-mocking helper. You already installed it above. Head to `test/sample.test.ts` and tell Vitest to mock the `src/lib/prisma.ts` module: ```ts // test/sample.test.ts import { expect, test, vi } from 'vitest' // 👈🏻 added the `vi` import import { createUser } from '../src/script' vi.mock('../src/lib/prisma') test('createUser should return the generated user', async () => { const newUser = { email: 'user@prisma.io', name: 'Prisma Fan' } const user = await createUser(newUser) expect(user).toStrictEqual({ ...newUser, id: 1 }) }) ``` The `vi.mock` function tells Vitest to mock the module at the given path. There are several ways it decides how to mock a module, described in the [documentation](https://vitest.dev/api/vi.html#vi-mock). By default, Vitest cannot automatically mock the deeply nested properties of the `prisma` object. For example, `prisma.user.create()` will not be mocked correctly because it is a nested property. To solve this, provide a manual mock that returns a deep mock of the client. Create a `__mocks__` folder next to `src/lib/prisma.ts`: ```sh mkdir src/lib/__mocks__ ``` The `__mocks__` folder is a common testing convention for manually created mocks. It must sit directly adjacent to the module it mocks, which is why it goes next to `src/lib/prisma.ts`. Inside it, create a file with the same name as the real module, `prisma.ts`: ```ts // src/lib/__mocks__/prisma.ts // 1 import { beforeEach } from 'vitest' import { mockDeep, mockReset } from 'vitest-mock-extended' import type { PrismaClient } from '../../generated/prisma/client' // 2 beforeEach(() => { mockReset(prisma) }) // 3 const prisma = mockDeep() export default prisma ``` The snippet above: 1. Imports the tools needed to create the mocked client, using a type-only import of `PrismaClient` from the generated path. 2. Resets the mock to its original state between each test. 3. Creates and exports a deep mock of Prisma Client with `mockDeep`, which mocks all properties, including deeply nested ones. > **Note**: `mockDeep` sets every Prisma Client function to the Vitest helper [`vi.fn()`](https://vitest.dev/api/vi.html#vi-fn). Because the mock is now in place, `prisma.user.create` no longer hits the database. Right now it returns `undefined`, so the test fails. Tell Vitest what `prisma.user.create` should return by mocking its behavior in the test: ```ts // test/sample.test.ts import { expect, test, vi } from 'vitest' import { createUser } from '../src/script' import prisma from '../src/lib/__mocks__/prisma' // 👈🏻 import the mocked client vi.mock('../src/lib/prisma') test('createUser should return the generated user', async () => { const newUser = { email: 'user@prisma.io', name: 'Prisma Fan' } prisma.user.create.mockResolvedValue({ ...newUser, id: 1 }) // 👈🏻 mock the response const user = await createUser(newUser) expect(user).toStrictEqual({ ...newUser, id: 1 }) }) ``` `mockResolvedValue` replaces `prisma.user.create` with a function that resolves to the value you provide. For the duration of that test, the call behaves as if you wrote: ```ts prisma.user.create = async () => ({ ...newUser, id: 1 }) ``` Now you can run functions that use Prisma Client by mocking the client's behavior beforehand. Rather than worrying about individual queries, you focus on the function's business logic. Running the tests now, they pass: ```text ✓ test/sample.test.ts (1 test) Test Files 1 passed (1) Tests 1 passed (1) ``` ## Using the mocked client [#using-the-mocked-client] You have a mocked Prisma Client and the ability to control query results. The rest of this article covers the functions your mocked client and Vitest offer. > **Note**: The examples below are functional samples of the tools available, not full unit tests. Part 2 covers unit testing in depth. ### Mocking query responses [#mocking-query-responses] One of the most common uses of the mocked client is mocking query responses. You already mocked `create`; there are multiple ways to do this, each with its own use case. Take this scenario: ```ts // src/script.ts export const getPosts = async () => { const published = await prisma.post.findMany({ where: { published: true } }) const unpublished = await prisma.post.findMany({ where: { published: false } }) return { published, unpublished } } ``` A single `mockResolvedValue` would return the same array for both `findMany` calls, which is unrealistic. Use [`mockResolvedValueOnce`](https://vitest.dev/api/mock.html#mockresolvedvalueonce), which can be chained to mock the first and second responses independently: ```ts // test/sample.test.ts import { getPosts } from '../src/script' test('getPosts should separate published & un-published posts', async () => { const mockPublishedPost = { id: 1, content: 'content', published: true, title: 'title', authorId: 1 } prisma.post.findMany .mockResolvedValueOnce([mockPublishedPost]) .mockResolvedValueOnce([{ ...mockPublishedPost, published: false }]) const posts = await getPosts() expect(posts).toStrictEqual({ published: [mockPublishedPost], unpublished: [{ ...mockPublishedPost, published: false }] }) }) ``` > **Note**: [`toStrictEqual`](https://vitest.dev/api/expect.html#tostrictequal) checks both structure and type when comparing objects. ### Triggering and capturing errors [#triggering-and-capturing-errors] You may want to test a case where a query throws. A good example is Prisma Client's [`findUniqueOrThrow`](https://www.prisma.io/docs/orm/v7/reference/prisma-client-reference#finduniqueorthrow), which throws if no record is found. Because the client is mocked, you trigger the errored state yourself: ```ts // src/script.ts export const getPostByID = async (id: number) => { try { return await prisma.post.findUniqueOrThrow({ where: { id } }) } catch (e: any) { return e.message } } ``` ```ts // test/sample.test.ts import { getPostByID } from '../src/script' test('getPostByID returns the error message when no post is found', async () => { prisma.post.findUniqueOrThrow.mockImplementation((() => { throw new Error('There was an error.') }) as any) const response = await getPostByID(200) expect(response).toBe('There was an error.') }) ``` [`mockImplementation`](https://vitest.dev/api/mock.html#mockimplementation) replaces the mocked function's behavior. Here it throws an error, giving you fine-grained control over the output in error states. If the function under test is meant to throw rather than catch, you can test that too: ```ts // src/script.ts (throwing variant) export const getPostByID = async (id: number) => { return await prisma.post.findUniqueOrThrow({ where: { id } }) } ``` ```ts // test/errors.test.ts test('getPostByID should throw an error', async () => { prisma.post.findUniqueOrThrow.mockImplementation((() => { throw new Error('There was an error.') }) as any) await expect(getPostByID(1)).rejects.toThrow() await expect(getPostByID(1)).rejects.toThrowError('There was an error') }) ``` The [`rejects`](https://vitest.dev/api/expect.html#rejects) modifier resolves the promise given to `expect` and checks for a rejection. `toThrow` and `toThrowError` then check details about the error. ### Mocking transactions [#mocking-transactions] Another part of Prisma Client you may need to mock is a [`$transaction`](https://www.prisma.io/docs/orm/v7/prisma-client/queries/transactions#the-transaction-api). There are two forms: [sequential operations](https://www.prisma.io/docs/orm/v7/prisma-client/queries/transactions#sequential-prisma-client-operations) and [interactive transactions](https://www.prisma.io/docs/orm/v7/prisma-client/queries/transactions#interactive-transactions). How you mock them depends on your test's goal. If your test only cares about the result of a sequential transaction, mock the return value of `$transaction` directly: ```ts // src/script-tx.ts export const addPost = async (data: Prisma.PostCreateInput) => { const [newPost, count] = await prisma.$transaction([ prisma.post.create({ data }), prisma.post.count() ]) return { newPost, count } } ``` ```ts // test/transactions.test.ts test('addPost (sequential) returns the new post and the total count', async () => { // 1 const mockPost = { authorId: 1, title: 'title', content: 'content', published: true } // 2 const mockResponse = [{ ...mockPost, id: 1 }, 100] prisma.$transaction.mockResolvedValue(mockResponse) // 3 const data = await addPost(mockPost) // 4 expect(data).toStrictEqual({ newPost: mockResponse[0], count: mockResponse[1] }) }) ``` In the test above you: 1. Mocked the data for the post you intend to create. 2. Mocked what `$transaction` should return. 3. Invoked the function after the mocks were set up. 4. Verified the returned value matched what you expected. By mocking the response of `$transaction` itself, you did not have to consider what happened inside the transaction. To test an interactive transaction with business logic you need to validate, mock the individual calls and the `$transaction` implementation: ```ts // src/script-itx.ts export const addPost = async (data: Prisma.PostCreateInput) => { return await prisma.$transaction(async (tx) => { if (!('published' in data)) { data['published'] = true } const newPost = await tx.post.create({ data }) const count = await tx.post.count() return { newPost, count } }) } ``` ```ts // test/transactions.test.ts test('addPost (interactive) returns the new post and the total count', async () => { // 1 const mockPost = { authorId: 1, title: 'title', content: 'content' } const mockResponse = { newPost: { ...mockPost, id: 1, published: true }, count: 100 } // 2 prisma.post.create.mockResolvedValue(mockResponse.newPost) prisma.post.count.mockResolvedValue(mockResponse.count) // 3 prisma.$transaction.mockImplementation((callback) => callback(prisma)) // 4 const data = await addPost(mockPost) // 5 expect(data.newPost.published).toBe(true) expect(data).toStrictEqual(mockResponse) }) ``` Here you: 1. Mock the post and response objects. 2. Mock the responses of `create` and `count`. 3. Mock the `$transaction` implementation so it passes the mocked client to the interactive callback rather than a real client. 4. Invoke `addPost`. 5. Validate the response, confirming the business logic set `published` to `true`. ## Spy on methods [#spy-on-methods] The last concept is *spying*. Vitest lets you spy on a function to observe how many times it was called, what arguments it received, and what it returned, without changing its behavior. A mocked function created with `vi.fn()` has all spying features by default. Because Prisma Client is mocked, every function can be spied on: ```ts // src/script.ts export const updateUser = async ( id: number, data: Prisma.UserUpdateInput, clearPosts: boolean ) => { const user = await prisma.user.update({ where: { id }, data }) if (clearPosts) { await prisma.post.deleteMany({ where: { authorId: id } }) } return user } ``` ```ts // test/sample.test.ts import { updateUser } from '../src/script' test('updateUser should delete user posts if clearPosts is true', async () => { prisma.user.update.mockResolvedValue({ id: 1, email: 'adams@prisma.io', name: 'Sabin Adams' }) await updateUser(1, {}, true) expect(prisma.post.deleteMany).toHaveBeenCalled() expect(prisma.post.deleteMany).toHaveBeenCalledWith({ where: { authorId: 1 } }) }) ``` Spy functions are useful when you want to confirm certain code paths run based on the inputs. Running the full set of mock and spy tests, they all pass: ```text Test Files 3 passed (3) Tests 7 passed (7) ``` ## Why Vitest? [#why-vitest] This series uses Vitest rather than Jest for a few practical reasons. Vitest runs your tests through the same Vite pipeline your app already uses, so ESM and TypeScript work without extra configuration, which matters because the Prisma 7 `prisma-client` generator emits ES modules. Vitest's API mirrors Jest's, so the concepts here transfer if your team uses Jest elsewhere. Jest remains a capable choice; the guidance in this series applies to either with minor changes to configuration and imports. ## Frequently asked questions [#frequently-asked-questions] ### How do I mock Prisma Client in Vitest? Create a manual mock in a `__mocks__` folder next to your Prisma Client module. In that file, export a deep mock built with `mockDeep()` from `vitest-mock-extended`, and reset it between tests with `mockReset`. In your test file, call `vi.mock('')` so Vitest swaps the real client for the mock. Because `mockDeep` mocks nested properties, calls like `prisma.user.create()` become controllable spy functions. This was verified on Vitest 4.1.10 and `vitest-mock-extended` 4.0.0. ### Do I need a running database to unit test Prisma code? No. Unit tests use a mocked client, so no database connection is opened. You mock each query's return value with helpers like `mockResolvedValue`. You only need a real database for integration and end-to-end tests, which are covered later in this series. ### On Prisma 7, does prisma generate run automatically after a schema change? No. On Prisma ORM 7, `prisma db push` and `prisma migrate dev` no longer regenerate Prisma Client. After changing your schema, run `npx prisma generate` yourself. This was verified by adding a model, running `db push`, and confirming the generated client stayed unchanged until `generate` was run. ## Summary & what's next [#summary--whats-next] In this article, you focused on *mocking* and *spying*, which play a major role in unit testing. Specifically, you explored: * What mocking is and why it is useful * How to set up a project with Vitest and Prisma ORM 7 configured * How to mock Prisma Client with a deep mock * How to use a mocked Prisma Client instance to control responses, trigger errors, mock transactions, and spy on methods With this foundation, you have the tools to unit test an application. In [part 2: Unit Testing](https://www.prisma.io/blog/testing-series-2-xPhjjmIEsM), you will put them to use against a real service. Looking ahead: [Prisma 8](https://www.prisma.io/docs/orm) is a TypeScript-native rewrite of Prisma ORM, built for AI coding agents, and it is now a [release candidate](https://www.prisma.io/docs/orm/release-status). Prisma 7 remains fully supported; its docs live at [prisma.io/docs/orm/v7](https://www.prisma.io/docs/orm/v7). To start with Prisma 8, run `npm create prisma@latest` or read the [Prisma 8 docs](https://www.prisma.io/docs/getting-started).