NestJS 单元测试中 Jest Mock 引发的循环依赖问题根因与修复方案

老丽姑娘_7545

老丽姑娘_7545

2026-09-11

855人浏览

原创

NestJS 单元测试中 Jest Mock 引发的循环依赖问题根因与修复方案

在 NestJS 单元测试中,jest.mock() 自动加载 __mocks__ 下同名模块时,若 mock 文件导出的类名与被测服务类名不一致(如导出 clientService 但原服务为 PrivateClientService),会导致 DI 容器解析失败并误报“circular dependency”——本质是类型/标识符不匹配引发的依赖图构建中断。

在 nestjs 单元测试中,jest.mock() 自动加载 __mocks__ 下同名模块时,若 mock 文件导出的类名与被测服务类名不一致(如导出 clientservice 但原服务为 privateclientservice),会导致 di 容器解析失败并误报“circular dependency”——本质是类型/标识符不匹配引发的依赖图构建中断。

当你在 client.controller.spec.ts 中执行:

jest.mock('../client.service');

Jest 会自动查找并加载 ../__mocks__/client.service.ts(注意路径与文件名需严格对应),而非你手动编写的 client.service.ts。此时,Nest 的测试模块构建器(Test.createTestingModule)在扫描 providers: [PrivateClientService] 时,实际尝试注入的是 __mocks__/client.service.ts 中导出的 值(如 clientService 函数),而非一个具有正确构造签名和元数据的 @Injectable() 类。

