Search code examples
angulartypescriptunit-testingjasmineangular-reactive-forms

Angular Unit Test: How to pass FormGroupDirective in a function?


I am trying to unit test a function which is taking a parameter of type FormGroupDirective. I am able to test all logic but cannot understand what should be taken as parameter to call the resetForm() function. Below is my code which I am testing.

<form [formGroup]="logForm" #formDirective="ngForm" (submit)="onSave(formDirective)">...</form>
onSave(formDirective: FormGroupDirective) {
  this._http.save(this.logForm.value).subscribe({
    next: response => {
      this._notification.show({ message: response.message, action: 'SUCCESS', panelClass: 'success' });
      formDirective.resetForm();
      this.logForm.reset();
      this.trailerHeading = 'Trailer';
      this.showAllTrailers = false;
      this.trailerList = [];
    }
  });
}

While unit testing I've passed null as value while calling onSave(...) method. Which is correctly throwing error while executing formDirective.resetForm().

The test case that I wrote is:

import { HttpClientTestingModule } from "@angular/common/http/testing";
import { CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { RouterTestingModule } from "@angular/router/testing";
import { HttpClientService } from "@service/http-client.service";
import { of } from "rxjs";
import { MaterialModule } from "src/app/material/material.module";
import { TrailerLogComponent } from "./trailer-log.component";

describe('TrailerLogComponent', () => {

    let component: TrailerLogComponent, fixture: ComponentFixture<TrailerLogComponent>,
        _httpService: HttpClientService;

    beforeEach(waitForAsync(() => {
        TestBed.configureTestingModule({
            imports: [MaterialModule, RouterTestingModule, FormsModule, ReactiveFormsModule, HttpClientTestingModule, BrowserAnimationsModule],
            declarations: [TrailerLogComponent],
            providers: [HttpClientService],
            schemas: [CUSTOM_ELEMENTS_SCHEMA],
            teardown: { destroyAfterEach: false }
        }).compileComponents();
        _httpService = TestBed.inject(HttpClientService);
        fixture = TestBed.createComponent(TrailerLogComponent);
        component = fixture.componentInstance;
        fixture.detectChanges();
    }));

    it('should save the trailer log form', () => {
        const response: { message: string } = { message: '' };
        spyOn(_httpService, 'save').and.returnValue(of(response));
        component.onSave(null); // what should I pass here??
        fixture.detectChanges();
        expect(component.trailerHeading).toEqual('Trailer');
        expect(component.showAllTrailers).toBeFalsy();
        expect(component.trailerList.length).toEqual(0);
    });
});

The error is getting thrown when component.onSave(null) is called as I am passing null.

What should I pass here instead of NULL to make it work properly?


Solution

  • Since you are doing unit testing you are not interested in passing a full instance. In general you are fine with a minimal required mock. This means that you need an object that has methods/props that your method is using. To do so you could alter your test body in the following way.

    const resetFormFn = jasmine.createSpy('resetFormFn')
    
    component.onSave({ resetForm: restFormFn } as any)
    
    // goes the test
    expect(resetFormFn).toHaveBeenCalled()
    

    Here we create a spy - function which can track its calls. We provide an object with one property resetForm which uses the spy. We typecast the object to any as we don't need the whole FormGroupDirective here and don't want TS to complain.

    And lastly we verify our spy has been called.