簡単なプロジェクトで練習してみたので自分用にまとめ直し。
- まずはテスト用の簡単なプロジェクトを準備。ディレクトリ構成は下記の通り。
jasmine
フォルダ以下はjasmine Standalone Distributionのファイル群。
test
ディレクトリ以下にテスト用のファイルを配置している。runner.html
にアクセスすることでテストが実行される。
今回作成したAngularアプリケーションは、app.js
、index.html
、indexController.js
、indexService.js
の4ファイルで構成。
それぞれのファイル内容は下記の通り。
app.js
1 2 3 4 5 6 7 8 9 10 11 |
void function() { angular.module('coreApp', [ 'coreControllers', 'coreServices' ]); angular.module('coreControllers', []); angular.module('coreServices', []); }(); |
index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<!DOCTYPE html> <html ng-app="coreApp"> <head> <meta charset="UTF-8"> <title>Angular</title> <script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.3/angular.min.js"></script> <script src="app.js"></script> <script src="indexController.js"></script> <script src="indexService.js"></script> </head> <body> <div ng-controller="indexController as index"> <span>{{index.value}}</span> </div> </body> </html> |
indexController.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
void function() { var indexController = function(indexService) { this.value = 'hello world.'; indexService.run(); indexService.run2('hello', 'world.'); this.result = indexService.run3(); for (var i = 0; i < 100; i++) { indexService.run5(i); } indexService.run6(); indexService.run6('foo'); indexService.run6('foo', 'bar'); indexService.run6('foo', 'bar', 'baz'); } angular .module('coreControllers') .controller('indexController', ['indexService', indexController]) ; }(); |
indexService.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
void function() { var indexService = function() { this.run = function() { console.log('call run() method!'); } this.run2 = function(arg1, arg2) { console.log(arg1 + ' ' + arg2); } this.run3 = function() { return 'foo'; } this.run4 = function() { return 'bar'; } this.run5 = function(mes) { console.log(mes); } this.run6 = function(arg1, arg2, arg3) { console.log(arg1, arg2, arg3); } } angular .module('coreServices') .service('indexService', [indexService]) ; }(); |
index.htmlにアクセスすると画面にhello world.
が表示される。見た目はそれでおしまいだが、テスト用に用意したサービスを実行しまくっているので、コンソールには色々と吐かれる。
これらをテストするためにトリガーファイルとなるrunner.html
を用意する。
runner.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Jasmine Spec Runner v2.1.3</title> <link rel="shortcut icon" type="image/png" href="../jasmine/lib/jasmine-2.1.3/jasmine_favicon.png"> <link rel="stylesheet" href="../jasmine/lib/jasmine-2.1.3/jasmine.css"> <script src="../jasmine/lib/jasmine-2.1.3/jasmine.js"></script> <script src="../jasmine/lib/jasmine-2.1.3/jasmine-html.js"></script> <script src="../jasmine/lib/jasmine-2.1.3/boot.js"></script> <script src="../jasmine/lib/jasmine-2.1.3/console.js"></script> <!-- include source files here... --> <script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.3/angular.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.3/angular-mocks.js"></script> <script src="../app.js"></script> <script src="../indexController.js"></script> <script src="../indexService.js"></script> <!-- include spec files here... --> <script src="controllers/indexControllerSpec.js"></script> </head> <body> </body> </html> |
index.html
との違いとして、angular.min.js
本体を読み込んだ直後にangular-mocks.js
を読み込んでいる。8行目から14行目はjasmine
本体のセットアップ処理なのでこれは必ず必要となる。
ここまでの設定が完了したら、実際にテストを記述していく。
今回はテスト本体をtest\controllers\indexControllerSpec.js
に配置した。
describe()
メソッドでテストのセットを作成するようなイメージ。この中にある複数のit()
メソッドがそれぞれのテスト内容となる。雰囲気はソースコード内のコメントアウトを参照。
indexControllerSpec.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 |
describe('indexController', function() { /** * 各テストの前に実行されるいわば初期化処理。 * `module`メソッドによって各テストから`coreApp`を利用できるようにする。 */ beforeEach(function() { module('coreApp'); }); /** * 値が等しいかを確認 */ it('spec1', inject(function($controller) { // このラインでindexControllerの処理が走り、valueに値がセットされる var controller = $controller('indexController'); // 値が等しいか確認する expect(controller.value).toBe('hello world.'); })); /** * 特定のメソッドが実行されたかを確認 */ it('spec2', inject(function($controller, indexService) { // コントローラーの初期化前に監視対象のメソッドを登録 // 監視対象に登録されたメソッドは本来の挙動を示さなくなる // よってログが吐かれない spyOn(indexService, 'run'); var controller = $controller('indexController'); // 監視下においたメソッドが実行されているかどうか確認 expect(indexService.run).toHaveBeenCalled(); })); /** * 特定のメソッドが、指定した引数と共に実行されているか確認 */ it('spec3', inject(function($controller, indexService) { // 監視対象に登録 spyOn(indexService, 'run2'); var controller = $controller('indexController'); // 監視下においたメソッドが、指定した引数(順番)と共に実行されているかどうか確認 expect(indexService.run2).toHaveBeenCalledWith('hello', 'world.'); })); /** * 監視下においたメソッドでも本来の実装を実行させる */ it('spec4', inject(function($controller, indexService) { // 監視対象に登録するが、本来の実装も実行したい場合 spyOn(indexService, 'run').and.callThrough(); spyOn(indexService, 'run2').and.callThrough(); var controller = $controller('indexController'); // 監視下においたメソッドが、指定した引数(順番)と共に実行されているかどうか確認 expect(indexService.run).toHaveBeenCalled(); expect(indexService.run2).toHaveBeenCalled(); })); /** * 監視下においたメソッドの返り値を強制的に指定する */ it('spec5', inject(function($controller, indexService) { // 監視対象においた関数から本来返される値を上書きする spyOn(indexService, 'run3').and.returnValue('bar'); var controller = $controller('indexController'); // 本来であれば'foo'が格納されているはずだが、強制的に'bar'に上書かれている expect(controller.result).toBe('bar'); })); /** * 監視下においたメソッドの関数を上書きする */ it('spec6', inject(function($controller, indexService) { // 監視対象においた関数を任意の関数と差し替える spyOn(indexService, 'run3').and.callFake(function() { return 'method is overrided!'; }); var controller = $controller('indexController'); // メソッドが上書かれ、返却値が変わっている expect(controller.result).toBe('method is overrided!'); })); /** * 特定のメソッドが実行された場合に例外を投げる */ it('spec7', inject(function($controller, indexService) { // 監視対象においた関数が実行された際に例外を投げる // 叩かれたらエラーになってテストが停まるので、ここでは叩かれない関数を指定 spyOn(indexService, 'run4').and.throwError('run4() is called!'); var controller = $controller('indexController'); // こうすることで実際に叩かれた際に投げられるエラーメッセージが一致していなくてもアウトとなる expect(indexService.run4).toThrowError('run4() is called!'); })); /** * 関数が実行された回数を確認する */ it('spec8', inject(function($controller, indexService) { // 監視対象に置く // 監視対象においているので、当然ログは吐かれない // callFakeかcallThroughを用いることで本来の挙動になる spyOn(indexService, 'run5'); var controller = $controller('indexController'); // 関数が実行された回数を確認する expect(indexService.run5.calls.count()).toBe(100); })); /** * 関数が実行された際の引数をキャプチャして確かめる */ it('spec9', inject(function($controller, indexService) { // 監視対象に置く spyOn(indexService, 'run6'); var controller = $controller('indexController'); // 関数が実行された回数を確認する expect(indexService.run6.calls.count()).toBe(4); // 関数が実行された際の引数をキャプチャする expect(indexService.run6.calls.argsFor(0)).toEqual([]); // 1回目の実行時 expect(indexService.run6.calls.argsFor(1)).toEqual(['foo']); // 2回目の実行時 expect(indexService.run6.calls.argsFor(2)).toEqual(['foo', 'bar']); // 3回目の実行時 expect(indexService.run6.calls.argsFor(3)).toEqual(['foo', 'bar', 'baz']); // 4回目の実行時 })); /** * 関数が実行された際の情報を取得する */ it('spec10', inject(function($controller, indexService) { // 監視対象に置く spyOn(indexService, 'run5'); var controller = $controller('indexController'); // 関数が最初に実行された際の情報を取得 expect(indexService.run5.calls.first()).toEqual({args: [0], object: indexService, returnValue: undefined}); // 1回目の実行時 // 関数が最後に実行された際の情報を取得 expect(indexService.run5.calls.mostRecent()).toEqual({args: [99], object: indexService, returnValue: undefined}); // 最後の実行時 })); /** * 監視関数をその場で作成する */ it('spec11', inject(function($controller, indexService) { // 監視関数を作る var createdSpy = jasmine.createSpy(); // 作成した監視関数を実行 createdSpy(); // 実行されたか確認 expect(createdSpy).toHaveBeenCalled(); })); /** * 監視オブジェクトを作成する */ it('spec12', inject(function($controller, indexService) { // 監視オブジェクトを作る var createdSpyObject = jasmine.createSpyObj('objectName', ['method1', 'method2', 'method3']); // 作成した監視オブジェクトの関数を実行 createdSpyObject.method1(); createdSpyObject.method3(); // 実行されたか確認 expect(createdSpyObject.method1).toHaveBeenCalled(); // 実行されていないか確認 expect(createdSpyObject.method2).not.toHaveBeenCalled(); // 実行されたか確認 expect(createdSpyObject.method3).toHaveBeenCalled(); })); /** * setTimeout, setIntervalを用いた関数の遅延実行を正常に追う */ it('spec13', inject(function($controller, indexService) { // setTimeOut()などで時間を操作したい場合 // 監視関数を作成 var spy = jasmine.createSpy(); // setTimeoutによりspyは500ms後に呼ばれる。 setTimeout(function() { spy(); }, 500); // よってこのタイミングではspyはまだ呼ばれていない expect(spy).not.toHaveBeenCalled(); // なのでsetTimeoutによる関数実行の遅延をキャッチしたい場合clockの仕組みを用いる // clockをセットアップ jasmine.clock().install(); // 新たに監視関数を作成 var spy2 = jasmine.createSpy(); // 1000ms後にspy2を実行 setTimeout(function() { spy2(); }, 1000); // まだ実行されていない expect(spy2).not.toHaveBeenCalled(); // 時を加速させる(1001ms時間を進める) jasmine.clock().tick(1001); // ここで確認すると実行されている expect(spy2).toHaveBeenCalled(); // 最後にclockをアンインストールして完了 jasmine.clock().uninstall(); })); /** * $timeoutサービスを用いた関数の遅延実行を正常に追う */ it('spec14', inject(function($controller, indexService, $timeout) { // clockシステムはあくまでもsetTimeout()、及びsetInterval()へ直に働きかけるため、 // angularの$timeoutサービスを通すと正常に動作しないので注意 // $timeoutを用いたい場合下記のようにする // 監視関数を作成 var spy = jasmine.createSpy(); // $timeoutによりspyは500ms後に呼ばれる。 $timeout(function() { spy(); }, 500); // よってこのタイミングではspyはまだ呼ばれていない expect(spy).not.toHaveBeenCalled(); // この行によて$timeoutは強制的に待機時間を消費させられる $timeout.flush(); // ここで確認すると実行されている expect(spy).toHaveBeenCalled(); })); /** * afterEachブロックは各テストの後に毎回実行されている。 * 引数に`done`を受け取るようにすることで、`done`が実行されるまで次の処理に移らないようになってくれる。 * Jasmineは引数を受け取るようにすると、処理を待機するようになる。 * よって、引数を渡したにも関わらずいつまでも`done`を実行しないとタイムアウトエラーになるので注意。 * この設定の場合、各テスト処理の後1000ms待機し処理を実行しているので、各テスト間に1000msの待機時間が発生している感じになる。 */ afterEach(function(done) { setTimeout(function() { console.log('afterEach'); done(); }, 1000); }); }); |
で実際runner.html
にアクセスすると下記画像の通りテストが実行されて、結果を知らせてくれる。
意外と簡単にテストを作成することが出来た。リソースが絡んだテストなんかはまた次回。