Unit Testing a Custom Angular Title Strategy

nordyj

Jamie Nordmeyer

Posted on March 5, 2023

Unit Testing a Custom Angular Title Strategy

I’ve been trying to figure this out for a while but couldn’t find any good resources online on how to unit test a custom Angular Title Strategy (the TitleStrategy abstract base class was introduced in Angular 14). As a result, I left it largely untested in my project for a while. Fortunately, the implementation is simple, but I still felt “naked” not having it tested. I finally managed to find the time to dig through the Angular source code and found the documentation on how the Angular team themselves are doing this. You can the Github link here: angular/page_title_strategy_spec.ts at c0c7efaf7c8a53c1a6f137aac960757cc804f263 · angular/angular (github.com)

Again, the implementation for my title strategy is extremely simple:

import { Injectable } from "@angular/core";
import { Title } from "@angular/platform-browser";
import { RouterStateSnapshot, TitleStrategy } from "@angular/router";

@Injectable()
export class AppTitleStrategy extends TitleStrategy {
  constructor(private readonly title: Title) {
    super();
  }

  updateTitle(routerState: RouterStateSnapshot): void {
    const title = this.buildTitle(routerState);
    this.title.setTitle(`My Application${ !!title ? ' - ' + title : '' }`);
  }
}
Enter fullscreen mode Exit fullscreen mode

All this does is set the page title to “My Application” (name changed to protect my work…), or to “My Application – Route Title” as the user navigates around the application. Simple. But how do I test it? Trying to mock up the RouterStateSnapshot wasn’t getting me anywhere.

After digging through the Github site for Angular itself, I realized that the trick was to NOT try mocking the RouterStateSnapshop, but instead, get a reference to the injected Router and Document instances, emulate a routing event, then check the document title. To do this, you need to pass the result of both the providerLocationMocks and providerRouter methods from the @angular/common/testing and @angular/router modules respectively during the configureTestingModulecall, then in each test, call router.resetConfig to emulate a route event. Here is my testing implementation:

import { fakeAsync, TestBed, tick } from '@angular/core/testing';
import { provideLocationMocks } from '@angular/common/testing';
import { provideRouter, Router, TitleStrategy } from '@angular/router';
import { DOCUMENT } from '@angular/common';
import { Component } from '@angular/core';
import { AppTitleStrategy } from './app-title-strategy.service';

describe('AppTitleStrategyService', () => {
  let router: Router;
  let document: Document;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        provideLocationMocks(),
        provideRouter([]),
        {
          provide: TitleStrategy,
          useClass: AppTitleStrategy,
        },
      ],
    });

    router = TestBed.inject(Router);
    document = TestBed.inject(DOCUMENT);
  });

  it('should set page title correctly when title is not provided', fakeAsync(() => {
    router.resetConfig([
      {
        path: 'home',
        component: TestComponent
      }
    ]);

    router.navigateByUrl('/home');
    tick();
    expect(document.title).toBe('My Application');
  }));

  it('should set page title correctly when title is empty string', fakeAsync(() => {
    router.resetConfig([
      {
        path: 'home',
        title: '',
        component: TestComponent
      }
    ]);

    router.navigateByUrl('/home');
    tick();
    expect(document.title).toBe('My Application');
  }));

  it('should set page title correctly when title is provided', fakeAsync(() => {
    router.resetConfig([
      {
        path: 'home',
        title: 'Home',
        component: TestComponent
      }
    ]);

    router.navigateByUrl('/home');
    tick();
    expect(document.title).toBe('My Application - Home');
  }));
});

@Component({template: ''})
export class TestComponent {
}

Enter fullscreen mode Exit fullscreen mode
💖 💪 🙅 🚩
nordyj
Jamie Nordmeyer

Posted on March 5, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related