TestBedにはdeclarationsではなくimportsを設定する
コンポーネントのテストにおいて、TestBed.configureTestingModule() の declarations を設定するユースケースはそれほど多くない。
Angular CLI の ng generate component コマンドで生成されるspecファイルが次のようなコードをスキャフォールドするため、それをそのまま使わなければならないと勘違いしている開発者も多いが、スキャフォールドはお手本ではない。 code:foo.component.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FooComponent } from './foo.component';
describe('FooComponent', () => {
let component: FooComponent;
let fixture: ComponentFixture<FooComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(FooComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
TestBedにdeclarationsを設定しない
TestBedに declarations を設定してコンポーネントテストをすると面倒なことがいくつかある
対象コンポーネントの子コンポーネントが解決できない
schemas: [NO_ERRORS_SCHEMA] で解消されがち
テンプレートがコンパイルエラーになっていることに気づかずデプロイ前のビルドで発覚することもしばしば
コンポーネントのコンストラクタで注入される依存オブジェクトが提供されていない
imports や providers でセットアップする
specファイル側での imports 忘れ
アプリケーションコードで新しく実装するたびにするたびにテスト側でも同じモジュールを追加する
アプリケーションコードでは不要になったモジュールをテスト側に残り続けることもしばしば
TestBed.configureTestingModule()の目的
1. テスト対象の依存関係解決
2. テストダブルのセットアップ
同じコンポーネントを二度宣言しない
アプリケーション側でそのコンポーネントを declarations に追加しているモジュールがすでにあるはず
TestBedでそのNgModuleをインポートすればテスト対象の依存関係解決は達成されるはず
テストダブルのセットアップはテストだけの関心なのでそのままで問題ない
コンポーネントのテストが同時にそのNgModuleのテストにもなる
解決されるべき依存関係が解決されないときテストが失敗する
code:foo.component.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FooComponent } from './foo.component';
import { FooModule } from './foo.module';
describe('FooComponent', () => {
let component: FooComponent;
let fixture: ComponentFixture<FooComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: FooModule, // 対象のコンポーネントを提供するモジュールをインポートするだけ }).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(FooComponent); // FooModuleで宣言されているため生成できる
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
NgModuleを分割するモチベーション
すべてをコンポーネントが AppModule で宣言されていると上記のアプローチはとりづらい
対象コンポーネントと関係ない依存オブジェクトが初期化されるオーバーヘッドが無駄
AppModuleにはアプリケーションの初期化に閉じた関心(いわゆる forRoot())が多くあり、ユニットテストで読み込まれるのが不都合な場面もある
再利用可能なNgModuleを分割しておくことはAppModuleの肥大化を防ぐだけでなくユニットテストの書きやすさにもつながる
TestBedにdeclarationsを設定するユースケース
TestHostを使うテストケース
対象コンポーネントを直接テストするのではなくテンプレート経由でテスト用のホストコンポーネントを用意する
この場合 declarations にはテストホストだけがあり、その依存関係を解決するために対象コンポーネントのNgModule を imports に追加すればよい
テストホストを使っても使わなくても imports: [FooModule] は変わらず有用である
ディレクティブのテストも基本的にこの形になる
code:foo.directive.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FooDirective } from './foo.directive';
import { FooModule } from './foo.module';
@Component({
template: <div appFoo></div>
})
class TestHostComponent {}
describe('FooDirective', () => {
let host: TestHostComponent;
let fixture: ComponentFixture<TestHostComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(TestHostComponent); // FooModuleで宣言されているため生成できる
host = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(host).toBeTruthy();
});
});