由于该 mock 导出的是一个普通函数(export const clientService = jest.fn().mockReturnValue(...)),它不携带 Nest 所需的反射元数据(如 @Injectable() 装饰器生成的 design:paramtypes,DI 容器无法识别其依赖项(例如 ClientRepository),也无法将其注册为合法 provider。更关键的是:当容器尝试解析 PrivateClientService 的依赖链时,发现该 token 对应的实现既非标准 provider、又未声明其自身依赖,便错误地将其判定为“无法解析的闭环节点”,从而抛出误导性错误:

A circular dependency has been detected inside RootTestModule...

抖音下载器(Node.js)
抖音下载器(Node.js)

抖音无水印视频下载和文案提取工具

下载

⚠️ 这并非真正的循环依赖(如 A→B→A),而是 provider 标识符失配 + 元数据缺失导致的依赖图构建失败

✅ 正确修复方式

  1. 确保 mock 文件导出的名称与被注入类名完全一致
    修改 __mocks__/client.service.ts,将导出变量重命名为 PrivateClientService,并确保它是一个可被 new 实例化的类(或至少模拟类行为):

    // __mocks__/client.service.ts
    import { PrivateClientService } from '../client.service';
    import { clientStub } from '../test/stubs/client.stub';
    
    // ✅ 正确:导出同名类(可为类或工厂函数,但必须能通过 DI 解析)
    export class PrivateClientService {
      createClient = jest.fn().mockResolvedValue(clientStub());
      updateClient = jest.fn().mockResolvedValue(clientStub());
      deleteClient = jest.fn().mockResolvedValue(clientStub());
    }

    或使用工厂函数(更贴近真实 Injectable 行为):

    // __mocks__/client.service.ts
    import { clientStub } from '../test/stubs/client.stub';
    
    export const PrivateClientService = jest.fn().mockImplementation(() => ({
      createClient: jest.fn().mockResolvedValue(clientStub()),
      updateClient: jest.fn().mockResolvedValue(clientStub()),
      deleteClient: jest.fn().mockResolvedValue(clientStub()),
    }));
  2. 移除 jest.mock() 的显式调用(推荐)
    Jest 默认启用 automock 模式,只要 __mocks__/client.service.ts 存在且命名正确,无需手动 jest.mock()。显式调用反而易引发重复 mock 或时机问题。直接删除该行:

    // ❌ 删除这一行
    // jest.mock('../client.service');
  3. 在测试模块中正常声明 provider
    确保 Test.createTestingModule 显式提供该 mock 类:

    // client.controller.spec.ts
    import { Test } from '@nestjs/testing';
    import { PrivateClientService } from '../client.service'; // ← 指向原始路径,Jest 自动代理
    import { responseMock } from '@test/mocks/response.mock';
    
    describe('PrivateClientController', () => {
      let privateClientService: PrivateClientService;
    
      beforeEach(async () => {
        console.log(responseMock);
    
        const moduleRef = await Test.createTestingModule({
          providers: [PrivateClientService], // ✅ Nest 将自动使用 __mocks__ 中的实现
        }).compile();
    
        privateClientService = moduleRef.get(PrivateClientService);
      });
    
      describe('createClient', () => {
        it('should pass', () => {
          expect(privateClientService).toBeDefined();
          expect(privateClientService.createClient).toBeInstanceOf(Function);
        });
      });
    });

? 验证与调试建议

  • 启用 Nest 调试日志定位真实问题:设置环境变量 NEST_DEBUG=true,运行测试可查看详细依赖解析过程;
  • 使用 console.log(Object.getOwnPropertyNames(PrivateClientService)) 检查 mock 是否具备 lengthprototype 等类特征;
  • 避免在 mock 中 import 原始服务文件(如 import { PrivateClientService } from '../client.service'),否则可能触发真实模块加载,导致循环依赖暴露。

? 总结

NestJS 测试中的 “circular dependency” 报错,90% 以上并非架构耦合问题,而是 Jest mock 配置不当引发的 DI 元数据断层。核心原则是:mock 的导出名 = 被注入的 token 名 = providers 数组中引用的类名。遵循此约定,即可让测试容器正确绑定模拟实现,彻底规避此类伪循环依赖陷阱。

相关文章

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

js jest 测试断言

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
js获取数组长度的方法
js获取数组长度的方法

在js中,可以利用array对象的length属性来获取数组长度,该属性可设置或返回数组中元素的数目,只需要使用“array.length”语句即可返回表示数组对象的元素个数的数值,也就是长度值。php中文网还提供JavaScript数组的相关下载、相关课程等内容,供大家免费下载使用。

2023.06.20

4106

5

js刷新当前页面
js刷新当前页面

js刷新当前页面的方法:1、reload方法,该方法强迫浏览器刷新当前页面,语法为“location.reload([bForceGet]) ”;2、replace方法,该方法通过指定URL替换当前缓存在历史里(客户端)的项目,因此当使用replace方法之后,不能通过“前进”和“后退”来访问已经被替换的URL,语法为“location.replace(URL) ”。php中文网为大家带来了js刷新当前页面的相关知识、以及相关文章等内容

2023.07.04

1049

3

js四舍五入
js四舍五入

js四舍五入的方法:1、tofixed方法,可把 Number 四舍五入为指定小数位数的数字;2、round() 方法,可把一个数字舍入为最接近的整数。php中文网为大家带来了js四舍五入的相关知识、以及相关文章等内容

2023.07.04

3984

6

js删除节点的方法
js删除节点的方法

js删除节点的方法有:1、removeChild()方法,用于从父节点中移除指定的子节点,它需要两个参数,第一个参数是要删除的子节点,第二个参数是父节点;2、parentNode.removeChild()方法,可以直接通过父节点调用来删除子节点;3、remove()方法,可以直接删除节点,而无需指定父节点;4、innerHTML属性,用于删除节点的内容。

2023.09.01

820

4

JavaScript转义字符
JavaScript转义字符

JavaScript中的转义字符是反斜杠和引号,可以在字符串中表示特殊字符或改变字符的含义。本专题为大家提供转义字符相关的文章、下载、课程内容,供大家免费下载体验。

2023.09.04

1716

5

js生成随机数的方法
js生成随机数的方法

js生成随机数的方法有:1、使用random函数生成0-1之间的随机数;2、使用random函数和特定范围来生成随机整数;3、使用random函数和round函数生成0-99之间的随机整数;4、使用random函数和其他函数生成更复杂的随机数;5、使用random函数和其他函数生成范围内的随机小数;6、使用random函数和其他函数生成范围内的随机整数或小数。

2023.09.04

3005

4

如何启用JavaScript
如何启用JavaScript

JavaScript启用方法有内联脚本、内部脚本、外部脚本和异步加载。详细介绍:1、内联脚本是将JavaScript代码直接嵌入到HTML标签中;2、内部脚本是将JavaScript代码放置在HTML文件的`<script>`标签中;3、外部脚本是将JavaScript代码放置在一个独立的文件;4、外部脚本是将JavaScript代码放置在一个独立的文件。

2023.09.12

3913

6

Js中Symbol类详解
Js中Symbol类详解

javascript中的Symbol数据类型是一种基本数据类型,用于表示独一无二的值。Symbol的特点:1、独一无二,每个Symbol值都是唯一的,不会与其他任何值相等;2、不可变性,Symbol值一旦创建,就不能修改或者重新赋值;3、隐藏性,Symbol值不会被隐式转换为其他类型;4、无法枚举,Symbol值作为对象的属性名时,默认是不可枚举的。

2023.09.20

2480

5

java访问控制修饰符介绍
java访问控制修饰符介绍

java访问控制修饰符有四种,分别是public、protected、private、默认访问修饰符。详细介绍:1、public,public是最宽松的访问控制修饰符,被修饰的类、方法和变量可以被任何其他类访问,当一个类、方法或变量被声明为public时,它们可以在任何地方被访问,无论是同一个包中的类还是不同包中的类;2、protected修饰符等等。

2023.09.20

828

7

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
WEB前端教程【HTML5+CSS3+JS】
WEB前端教程【HTML5+CSS3+JS】

共101课时 | 20.5万人学习

JS进阶与BootStrap学习
JS进阶与BootStrap学习

共39课时 | 4.7万人学习