Search code examples
angularangular-directiveangular-test

Testing a click from an Angular Attribute Directive


I'm having trouble creating a unit test for an Angular Attribute Directive that I've written. The directive is called TrackClickDirective, and I'm trying to test the following.

  • When an element which has this directive attached is clicked, it should call a specific method on the directive.

I suspect it's a problem with my unit test configuration.

Please see my implementation on StackBlitz, where you can see the test running:

StackBltiz: https://stackblitz.com/edit/angular-test-click-on-attribute-directive-with-hostlistener

Here is my unit test code - track-click.directive.spec.ts:

import { Component, ElementRef, DebugElement } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from "@angular/platform-browser";
import { TrackClickDirective } from './track-click.directive';
import { Analytics} from './analytics.service';

class MockAnalytics {
  eventTrack() {}
};
class MockElementRef {
}

@Component({
  template: '<button appTrackClick>Test</button>'
})
export class TestButtonWithoutNameComponent { }

describe('TrackClickDirective', () => {

  let component: TestButtonWithoutNameComponent;
  let fixture: ComponentFixture<TestButtonWithoutNameComponent>;
  let directive: TrackClickDirective;
  let inputEl: DebugElement;
  let spy: any;

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [
        TestButtonWithoutNameComponent
      ],
      providers: [
        TrackClickDirective,
        { provide: Analytics, useClass: MockAnalytics },
        { provide: ElementRef, useClass: MockElementRef }
      ]
    });
    fixture = TestBed.createComponent(TestButtonWithoutNameComponent);
    component = fixture.componentInstance;
    directive = TestBed.get(TrackClickDirective);
    inputEl = fixture.debugElement.query(By.css('button'));
  });

  it('should call the trackClick methoe when the button is clicked', () => {
    spy = spyOn(directive, 'trackClick');
    inputEl.triggerEventHandler('click', null);
    fixture.detectChanges();
    expect(directive.trackClick).toHaveBeenCalled();
  });
});

What am I doing wrong here? When I run the unit test, I get the following:

FAILED TESTS:
  TrackClickDirective
    ✖ should call the trackClick method when the button is clicked
      HeadlessChrome 72.0.3626 (Mac OS X 10.14.0)
    Expected spy trackClick to have been called.

Solution

  • Following the example on Angular's docs.

    The following should do the trick:

    import { Component, ElementRef, DebugElement } from '@angular/core';
    import { ComponentFixture, TestBed } from '@angular/core/testing';
    import { By } from "@angular/platform-browser";
    import { TrackClickDirective } from './track-click.directive';
    import { Analytics} from './analytics.service';
    
    class MockAnalytics {
      eventTrack() {}
    };
    class MockElementRef {
    }
    
    @Component({
      template: '<button appTrackClick>Test</button>'
    })
    export class TestButtonWithoutNameComponent { }
    
    describe('TrackClickDirective', () => {
    
      let component: TestButtonWithoutNameComponent;
      let fixture: ComponentFixture<TestButtonWithoutNameComponent>;
      let directive: TrackClickDirective;
      let inputEl: DebugElement;
      let spy: any;
    
      beforeEach(() => {
        TestBed.configureTestingModule({
          declarations: [
            TestButtonWithoutNameComponent,
            TrackClickDirective
          ],
          providers: [
            TrackClickDirective,
            { provide: Analytics, useClass: MockAnalytics },
            { provide: ElementRef, useClass: MockElementRef }
          ]
        });
        fixture = TestBed.createComponent(TestButtonWithoutNameComponent);
        component = fixture.componentInstance;
        directive = TestBed.get(TrackClickDirective);
        inputEl = fixture.debugElement.query(By.css('button'));
      });
    
      it('should call the trackClick method when the button is clicked', () => {
        directive = fixture.debugElement.query(By.directive(TrackClickDirective)).injector.get(TrackClickDirective) as TrackClickDirective;
        spy = spyOn(directive, 'trackClick');
        inputEl.triggerEventHandler('click', null);
        fixture.detectChanges();
        expect(directive.trackClick).toHaveBeenCalled();
      });
    });
    

    The key is in this line:

    fixture.debugElement.query(By.directive(TrackClickDirective)).injector.get(TrackClickDirective) as TrackClickDirective;

    While very verbose, it allows you to get the actual instance of your directive that was created and is used in the component which in turns allows you to spy on its methods.

    Updated StackBlitz example: https://stackblitz.com/edit/angular-test-click-on-attribute-directive-with-hostlistener