4 ways to test button click event handler in Angular unit testing

In this tutorial, You learned unit testing button click event in Angular application.

You can see my previous about Angular button click event example

Let’s define an angular component - ButtonDemoComponent for the button click event In this component, Button is displayed and on clicking the button, the Click event is called and displays the message to the UI element.

<div style="text-align:center">
  <h1>Angular Button Click Event Example</h1>
  <button (click)="clickEvent()">Click Me</button>
  <h2>{{msg}}</h2>
</div>

Typescript component

import { Component, OnInit } from "@angular/core";

@Component({
  selector: "app-button-demo",
  templateUrl: "./button-demo.component.html",
  styleUrls: ["./button-demo.component.scss"],
})
export class ButtonDemoComponent implements OnInit {
  msg: string = "";
  constructor() {}
  ngOnInit(): void {}
  clickEvent() {
    this.msg = "Button is Clicked";
    return this.msg;
  }
}

There are multiple ways we can check events in Angular applications.

Button clicks an event, events are browser-based asynchronous. It can use with or without async and fakeAscyn functions.

First, get the instance of a component in beforeEach of the spec.ts file

  import { TestBed, async, ComponentFixture, fakeAsync, tick } from '@angular/core/testing';
import { By } from '@angular/platform-browser';

import { ButtonDemoComponent } from './button-demo.component';

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

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [ButtonDemoComponent]
    })
      .compileComponents();
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(ButtonDemoComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

Let’s see an example without asynchronous behavior.

Jasmine spyOn click event example in Angular

Jasmine API provides the spyOn method to call methods of a class or components.

It uses to call methods with or without arguments to create a stub of the method.

Syntax

spyOn("instance", "method");

Here instance is a component or a class that we can get using TestBed.createComponent(ButtonDemoComponent) method. We can supply parameters as well as return types for the spyon method.

Here is a spyon example of how it is created for arguments and return type

spyOn(Calculator, "add")
  .withArgs(1, 9)
  .and.returnValue(10)
  .withArgs(0, 1)
  .and.returnValue(1)
  .withArgs(undefined, undefined)
  .and.returnValue(0);

We have created a spy or stub for an add method.

It returns the possible values for executing add method with the Calculator. add() method. Add method does not add real logic, but we are defining what values are returned based on arguments.

Calculator.add(undefined,undefined) returns 0
Calculator.add(1,9) returns 10
Calculator.add(0,1) returns 1

The spyon method is useful on a method when we have dependencies inside a method logic like subscribe and API calls. Let’s see step-by-step calls for buttons even

  • create a stub object using spyon on a given function - clickEvent

  • call actual function component.clickEvent() which not calls function but calls the stub object

  • get the button selector using By.css selector

  • Finally, jasmine matcher toHaveBeenCalled checked against the method is called or not.

it("Button click event using spyon", () => {
  spyOn(component, "clickEvent");
  component.clickEvent();

  fixture.detectChanges();

  let buton = fixture.debugElement
    .query(By.css("button"))
    .nativeElement.click();
  expect(component.clickEvent).toHaveBeenCalled();
});

triggerEventHandler in unit testing angular

triggerEventHandler is a function event available in DebugElement in Angular testing.

It triggers an event by name on a DOM Object and the corresponding event handler calls on the native element.

When testing code, You have to call triggerEventHandler after `detectChanges’ and before assertion.

it("button click event triggerEventHandler ", () => {
  spyOn(component, "clickEvent"); // attach events to component with bining
  let btn = fixture.debugElement.query(By.css("button"));
  btn.triggerEventHandler("click", null);
  fixture.detectChanges();
  expect(component.clickEvent).toHaveBeenCalled();
});

Steps

  • Create a stub object with the `spyon’ function
  • get Button object using `DebugElement’ with By API
  • call the event handler function with `triggerEventHandler’
  • Finally, the Assert event handler function is called or not toHaveBeenCalled()' in theexpect` function

async button click event testing example in Angular

In this approach, using the async function from Angular testing.

This helps to create an asynchronous function, inside it, all asynchronous functions are written and executed.

  • create a stub object clickevent function
  • get Button element and called using click event or triggerevent handler
  • detectChanges executes detection change events for a component.
  • whenStable function is defined in ComponentFixture which gives promise object. It waits for all tasks to be completed in ngZone, without using this, it calls immediately
it("button click event one way", async(() => {
  spyOn(component, "clickEvent");

  let button = fixture.debugElement.nativeElement.querySelector("button");
  button.click(); // you can use     btn.triggerEventHandler('click', null);

  fixture.detectChanges();

  fixture.whenStable().then(() => {
    expect(component.clickEvent).toHaveBeenCalled();
  });
}));

fakeAsync with tick method in Angular

tick method is defined in Angular testing API. It uses with fakeAync only. It waits for time to finish all pending tasks fakeAsync is used to run the test code in an asynchronous test zone

import { fakeAsync, tick } from "@angular/core/testing";

Here is an example using fakeAsync and tick for button click event handler

it("button click event triggerEventHandler ", fakeAsync(() => {
  fixture.detectChanges();
  spyOn(component, "clickEvent"); //method attached to the click.
  let btn = fixture.debugElement.query(By.css("button"));
  btn.triggerEventHandler("click", null);
  tick();
  fixture.detectChanges();
  expect(component.clickEvent).toHaveBeenCalled();
}));

Angular button click event hander complete example

Here is a complete testing code for button click event testing.

import {
  TestBed,
  async,
  ComponentFixture,
  fakeAsync,
  tick,
} from "@angular/core/testing";
import { By } from "@angular/platform-browser";

import { ButtonDemoComponent } from "./button-demo.component";

describe("ButtonDemoComponent", () => {
  let component: ButtonDemoComponent;
  let fixture: ComponentFixture<ButtonDemoComponent>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [ButtonDemoComponent],
    }).compileComponents();
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(ButtonDemoComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

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

  it("button click event one way", async(() => {
    spyOn(component, "clickEvent");

    let button = fixture.debugElement.nativeElement.querySelector("button");
    button.click();
    fixture.detectChanges();

    fixture.whenStable().then(() => {
      expect(component.clickEvent).toHaveBeenCalled();
    });
  }));
  it("button click event triggerEventHandler ", fakeAsync(() => {
    fixture.detectChanges();
    spyOn(component, "clickEvent");
    let btn = fixture.debugElement.query(By.css("button"));
    btn.triggerEventHandler("click", null);
    tick();
    fixture.detectChanges();
    expect(component.clickEvent).toHaveBeenCalled();
  }));
  it("button click event triggerEventHandler ", () => {
    fixture.detectChanges();
    spyOn(component, "clickEvent");
    let btn = fixture.debugElement.query(By.css("button"));
    btn.triggerEventHandler("click", null);
    fixture.detectChanges();
    expect(component.clickEvent).toHaveBeenCalled();
  });
  it("Button click event using spyon", () => {
    spyOn(component, "clickEvent");
    component.clickEvent();

    fixture.detectChanges();

    let buton = fixture.debugElement
      .query(By.css("button"))
      .nativeElement.click();
    expect(component.clickEvent).toHaveBeenCalled();
  });
});

Conclusion

You learned multiple ways to test the event handler for button click events in the Angular unit test

  • spyon
  • triggerEventHandler
  • async and whenstable fixture
  • fakeAync and tick methods