You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Sinon Stub Prisma Client方法未生效的问题排查

问题描述

我正尝试为一个通过Prisma Client保存任务的类编写单元测试,相关代码如下:

export class TaskHandler {
  ...
  public async save(ctx: PrismaClient, input: TaskInput): Promise<Task> {
    if (input.label == null || input.number == null) {
      throw new Error('Input not complete');
    }
    return ctx.task.create({
      data: {
        label: input.label,
        number: input.number,
      },
    });
  }
}

我使用Sinon对相关方法进行Stub,单元测试代码如下:

import { stub } from 'sinon';
...
describe('Tasks', () => {
  const prismaClient = new PrismaClient();
  describe('Task handler', () => {
    it('should save a item', async () => {
      const instance = new TaskHandler();
      const task: TaskInput = {
        label: 'test',
        number: 12,
      };
      const resultTask: Task = {
        id: '1',
        label: 'test',
        number: 12,
      };
      const stubInstance = stub(prismaClient.task, 'create').resolves(
        resultTask,
      );
      const result = await instance.save(prismaClient, task);
      console.log(JSON.stringify(result));
      stubInstance.reset();
    });
  });
});

但调用TaskHandler的save方法时,Prisma仍在验证数据库Schema,推测原create方法仍被执行而非返回Stub结果,请问我哪里操作有误?

问题原因与解决办法

核心问题

Prisma Client的task属性是动态生成的代理对象,直接用Sinon stub prismaClient.task.create不会生效——因为代理会拦截属性访问,每次访问prismaClient.task都会返回一个新的实例,你stub的只是某一次获取到的实例上的create方法,后续调用时拿到的是新对象,原方法依然会执行。

解决步骤

  • 方案一:使用模拟对象替代真实PrismaClient实例
    单元测试应完全隔离数据库依赖,直接创建一个带有stub方法的模拟对象传入save:

    import { stub } from 'sinon';
    ...
    describe('Tasks', () => {
      describe('Task handler', () => {
        it('should save an item', async () => {
          const instance = new TaskHandler();
          const task: TaskInput = {
            label: 'test',
            number: 12,
          };
          const resultTask: Task = {
            id: '1',
            label: 'test',
            number: 12,
          };
    
          // 创建模拟的PrismaClient对象
          const mockPrisma = {
            task: {
              create: stub().resolves(resultTask)
            }
          } as unknown as PrismaClient;
          
          const result = await instance.save(mockPrisma, task);
          console.log(JSON.stringify(result));
    
          // 验证create方法是否按预期调用
          mockPrisma.task.create.calledWith({
            data: { label: 'test', number: 12 }
          }).should.be.true;
        });
      });
    });
    
  • 方案二:Stub PrismaClient原型上的方法
    如果必须使用真实实例结构,可以直接Stub原型上的方法,确保所有实例都能应用stub:

    import { stub } from 'sinon';
    ...
    describe('Tasks', () => {
      let stubCreate: sinon.SinonStub;
    
      before(() => {
        // 在所有测试前Stub原型上的create方法
        stubCreate = stub(PrismaClient.prototype.task, 'create');
      });
    
      after(() => {
        // 测试完成后恢复原型
        stubCreate.restore();
      });
    
      describe('Task handler', () => {
        it('should save an item', async () => {
          const instance = new TaskHandler();
          const prismaClient = new PrismaClient();
          const task: TaskInput = {
            label: 'test',
            number: 12,
          };
          const resultTask: Task = {
            id: '1',
            label: 'test',
            number: 12,
          };
    
          stubCreate.resolves(resultTask);
          const result = await instance.save(prismaClient, task);
          console.log(JSON.stringify(result));
        });
      });
    });
    

内容的提问来源于stack exchange,提问作者glm9637

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 14:30:53