You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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]],
    })
  })

错误原因

原测试代码存在两个关键问题:

  1. 手动给component.stepForm赋值后,执行fixture.detectChanges()会触发ngOnInit,而Mock的FormGroupDirective.control.get未返回有效实例,导致stepForm被覆盖为undefined。
  2. 未MockFormService,测试中会实例化真实服务,可能引入不必要的依赖或副作用。

修正后的测试代码

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 13:55:32