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

NestJS测试疑问:为何jest.fn()无法让Prisma findUnique返回null?

问题:Jest模拟Prisma findUnique方法时,直接用jest.fn()不生效?

我正在测试一个简单的createUser行为,希望通过jest.fn()让Prisma的findUnique方法返回null,但未能成功。如果通过jest.spyOn设置mock返回值,测试就能通过。

当我移除jest.spyOn(prismaMock.user, 'findUnique').mockReturnValue(null);这行代码,直接让jest.fn()返回null时,就会触发ConflictException: User already exists,不符合预期。我是不是忽略了jest.fn()的某些特性?

user.service.spec.ts

// [..] omitted imports 

describe('UserService', () => {
  let service: UserService;
  const prismaMock = {
    user: {
      create: jest.fn(() => {
        return { id: 1, name: 'any_name', email: 'any_email@mail.com' };
      }),
      findUnique: jest.fn(),
    },
  };

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        UserService,
        { provide: PrismaService, useValue: prismaMock },
      ],
    }).compile();

    service = module.get<UserService>(UserService);
  });

// [...] omitted tests above
  it('should return a new user', async () => {
    const createUserInput = {
      name: 'any_name',
      email: 'any_email@mail.com',
    };

    jest.spyOn(prismaMock.user, 'findUnique').mockReturnValue(null);
    const response = await service.create(createUserInput);

    expect(response).toEqual({
      id: expect.any(Number),
      name: 'any_name',
      email: 'any_email@mail.com',
    });
  });

user.service.ts

// omitted imports 
@Injectable()
export class UserService {
  constructor(private readonly prisma: PrismaService) {}

  async create(createUserDto: CreateUserDto): Promise<User> {
    const userAlreadyExists = await this.prisma.user.findUnique({
      where: { email: createUserDto.email },
    });
    
    if (userAlreadyExists) {
      throw new ConflictException('User already exists');
    }

    const createdUser = this.prisma.user.create({ data: createUserDto });
    return createdUser;
  }
}

解答

核心问题出在mock函数的默认行为和测试用例之间的mock污染上,具体原因和解决方法如下:

1. jest.fn()的默认返回值不是null

你当前定义的findUnique: jest.fn(),这个mock函数默认调用后返回的是undefined,而不是null。虽然await undefined得到的还是undefined,理论上不会触发if (userAlreadyExists)的判断,但你说移除spyOn后触发了冲突异常,这说明之前的测试用例修改了共享的prismaMock对象——因为prismaMock是在describe外部定义的,所有测试用例会复用这个对象,若之前的测试给findUnique设置过返回用户对象的mock,且没有重置,当前测试就会沿用这个返回值,导致触发“用户已存在”的异常。

2. 异步方法的mock需要返回Promise

Prisma的所有数据库操作都是异步的,返回Promise。即使你想让findUnique返回null,也不能直接写jest.fn(() => null),虽然await可以处理非Promise值,但更规范的做法是用jest.fn(() => Promise.resolve(null))来模拟异步行为,和Prisma的实际行为保持一致。

3. spyOn的作用是覆盖并重置mock

jest.spyOn(prismaMock.user, 'findUnique').mockReturnValue(null)会重新包装原有的mock函数,强制给当前测试用例设置新的返回值,相当于临时覆盖了之前可能被修改过的mock实现,所以测试能通过。


解决方法

方法一:初始化时设置正确的异步mock返回值

直接在prismaMock里给findUnique设置默认返回resolved为null的Promise:

const prismaMock = {
  user: {
    create: jest.fn(() => Promise.resolve({ id: 1, name: 'any_name', email: 'any_email@mail.com' })),
    // 模拟异步返回null
    findUnique: jest.fn(() => Promise.resolve(null)),
  },
};

注意:你的service里的create方法没有用await接收prisma.create的结果,实际返回的是Promise而非User对象,建议加上await:

const createdUser = await this.prisma.user.create({ data: createUserDto });

方法二:在beforeEach中重置mock,避免测试污染

因为prismaMock是共享对象,在每个测试前重置mock的实现和调用记录,确保测试用例之间相互独立:

beforeEach(async () => {
  // 清除所有mock的调用记录和实例
  jest.clearAllMocks();
  // 重置findUnique的默认返回值
  prismaMock.user.findUnique.mockResolvedValue(null);

  const module: TestingModule = await Test.createTestingModule({
    providers: [
      UserService,
      { provide: PrismaService, useValue: prismaMock },
    ],
  }).compile();

  service = module.get<UserService>(UserService);
});

方法三:用mockResolvedValue处理异步mock

对于异步方法,Jest提供了更清晰的mockResolvedValue方法来模拟成功的异步返回,替代mockReturnValue:

jest.spyOn(prismaMock.user, 'findUnique').mockResolvedValue(null);

内容的提问来源于stack exchange,提问作者Lucas César

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 11:07:14