如何在Angular单元测试中配置含NgForm的viewProviders?
解决Angular 16独立组件单元测试中NgForm提供者配置问题
问题背景
我有一个Angular 16独立组件,通过viewProviders注入所在表单作为输入,组件运行正常,但单元测试时出现NullInjectorError: No provider for NgForm!错误,无法正确配置提供者。
组件代码
@Component({ selector: 'app-question-form-editor', standalone: true, imports: [ CommonModule, FormsModule ], templateUrl: './question-form-editor.component.html', viewProviders: [{ provide: ControlContainer, useExisting: NgForm }], }) export class QuestionFormEditorComponent implements OnInit { @Input() form!: NgForm; //Logic goes here... }
当前测试代码
const mockQuestionForm = new NgForm([], []); mockQuestionForm.form.addControl('example', new FormControl('', Validators.required)); describe('QuestionFormEditorComponent', () => { let component: QuestionFormEditorComponent; let fixture: ComponentFixture<QuestionFormEditorComponent>; beforeEach(() => { TestBed.configureTestingModule({ imports: [QuestionFormEditorComponent], providers: [{ provide: ControlContainer, useValue: mockQuestionForm }], }); fixture = TestBed.createComponent(QuestionFormEditorComponent); component = fixture.componentInstance; component.form = mockQuestionForm; }); it('should create', () => { fixture.detectChanges(); //triggers ngOnInit() expect(component).toBeTruthy(); }); });
报错信息
NullInjectorError: R3InjectorError(Standalone[QuestionFormEditorComponent])[NgForm -> NgForm -> NgForm]: NullInjectorError: No provider for NgForm! error properties: Object({ ngTempTokenPath: null, ngTokenPath: [ 'NgForm', 'NgForm', 'NgForm' ] }) at NullInjector.get (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/fesm2022/core.mjs:8890:27) at R3Injector.get (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/fesm2022/core.mjs:9334:33) at R3Injector.get (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/fesm2022/core.mjs:9334:33) at R3Injector.get (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/fesm2022/core.mjs:9334:33) at ChainedInjector.get (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/fesm2022/core.mjs:14018:36) at lookupTokenUsingModuleInjector (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/fesm2022/core.mjs:4608:39) at getOrCreateInjectable (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/fesm2022/core.mjs:4656:12) at ɵɵdirectiveInject (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/fesm2022/core.mjs:11801:19) at ɵɵinject (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/fesm2022/core.mjs:848:60) at NodeInjectorFactory.factory (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/fesm2022/core.mjs:9556:29)
解决方案
问题原因
组件的viewProviders中使用useExisting: NgForm,意味着Angular需要先找到一个已存在的NgForm实例来作为ControlContainer的提供者,但测试环境中没有注册NgForm的提供者,导致注入失败。
修复步骤
- 同时注册NgForm和ControlContainer提供者
修改测试模块的providers配置,同时提供NgForm的mock实例,并将ControlContainer指向该实例:
beforeEach(() => { TestBed.configureTestingModule({ imports: [QuestionFormEditorComponent], providers: [ { provide: NgForm, useValue: mockQuestionForm }, { provide: ControlContainer, useExisting: NgForm } ], }); fixture = TestBed.createComponent(QuestionFormEditorComponent); component = fixture.componentInstance; component.form = mockQuestionForm; });
- 简化配置(可选)
如果不需要严格匹配组件的useExisting逻辑,也可以直接将ControlContainer和NgForm都指向同一个mock实例:
beforeEach(() => { TestBed.configureTestingModule({ imports: [QuestionFormEditorComponent], providers: [ { provide: NgForm, useValue: mockQuestionForm }, { provide: ControlContainer, useValue: mockQuestionForm } ], }); fixture = TestBed.createComponent(QuestionFormEditorComponent); component = fixture.componentInstance; component.form = mockQuestionForm; });
关键说明
- 原测试仅配置了
ControlContainer,但组件的viewProviders依赖NgForm实例的存在,因此必须显式注册NgForm的提供者。 - 使用
useExisting: NgForm时,Angular会优先查找NgForm的注入实例,再将其作为ControlContainer的实现,因此两者的提供者配置缺一不可。
内容的提问来源于stack exchange,提问作者Chris Barr
相关产品推荐
相关产品推荐

