
在 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...
⚠️ 这并非真正的循环依赖(如 A→B→A),而是 provider 标识符失配 + 元数据缺失导致的依赖图构建失败。
✅ 正确修复方式
-
确保 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()), })); -
移除
jest.mock()的显式调用(推荐)
Jest 默认启用automock模式,只要__mocks__/client.service.ts存在且命名正确,无需手动jest.mock()。显式调用反而易引发重复 mock 或时机问题。直接删除该行:// ❌ 删除这一行 // jest.mock('../client.service'); -
在测试模块中正常声明 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 是否具备length、prototype等类特征; - 避免在 mock 中
import原始服务文件(如import { PrivateClientService } from '../client.service'),否则可能触发真实模块加载,导致循环依赖暴露。
? 总结
NestJS 测试中的 “circular dependency” 报错,90% 以上并非架构耦合问题,而是 Jest mock 配置不当引发的 DI 元数据断层。核心原则是:mock 的导出名 = 被注入的 token 名 = providers 数组中引用的类名。遵循此约定,即可让测试容器正确绑定模拟实现,彻底规避此类伪循环依赖陷阱。











