DEV Community

Cover image for Unit Testing a Custom Angular Title Strategy
Jamie Nordmeyer
Jamie Nordmeyer

Posted on • Originally published at jamienordmeyer.net on

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

Top comments (0)