NestJS中Jest单元测试方法依赖Mock未生效问题咨询
问题描述
在如下代码示例中,尝试测试 "someMethodToTest" 方法,按测试需求需要将其依赖的 "someDependencyMethod" 方法做Mock处理,但当前测试方案未生效,每次通过Jest运行测试都会抛出"I MUST NOT BE CALLED!"异常,报错信息如下:
FAIL src/user-files/user-files.service.spec.ts (0.123 s) ● SomeClassToTest › test someMethodToTest thrown: "I MUST NOT BE CALLED!"
备注说明:
- 项目为NestJS应用
- 单元测试基于Jest实现
- 以下展示代码均为简化版本,用于方便问题定位
已尝试多种Mock实现方案均未解决问题:代码中存在什么错误?为什么someDependencyMethod方法始终会被实际调用?
相关代码
some-mdl-to-test.service.ts
import { Injectable } from '@nestjs/common'; import { SomeDependencyClass } from './some-dependency-mdl'; @Injectable() export class SomeClassToTest { async someMethodToTest(someMethodToTestArg: string): Promise<string> { const someDependencyMethodOutput: string = await new SomeDependencyClass().someDependencyMethod(someMethodToTestArg); return someDependencyMethodOutput; } }
some-dependency-mdl.ts
export class SomeDependencyClass { async someDependencyMethod(someDependencyMethodArg: string): Promise<string> { throw 'I MUST NOT BE CALLED!'; // ... // SOME LOGIC! // ... return "SOME_VALUE_A"; } }
some-mdl-to-test.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing'; import { SomeClassToTest } from './some-mdl-to-test.service'; import { SomeDependencyClass } from './some-dependency-mdl'; describe('SomeClassToTest', () => { let someClassToTest: SomeClassToTest; let someDependencyClass: SomeDependencyClass; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [SomeClassToTest, SomeDependencyClass], }).compile(); someClassToTest = module.get<SomeClassToTest>(SomeClassToTest); someDependencyClass = module.get<SomeDependencyClass>(SomeDependencyClass); }); it('should be defined', () => { expect(someClassToTest).toBeDefined(); }); it('Test someMethodToTest!', async () => { const someDependencyMethodOutputMock = "SOME_VALUE_A"; const someMethodToTestInputMock = "SOME_VALUE_B"; const someMethodToTestOutput = "SOME_VALUE_A"; const someDependencyMethodPromise: Promise<string> = Promise.resolve(someDependencyMethodOutputMock); jest.spyOn(someDependencyClass, 'someDependencyMethod').mockImplementation(() => someDependencyMethodPromise); const someMethodToTestOutputToCheck = await someClassToTest.someMethodToTest(someMethodToTestInputMock); expect(someMethodToTestOutputToCheck).toEqual(someMethodToTestOutput); }); });
问题原因
Mock不生效的核心原因有两个:
- 你在
someMethodToTest方法内部直接用new SomeDependencyClass()实例化依赖,而不是通过NestJS的依赖注入机制获取实例。你在测试文件中spy的是Nest IoC容器创建的SomeDependencyClass实例,和方法内部手动new出来的实例完全是两个独立对象,自然不会被spy拦截。 - 你当前的测试配置把
SomeDependencyClass作为真实provider注入,就算改成依赖注入写法,也需要优先替换为Mock实现,否则还是会调用真实逻辑。
修复方案
推荐方案:改造业务代码使用依赖注入
不要在类方法内部手动new依赖类,交给Nest容器管理依赖,这也是NestJS的标准开发范式:
import { Injectable } from '@nestjs/common'; import { SomeDependencyClass } from './some-dependency-mdl'; @Injectable() export class SomeClassToTest { // 通过构造函数注入依赖实例 constructor(private readonly someDependencyClass: SomeDependencyClass) {} async someMethodToTest(someMethodToTestArg: string): Promise<string> { // 直接使用注入的实例,不要手动new const someDependencyMethodOutput: string = await this.someDependencyClass.someDependencyMethod(someMethodToTestArg); return someDependencyMethodOutput; } }
同步修改测试代码,替换依赖为Mock实现:
import { Test, TestingModule } from '@nestjs/testing'; import { SomeClassToTest } from './some-mdl-to-test.service'; import { SomeDependencyClass } from './some-dependency-mdl'; describe('SomeClassToTest', () => { let someClassToTest: SomeClassToTest; let someDependencyClass: SomeDependencyClass; beforeEach(async () => { // 先创建Mock实例 const mockDependencyInstance = { someDependencyMethod: jest.fn() }; const module: TestingModule = await Test.createTestingModule({ providers: [ SomeClassToTest, // 用Mock实例替换真实的依赖provider { provide: SomeDependencyClass, useValue: mockDependencyInstance } ], }).compile(); someClassToTest = module.get<SomeClassToTest>(SomeClassToTest); someDependencyClass = module.get<SomeDependencyClass>(SomeDependencyClass); }); it('should be defined', () => { expect(someClassToTest).toBeDefined(); }); it('Test someMethodToTest!', async () => { const someDependencyMethodOutputMock = "SOME_VALUE_A"; const someMethodToTestInputMock = "SOME_VALUE_B"; const someMethodToTestOutput = "SOME_VALUE_A"; // 给Mock方法设置返回值 (someDependencyClass.someDependencyMethod as jest.Mock).mockResolvedValue(someDependencyMethodOutputMock); const someMethodToTestOutputToCheck = await someClassToTest.someMethodToTest(someMethodToTestInputMock); expect(someMethodToTestOutputToCheck).toEqual(someMethodToTestOutput); // 可选:断言依赖方法被正确调用 expect(someDependencyClass.someDependencyMethod).toHaveBeenCalledWith(someMethodToTestInputMock); }); });
临时方案:不修改业务代码直接Mock类
如果暂时不想改造业务代码(极度不推荐,违反NestJS依赖注入设计原则),可以直接Mock整个依赖模块,拦截类的实例化逻辑:
// 在测试文件顶部直接Mock整个依赖模块 jest.mock('./some-dependency-mdl', () => { return { SomeDependencyClass: jest.fn().mockImplementation(() => { return { someDependencyMethod: jest.fn().mockResolvedValue("SOME_VALUE_A") } }) } });
内容的提问来源于stack exchange,提问作者Eduardo Lúcio
相关产品推荐
相关产品推荐

