Jasmineで非同期処理のテスト
Jasmineの使い方を覚えるために複数のWebSocketコネクションが協調して動作する、というよくありがちなシナリオのテストケースを書いてみた。非同期テストの書き方のページを読んでもよくわからなったので、最初わりと苦戦した。
メモ
- waitsForは、渡した関数がtrueを返すか指定したタイムアウトまで待つ
- runsに渡した関数は遅延評価される
- waitsForの結果を待つ場合はrunsを使う
- setUpとtearDownが無いので、runsを最初と最後に使う
- waitsForを含むit以降のdescribe内のitは遅延評価される
コード
Jasmine以外にライブラリはSenchaとSocket.ioを使っている。
var appServerUrl = 'http://dev.hagino3000.com:8888'; describe("Chat Test", function() { var serverResponse = null; describe("サーバーからWebSocket接続先を取得する", function() { it("サーバーが応答する", function() { var spyF = jasmine.createSpy(); var callbacks = { onFailure: spyF, onSuccess: function(res) { serverResponse = res; } } Ext.Ajax.request({ url: appServerUrl, method: 'POST', jsonData: JSON.stringify({ method: 'getWebSocketUrl' }), success: callbacks.onSuccess, failure: callbacks.onFailure }); waitsFor(function() { return !!serverResponse; }, 'サーバーが応答を返す', 5000); runs(function() { // 失敗時のコールバックが呼ばれていない事を確認する expect(spyF).not.toHaveBeenCalled(); }); }); }); describe("サーバーレスポンスに接続先が含まれている", function() { var response; // この行は即時実行される runs(function() { // setUp相当の処理 // 一つ前のテストケースの完了後に実行される response = JSON.parse(serverResponse.responseText); }); it("エラーになっていない事", function() { expect(response.error).toBeNull(); }); it("WebSocketサーバー接続先が含まれる", function() { expect(response.result.wsServerUrl).not.toBeNull(); }); }); describe("WebSocektクライアント同士でやりとりができる", function() { var socket1, socket2, wsServerUrl; var socketOption = { // こいつをtrueにしないとサーバーからは同じ接続に見える 'force new connection': true } runs(function() { // setUp var response = JSON.parse(serverResponse.responseText); wsServerUrl = response.result.wsServerUrl; }); it("複数クライアントがWebSocketサーバーに接続ができる", function() { var ownerConnected = false, guestConnected = false; socket1 = io.connect(wsServerUrl, socketOption); socket1.on('connect', function(){ ownerConnected = true; }); socket2 = io.connect(wsServerUrl, socketOption); socket2.on('connect', function(){ guestConnected = true; }); waitsFor(function() { return ownerConnected && guestConnected; }, 1000, "両方の接続が確立する"); }); it("クライアント同士でやりとりができる", function() { var receivedMessage; var receivedOwnMessage = jasmine.createSpy(); socket1.on('message', function(msg) { receivedMessage = msg; }); socket2.on('message', receivedOwnMessage); socket2.emit('message', { text: 'Hello world', hoge: 'fuga' }); waitsFor(function() { return !!receivedMessage; }, 1000, "Client 1から送信したメッセージがClient 2に到達する"); runs(function() { // 自信が送信したメッセージは受信していない expect(receivedOwnMessage).not.toHaveBeenCalled(); // 受信データのチェック expect(receivedMessage.text).toBe('Hello world'); expect(receivedMessage.hoge).toBe('fuga'); }); }); it("片方が接続を切った場合、もう片方に通知が行く", function() { var gotChatLeaveSignal; socket1.on('chat_leave', function() { gotChatLeaveSignal = true; }); socket2.disconnect(); waitsFor(function() { return gotChatLeaveSignal; }, 1000, "切断通知の受信"); }); it("切断時にコールバックが呼ばれる", function() { var disconnectCallback = jasmine.createSpy(); socket1.on('disconnect', disconnectCallback, this); socket1.disconnect(); waits(50); runs(function() { expect(disconnectCallback).toHaveBeenCalled(); }); }); runs(function() { // tearDown if (socket1) {socket1.disconnect();} if (socket2) {socket2.disconnect();} }); }); });
ちょっと使ってみたが、指定した関数が呼ばれた or 呼ばれていないかがチェックできるspyの使い勝手が良い。タイプ数はqunitに比べて増えるけど説明を書かざるをえないので何のテストを書いたのか後でわかりやすい。