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 的
依赖总是占大多数的, 而关心的, 往往只是那么一两个.