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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 06:27:25