突然ですが、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
0 件のコメント:
コメントを投稿