首页 / Prisma ORM 入门教程 / 测试 Prisma 应用:单元测试与集成测试

Prisma ORM 入门教程

测试 Prisma 应用:单元测试与集成测试

本教程共 54 篇 · 第 43 篇 · 更新于 2026-08-11 · 约 6 分钟阅读

单元测试集成测试mockDeepjest-mock-extendedTestcontainersTRUNCATE测试隔离迁移测试

本节目标:单测用 mock 隔离数据库,集成测试跑真库,学会快速重置与事务隔离。

测试分两层:单元测试验证函数逻辑,不碰数据库;集成测试验证真实 SQL 和关系约束,需要真库。

单元测试:mockDeep

单元测试要隔离外部依赖。mock 掉 Prisma Client,测试快且不依赖数据库,同时保留类型安全。工具用 jest-mock-extended(Vitest 用 vitest-mock-extended),核心是 mockDeep:把 PrismaClient 的每个方法都变成 mock,还能用 mockResolvedValue 指定返回值。

npm install -D jest-mock-extended

mockDeep 的好处是类型安全:mock 出来的对象和真实 PrismaClient 有一样的类型,测试里写错方法名、传错参数,编译期就报错。mock 对象上每个方法默认返回 undefined,要用 mockResolvedValue 指定返回值,或 mockRejectedValue 模拟失败。

两种组织方式:

  1. 单例模式:客户端集中在一个文件,测试里 jest.mock 整个模块,导出深度 mock 实例。适合已经用单例的项目,改动最小。
  2. 依赖注入:函数把 Prisma Client 作为参数传入,测试传 mock 实例,生产传真实例。更干净,跨测试框架可移植。

单例模式的 mock 文件长这样:

// singleton.ts
import { PrismaClient } from "../generated/prisma/client";
import { mockDeep, mockReset, DeepMockProxy } from "jest-mock-extended";
import { prisma } from "./client";

jest.mock("./client", () => ({
  prisma: mockDeep<PrismaClient>(),
}));

beforeEach(() => {
  mockReset(prismaMock);
});

export const prismaMock = prisma as unknown as DeepMockProxy<PrismaClient>;

再把 singleton.ts 配到 jest 的 setupFilesAfterEnv 里,每个测试文件都会自动重置 mock。

依赖注入的写法:

// context.ts
import { PrismaClient } from "../generated/prisma/client";
import { mockDeep, DeepMockProxy } from "jest-mock-extended";

export type Context = { prisma: PrismaClient };
export type MockContext = { prisma: DeepMockProxy<PrismaClient> };

export const createMockContext = (): MockContext => ({
  prisma: mockDeep<PrismaClient>(),
});
// create-user.ts:prisma 通过参数注入
export async function createUser(
  data: { email: string; name: string },
  ctx: Context,
) {
  return ctx.prisma.user.create({ data });
}
// create-user.test.ts
import { createMockContext, MockContext, Context } from "./context";
import { createUser } from "./create-user";

let mockCtx: MockContext;
let ctx: Context;

beforeEach(() => {
  mockCtx = createMockContext();
  ctx = mockCtx as unknown as Context;
});

test("创建用户", async () => {
  mockCtx.prisma.user.create.mockResolvedValue({
    id: 1,
    email: "a@b.com",
    name: "A",
  });

  await expect(createUser({ email: "a@b.com", name: "A" }, ctx)).resolves.toEqual({
    id: 1,
    email: "a@b.com",
    name: "A",
  });
});
Tip

依赖注入不依赖 Jest 的模块替换机制,换 Vitest 也不用改代码。推荐默认用这个。

单测写什么?重点测函数自己的逻辑:条件分支(比如未勾选条款时拒绝注册)、参数转换、错误路径。mock 掉数据库后,测试只关心「给了这个输入,调用了哪个方法、传了什么参数」,数据库行为不归单测管。

mock 的 PrismaClient 不需要适配器,因为根本不连数据库。遇到循环依赖报错时,在 tsconfig 里开 strictNullChecks 通常能解决。

集成测试:Docker 起真库

集成测试要验证真实行为:唯一约束、外键、级联删除。用 Docker 起一个测试专用数据库:

  1. 写 docker-compose.yml,映射到非默认端口,避免和本地开发库冲突。
  2. 测试前跑 prisma migrate deploy 建表。
  3. 跑测试,结束后销毁容器。
services:
  db:
    image: postgres:17-alpine
    ports:
      - "5433:5432"
    environment:
      POSTGRES_USER: prisma
      POSTGRES_PASSWORD: prisma
      POSTGRES_DB: tests
docker compose up -d
npx prisma migrate deploy   # 对测试库应用迁移
npm test

更自动化的方案是 Testcontainers:测试代码里启动容器、等数据库就绪、跑完自动销毁,不用手动敲 docker 命令。生命周期固定四步:启动 → 迁移 → 跑测试 → 停止。

种子数据(seed)的三种来源:工厂函数按测试定制数据、静态 JSON fixture、共享种子脚本做基线。简单场景用工厂函数最灵活。

集成测试的用例要覆盖数据库强约束:唯一字段重复插入抛 P2002、外键不存在的关联写不进去、级联删除真的删干净。这些行为 mock 测不出来,只有真库能验证。

测试间数据隔离:TRUNCATE 与事务回滚

集成测试最烦的是数据残留。两种主流隔离:

  1. TRUNCATE 重置。每个测试前清空表,Postgres 上最快:
beforeEach(async () => {
  await prisma.$executeRaw`TRUNCATE "User", "Post" RESTART IDENTITY CASCADE`;
});
  1. 事务回滚。每个测试开一个事务,测试结束抛错回滚,数据自动还原:
test("创建用户", async () => {
  await prisma.$transaction(async (tx) => {
    const user = await tx.user.create({ data: { email: "a@b.com" } });
    expect(user.id).toBeDefined();
    throw new Error("rollback"); // 回滚,不留数据
  });
});
Note

事务回滚有个前提:被测代码必须复用同一个事务客户端(tx),不能自己另起事务。做不到就用 TRUNCATE。

TRUNCATE 要按外键顺序清空,或直接 CASCADE 一把梭;RESTART IDENTITY 顺手重置自增主键,保证每次测试的主键从 1 开始,断言好写。deleteMany 也能清数据,各数据库通用,但比 TRUNCATE 慢,适合表少、行少的情况。

迁移测试

迁移文件本身也要测。做法:在 CI 里对测试库(或影子数据库 shadow database)跑 migrate deploy,再用 migrate diff 对比迁移历史与 Schema,确认无漂移:

npx prisma migrate deploy
npx prisma migrate diff \
  --from-migrations prisma/migrations \
  --to-schema prisma/schema.prisma \
  --exit-code

diff 有输出说明迁移历史与 Schema 不一致,CI 就该失败。

再往上还有端到端(E2E)测试:Supertest 打 HTTP 接口、Playwright 跑浏览器,验证完整链路。E2E 也复用同一个测试库,跑完一起清理。覆盖率用 vitest —coverage 或 c8 统计,重点关注数据访问层,别为了数字好看硬凑。

整套测试金字塔自下而上:单测最多最快,集成测试次之,E2E 最少最慢。数据库相关逻辑优先写集成测试,因为 mock 会掩盖真实 SQL 的问题。

Tip

测试脚本串起来:docker compose up -d → migrate deploy → 跑测试 → docker compose down。任何一步失败都中断。

参考来源

  • Prisma 官方文档:Unit testing / Integration testing
  • Mapagam:Testing Prisma Applications
  • Tech Insider:Prisma ORM Tutorial: Build a Type-Safe API in 13 Steps