Angular2 Unit Testing - 基本編

Angular2の実装の方法は記事をよく目にする機会が増えたので、テストについての自分が困らないように調べてみたシリーズ。

今回は基本編。

(注意)Angular 2.0.0-beta.9 をベースに話しています。
E2Eテストはprotractorがそのまま利用できると思うので、ここでのテストはユニットテストの話です。

Angular2 Unit Testing

  1. 準備
  2. 基本
  3. Mock, Spyの基本(TBD)
  4. DOMのテスト
  5. XHRのテスト
  6. Componentのテスト(TBD)
  7. Serviceのテスト(TBD)
  8. Pipeのテスト
  9. Directiveのテスト(TBD)
  10. カバレッジ

基本編

Angular1は他のJavaScriptフレームワークと比較して、テスタビリティ(テストのしやすさを)重視したフレームワークでした。

Angular2もテスタビリティを重視したフレームワークとなっており、本体のAPIの中にangular2/testing, angular2/http/testing, angular2/router/testingなどテスト専用のものが組み込まれていることからも伺い知ることができます。

テストフレームワークはJasmineが基本

テストフレームワークはJasmineを利用します。
これは、angular2/testingの中でJasmineのAPIをoverwrapしているためです。
いまのところ、Angular2のテストはJasmineを利用したほうが幸せになれると思います。

テストクラスの構成

簡単なテストクラス(.tsファイル)の構成についてです。
ファイル名は慣例としてテスト対象のファイルに.spec.tsを付けることが多いです。

1
2
3
例)
hero.service.ts
-> hero.service.spec.ts

angular2/testingの中でJasmineのAPIをoverwrapしているため、基本的にはJasmineの書き方と同じです。
テストクラスに含まれる要素は大きく3つです。

  1. import
  2. describe
  3. it
  4. expect

テストで利用するモジュールを読み込む(import)

import部分では、テストで利用するモジュールを読み込みます。
最低限angular2/testingとテストするモジュールを読み込む必要があります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import {
describe,
it,
inject,
injectAsync,
expect,
beforeEach,
beforeEachProviders,
TestComponentBuilder
} from 'angular2/testing';

import {HeroService} from './hero.service';

...つづく

テストケースをグルーピングする(describe)

複数のテストケースをまとめてグルーピングします。
describeはネストできるため、複数のテストケースをグルーピングして見通しを良くすることができます。

1
2
3
4
5
6
7
8
9
10
11
describe('Test: なにかのServiceのテスト', () => {

describe('Test: 正常系', () => {

});

describe('Test: 異常系', () => {

});

});

テストケースを記述する(it)

itには実際のテストケースを記述していきます。itの中にはテストを検証するためにいくつかのexpectが含まれます。

1
2
3
4
5
6
7
describe('Test: 正常系', () => {

it('ステータスコードが200であること', () => {

});

});

テストを検証する(expect)

expectはテストを検証するfunctionです。matcharとも呼ばれます。

expectは用途に応じていくつか種類があり、angular2/testingexpectはJasmineのmatcharをoverwrapしているため、JasmineのAPIはそのまま利用できます。
他にもAngular2独自で拡張しているmatchar(NgMatchers)もあります。詳細については割愛します。

NgMatchers - ts

1
expect(testee).not.toBe(undefined);

全体像はこちらで雰囲気を掴んでください。

app.component.spec.ts

つづいてAngular2ならではの機能について紹介します。

beforeEachProviders

beforeEachProvidersはテストで利用するモジュールをoverrideする仕組みです。
Angular1の$provideを利用したモジュールの上書きや、angular.mock.inject(_moduleName_の気持ち悪いやつ)と同等です。

1
2
3
4
5
6
7
8
9
// Angular1($window.locationを上書きする例)
beforeEach(function() {
module(function($provide) {
$window = {
location: { href: null }
};
$provide.value('$window', $window)
});
});
1
2
3
4
5
6
7
8
// Angular2(MyServiceをMyServiceMockで上書きする例)
describe('', () => {
beforeEachProviders(() => [
provide(MyService, {
useClass: MyServiceMock
})
]);
});

beforeEachProvidersではProviderと呼ばれるInjectorインタフェース(@Injectable指定したもの)を持つものや、@Componentを設定します。(要確認)

beforeEachProviders - ts

inject, injectAsync

inject, injectAsyncはbeforeEachProvidersで読み込んだProviderをitdescribeの中にDIします。
Angular1でのinjectと同等のものです。

Angular2でも@Injectableを利用した強力なDI機能を持っており、異なるitのコンテキストの中で利用するProviderを柔軟に選択することができます。

1
2
3
4
5
// Angular1
it('should provide a version', inject(function(mode, version) {
expect(version).toEqual('v1.0.1');
expect(mode).toEqual('app');
}));
1
2
3
4
5
// Angular2
it('should provide a version', inject([mode, version], (mode: string, version: string) => {
expect(version).toEqual('v1.0.1');
expect(mode).toEqual('app');
});

injectAsyncitdescribeの中が非同期の結果を返す場合に利用します。

1
2
3
4
5
it('...', injectAsync([AClass], (object) => {
return object.doSomething().then(() => {
expect(...);
});
})

まとめ

基本編は以上です。

Angular1と同じ系譜を辿っていることがわかると思いますが、モジュールのoverrideの部分が少し洗練されてきたかなと感じます。

PR

こちらに初学者のためのMinimum starter kitを作成しましたので、ぜひ利用してください。
(もちろんテストもできます!!)

mitsuruog/angular2-minimum-starter: Minimum starter kit for angular2 https://github.com/mitsuruog/angular2-minimum-starter