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
無限ループで待機し続けます。ファイル編集を検知すると即時テストが実行されます。
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();
});
});
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);
}));
});