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

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read more →

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more