【Angular6】Jasmine + Karma で単体テストを書いてみた

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

どうも、福岡のmeです。
今回はAngular6で実装したSPAの単体テストをJasmine + Karma を使って書いてみたのでまとめました。

今回やること

Jasmineというテストフレームワークを用いて記述したテストケースを
Karmaというタスクランナーでコードが書き換わるたびに実行するようにします。

サンプルプロジェクトの作成

Angular CLIから新規プロジェクトを作成します。

ng new 

今回使用したAngular環境は以下の通りです。

ng -v

     _                      _                 ____ _     ___
    / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
   / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
  / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
 /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                |___/


Angular CLI: 6.0.8
Node: 8.11.2
OS: darwin x64
Angular: 6.1.9
... animations, common, compiler, compiler-cli, core, forms
... http, language-service, platform-browser
... platform-browser-dynamic, router

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.6.8
@angular-devkit/build-angular     0.6.8
@angular-devkit/build-optimizer   0.6.8
@angular-devkit/core              0.6.8
@angular-devkit/schematics        0.6.8
@angular/cli                      6.0.8
@ngtools/webpack                  6.0.8
@schematics/angular               0.6.8
@schematics/update                0.6.8
rxjs                              6.3.3
typescript                        2.7.2
webpack                           4.8.3

テストを走らせてみる

Angular CLIを用いてプロジェクトを作成した場合、ユニットテストに必要なモジュール等は全てインストールしてくれます。 以下のコマンドでデフォルトのテストを走らせてみましょう。

ng test

ng test コマンドでプロジェクトがwatchモードでビルドされ、テストが走ります。
テスト結果はターミナルもしくは立ち上がったブラウザページから確認することができます。
結果はこうなりました。 なぜか最後のテストにFailしていますね・・

どんなテストが実行されたのか見てみましょう

import { TestBed, async } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [
        RouterTestingModule
      ],
      declarations: [
        AppComponent
      ],
    }).compileComponents();
  }));
  it('should create the app', async(() => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.debugElement.componentInstance;
    expect(app).toBeTruthy();
  }));
  it(`should have as title 'app'`, async(() => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.debugElement.componentInstance;
    expect(app.title).toEqual('app');
  }));
  it('should render title in a h1 tag', async(() => {
    const fixture = TestBed.createComponent(AppComponent);
    fixture.detectChanges();
    const compiled = fixture.debugElement.nativeElement;
    expect(compiled.querySelector('h1').textContent).toContain('Welcome to UnitTestDemo!');
  }));
});

最後のテストを見ると、どうやらh2ヘッダが'Welcome to UnitTestDemo!'という内容を含んでいないよ、 という理由のようです。 試しにhtmlファイルの{{title}} となっているところを’UnitTestDemo’に変えてセーブしてみます。

  <h1>
    Welcome to UnitTestDemo!
  </h1>

変更を保存すると再度テストが走り、以下の結果になりました。

Jasmineでテストを書いてみる

テストコードは.spec.tsで終わる名称のファイルに記述する必要があります。
CLIからコンポーネントを作成する際には.component.tsファイルと対になる形で.spec.tsファイルも自動生成されます。

ここではapp.component.tsファイルに新しい変数を作成し、その値が正しいものか見るテストを書きます。

describeメソッドでTest Suitを定義し、この中にユニットテストを記述してゆきます。 メソッドの一つ目の引数にテストしたいコンポーネント名を定義します。

describe('AppComponent', () => {
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [
        RouterTestingModule
      ],
      declarations: [
        AppComponent
      ],
    }).compileComponents();
  }));

});

新たな変数”greetings”を追加し、その値が”Hello World!”かどうかをテストしてみましょう。

テストケースはitキーワードから始まり、初めのパラメータで何をテストするのかを明記します。
Jasmineを使う利点の一つとして、人間が読める形でテストケースを表記できる点にあります。

export class AppComponent {
  title = 'app';
  greetings = 'Hello World!';
}
it('greetings は”Hello World!”であること', async(() => {
    const fixture = TestBed.createComponent(AppComponent);
    fixture.detectChanges();
    const app = fixture.debugElement.componentInstance;
    expect(app.greetings).toEqual('Hello World!');
  }));

expectに期待値を記述

expectメソッドで期待する返り値を記述します。 例ではapp.greetingsの期待値を'Hello World!'としています。

expect(app.greetings).toEqual('Hello World!');

よく使われるものをいくつか挙げてみます。

 toEqual() // 期待値との完全一致をチェックする
 toContains() // 期待値が含まれるかをチェックする
 toBeTrusty() // 返り値がTrueかどうかをチェックする
 expect(a.foo).toBeDefined() // 返り値がundefinedじゃないかチェックする
 expect(a.foo).toBeUndefined() // 返り値がundefinedかをチェックする
 expect(a.foo).toBeNull() // 返り値がnullかどうかをチェックする

参考サイト
Jasmine Cheat Sheet

特定のテストのみを走らせたい場合

disable

itの前にxを記述することでそのテストの実行をスキップすることができます。

xit('greetings は”Hello World!”であること', async(() => {
  const fixture = TestBed.createComponent(AppComponent);
  fixture.detectChanges();
  const app = fixture.debugElement.componentInstance;
  expect(app.greetings).toEqual('Hello World!');
}));

focus

itの前にfを記述することでそのテストを優先的に実行することができます。

it('greetings は”Hello World!”であること1', async(() => {
  const fixture = TestBed.createComponent(AppComponent);
  fixture.detectChanges();
  const app = fixture.debugElement.componentInstance;
  expect(app.greetings).toEqual('Hello World!');
}));
fit('greetings は”Hello World!”であること2', async(() => {
  const fixture = TestBed.createComponent(AppComponent);
  fixture.detectChanges();
  const app = fixture.debugElement.componentInstance;
  expect(app.greetings).toEqual('Hello World!');
}));

最後に記述したテストが一番初めに実行されているのがわかります。

*Tip: テストメッセージをクリックすると、そのテストのみを走らせることができます。

最後に

今回初めてAngularのユニットテストを書いてみたのですが、 実際やってみて思ったのは、テストを実行する環境が初めから整っているため 環境設定に頭を煩わせることなくテストケースの内容に専念でき、楽しく書くことができました。

当たり前なのですがテストを書いておくと、書いたテストに100%綺麗にパスする達成感なども味わえ、とてもよいです。 また近いうちにユニットテストについて深掘りした記事を書ければなと思っています。

参考サイト

Jasmine Test Framework
Angular5 Unit Testing