How to test an Angular attribute directives


Got an Angular attribute directive to test?

Here's the complete guide to getting started. In only 5 minutes.

Angular attribute directives.

How do you test them? Where do you start?

How do you test an Angular attribute directive to make sure that it's acting like you built it to behave?

An interesting scene... 🥸

The Angular attribute directive creates an interesting testing scenario.

Why?

Because most, if not all, of the times you test an attribute directive you will always have to check the DOM to make sure the changes are being reflected.

This is unlike writing unit tests for an Angular component where you can test functionality without verifying the DOM.

When testing an Angular attribute directive we usually need to check the DOM to make sure the appearance and behavior of our attribute directive is actually being reflected on the page.

Take a look at this example...

Liking my favorite cats... with an Angular attribute directive.

In one of my demo apps, I want to be able to show a star for some of my favorite cats.

So I decided to create an attribute directive to decide if the star icon should be empty or filled, that way I can see my favorite cats.

Here's what it'll look like.

cat-work

Using the Angular CLI I ran the following command to generate my new attribute directive.

ng generate directive stared

And here's the code.

import { Directive, HostBinding, Input } from '@angular/core';

@Directive({
  selector: '[stared]'
})
export class StaredDirective {

  @Input() stared = true;

  constructor() { }

  @Input("class")
  @HostBinding('class')
  get elementClass(): string {
    if (this.stared) {
      return 'bi-star-fill';
    } else {
      return 'bi-star';
    }
  }
}

So what are we doing with this new attribute directive?

How does it work?

Based on the input property, we'll add a CSS class to our HTML element that will determine if it's a full or empty star.

Here's how we use it in our component.

<i [stared]="true"></i>

Cool, eh? 😺

But how do we test it?

How do we test a directive?

So... to test our new directive the first step is to create a bogus test component that uses our new attribute directive.

Like this.

import { Component } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { StaredDirective } from './stared.directive';

@Component({
  template: `<i [stared]="true"></i>`
})
class TestComponent { }

describe('StaredDirective', () => {

  let fixture: ComponentFixture<TestComponent>;

  beforeEach(() => {
    fixture = TestBed.configureTestingModule({
      declarations: [TestComponent, StaredDirective]
    })
    .createComponent(TestComponent);

    fixture.detectChanges();
  })
});

And then we'll write a test case to check that the correct class has been added.

it('should be stared', () => {
    var element: HTMLElement = fixture.nativeElement.querySelector("i"); // query the proper element inside our test component's template.
    expect(element.className).toEqual("bi-star-fill");
});

You follow??? 🙂

Here's the full test example.

import { Component } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { StaredDirective } from './stared.directive';

@Component({
  template: `<i [stared]="true"></i>`
})
class TestComponent { }

describe('StaredDirective', () => {

  let fixture: ComponentFixture<TestComponent>;

  beforeEach(() => {
    fixture = TestBed.configureTestingModule({
      declarations: [TestComponent, StaredDirective]
    })
    .createComponent(TestComponent);

    fixture.detectChanges();
  })

  it('should create an instance', () => {
    const directive = new StaredDirective();
    expect(directive).toBeTruthy();
  });

  it('should be stared', () => {
    var element: HTMLElement = fixture.nativeElement.querySelector("i");
    expect(element.className).toEqual("bi-star-fill");
  });
});

And that, my friend, is how to test an attribute in Angular. And verify that the behavior and appearance of our attribute is always working like we expect it to work.

Other random directive test examples you can copy and learn from.

Testing a highlight directive (source)

beforeEach(() => {
  fixture = TestBed.configureTestingModule({
    declarations: [ HighlightDirective, TestComponent ]
  })
  .createComponent(TestComponent);

  fixture.detectChanges();
  des = fixture.debugElement.queryAll(By.directive(HighlightDirective));
  bareH2 = fixture.debugElement.query(By.css('h2:not([highlight])'));
});

it('should have three highlighted elements', () => {
  expect(des.length).toBe(3);
});

it('should color 1st <h2> background "yellow"', () => {
  const bgColor = des[0].nativeElement.style.backgroundColor;
  expect(bgColor).toBe('yellow');
});

it('should color 2nd <h2> background w/ default color', () => {
  const dir = des[1].injector.get(HighlightDirective) as HighlightDirective;
  const bgColor = des[1].nativeElement.style.backgroundColor;
  expect(bgColor).toBe(dir.defaultColor);
});

it('bare <h2> should not have a customProperty', () => {
  expect(bareH2.properties.customProperty).toBeUndefined();
});

Testing a highlight hover directive (source)

it('hovering over input', () => {
  inputEl.triggerEventHandler('mouseover', null); (1)
  fixture.detectChanges();
  expect(inputEl.nativeElement.style.backgroundColor).toBe('blue'); (2)

  inputEl.triggerEventHandler('mouseout', null);
  fixture.detectChanges();
  console.log(inputEl.nativeElement.style.backgroundColor);
  expect(inputEl.nativeElement.style.backgroundColor).toBe('inherit');
});

Test a hover focus directive

import { TestBed, ComponentFixture } from '@angular/core/testing';
import { Component, DebugElement } from "@angular/core";
import { By } from "@angular/platform-browser";
import { HoverFocusDirective } from './hoverfocus.directive';

@Component({
    template: `<input type="text" hoverfocus>`
})
class TestHoverFocusComponent {
}

describe('Directive: HoverFocus', () => {

    let component: TestHoverFocusComponent;
    let fixture: ComponentFixture<TestHoverFocusComponent>;
    let inputEl: DebugElement;

    beforeEach(() => {
        TestBed.configureTestingModule({
            declarations: [TestHoverFocusComponent, HoverFocusDirective]
        });
        fixture = TestBed.createComponent(TestHoverFocusComponent);
        component = fixture.componentInstance;
        inputEl = fixture.debugElement.query(By.css('input'));
    });

    it('hovering over input', () => {
        inputEl.triggerEventHandler('mouseover', null);
        fixture.detectChanges();
        expect(inputEl.nativeElement.style.backgroundColor).toBe('blue');

        inputEl.triggerEventHandler('mouseout', null);
        fixture.detectChanges();
        expect(inputEl.nativeElement.style.backgroundColor).toBe('inherit');
    });
});

And then what?

Of course, attribute directives aren't the only thing you should be testing.

There are also components that need to be tested. As well as services and pipes and a slew of other pieces that need attention.

Questions? Comments? Don't hesitate to reach out.

signature

Angular Consultant