Jest测试中save方法未被调用的问题排查求助
Jest测试中Mock的save方法调用次数始终为0的问题
问题描述
我在验证Jest测试中被mock的save方法是否被调用时遇到问题:每次运行测试,均显示该方法的调用次数为0,但生产环境中一切正常,新数据可正常保存,ORM的save方法也能按预期被调用。
测试代码示例
describe('updateEmail', () => { it('should update user email', async () => { const user: User = { id: 'abc', email: 'old@email.org', password: 'password', }; const updatedUser: User = Object.assign({}, user); updatedUser.email = 'new@email.org'; const findOneSpy = jest .spyOn(repository, 'findOne') .mockResolvedValue(user); const saveSpy = jest .spyOn(repository, 'save') .mockResolvedValue(updatedUser); expect( service.updateEmail({ oldEmail: user.email, newEmail: updatedUser.email, }), ).resolves.toEqual(updatedUser); expect(findOneSpy).toHaveBeenCalledWith({ where: { email: user.email } }); expect(saveSpy).toHaveBeenCalledWith(updatedUser); }); });
业务逻辑代码
async updateEmail({ oldEmail, newEmail }: UpdateEmailDto) { const user = await this.findByEmail(oldEmail); if (!user) throw new NotFoundException(); user.email = newEmail; return this.usersRepository.save(user); }
报错信息
expect(jest.fn()).toHaveBeenCalledWith(...expected) Expected: {"email": "new@email.org", "id": "abc", "password": "password"} Number of calls: 0 133 | expect(findOneSpy).toHaveBeenCalledWith({ where: { email: user.email } }); 134 | > 135 | expect(saveSpy).toHaveBeenCalledWith(updatedUser); | ^ 136 | }); 137 | }); 138 | }); at Object.<anonymous> (users/users.service.spec.ts:135:23)
问题原因与解决办法
问题出在异步代码的处理时机和对象匹配逻辑:
- 调用
service.updateEmail后,你直接用.resolves.toEqual断言,但没有等待异步操作完成就检查saveSpy的调用情况,此时save方法还未执行,导致调用次数为0。 - 业务代码中直接修改了
findOne返回的原user对象的email属性,但测试里的updatedUser是新创建的对象,Jest的toHaveBeenCalledWith默认严格匹配对象引用,所以即使属性相同也会匹配失败。
具体修复步骤
- 等待异步操作完成:用
await直接调用service.updateEmail,确保异步逻辑执行完毕后再做断言。 - 调整对象匹配方式:要么断言被修改后的原
user对象,要么用expect.objectContaining做属性匹配,跳过对象引用的严格校验。
修复后的测试代码:
describe('updateEmail', () => { it('should update user email', async () => { const user: User = { id: 'abc', email: 'old@email.org', password: 'password', }; const updatedEmail = 'new@email.org'; const findOneSpy = jest .spyOn(repository, 'findOne') .mockResolvedValue(user); const saveSpy = jest .spyOn(repository, 'save') .mockResolvedValue({ ...user, email: updatedEmail }); // 等待异步方法执行完成 const result = await service.updateEmail({ oldEmail: user.email, newEmail: updatedEmail, }); expect(result).toEqual({ ...user, email: updatedEmail }); expect(findOneSpy).toHaveBeenCalledWith({ where: { email: user.email } }); // 方式1:用objectContaining匹配属性,忽略对象引用 expect(saveSpy).toHaveBeenCalledWith(expect.objectContaining({ email: updatedEmail, id: user.id })); // 方式2:直接断言被修改后的原对象(此时user的email已经被业务代码修改) // expect(saveSpy).toHaveBeenCalledWith(user); }); });
额外说明
- 测试异步代码时,必须确保所有异步操作完成后再执行断言,否则会出现时序问题导致断言失败。
- 当业务逻辑是修改原对象而非创建新对象时,优先用
expect.objectContaining做部分匹配,避免对象引用不一致导致的断言失败。
内容的提问来源于stack exchange,提问作者nerrood
相关产品推荐
相关产品推荐

