使用ng-mocks测试Angular应用时嵌套服务模拟注入失败
问题背景
服务依赖链:OurService 依赖 DatetimeService 和 NumberFormatterService,NumberFormatterService 同样依赖 DatetimeService。测试目标为模拟 DatetimeService,同时保留 NumberFormatterService 的真实实现以验证服务协作。
测试中出现偶发异常:前两个验证 DatetimeService.locale() 返回 'en-US' 均正常,但验证 service.numberFormatter.datetimeService.locale() 时,偶发返回 undefined,且断点未触发。
使用依赖版本:jest@29.7.0、jest-preset-angular@14.0.0、@nx/jest@17.2.8、ng-mocks@14.12.1、@angular/core@17.1.0
可能原因
- 依赖注入实例不一致:Angular 测试模块初始化时,
NumberFormatterService可能未正确获取到模拟的DatetimeService单例,而是创建了新的真实实例或未注入模拟实例。 - 测试上下文污染:Jest 并行执行测试时,不同测试用例的上下文残留可能导致模拟服务实例被覆盖或未正确初始化。
- 版本兼容性问题:当前
ng-mocks@14.12.1版本低于 Angular 17,版本不匹配可能引发偶发的依赖注入逻辑异常。
解决方案
1. 强制使用单例模拟服务实例
创建全局模拟对象,通过 MockProvider 注入,确保整个测试模块中 DatetimeService 实例唯一:
import { MockProvider } from 'ng-mocks'; import { DatetimeService } from './datetime.service'; // 全局单例模拟对象 const mockDatetimeService = { locale: jest.fn().mockReturnValue('en-US') }; beforeEach(() => { TestBed.configureTestingModule({ providers: [ OurService, // 保留NumberFormatterService真实实现 NumberFormatterService, // 注入单例模拟服务 MockProvider(DatetimeService, mockDatetimeService) ] }); });
2. 重置测试上下文避免污染
在每个测试用例前清空 mock 并重置 TestBed,消除并行测试的上下文残留:
beforeEach(async () => { jest.clearAllMocks(); await TestBed.resetTestingModule() .configureTestingModule({ providers: [ OurService, NumberFormatterService, MockProvider(DatetimeService, mockDatetimeService) ] }) .compileComponents(); });
3. 验证实例一致性
添加断言确保 OurService 和 NumberFormatterService 依赖的 DatetimeService 是同一个实例:
it('should share the same mock DatetimeService instance', () => { const service = TestBed.inject(OurService); const datetimeService = TestBed.inject(DatetimeService); expect(service.numberFormatter.datetimeService).toBe(datetimeService); expect(service.numberFormatter.datetimeService.locale()).toBe('en-US'); });
4. 升级ng-mocks适配Angular 17
将 ng-mocks 升级至与 Angular 17 匹配的版本(如 ng-mocks@17.x.x),解决版本不兼容导致的偶发问题。升级后需同步检查 jest-preset-angular 等依赖的兼容性。
验证步骤
- 用
jest --runInBand禁用并行执行,排查是否为并行测试导致的上下文污染。 - 检查
mockDatetimeService.locale的调用次数,确认NumberFormatterService是否正确调用模拟方法。 - 多次运行测试用例,验证异常是否不再复现。
内容的提问来源于stack exchange,提问作者Peter T.

