前回に引き続き、Angularでユニットテスト作成時のエラー対応です。
今回もDI関連のエラーです。
NullInjectorError: R3InjectorError(DynamicTestModule)[Store -> Store]: NullInjectorError: No provider for Store!上記のエラーが出てしまった場合の対応です。
これは、NgRxのStoreを使用している場合に出てくるエラーとなります。
NgRxの公式サイトを見ると、コンポーネント側のテスト記載のサンプルがあるので、詳細はそちらを見るとして、とりあえず、エラーを無くしてみます。
まずは、コンポーネント側のロジックを確認してみましょう。
例として、よくある「入力画面(から確認画面)」を挙げてみます。
「入力画面で、入力した値をSubmitによって、Storeに保存し、確認画面へ引き継ぐ」って感じですね。
[input.component.ts]
import { Router } from '@angular/router'; import { Store } from '@ngrx/store'; import * as fromInput from '../../store/reducers'; import * as InputActions from '../../store/actions/input.actions'; import { InputViewSaveModel } from '../../models/input'; // (中略) constructor( private router: Router, public store: Store<fromInput.State>, ) {} // (中略) onSubmit(inputData: InputViewSaveModel): void { // 入力情報をStoreに格納して(=引き継ぎ情報として)確認画面へ遷移 this.store.dispatch(InputActions.setRegisterPostData({ data: inputData })); this.router.navigateByUrl('/koma/confirm'); }
ここではNgRxの仕様については触れませんので、NgRxを知っている前提で進めます。
それと、前回やった「Router」もありますので、一緒にエラーが出ないように対応してみます。
まずは必要モジュールのインポートと、DIをします。
(Angularでは、DI可能なクラスであれば、コンポーネントのコンストラクタに定義する事で、DIして使用可能になります。)
[input.component.spec.ts]
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { provideMockStore } from '@ngrx/store/testing'; import { InputComponent } from './input.component'; describe('InputComponent', () => { let component: InputComponent; let fixture: ComponentFixture<InputComponent>; beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [InputComponent], providers: [provideMockStore()], imports: [RouterTestingModule], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(InputComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); });と言う感じになります。
Storeに関する部分は、
import { provideMockStore } from '@ngrx/store/testing';と、
providers: [provideMockStore()],を追加しています。
先ほどのエラー(NullInjectorError)を無くすだけであれば、実は上記でOKです。
ただ、当然テストケースは作らないといけないので、ついでにやってみましょう
[input.component.spec.ts]
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { Router } from '@angular/router'; import { provideMockStore, MockStore } from '@ngrx/store/testing'; import * as fromInput from '../../store/reducers'; import * as InputActions from '../../store/actions/input.actions'; import { InputViewSaveModel } from '../../models/input'; import { InputComponent } from './input.component'; describe('InputComponent', () => { let component: InputComponent; let fixture: ComponentFixture<InputComponent>; // 全テストケースで利用出来るように、ここで定義 let router: Router; let store: MockStore<fromInput.State>; beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [InputComponent], providers: [provideMockStore()], imports: [RouterTestingModule], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(InputComponent); // DI後のインスタンスを取得 router = TestBed.inject(Router); store = TestBed.inject(MockStore); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); describe('ボタンアクションテスト', () => { it('確認ボタン押下時 Storeにデータを保存し、確認画面へ遷移する事', () => { // 処理のモックを作成 const dispatchSpy = spyOn(store, 'dispatch'); const navigateByUrlSpy = spyOn(router, 'navigateByUrl'); // 画面から入力された値を再現 const formModel: InputViewSaveModel = { id: '111', name: 'koma-chan', address: 'tokyo', }; // 実行されるアクションを定義(画面からの入力値をStoreへ保存するアクション) const expectedAction = InputActions.setRegisterPostData({ data: formModel }); // テスト対象のメソッドを呼び出す component.onSubmit(formModel); // Storeのdispatchが、アクションを引数として呼ばれているか確認 expect(dispatchSpy).toHaveBeenCalledWith(expectedAction); // navigateByUrlが、'/koma/confirm'を引数として呼ばれているか確認 expect(navigateByUrlSpy).toHaveBeenCalledWith('/koma/confirm'); }); }); });
ポイントとしては、DIしたクラスをテスト側で使用する為には、
store = TestBed.inject(MockStore);が必要、というところです。
実際のテストの部分は、プログラム内のコメントを参照して頂ければと思いますが、流れ的に、
① spyOn で、Store の dispatch に対するモックを作成
② 画面の入力値を Store に保存するアクションを定義
③ コンポーネント側の onSubmit を呼び出す
③ expect の toHaveBeenCalledWith で、指定の引数(アクション)で dispatch が呼ばれたかを判定
となっています。
ちょっと長くなってしまったので、その他のStore関連のテストエラーは別途記事にしようと思います。
Angular
0 件のコメント:
コメントを投稿