とんかつ時々あんどーなつ

〜たとえ低空でも飛行していられるように〜

ActivatedRouteStub に代わる RouterTestingHarness について

はじめに

Angular v15.2 で RouterTestingHarness という API が追加された。これはルーテッドコンポーネントをテストするための Test Harness として提供されている。

この API は以下のブログで知ったので参考文献として貼っておく。

What’s new in Angular 15.2? | Ninja Squad

ルーテッドコンポーネントとそのテスト

ルーテッドコンポーネント

ルーテッドコンポーネントはルーティングのパラメータによって振る舞いが変わることがありテストが複雑になることがある。ルーテッドコンポーネントがどういうものかは公式ドキュメントを見るのが早いが、今回イメージしているのは Tour of Heroes の HeroDetailComponent である。

https://angular.io/guide/testing-components-scenarios#routed-components

ルーテッドコンポーネントのテスト

ルーテッドコンポーネントのテストの複雑さはルーティングパラメータの有無に左右される。つまり ActivatedRoute の扱いである。

これも公式ドキュメントに頼るが、ルーテッドコンポーネントのテストをする際には ActivatedRoute のテストダブルとして ActivatedRouteStub を用意すること多かった(と言いつつルーテッドコンポーネントのテストについて誰かと語ったことはないので個人的な経験としてという意味に留める)。

https://angular.io/guide/testing-components-scenarios#activatedroutestub

どういう class を作ればいいかなどはドキュメントに書いてあるので特に問題はないのだが、新しいプロジェクトのたびにテスト用の class を開発者が用意する必要があった。そんなちょっとした手間を解消する API が今回 v15.2 で追加されたというわけである。

RouterTestingHarness

API ドキュメントはすでに用意されている。

https://angular.io/api/router/testing/RouterTestingHarness

まず対象のテストケースの TestBed.configureTestingModule() で routes の情報を設定する必要がある。設定方法は NgModule を使っているか standalone API を使っているかで変わるが、以下のどちらかがあればよい。

// NgModule を使っている場合
imports: [RouterTestingModule.withRoutes(routes)]

// standalone API を使っている場合
providers: [provideRouter(routes)],

routesRoutes 型の配列である。対象のテストケース内でも用意できるが AppRouting または類似の場所で用意している実際の routes をテストでも使うことで path 自体のテストもできるのでこちらを推奨する。

準備ができたら Harness を使っていく。RouterTestingHarness が提供している static メソッドの create() を使ってインスタンス化する。続けてインスタンスメソッドの navigateByUrl() を呼ぶことで component が手に入るので、あとはいつもどおりテストを書くことができる。

import { HttpClientTestingModule } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
import { RouterTestingHarness, RouterTestingModule } from '@angular/router/testing';
import { routes } from '../app-routing.module';
import { HeroDetailComponent } from './hero-detail.component';

describe('HeroDetailComponent', () => {
  let harness: RouterTestingHarness;
  let component: HeroDetailComponent;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [RouterTestingModule.withRoutes(routes), HttpClientTestingModule],
      declarations: [HeroDetailComponent],
    }).compileComponents();

    harness = await RouterTestingHarness.create();
    component = await harness.navigateByUrl('detail/1', HeroDetailComponent);

    harness.detectChanges();
  });

  it('should create the app', () => {
    expect(component).toBeTruthy();
  });
});

これによって ActivatedRouteStub が不要になる。

まとめ

今回は v15.2 で追加された RouterTestingHarness について調べてみた。個人的には単体テストへの関心が高いので、テスト周りの API が充実するのは嬉しい。