AngularJS 単体テスト
[履歴] [最終更新] (2016/06/03 22:07:55)

概要

AngularJS の単体テストに Node.js, Karma, Jasmine, PhantomJS を利用する場合の例をまとめます。

インストール

サーバサイド JavaScript 環境である Node.js およびそのパッケージマネージャ npm をインストールします。CentOS 6 の場合は epel を利用すると簡単です。

$ sudo yum install epel-release
$ sudo yum install nodejs
$ sudo yum install npm

テスト実行環境 Karma パッケージをシステムインストールします。

$ sudo npm install -g karma

Karma 上で動作するテストフレームワーク Jasmine をシステムインストールします。

$ sudo npm install -g karma-jasmine

Jasmine 自体はブラウザをエミュレートできないため、テスト用のブラウザプラグインをシステムインストールします。PhantomJS は CUI で動作する画面出力のないブラウザです。例えば今回のようなテスト目的で使用されます。以下のコマンドで PhantomJS およびその Karma プラグインがインストールされます。

$ sudo npm install -g karma-phantomjs-launcher

この時点で karma コマンドが使用できない場合は別途以下のコマンドでインストールします。

$ which karma  ←見つからないならば↓
$ sudo npm install -g karma-cli

動作検証

初期設定ファイルを生成します。候補はタブで切り替え選択します。

$ karma init

質問への回答例

Which testing framework do you want to use ?
Press tab to list possible options. Enter to move to the next question.
> jasmine

Do you want to use Require.js ?
This will add Require.js plugin.
Press tab to list possible options. Enter to move to the next question.
> no

Do you want to capture any browsers automatically ?
Press tab to list possible options. Enter empty string to move to the next question.
> PhantomJS
> 

What is the location of your source and test files ?
You can use glob patterns, eg. "js/*.js" or "test/**/*Spec.js".
Enter empty string to move to the next question.
> angular.js  ← minifyされていないもの
30 07 2015 00:40:10.818:WARN [init]: There is no file matching this pattern.

> angular-mocks.js  ← minifyされたものは提供されていない
30 07 2015 00:40:19.179:WARN [init]: There is no file matching this pattern.

> *_spec.js
30 07 2015 00:40:26.764:WARN [init]: There is no file matching this pattern.

> 

Should any of the files included by the previous patterns be excluded ?
You can use glob patterns, eg. "**/*.swp".
Enter empty string to move to the next question.
> 

Do you want Karma to watch all the files and run the tests on change ?
Press tab to list possible options.
> yes

Config file generated at "/vagrant/karma.conf.js".

以下のテストコードを用意します。Jasmine の記法で記述します。

sample_spec.js

describe('テスト内容', function(){
  it('加算試験', function(){
    expect(1+2).toEqual(3);
  });
});

テストの開始

$ karma start karma.conf.js

無限ループで待機し続けます。ファイル編集を検知すると即時テストが実行されます。

Jasmine 記法

describe('Jasmine の使い方', function(){
  it('同一オブジェクトである', function(){
    var obj = {};
    var obj_ = obj;
    expect(obj_).toBe(obj);
    expect({}).not.toBe(obj);
  });

  it('値が等しい', function(){
    var obj = {};
    expect({}).toEqual(obj);
  });

  it('正規表現にマッチ', function(){
    expect('abc').toMatch('^[a-z]+$');
  });

  it('真偽値', function(){
    expect(true).toBeTruthy();
    expect(false).toBeFalsy();
  });

  it('値を含むかどうか', function(){
    expect([1,2,3]).toContain(1);
  });

  it('値の大小', function(){
    expect(1).toBeGreaterThan(0);
    expect(-1).toBeLessThan(0);
  });

  it('値が近い', function(){
    expect(0.001).toBeCloseTo(0);
  });

  it('例外を投げること', function(){
    var myfunc = function(){
      myUndefinedVar;
    };
    expect(myfunc).toThrow();
  });

  it('undefined であるかどうか', function(){
    expect(undefined).toBeUndefined();
  });

  it('null であるかどうか', function(){
    expect(null).toBeNull();
  });

  it('NaN であるかどうか', function(){
    expect(parseInt("str")).toBeNaN();
  });

});

AngularJS のテスト方法

angular.js および angular-mock.js をダウンロードしておきます。

sample_spec.js

var app = angular.module('myApp', []);

// フィルターの自作
// https://www.qoosky.io/techs/8a4a4365aa
app.filter('myfilter', function(){
  return function(value){
    return angular.isNumber(value);
  };
});

// テストコード
describe('カスタムフィルターのテスト例', function(){
  beforeEach(module('myApp'));

  it('myfilter 動作確認', inject(function($filter){
    var myfilter = $filter('myfilter');
    expect(myfilter(1)).toEqual(true);
    expect(myfilter('abc')).toEqual(false);
  }));
});

強制的にタイムアウトさせる必要があるサービスをテストする場合は $timeout のモックを利用します。

var app = angular.module('myApp', []);

app.service('mytimer', ['$timeout', function($timeout){
  this.prop = 'ABC';
  var self = this;
  $timeout(function(){
    self.prop = 'XYZ';
  }, 1000000); //msec
}]);

describe('$timeout を利用したカスタムサービスのテスト例', function(){
  beforeEach(module('myApp'));

  it('mytimer 動作確認', inject(function($timeout, mytimer){
    expect(mytimer.prop).toEqual('ABC');
    $timeout.flush();
    expect(mytimer.prop).toEqual('XYZ');
  }));
});

HTTP 通信を行うサービスをテストする場合は $httpBackend のモックを利用します。

var app = angular.module('myApp', []);

app.factory('myhttp', ['$http', function($http){
  return function(path){
    return $http.get(path);
  }
}]);

describe('$http を利用したカスタムサービスのテスト例', function(){
  beforeEach(module('myApp'));

  var httpBackend;
  beforeEach(inject(function($httpBackend){
    httpBackend = $httpBackend;
  }));

  it('myhttp 動作確認', inject(function(myhttp){

    httpBackend.expect('GET', '/api/mock').respond([
      {itemId: 0, value: 'a'},
      {itemId: 1, value: 'b'},
    ]);

    var promise = myhttp('/api/mock');
    var response;
    promise
      .success(function(data){
        response = data;
      })
      .error(function(data){
        response = data;
      });

    expect(response).toBeUndefined();
    httpBackend.flush();
    expect(response.length).toEqual(2);

  }));
});
関連ページ