突然ですが、Angularでユニットテストしてますか?
CLIでコンポーネントを作ると、「xxxxxx.component.spec.ts」が作成されますが、
「そのまま何も変えない(ユニットテスト使わない) or 邪魔だから消す」と、なっているかもしれないですね・・・・
まあ、Angularのユニットテスト、割と難しいので、気持ちは分かります。
という事で、Angularでユニットテストを書いていく上で、良く出るエラーの対処方法を記載していこうと思います。
今回はDI関連のエラーです。
NullInjectorError: R3InjectorError(DynamicTestModule)[Router -> Router]: NullInjectorError: No provider for Router!みたいなエラーが出てしまった場合の対応ですね。
メッセージそのままですが、Router絡みのエラーであることが分かります。
Angularでは、DI可能なクラスであれば、コンポーネントのコンストラクタに定義する事で、DIして使用可能になります。
[hoge.component.ts]
import { Router } from '@angular/router'; // (中略) constructor(private router: Router) {} // (中略) onClickBack(): void { // 「戻る」ボタンを押したら、「Top画面」に遷移する this.router.navigateByUrl('/hoge/top'); }
・・・つまりテストクラス側でもDIをする必要があるって事ですね
[hoge.component.spec.ts]
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { HogeComponent } from './hoge.component'; describe('HogeComponent', () => { let component: HogeComponent; let fixture: ComponentFixture<HogeComponent>; beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [HogeComponent], imports: [ RouterTestingModule, ], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(HogeComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); });って感じになります。
デフォルト(CLIによって作成された状態)から、
import { RouterTestingModule } from '@angular/router/testing';と、
imports: [ RouterTestingModule, ],を追加しています。
先ほどのエラー(NullInjectorError)を無くすだけであれば、実は上記でOKです。
ただ、当然テストケースは作らないといけないので、ついでにやってみましょう
[hoge.component.spec.ts]
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; // Routerをimport import { Router } from '@angular/router'; import { HogeComponent } from './hoge.component'; describe('HogeComponent', () => { let component: HogeComponent; let fixture: ComponentFixture<HogeComponent>; // 全テストケースで利用出来るように、ここで定義 let router: Router; beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [HogeComponent], imports: [ RouterTestingModule, ], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(HogeComponent); // DI後のインスタンスを取得 router = TestBed.inject(Router); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); describe('ボタンアクションテスト', () => { it('戻るボタン押下時 ホーム画面へ遷移する事', () => { // navigateByUrl処理のモックを作成 const navigateByUrlSpy = spyOn(router, 'navigateByUrl'); // テスト対象のメソッドを呼び出す component.onClickBack(); // navigateByUrlが、'/hoge/top'を引数として呼ばれているか確認 expect(navigateByUrlSpy).toHaveBeenCalledWith('/hoge/top'); }); }); });
ポイントとしては、DIしたクラスをテスト側で使用する為には、
router = TestBed.inject(Router);が必要、というところです。
実際のテストの部分は、プログラム内のコメントを参照して頂ければと思いますが、流れ的に、
① spyOn で、navigateByUrl のモックを作成
② コンポーネント側の onClickBack を呼び出す
③ expect の toHaveBeenCalledWith で、指定の引数で navigateByUrl が呼ばれたかを判定
となっています。
ちょっと長くなってしまったので、その他のDI関連のテストエラーは別途記事にしようと思います。
Angular