升级至Angular 15后NgRx Effects测试失效问题排查
问题场景
将Angular升级至15.0.2、NgRx Effects升级至15.0.0后,运行Effects单元测试时触发NG0202错误,提示构造函数依赖注入不兼容,但被测类已添加@Injectable()装饰器,构造函数参数类型也符合要求。
错误信息
Error: NG0202: This constructor is not compatible with Angular Dependency Injection because its dependency at index 0 of the parameter list is invalid.
This can happen if the dependency type is a primitive like a string or if an ancestor of this class is missing an Angular decorator.Please check that 1) the type for the parameter at index 0 is correct and 2) the correct Angular decorators are defined for this class and its ancestors.
相关代码
被测Effects类
@Injectable() export class ProductEffects { constructor(private store$: Store<ProductState>, private actions$: Actions<ProductAction>, private productService: ProductService) {} requestActiveProducts = createEffect(() => // ... effects业务逻辑 }
测试代码
describe('ProductEffects', () => { let storeSpy: Mocked<Store<ProductState>>; let productServiceSpy: Mocked<ProductService>; let actions$: Observable<ProductAction>; let effects: ProductEffects; beforeEach(() => { storeSpy = createSpyObj('Store', ['select']); productServiceSpy = createSpyObj('ProductService', [... ]); TestBed.configureTestingModule({ providers: [ { provide: Store, useValue: storeSpy }, { provide: ProductService, useValue: productServiceSpy }, ProductEffects, provideMockActions(() => actions$), ], imports: [StoreModule.forRoot({})], teardown: { destroyAfterEach: false }, }); effects = TestBed.inject(ProductEffects); // 错误触发位置 });
依赖版本
"@angular/core": "15.0.2", "@ngrx/effects": "15.0.0"
解决办法
问题根源在于Angular 15对依赖注入的严格校验,以及NgRx Effects 15中Actions泛型类型的注入处理逻辑变化。
核心原因
测试模块中未正确提供Actions<ProductAction>的依赖实例:虽然使用了provideMockActions,但未提前初始化actions$,且引入的StoreModule.forRoot({})会干扰DI容器对Actions令牌的解析。
修复步骤
- 移除不必要的Store模块导入:测试Effects无需完整的Store模块,
StoreModule.forRoot({})会导致DI容器冲突,直接删除该导入项。 - 提前初始化actions$:在
beforeEach中给actions$赋值为Subject实例,确保provideMockActions能返回有效的可观察对象。 - 确保provideMockActions正确配置:这是NgRx官方推荐的模拟Actions的方式,无需额外手动提供
Actions令牌。
修改后的测试代码片段:
beforeEach(() => { storeSpy = createSpyObj('Store', ['select']); productServiceSpy = createSpyObj('ProductService', [... ]); actions$ = new Subject<ProductAction>(); // 初始化actions$为Subject TestBed.configureTestingModule({ providers: [ { provide: Store, useValue: storeSpy }, { provide: ProductService, useValue: productServiceSpy }, ProductEffects, provideMockActions(() => actions$), ], teardown: { destroyAfterEach: false }, }); effects = TestBed.inject(ProductEffects); });
此外,若仍有问题,可检查@Injectable()装饰器是否添加了providedIn配置,但本场景中测试时已手动在providers中注册ProductEffects,因此无需额外调整。
内容的提问来源于stack exchange,提问作者tiefenauer

