Angular-Karma 単体テストのNullInjectorErrorなどの対応[ionic4]

Angularで単体テストを行う場合、Karmaによるテストを実施する。Serviceクラスのテストは特に問題ないことが多いのだが、PageやComponentのテストはそこでinjectionしているモジュールをTestクラスでもセットしなければいけない。

しかし、モジュールによってこのやり方が様々で、ただimportすれば良いもの、mockをセットする必要があるものなどややこしいので、情報をまとめる。

providersにセット

以下のようなエラーが出た時。

NullInjectorError: StaticInjectorError(DynamicTestModule)[AppVersion]:

app.module.tsやテスト対象のmodule.tsの"providers"にセットされているものの場合は、TestingModuleでも"providers"でセットすれば大体解決する。

解決策

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ TestPage ],
      providers: [AppVersion]
    })
    .compileComponents();
  }));

RouterTestingModule

以下のエラーが出た時。

NullInjectorError: StaticInjectorError(DynamicTestModule)[Router -> Location]: 

解決策

PageClassなどでNavControllerをインジェクションしているときは以下のように対応。

  import {RouterTestingModule} from "@angular/router/testing";
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ TestPage ],
      imports: [
        RouterTestingModule
      ]
    })
    .compileComponents();
  }));

TranslateTestingModule

以下のエラーが出た時。

NullInjectorError: StaticInjectorError(DynamicTestModule)[TranslateService]:

解決策

以下のようなMockファイルを自作する。
translate-testing.module

import { Injectable, NgModule, Pipe, PipeTransform } from '@angular/core';
import { TranslateModule, TranslatePipe, TranslateService } from '@ngx-translate/core';
import { Observable, of } from 'rxjs';

const translations: any = {};

@Pipe({
  name: 'translate'
})
export class TranslatePipeMock implements PipeTransform {
  public name = 'translate';

  public transform(query: string, ...args: any[]): any {
    return query;
  }
}

@Injectable()
export class TranslateServiceStub {
  public get(key: T): Observable {
    return of(key);
  }

  public addLangs(langs: string[]) { }

  public setDefaultLang(lang: string) { }

  public getBrowserLang(): string {
    return 'en';
  }

  public use(lang: string) { }
}

@NgModule({
  declarations: [
    TranslatePipeMock
  ],
  providers: [
    {
      provide: TranslateService,
      useClass: TranslateServiceStub
    },
    {
      provide: TranslatePipe,
      useClass: TranslatePipeMock
    },
  ],
  imports: [
    TranslateModule.forRoot({})
  ],
  exports: [
    TranslatePipeMock,
    TranslateModule
  ]
})
export class TranslateTestingModule { }

この自作MockをTestingModuleのimportsにセット。

  import {TranslateTestingModule} from "@/your-path/translate-testing.module";
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ TestPage ],
      imports: [
        TranslateTestingModule
      ],
    })
    .compileComponents();
  }));

@NgModule.entryComponentsエラー

テスト対象moduleで他のコンポーネントをloadしている場合で、以下のエラーが出た時。

Error: No component factory found for YOURComponent. Did you add it to @NgModule.entryComponents?

解決策

  beforeEach(async(() => {
    TestBed.configureTestingModule({
	declarations: [YOURComponent],
    }).overrideModule(BrowserDynamicTestingModule, {
      set: {
        entryComponents: [YOURComponent]
      }
    })
    .compileComponents();
  }));

ModalControllerエラー

テスト対象moduleでModalControllerをインジェクションしている場合。

NullInjectorError: StaticInjectorError(DynamicTestModule)[TestPage -> ModalController]:

解決策

  let modalSpy = jasmine.createSpyObj('Modal', ['present']);
  let modalCtrlSpy = jasmine.createSpyObj('ModalController', ['create']);
  modalCtrlSpy.create.and.callFake(function () {
    return modalSpy;
  });
  beforeEach(async(() => {
    TestBed.configureTestingModule({
	providers: [
        {
          provide: ModalController,
          useValue: modalCtrlSpy
        }
      ]
    }).compileComponents();
  }));

APP_BASE_HREFエラー

Error: No base href set. Please provide a value for the APP_BASE_HREF token or add a base element to the document.

解決策

以下のようにするとエラーを解消できる。

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      providers: [
        {provide: APP_BASE_HREF, useValue: '/'}
      ]
    }).compileComponents();
  }));