Search code examples
angularjasmineangular-reactive-forms

Angular test formGroupDirective get function on init


I am trying to test a multistep form that I've created. For each step I'm passing in a form group name then calling FormGroupDirective.control.get in ngOnInit to get the formGroup used in that particular component.

Here's my step-one component setup.

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();
  }
}

And the form initialized in my 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]],
    })
  })

For the test I am trying to just get the basic setup figured out. Despite setting up a mock formGroup for stepForm, I keep getting NG01052: formGroup expects a FormGroup instance. Please pass one in.

Here's my test so far

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 service: FormService;
  const formgroupstub = {
    control: {
        get: jasmine.createSpy('get')
    }
  };

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [StepOneComponent],
      providers: [
        { provide: FormGroupDirective, useValue: formgroupstub },
      ]
    }).compileComponents();
    fixture = TestBed.createComponent(StepOneComponent);
    component = fixture.componentInstance;

    service = TestBed.inject(FormService);

    component.formGroupName = 'orgInformation';
    component.stepForm = new FormGroup({
      orgInformation: new FormGroup({
        name: new FormControl('', Validators.required),
      }),
    });

    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});

I'm trying to mock the formGroupDirective properly so I can test this component, but I can't figure out how to do it.


Solution

  • You most likely need to add ReactiveFormsModule to the imports array.

    imports: [
      StepOneComponent,
      ReactiveFormsModule,
    ],
    

    But that's most likely not the main issue.

    The main issue is that we are mocking stepForm for it to be overridden in the ngOnInit. The first fixture.detectChanges is when ngOnInit is called.

    So we assign a correct value to stepForm only for it to be overridden in the fixture.detectChanges() call where ngOnInit is called and assigns it to the mock service's value. The mock service value is returning undefined at the moment.

    To potentially fix it, we could try something like:

    component.formGroupName = 'orgInformation';
    // Make the get of the stub return the form value for it to be populated
    // in the ngOnInit.
    formgroupstub.control.get.and.returnValue(new FormGroup({
            name: new FormControl('', Validators.required),
          }),
        }));
    // Now call fixture.detectChanges() which will call ngOnInit
    fixture.detectChanges();