Search code examples
angularunit-testingjasmineangular-forms

How to unit test validation message in Angular template driven forms


I have a very basic Angular template driven form with a single field which is required. A validation message is displayed if the field is invalid, which is the case when the component is first loaded as the field is required but empty. The code runs as expected when viewed in the application and the validation message is displayed.

When testing the component via a Jasmine unit test the validation message is not picked up and the test fails.

I am confident that the logic to look for the validation message is working, because if I remove the *ngIf directive on the message DIV then the test passes.

I have tried the following:

  • importing the BrowserModule into the test spec
  • running the test within a fakeAsync() block

Template:

<form #form="ngForm">

  <label>First name:</label>

  <input #firstName="ngModel"
    type="text"
    name="firstName"
    [(ngModel)]="firstNameText"
    required />

  <div class="validation-error" *ngIf="firstName.invalid">
      Please enter a valid first name
  </div>
</form>

Component class:

import { Component } from '@angular/core';

@Component({
  selector: 'app-person-form',
  templateUrl: './person-form.component.html'
})
export class PersonFormComponent  {
  public firstNameText: string;
}

Jasmine test spec:

import { ComponentFixture, TestBed, fakeAsync } from '@angular/core/testing';
import { PersonFormComponent } from './person-form.component';
import { FormsModule } from '@angular/forms';
import { DebugElement } from '@angular/core';
import { By } from '@angular/platform-browser';

describe('PersonFormComponent', () => {
  let fixture: ComponentFixture<PersonFormComponent>;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [ FormsModule ],
      declarations: [ PersonFormComponent ]
    });

    fixture = TestBed.createComponent(PersonFormComponent);
    fixture.detectChanges();
  });

  it('should show a validation error if the first name was touched but left empty', () => {
    let firstNameValidationError: DebugElement;

    // try to get a handle to the validation message (should exist as form is invalid):
    firstNameValidationError = fixture.debugElement.query(By.css('.validation-error'));

    // the validation error should be found:
    expect(firstNameValidationError).toBeTruthy();
  });
});

Solution

  • Component initialization should always be done in a async block

    beforeEach(async() => {
        TestBed.configureTestingModule({
          imports: [ FormsModule ],
          declarations: [ PersonFormComponent ]
        });
    
        fixture = TestBed.createComponent(PersonFormComponent);
        fixture.detectChanges();
    });
    

    Also you may need to run change detection once again after the component is initialised

    it('should show a validation error if the first name was touched but left empty', () => {
        let firstNameValidationError: DebugElement;
    
        fixture.detectChanges(); // run change detection
        firstNameValidationError = fixture.debugElement.query(By.css('.validation-error'));
    
        // the validation error should be found:
        expect(firstNameValidationError).toBeTruthy();
    });