Cover image for Angular2 Unit Testing - 基本編
angularangular2karmajasmineunit test

Angular2 Unit Testing - 基本編

March 15, 2016

6 min read

mitsuruogMitsuru Ogawa

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を付けることが多いです。

例)
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とテストするモジュールを読み込む必要があります。

import {
  describe,
  it,
  inject,
  injectAsync,
  expect,
  beforeEach,
  beforeEachProviders,
  TestComponentBuilder
} from 'angular2/testing';

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

...つづく

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

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

describe("Test: なにかのServiceのテスト", () => {
  describe("Test: 正常系", () => {});

  describe("Test: 異常系", () => {});
});

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

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

describe("Test: 正常系", () => {
  it("ステータスコードが200であること", () => {});
});

テストを検証する(expect)

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

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

NgMatchers - ts

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

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

app.component.spec.ts

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

beforeEachProviders

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

// Angular1($window.locationを上書きする例)
beforeEach(function () {
  module(function ($provide) {
    $window = {
      location: { href: null },
    };
    $provide.value("$window", $window);
  });
});
// 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 を柔軟に選択することができます。

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

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

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

まとめ

基本編は以上です。

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

PR

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

mitsuruog/angular2-minimum-starter: Minimum starter kit for angular2