Angular多步骤表单测试:FormGroupDirective初始化调用问题
解决Angular多步骤表单组件测试中的NG01052错误
问题场景
开发了一个多步骤表单,每个步骤组件通过@Input()接收formGroupName,在ngOnInit中通过FormGroupDirective.control.get获取对应子表单组。但在单元测试中,尽管尝试Mock表单组,仍触发NG01052: formGroup expects a FormGroup instance错误。
组件核心代码:
export class StepOneComponent implements OnInit { stepForm!: FormGroup; billingRequired = false; @Input() formGroupName!: string; @Input() formHeading: string; constructor(private rootFormGroup: FormGroupDirective, private formService: FormService) { } ngOnInit(): void { this.stepForm = this.rootFormGroup.control.get(this.formGroupName) as FormGroup; } toggle() { this.formService.billingToggle(); } }
FormService中的表单结构:
multiStepForm: FormGroup = this.fb.group({ orgInformation: this.fb.group({ name: ['', [Validators.required]], address: ['', [Validators.required]], country: ['', [Validators.required]], city: ['', [Validators.required]], state: ['', [Validators.required]], zip: ['', [Validators.required]], contactFirstName: ['', [Validators.required]], contactMiddleName: [''], contactLastName: ['', [Validators.required]], contactSuffix: [''], contactEmail: ['', [Validators.required, Validators.email]], contactPhone: ['', [Validators.required, Validators.minLength(10)]], contactExt: [''], }), billingInformation: this.fb.group({ cardNumber: ['', [Validators.required]], expiration: ['', [Validators.required]], cvv: ['', [Validators.required, Validators.minLength(3)]], cardName: ['', [Validators.required]], }) })
错误原因
原测试代码存在两个关键问题:
- 手动给
component.stepForm赋值后,执行fixture.detectChanges()会触发ngOnInit,而Mock的FormGroupDirective.control.get未返回有效实例,导致stepForm被覆盖为undefined。 - 未Mock
FormService,测试中会实例化真实服务,可能引入不必要的依赖或副作用。
修正后的测试代码
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { StepOneComponent } from './step-one.component'; import { FormControl, FormGroup, FormGroupDirective, Validators } from '@angular/forms'; import { FormService } from 'src/app/services/form/formservice.service'; describe('StepOneComponent', () => { let component: StepOneComponent; let fixture: ComponentFixture<StepOneComponent>; let mockFormGroupDirective: jasmine.SpyObj<FormGroupDirective>; let mockFormService: jasmine.SpyObj<FormService>; // 创建测试用的子表单组,匹配orgInformation结构 const mockOrgFormGroup = new FormGroup({ name: new FormControl('', Validators.required), address: new FormControl('', Validators.required), country: new FormControl('', Validators.required), city: new FormControl('', Validators.required), state: new FormControl('', Validators.required), zip: new FormControl('', Validators.required), contactFirstName: new FormControl('', Validators.required), contactMiddleName: new FormControl(''), contactLastName: new FormControl('', Validators.required), contactSuffix: new FormControl(''), contactEmail: new FormControl('', [Validators.required, Validators.email]), contactPhone: new FormControl('', [Validators.required, Validators.minLength(10)]), contactExt: new FormControl(''), }); beforeEach(() => { // 创建FormGroupDirective的Spy对象,包含完整的根表单结构 mockFormGroupDirective = jasmine.createSpyObj('FormGroupDirective', [], { control: new FormGroup({ orgInformation: mockOrgFormGroup }) }); // 创建FormService的Spy对象,Mock需要的方法 mockFormService = jasmine.createSpyObj('FormService', ['billingToggle']); TestBed.configureTestingModule({ imports: [StepOneComponent], providers: [ { provide: FormGroupDirective, useValue: mockFormGroupDirective }, { provide: FormService, useValue: mockFormService } ] }).compileComponents(); fixture = TestBed.createComponent(StepOneComponent); component = fixture.componentInstance; // 设置输入属性 component.formGroupName = 'orgInformation'; component.formHeading = '组织信息'; // 触发变更检测,执行ngOnInit fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); it('should get correct form group from root form in ngOnInit', () => { expect(component.stepForm).toBe(mockOrgFormGroup); }); it('should call billingToggle on formService when toggle is triggered', () => { component.toggle(); expect(mockFormService.billingToggle).toHaveBeenCalled(); }); });
关键修正点
- 正确Mock FormGroupDirective:通过
jasmine.createSpyObj创建包含control属性的Mock对象,control是一个包含目标子表单的完整FormGroup,确保rootFormGroup.control.get能返回有效实例。 - Mock FormService:避免实例化真实服务,聚焦测试组件与服务的交互逻辑。
- 调整执行顺序:先设置输入属性,再执行
fixture.detectChanges()触发ngOnInit,确保stepForm正确初始化。
内容的提问来源于stack exchange,提问作者KathrynCrawford
相关产品推荐
相关产品推荐

