[gist]用 jest 輕鬆測試 JavaScript
from oyanglul.us
Javascript 的測試, 不管在用 jasmine 還是 mocha,
都是很頭疼的事情. 但是自從有了 jest, 一口氣寫7個測試, 腰也不疼了, 頭也不疼了.
只需要 3 個理由
在說用 jest 測為什麼好之前,我們先來看我們要測的一個例子.
栗子
比如我要寫一個模組要去取github 使用者的follower 和他所有 repo 的 follower 數量.
那麼我們應該有一個 User 的 Model.
// user.js var $ = require('jquery'); function User(name) { this.name = name; this.followers = 0; } User.prototype.fetch = function(){ return $.ajax({ url: 'https://api.github.com/users/' + this.name, method: 'get', dataType: 'json' }).then(function(data){ this.followers = data.followers; }.bind(this)); }; module.exports = User;
我們還需要一個 repo 的 model, 大同小異略去
最後, 整合這倆我要的東西, 並顯示在頁面上
// follower.js var $ = require('jquery'); function followerOf(user, repo) { user.fetch().then(repo.fetch).then(function(_){ $('#content').text(user.name +"'s followers: " + user.followers + " and his repo "+ repo.name +"'s followers:" + repo.followers); }); }; module.exports = followerOf;
--
1. Auto Mock
自動 mock 實在是最大的亮點, jest 重寫了 require, 所以你的程式碼裡的所有 require 來的東西都自動 mock.
因為在你的測試中往往只關心一個模組, 對於他的所有依賴其實都是無所謂的.
在例子中, 如果我們在測 repo.js 的時候完全不關心那兩個 jquery 的 ajax 方法到底
寫對沒寫對,反正我們期望能從 ajax 裡面拿到我們想要的東西就對了. 因此, 我希望 jquery 的
所有方法都是 mock 的. jest 讓你很輕鬆的做到這點, 因為是自動mock所有require 的東西, 而
對於目標測試模組, 只需要說我dontMock
jest.dontMock('../repo');
describe('Repo Model', function(){
var repo;
beforeEach(function(){
var $ = require('jquery').setAjaxReturn({stargazers_count: 23});
var Repo = require('../repo');
repo = new Repo('jcouyang', 'gira');
});
it('should populate properties with data from github api', function(){
repo.fetch();
expect(repo.followers).toBe(23);
});
});
所以這個測試看起來就跟文件一樣了,
dontMock('./repo')
說明我關心repo
這個模組, 其他我都不 care.- before 是我要進行操作所需要的東西.
- 我要 jquery ajax 請求給我想要的資料
- 我要一個我要測的 Repo 類的例項
- it 說明我關心地行為是神馬
- 我關心 fetch 的行為,是去取資料並給我把資料填充到我的 repo 例項中
你可能要問
segAjaxReturn
是哪裡冒出來的. 忍一忍稍後告訴你.
有沒有看雖然我顯式的 mock jquery, 但是 Repo 裡面 require 的 jquery 其實是假的, 不然我們就真的訪問
github api 了. 那樣就不會每次都返回 23 個 follower 了.
2. jsdom
好了現在我們來測 follower.js, 先看 follower 到底幹了什麼, 拿到 user 和 repo
的資訊然後組成一句話放到頁面 id 為 content 的元素下面.
好, 所以我們關心
- 組出來的話對不對
- 有沒有放到 content 元素下, 所以 jquery 的操作對不對也是我們關心的一部分
我們不關心
- user 幹了什麼
- repo 幹了什麼
這樣,關心的就是不能 mock 的
jest.dontMock('../follower')
.dontMock('jquery');
describe('follower', function(){
var user, repo, follower;
var $ = require('jquery');
beforeEach(function(){
var Repo = require('../repo');
var User = require('../user');
follower = require('../follower');
user = new User('jcouyang');
repo = new Repo('jcouyang', 'gira');
// 我們不關心 user, 但是我們希望他能返回一個 deferred 型別
user.fetch.mockReturnValue($.Deferred().resolve('dont care'));
// 我們讓我們不關心的 user 和 repo 返回我們期望的東西就好
user.name ='jcouyang';
user.followers = 20;
repo.name = 'gira';
repo.followers = 21;
// 期待頁面上有一個 id 為 content 的元素
document.body.innerHTML = '
<div id="content"></div>
';
});
it('should populate properties with data from github api', function(){
follower(user,repo);
// 希望 content 上能得到想要的內容
expect($("#content").text()).toBe('jcouyang\'s followers: 20 and his repo gira\'s followers:21');
});
});
3. Manual Mock
好了, 說好的解釋 setAjaxReturn
是怎麼回事的
嗯嗯, 是這樣的, 雖然 jest 自動 mock 了我們不關心的模組, 但是我們還是會希望
這個 mock 的玩意能有一些我們期望的行為, 也就是按我們的期望返回一些東西. 比如
這裡就是我們不關心 ajax 的邏輯, 但是我們需要他能給我們返回一個東西,並且可以
thenable. 所以單純的 mock 物件或函式都不能做到, 所以有了 manual mock 這種東西.
用 manual mock 需要建一個__ mocks__
資料夾,然後把所有的 mock 都扔進去. 比如
我想 mock jquery, 那麼我建一個jquery.js
扔進去
var data = {};
var mockDefered = function(data){
return {
then: function(cb){
return mockDefered(cb(data));
}
};
};
function ajax() {
return mockDefered(data);
}
function setAjaxReturn(shouldbe){
data = shouldbe;
}
exports.setAjaxReturn = setAjaxReturn;
exports.ajax = ajax;
終於看見setAjaxReturn
在哪裡定義了:sweat_smile: 這裡暴露兩個函式
- setAjaxReturn: 可以設定我希望 ajax 返回的值
- ajax: 單純的返回這個 thenable.
所以我也不需要顯示的宣告 mock jquery什麼什麼的, 直接在測試裡設定ajax 的返回值就好了.
var $ = require('jquery').setAjaxReturn({stargazers_count: 23});
這是 repo 裡面 require 的 jquery 已經被 mock 並且只要掉 ajax 都會返回我
期望的值.
etc
- 並行測試:
還用說麼, 既然已經如此模組化好了, user repo 以及 follower 的測試完全是互不依賴.
沒有什麼理由一個一個測. 因此3個測試的耗時取決於最長時間的那個. 所以如果有
那個測試特別耗時,說明模組還不夠細, 多拆幾個就快了. - promise: 使用 pit() 來測試 thenable 的物件, 比如 repo 的例子,就 keyi
寫成
pit('should populate properties with data from github api', function(){
return repo.fetch().then(
expect(repo.followers).toBe(23);
);
});
- Timer mocks: 可以使用 mock 的 timer 和 ticks, 也就是你可以加速
所有的setTimeout, setInterval, clearTimeout, clearInterval行為. 不需要等待.
setTimeout(function() { callback(); }, 1000);
expect(callback).not.toBeCalled();
jest.runAllTimers();
expect(callback).toBeCalled()
Wrapup
所以說白了, jest 其實也是個概念, 推薦使用模組化的思想, 這樣我只需要保證每個介面的 IO 正確, 就可以保證整個程式沒問題. 這樣劃分下來測試就會變得簡單到只需要關心當然模組的 IO 從而
可以 mock 掉所有其他依賴. 真正模組化好的程式碼單純的只用 jasmine 或者 mocha
都應該是很好測的. 只是在這個概念之上省去了很多不必要的 mock 程式碼, 因為要 mock 的
依賴總是佔大多數的, 而關心的, 往往只是那麼一兩個.