Angular14+用inject函数重构组件后,如何在Jest测试中注入依赖?
解决Angular 14+ inject函数与Jest直接实例化组件的兼容问题
核心问题原因
inject()函数必须在Angular的注入上下文(比如组件构造函数执行时、工厂函数、runInInjectionContext包裹的代码块)中调用。直接通过new MyComponent()实例化组件时,没有触发Angular的注入上下文,因此inject()会抛出错误。
解决方案
方案1:组件层兼容(最小改动测试代码)
给组件构造函数添加可选参数,内部用inject()兜底赋值。这样测试时可以手动传入mock依赖,正常运行时Angular会自动注入:
@Component({ selector: "app-cmpt", templateUrl: "./cmpt.component.html", styleUrls: ["./cmpt.component.scss"], }) export class MyComponent implements OnInit { private fb: NonNullableFormBuilder; private service: MyService; private router: Router; constructor( fb?: NonNullableFormBuilder, service?: MyService, router?: Router ) { // 优先使用构造函数传入的参数,否则用inject自动注入 this.fb = fb ?? inject(NonNullableFormBuilder); this.service = service ?? inject(MyService); this.router = router ?? inject(Router); } // 其余组件逻辑... }
测试代码可以完全保留原来的写法:
describe('MyComponent', () => { let component: MyComponent; let nnfb = new FormBuilder().nonNullable; let serviceSpy: any; let routerMock: Partial<Router>; beforeEach(() => { serviceSpy = { get: jest.fn(), update: jest.fn(), delete: jest.fn() }; routerMock = { navigate: jest.fn() }; // 依然可以直接new组件并传入mock依赖 component = new MyComponent(nnfb, serviceSpy, routerMock as Router); }); // 测试用例... });
方案2:测试层处理(不改动组件代码)
使用Angular的createInjector和runInInjectionContext手动创建注入上下文,在上下文内实例化组件。这种方式不需要修改组件,且createInjector比TestBed轻量很多,不会大幅降低测试速度:
import { runInInjectionContext, createInjector } from '@angular/core'; import { NonNullableFormBuilder, FormBuilder } from '@angular/forms'; import { Router } from '@angular/router'; import { MyComponent } from './cmpt.component'; import { MyService } from '../services/my.service'; describe('MyComponent', () => { let component: MyComponent; let serviceSpy: any; let routerMock: Partial<Router>; beforeEach(() => { serviceSpy = { get: jest.fn(), update: jest.fn(), delete: jest.fn() }; routerMock = { navigate: jest.fn() }; // 创建轻量注入器,注册mock依赖 const injector = createInjector({ providers: [ { provide: NonNullableFormBuilder, useValue: new FormBuilder().nonNullable }, { provide: MyService, useValue: serviceSpy }, { provide: Router, useValue: routerMock } ] }); // 在注入上下文中实例化组件 runInInjectionContext(injector, () => { component = new MyComponent(); }); }); // 测试用例示例 it('should call service.get on init', () => { component.ngOnInit(); expect(serviceSpy.get).toHaveBeenCalled(); }); });
方案选择建议
- 如果希望测试代码完全不变,优先选方案1,组件改动极小且不影响生产环境的注入逻辑。
- 如果不想修改组件代码,优先选方案2,用Angular原生API创建轻量注入上下文,避免TestBed的繁琐配置。
内容的提问来源于stack exchange,提问作者Sharlaan
相关产品推荐
相关产品推荐

