TypeScript で Angular コンポーネントの単体テスト:Router テストのベストプラクティス
Angular における Router を使用するコンポーネントの単体テスト
テストの目的
単体テストでは、コンポーネントの内部実装のみをテストし、外部要因の影響を受けないようにします。具体的には、以下の点を検証します。
- コンポーネントの入力値に対するコンポーネントの状態変化
- テンプレートのレンダリング
- イベントハンドラーの動作
- Router を使用したナビゲーション機能
テストの手順
Angular には、コンポーネントの単体テストを容易にする TestBed
と ComponentFixture
などのツールが用意されています。
-
テスト対象コンポーネントのモックを作成
-
Router のモックを使用する
-
コンポーネントの状態とテンプレートを検証する
-
ナビゲーション機能を検証する
テスト例
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { MyComponent } from './my-component';
import { MockRouter } from './mock-router';
describe('MyComponent', () => {
let component: MyComponent;
let fixture: ComponentFixture<MyComponent>;
let router: MockRouter;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [MyComponent],
imports: [RouterTestingModule]
});
fixture = TestBed.createComponent(MyComponent);
component = fixture.componentInstance;
router = TestBed.get(MockRouter);
});
it('should navigate to /home when click the home button', () => {
const button = fixture.nativeElement.querySelector('button');
button.click();
expect(router.navigateUrl).toBe('/home');
});
it('should pass id parameter to /detail/:id when click the detail button', () => {
const button = fixture.nativeElement.querySelector('button[id="detail-button"]');
button.click();
expect(router.navigateUrl).toBe('/detail/123');
});
});
注意点
- テスト対象コンポーネントのみをテストし、外部要因の影響を受けないようにすることが重要です。
- モック Router を使用してナビゲーションをシミュレーションすることで、実際の Router に依存せずにテストすることができます。
expect
アサーションを使用して、テスト対象の値が期待通りの値であることを確認します。
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
@Component({
selector: 'app-my-component',
templateUrl: './my-component.html',
styleUrls: ['./my-component.css']
})
export class MyComponent implements OnInit {
constructor(private router: Router) { }
ngOnInit(): void {
}
goToHome(): void {
this.router.navigate(['/home']);
}
goToDetail(id: number): void {
this.router.navigate(['/detail', id]);
}
}
<div>
<button (click)="goToHome()">Home</button>
<button id="detail-button" (click)="goToDetail(123)">Detail</button>
</div>
mock-router.ts
import { Router } from '@angular/router';
export class MockRouter extends Router {
public navigateUrl: string;
navigate(url: string[] | string): Promise<boolean> {
this.navigateUrl = url[0];
return Promise.resolve(true);
}
}
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { MyComponent } from './my-component';
import { MockRouter } from './mock-router';
describe('MyComponent', () => {
let component: MyComponent;
let fixture: ComponentFixture<MyComponent>;
let router: MockRouter;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [MyComponent],
imports: [RouterTestingModule]
});
fixture = TestBed.createComponent(MyComponent);
component = fixture.componentInstance;
router = TestBed.get(MockRouter);
});
it('should navigate to /home when click the home button', () => {
const button = fixture.nativeElement.querySelector('button');
button.click();
expect(router.navigateUrl).toBe('/home');
});
it('should pass id parameter to /detail/:id when click the detail button', () => {
const button = fixture.nativeElement.querySelector('button[id="detail-button"]');
button.click();
expect(router.navigateUrl).toBe('/detail/123');
});
});
MockRouter
クラスを使用して、実際の Router の代わりにモック Router を作成します。TestBed
を使用してコンポーネントのテスト環境をセットアップします。ComponentFixture
を使用してコンポーネントインスタンスにアクセスします。- モック Router を使用してナビゲーションをシミュレーションします。
Angular で Router を使用するコンポーネントを単体テストする方法:代替アプローチ
Router Spy
RouterSpy
は、実際の Router の代わりに使用できるテスト用のモック Router です。TestBed
によって自動的に提供され、以下の機能を提供します。
- ナビゲーションされた URL を記録する
- ナビゲーションパラメータを記録する
- ナビゲーションの成功/失敗を記録する
RouterSpy
を使用するには、以下の手順を行います。
- テスト対象コンポーネントのコンストラクタで
RouterSpy
を注入します。 RouterSpy
のプロパティを使用して、ナビゲーション情報を確認します。
import { ComponentFixture, TestBed, inject } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { MyComponent } from './my-component';
import { Router } from '@angular/router';
describe('MyComponent', () => {
let component: MyComponent;
let fixture: ComponentFixture<MyComponent>;
let router: Router;
let routerSpy: RouterSpy;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [MyComponent],
imports: [RouterTestingModule]
});
fixture = TestBed.createComponent(MyComponent);
component = fixture.componentInstance;
router = TestBed.inject(Router);
routerSpy = TestBed.inject(RouterSpy);
});
it('should navigate to /home when click the home button', () => {
const button = fixture.nativeElement.querySelector('button');
button.click();
expect(routerSpy.navigatedUrl).toBe('/home');
});
it('should pass id parameter to /detail/:id when click the detail button', () => {
const button = fixture.nativeElement.querySelector('button[id="detail-button"]');
button.click();
expect(routerSpy.navigatedUrl).toBe('/detail/123');
expect(routerSpy.navigatedExtras.queryParams['id']).toBe(123);
});
});
Location
Location
サービスは、ブラウザの履歴と現在の URL を管理します。テストでは、Location
サービスを使用して、現在の URL を確認したり、ナビゲーションをシミュレーションしたりすることができます。
Location
のプロパティを使用して、現在の URL を確認したり、ナビゲーションをシミュレーションしたりします。
import { ComponentFixture, TestBed, inject } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { MyComponent } from './my-component';
import { Location, LocationStrategy } from '@angular/common';
describe('MyComponent', () => {
let component: MyComponent;
let fixture: ComponentFixture<MyComponent>;
let location: Location;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [MyComponent],
imports: [RouterTestingModule]
});
fixture = TestBed.createComponent(MyComponent);
component = fixture.componentInstance;
location = TestBed.inject(Location);
});
it('should navigate to /home when click the home button', () => {
const button = fixture.nativeElement.querySelector('button');
button.click();
expect(location.path()).toBe('/home');
});
it('should pass id parameter to /detail/:id when click the detail button', () => {
const button = fixture.nativeElement.querySelector('button[id="detail-button"]');
button.click();
expect(location.path()).toBe('/detail/123');
const queryParams = location.search().split('?')[1].split('&').map(x => x.split('=')[0]);
expect(queryParams[0]).toBe('id');
expect(queryParams[1]).toBe('123');
});
});
NgZone
NgZone
は、Angular アプリケーション内の非同期処理を管理します。テストでは、NgZone
を使用して、非同期処理が完了するのを待ったり、ナビゲーションをシミュレーションしたりすることができます。
angular typescript unit-testing