1

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();
  });
});
2
  • Have you tried setting a value to firstName and then running fixture.detectChanges to update your bindings? Commented Feb 5, 2019 at 15:22
  • Yes, however this on its own did not resolve the issue. I have posted the solution below, essentially I needed to perform the component initialisation inside an async() block and run an additional fixture.detectChanges. Thanks for taking the time to reply to my question, much appreciated. Commented Feb 5, 2019 at 17:00

3 Answers 3

1

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();
});
Sign up to request clarification or add additional context in comments.

1 Comment

Hi @amit-chigadani, thanks for taking the time to answer my question, your solution has solved the problem! I will post the revised spec so that others may benefit. I'm still trying to figure out why the additional fixture.detectChanges() makes such a difference, as it is the last command in the before each and all we are doing in the test is declaring a variable before calling running change detection again.
1

The complete revised test is as follows, thanks to Amit Chigadani for solving this:

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(async() => {
    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;
    fixture.detectChanges(); // run change detection

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

Comments

0

Add these lines at the beginning of the test

 let component = fixture.componentInstance;
    component.firstNameText = '';
    fixture.detectChanges();

and after this, set some name to the firstNameText as below

component.firstNameText = 'test';

At this point, you should expect the error to be falsy.

1 Comment

Hi Dileepkumar, thanks for taking the time to answer my question, I tried your suggestions but the first test continued to fail (the 'truthy' test). Amit pointed out that I should do the component initialisation in an async block and that resolved the issue.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.