はじめに
- Angular でコンポーネントとサービスを普通にテストする方法をまとめる。
- 取り扱うケースは以下
- コンポーネントの初期化のテスト
- コンポーネントのクリックイベントのテスト
- 親子関係のあるコンポーネントの初期化のテスト
- コンポーネントからサービスを初期化するテスト
コンポーネントの初期化のテスト
- コンポーネントクラスを定義し、テストモジュール経由で初期化するテスト。
import {Component} from '@angular/core';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
// コンポーネントの定義
@Component({
selector: 'parent-component',
template: '<p>parent component working.</p>'
})
class ParentComponent {
name = 'parent component';
}
// テスト
describe('コンポーネントを初期化するだけのテスト', () => {
// コンポーネントの雛形
let fixture: ComponentFixture<ParentComponent>;
// コンポーネント
let comp: ParentComponent;
// 事前にコンポーネントを作成する
beforeEach(async(() => {
// テスト環境の @NgModule の設定
TestBed.configureTestingModule({
declarations: [ParentComponent]
});
// コンポーネントを作成して変数にセット
TestBed.compileComponents().then(() => {
fixture = TestBed.createComponent(ParentComponent);
comp = fixture.componentInstance;
});
}));
// テストの実行
it('プロパティの初期値が正常に取得できる', () => {
// プロパティのチェック
expect(comp.name).toBe('parent component');
});
});
コンポーネントのクリックイベントのテスト
- クリックイベントをハンドリングするボタンを持ったコンポーネントを定義して、テストモジュール経由で初期化しクリックイベントを発火させるテスト。
- ほぼ前出のものと一緒なので変更点だけ。全文はこちら。
クリックイベントをハンドリングするボタンの定義
- ボタン押下で onClick が実行されるように。
// コンポーネントの定義
@Component({
selector: 'parent-component',
template: '<button (click)="onClick()">btn</button>'
})
class ParentComponent {
name = 'parent component';
onClick() {
this.name = 'clicked';
}
}
クリックイベントの発火のテスト
// テストの実行
it('クリックイベントを実行してメソッドが実行されている', fakeAsync(() => {
// onClick メソッドを監視
spyOn(comp, 'onClick');
// ボタンを取得してクリックイベントを送信
const btn = fixture.debugElement.query(By.css('button'));
btn.triggerEventHandler('click', null);
// fakeAsync 環境で非同期イベント待ちをシミュレート
tick();
// 更新を検知
fixture.detectChanges();
// onClick メソッドが実行されたことの確認
expect(comp.onClick).toHaveBeenCalled();
}));
親子関係のあるコンポーネントの初期化のテスト
- 親子関係のあるコンポーネントを定義して、テストモジュール経由での初期化後に子コンポーネントを取得してテストする。
- こちらもほぼ前出のものと同じなので要点のみで全文はこちら。
親子関係のあるコンポーネント定義
// コンポーネントの定義
@Component({
selector: 'parent-component',
template: '<p>parent component working.</p> <child-component></child-component>'
})
class ParentComponent {
name = 'parent component';
}
// 子コンポーネントの定義
@Component({
selector: 'child-component',
template: '<p>child component working</p>'
})
class ChildComponent {
name = 'child component';
}
子コンポーネントの取得とテスト
// テストの実行
it('子コンポーネントを取得してプロパティが取得できることをテストする', () => {
// CSS で子コンポーネントの HTML 要素取得
const el = fixture.debugElement.query(By.css('child-component'));
// HTML 要素からコンポーネントを取得
const child = el.componentInstance;
// プロパティのチェック
expect(comp.name).toBe('parent component');
expect(child.name).toBe('child component');
});
コンポーネントからサービスを初期化するテスト
- サービスだけならただ new してテストすればいいがコンポーネントとの連携をテストしたいなどの場合。
- コンポーネントとサービスを定義し、テストモジュール経由で初期化するテスト。
- これも要点のみで。全文はこちら。
サービスとコンポーネントの定義
// サービスの定義
@Injectable()
class TestService {
name = 'test service';
}
// 子コンポーネントの定義
@Component({
selector: 'child-component',
template: '<p>child component working</p>'
})
class ChildComponent {
name = 'child component';
constructor(public testService: TestService) { } // DI でサービスを初期化する
}
サービスの DI 設定
- テストモジュールの設定でサービスが DI されるようにする。
TestBed.configureTestingModule({
declarations: [ParentComponent, ChildComponent],
providers: [TestService], // TestService が DI されるように設定
});
サービスのテスト
- サービスがコンポーネント経由で初期化されているかどうかをテスト
expect(child.testService.name).toBe('test service');
Failed: Can't resolve all parameters for ChildComponent
-
TestService
をChildComponent
よりも後に定義するとFailed: Can't resolve all parameters for ChildComponent
というエラーになり正常に実行できなかった。 - コンパイルエラーにはならないが、依存関係のあるクラスの定義と実行を同一ファイルで行う場合は、被依存クラスを依存クラスよりも前の行で定義しないと実行時エラーになる様子。