コンポーネントの単体テスト
ロジックだけが含まれるサービスと異なり、コンポーネントはロジックとUIの組み合わせなので、画面の入力や表示を確認したい場合があります。以下では、図1と同じ動作をするサンプル(P003-component)を例に、コンポーネントの単体テストを説明します。
テスト観点と単体テストコード
テスト対象コンポーネント(AppComponent)は、入力内容をサービス(GreetService)に渡してあいさつ文を取得し、画面に表示します。GreetServiceの単体テストは別に行うので、コンポーネントの単体テストでは「入力内容がGreetServiceに正しく渡せていること」と、「GreetServiceから取得したあいさつ文が正しく画面表示されていること」を確認すれば十分です。この観点で、コンポーネントの単体テストを作っていきます。
まず、テスト前に実行されるbeforEach部をリスト7に示します。
beforeEach(() => { // GreetServiceのモックを作成 ...(1) const mockService = jasmine.createSpyObj('GreetService', ['getGreet']); mockService.getGreet.and.callFake( (name: string, date: Date, locale: string) => name + ':' + locale ); // TestBedの設定処理 ...(2) TestBed.configureTestingModule({ declarations: [AppComponent], imports: [FormsModule], providers: [ // GreetServiceをモックに入れ替え ...(3) { provide: GreetService, useValue: mockService } ] }); });
(1)は、GreetServiceのモックを作成する処理です。モック変数mockServiceに対してgetGreet.and.callFakeメソッドを利用すると、getGreetメソッドの処理内容を置換できます。ここでは名前とロケールの引数をつないで返却させるように処理を単純化して、コンポーネントの単体テストを行いやすくします。
(2)で、コンポーネントの動作に必要な設定を行っています。configureTestingModuleメソッドは、app.module.tsの@NgModuleデコレーターと同じ内容を与えて設定できます。ここでは、declarationsにテスト対象コンポーネント(AppComponent)を、importsにはコンポーネントが必要とするフォーム関連モジュールFormsModuleを指定しています。依存性注入の動作を指定するprovidersには、GreetServiceをモックに入れ替えるように記述します(3)。
実際のテストはリスト8のようになります。
it('コンポーネントクラスだけのテスト', () => { const fixture = TestBed.createComponent(AppComponent); // ComponentFixtureを取得 ...(1) const app = fixture.componentInstance; // コンポーネントを取得 ...(2) // コンポーネントのプロパティを設定してクリックメソッドを実行 ...(3) app.name = '太郎'; app.locale = 'ja-JP'; app.onClickGreet(); // 実行結果、画面に表示するプロパティの内容を確認 ...(4) expect(app.greetText).toEqual('太郎:ja-JP'); });
(1)のTestBed.createComponentメソッドでComponentFixtureを取得します。ComponentFixtureは、コンポーネントとUI要素の両方にアクセスできるテスト用クラスです。(2)のように、ComponentFixtureのcomponentInstanceプロパティで、コンポーネントの実体にアクセスできます。
(3)で、画面のテキストボックス内容に対応するプロパティ(name、locale)をコンポーネントに設定後、ボタンクリックに対応したonClickGreetメソッドを実行します。実行後に、あいさつ文の画面表示に対応するプロパティ(greetText)が想定通りの値になっているか確認します(4)。多くの場合、画面要素そのものを参照しなくても、画面要素に対応するプロパティを確認すれば、十分な単体テストができます。
画面要素を直接確認する単体テストコード
とはいえ、やはりプロパティではなく画面要素を直接確認したい場合もあります。画面要素を確認するテストの例をリスト9に示します。
it('画面要素を確認するテスト', () => { (略:途中まではリスト8と同じ) app.onClickGreet(); // 変更検知を実行して画面を更新 ...(1) fixture.detectChanges(); // 画面のHTML要素から文字列を取得して、内容を確認 ...(2) const greetText = fixture.nativeElement.querySelector('#greetText').textContent; expect(greetText).toEqual('あいさつ文:太郎:ja-JP'); });
onClickGreetメソッドを実行後、まず(1)のfixture.detectChangesメソッドで変更検知を模擬します。単体テストの実行時は、画面の変更検知が自動的に行われないので、明示的に変更検知を実行させます。
(2)のfixture.nativeElementプロパティは、コンポーネントの画面に対応するオブジェクトです。querySelectorメソッドでCSSセレクターを指定すると、コンポーネントの画面から特定のHTML要素を取得できます。ここでは「#greetText」と指定して、idに「greetText」と指定された要素を取得します。要素のtextContentプロパティを参照して、画面に表示された内容を変数greetTextに取得して、expectメソッドで内容を確認します。
[参考]Angular公式ページの単体テスト関連ドキュメント
Angularの公式ページでは、PipesやRouterのテスト方法など、単体テストに関するさまざまな内容が説明されています。単体テストについて、より詳細な情報が必要な場合に参照してください。
まとめ
本記事では、Angularのプロジェクトで単体テストを行う方法について説明しました。Webページを構成するコンポーネントやサービスを細かく分割して、それぞれに単体テストを設定すれば、部品単位でコードの品質を確保しながら開発を進めることができます。
次回は、2018年5月にリリースされたAngularのバージョン6について、新機能などを説明していきます。