Unit Testing a Custom Angular Title Strategy
I’d been trying to figure this out for a while, but couldn’t find good resources online for unit testing 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 covered by tests. I finally found time to dig through the Angular source code and found documentation for how the Angular team tests this themselves. Here’s the GitHub link: 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 : '' }`);
}
}
All this does is set the page title to “My Application” (name changed to protect my work…), or “My Application - Route Title” as the user navigates around the application. Simple. But how do I test it? Trying to mock up RouterStateSnapshot wasn’t getting me anywhere.
After digging through Angular’s GitHub repo, I realized the trick was to NOT mock RouterStateSnapshot. Instead, get a reference to the injected Router and Document instances, emulate a routing event, then check the document title. To do this, pass the results of both the provideLocationMocks and provideRouter methods from @angular/common/testing and @angular/router respectively during the configureTestingModule call. 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 {
}