Mocha是什么
Mocha是一个Javascript测试框架。只要是Javascript能运行的地方,都可以运行mocha。其主要特征是能够使得异步测试更加简单以及提供了精确的报告。
安装方法
使用npm
全局安装 npm install -g mocha
;
作为开发依赖本地安装 npm install --save-dev mocha
一个简单的例子
var assert = require('assert');
describe('Array', function() {
describe('#indexOf()', function() {
it('should return -1 when the value is not present', function() {
assert.equal(-1, [1,2,3].indexOf(4)); // pass
assert.equal(0, [1,2,3].indexOf(2)); // fail. index should be 1
});
});
});
上面的例子使用了测试集定义函数describe()
和测试用例定义函数it()
,还使用了node的assert模块来做验证来检查数组索引的值是否匹配。很明显第一个验证可以通过,而第二个不行。可以使用命令mocha
来执行测试,也可以在package.json
里面配置了test
命令使用npm test
执行,无论哪种方式,运行效果都如下
➜ mocha_demo mocha test.js
Array
#indexOf()
1) should return -1 when the value is not present
0 passing (9ms)
1 failing
1) Array
#indexOf()
should return -1 when the value is not present:
AssertionError [ERR_ASSERTION]: 0 == 1
+ expected - actual
-0
+1
at Context.<anonymous> (test.js:8:14)
把第二个测试用例期待值修改正确后,执行通过的效果如下
➜ mocha_demo mocha test.js
Array
#indexOf()
✓ should return -1 when the value is not present
1 passing (7ms)
➜ mocha_demo
Mocha
的执行会找到当前命令执行目录下的test
目录。./test/*.js
是Mocha
寻找的目标。
Mocha
提供了多种便利的执行方式
- 多个文件
mocha file1 file2 file3
- 指定目录
mocha ./test/dir1/*.js
- 通配符
mocha ./spec/test*.js
,mocha ./spec/{my, awesome}.js
- node通配符
mocha './spec/**/*.@(js|jsx)'
验证 assertions
mocha不像jasmine那样自身内建具有验证功能的方法,但是我们可以在测试用例中使用诸如 should.js、expect.js 和chai等验证库来完成验证功能。推荐使用chai。
异步代码测试
当测试异步的代码时,通过给it()
加一个回调函数(通常命名为done())即可让mocha
知道异步代码执行之后需要调用done()
来表示测试完成
describe('User', function() {
describe('#save()', function() {
it('should save without error', function(done) {
var user = new User('Luna');
user.save(function(err) {
if (err) done(err);
else done();
});
});
});
});
当done()
可以接受异步代码错误的时候,上面代码还可以简化为
describe('User', function() {
describe('#save()', function() {
it('should save without error', function(done) {
var user = new User('Luna');
user.save(done);
});
});
});
promises代码测试
当异步代码返回的是promise
而不是callback
的时候,用例也可以返回promise
beforeEach(function() {
return db.clear()
.then(function() {
return db.save([tobi, loki, jane]);
});
});
describe('#find()', function() {
it('respond with matching records', function() {
return db.find({ type: 'User' }).should.eventually.have.length(3); // https://www.npmjs.com/package/chai-as-promised
});
});
同步代码测试
当测试同步的代码时,mocha
会自动的执行下一个测试用例,所以mocha
会连续地执行所有的测试
'use strict';
const assert = require('assert');
describe('Array', function() {
describe('#indexOf()', function() {
it('should return -1 when the value is not present', function() {
assert.equal(-1, [1,2,3].indexOf(5));
assert.equal(-1, [1,2,3].indexOf(0));
});
it('should not return -1 when the value is present', function() {
assert.equal(0, [1,2,3].indexOf(1));
assert.equal(1, [1,2,3].indexOf(2));
assert.equal(2, [1,2,3].indexOf(3));
});
});
describe('#length()', function() {
it('should have length 3', function() {
assert.equal(3, [1,2,3].length);
});
});
});
上面的测试代码执行效果如下
➜ mocha_demo mocha test.js
Array
#indexOf()
✓ should return -1 when the value is not present
✓ should not return -1 when the value is present
#length()
✓ should have length 3
3 passing (7ms)
箭头函数
不建议在mocha
中使用箭头函数,因为箭头函数对this
的绑定会使测试用例无法访问Mocha
上下文中的一些方法。
比如下面代码this.timeout(1000)
就会执行失败
'use strict';
const assert = require('assert');
describe('my suite', () => {
it('my test', () => {
// should set the timeout of this test to 1000 ms; instead will fail
this.timeout(1000);
assert.ok(true);
});
});
➜ mocha_demo mocha test.js
my suite
1) my test
0 passing (7ms)
1 failing
1) my suite
my test:
TypeError: this.timeout is not a function
at Context.it (test.js:8:10)
把箭头函数删除后,就可以运行成功
'use strict';
const assert = require('assert');
describe('my suite', function() {
it('my test', function() {
// should set the timeout of this test to 1000 ms; instead will fail
this.timeout(1000);
assert.ok(true);
});
});
➜ mocha_demo mocha test.js
my suite
✓ my test
1 passing (6ms)
Hooks
Mocha
提供了四种hooks用来做测试准备和清理工作
- before() - 在所有测试套件运行之前执行
- after() - 在所有测试套件运行之后执行
- beforeEach() - 在每个测试用例运行之前执行
- afterEach() - 在每个测试用例运行之后执行
下面是一个简单的例子
'use strict';
const assert = require('assert');
describe('my suite', function() {
before('This is before method description', function(){
console.log('this is before(), runs before all tests in this block');
});
after(function aftername(){
console.log('this is after(), runs after all tests in this block');
});
beforeEach(function(){
console.log('this is beforeEach(), runs before each test in this block');
});
afterEach(function(){
console.log('this is afterEach(), runs after each test in this block');
});
it('my test case 1', function() {
assert.ok(true);
});
it('my test case 2', function(){
assert.equal(1, 1);
})
});
describe('my suite 2', function(){
it('my test case 3', function(){
assert.equal('bar', 'bar')
})
});
➜ mocha_demo mocha test.js
my suite
this is before(), runs before all tests in this block
this is beforeEach(), runs before each test in this block
✓ my test case 1
this is afterEach(), runs after each test in this block
this is beforeEach(), runs before each test in this block
✓ my test case 2
this is afterEach(), runs after each test in this block
this is after(), runs after all tests in this block
my suite 2
✓ my test case 3
3 passing (11ms)
hooks也能用于异步代码的处理
describe('Connection', function() {
var db = new Connection,
tobi = new User('tobi'),
loki = new User('loki'),
jane = new User('jane');
beforeEach(function(done) {
db.clear(function(err) {
if (err) return done(err);
db.save([tobi, loki, jane], done);
});
});
describe('#find()', function() {
it('respond with matching records', function(done) {
db.find({type: 'User'}, function(err, res) {
if (err) return done(err);
res.should.have.length(3);
done();
});
});
});
});
全局hooks
刚才提到的hooks
都是定义在describe()
里面,如果hooks
定义在了describe()
外面,那么这个hooks
的作用域就是整个文件的全部测试套件。这是因为mocha
有一个implied describe(),称之为”root suite”。
'use strict';
const assert = require('assert');
before('This is before method description', function(){
console.log('this is before(), runs before all tests in this block');
});
after(function aftername(){
console.log('this is after(), runs after all tests in this block');
});
beforeEach(function(){
console.log('this is beforeEach(), runs before each test in this block');
});
afterEach(function(){
console.log('this is afterEach(), runs after each test in this block');
});
describe('my suite', function() {
it('my test case 1', function() {
assert.ok(true);
});
it('my test case 2', function(){
assert.equal(1, 1);
})
});
describe('my suite 2', function(){
it('my test case 3', function(){
assert.equal('bar', 'bar')
})
});
➜ mocha_demo mocha test.js
this is before(), runs before all tests in this block
my suite
this is beforeEach(), runs before each test in this block
✓ my test case 1
this is afterEach(), runs after each test in this block
this is beforeEach(), runs before each test in this block
✓ my test case 2
this is afterEach(), runs after each test in this block
my suite 2
this is beforeEach(), runs before each test in this block
✓ my test case 3
this is afterEach(), runs after each test in this block
this is after(), runs after all tests in this block
3 passing (13ms)
延迟测试
如果想要通过延迟测试执行,而在测试之前完成某些特定的工作,我们需要命令mocha --delay
来实现。这个命令会把run()
函数加上全局环境中,等特定工作完成后调用run()
即可启动测试执行。
setTimeout(function() {
// do some setup
describe('my suite', function() {
// ...
});
run();
}, 5000);
待定测试
当一个测试没有完成,需要以后来继续完成的时候,这个测试就进入了pending待定状态。为了测试执行不被未完成的用例影响,我们只需要简单地不添加回调函数callback即可。
'use strict';
const assert = require('assert');
describe('my suite 2', function(){
it('my test case 3')
});
上面的it()
没有加上回调函数,于是执行结果中 pending
就会+1
my suite 2
- my test case 3
0 passing (6ms)
1 pending
单独执行某一个测试套件或是测试用例
通过给describe()
和it()
加上.only()
,我们可以指定执行只加上了.only()
的测试套件或是测试用例
下面这个列子是只执行内嵌的测试套件
'use strict';
const assert = require('assert');
describe('my suite 1', function(){
describe('my nested suite 1', function(){
it('my test case 1', function(){
console.log('this is test case 1');
});
it('my test case 2', function(){
console.log('this is test case 2');
});
});
describe.only('my nested suite 2', function(){
it('my test case 3', function(){
console.log('this is test case 3');
});
});
});
describe('my suite 2', function(){
it('my test case 4', function(){
console.log('this is test case 4');
});
it('my test case 5', function(){
console.log('this is test case 5');
});
});
➜ mocha_demo mocha test.js
my suite 1
my nested suite 2
this is test case 3
✓ my test case 3
1 passing (6ms)
下面列子指定了只执行两个测试用例,所以.only()
是可以使用多次来定义测试套件或是测试用例的子集
'use strict';
const assert = require('assert');
describe('my suite 1', function(){
describe('my nested suite 1', function(){
it('my test case 1', function(){
console.log('this is test case 1');
});
it.only('my test case 2', function(){
console.log('this is test case 2');
});
});
describe('my nested suite 2', function(){
it('my test case 3', function(){
console.log('this is test case 3');
});
});
});
describe('my suite 2', function(){
it('my test case 4', function(){
console.log('this is test case 4');
});
it.only('my test case 5', function(){
console.log('this is test case 5');
});
});
➜ mocha_demo mocha test.js
my suite 1
my nested suite 1
this is test case 2
✓ my test case 2
my suite 2
this is test case 5
✓ my test case 5
2 passing (8ms)
当测试套件和测试用例同时都加上了.only()
的时候,测试用例的执行是优先的。下面这个列子只会执行it.only()
'use strict';
const assert = require('assert');
describe('my suite 1', function(){
describe.only('my nested suite 1', function(){
it.only('my test case 1', function(){
console.log('this is test case 1');
});
it('my test case 2', function(){
console.log('this is test case 2');
});
});
describe('my nested suite 2', function(){
it('my test case 3', function(){
console.log('this is test case 3');
});
});
});
➜ mocha_demo mocha test.js
my suite 1
my nested suite 1
this is test case 1
✓ my test case 1
1 passing (11ms)
如果有Hooks的话,Hooks会一直被执行的
跳过测试
既然有指定执行某几个测试,当然Mocha
也提供不执行某些测试,也就是跳过测试。通过给describe()
和it()
加上.skip()
,我们可以跳过只加上了.skip()
的测试套件或是测试用例。任何被跳过没有执行的测试用例都会被在结果中标记为pending
。
下面的例子中,两个测试套件都被跳过了,所有结果里面被标记为pending
'use strict';
const assert = require('assert');
describe('my suite 1', function(){
describe.skip('my nested suite 1', function(){
it('my test case 1', function(){
console.log('this is test case 1');
});
it('my test case 2', function(){
console.log('this is test case 2');
});
});
describe('my nested suite 2', function(){
it('my test case 3', function(){
console.log('this is test case 3');
});
});
});
describe.skip('my suite 2', function(){
it('my test case 4', function(){
console.log('this is test case 4');
});
it('my test case 5', function(){
console.log('this is test case 5');
});
});
➜ mocha_demo mocha test.js
my suite 1
my nested suite 1
- my test case 1
- my test case 2
my nested suite 2
this is test case 3
✓ my test case 3
my suite 2
- my test case 4
- my test case 5
1 passing (8ms)
4 pending
下面这个例子中,有两个测试用例被跳过了
'use strict';
const assert = require('assert');
describe('my suite 1', function(){
describe('my nested suite 1', function(){
it('my test case 1', function(){
console.log('this is test case 1');
});
it.skip('my test case 2', function(){
console.log('this is test case 2');
});
});
describe('my nested suite 2', function(){
it('my test case 3', function(){
console.log('this is test case 3');
});
});
});
describe('my suite 2', function(){
it('my test case 4', function(){
console.log('this is test case 4');
});
it.skip('my test case 5', function(){
console.log('this is test case 5');
});
});
➜ mocha_demo mocha test.js
my suite 1
my nested suite 1
this is test case 1
✓ my test case 1
- my test case 2
my nested suite 2
this is test case 3
✓ my test case 3
my suite 2
this is test case 4
✓ my test case 4
- my test case 5
3 passing (8ms)
2 pending
使用.skip()
是比注释更好的能够不执行指定测试的方法。
使用this.skip()
可以在运行时动态的跳过测试。比如一个测试需要在运行前检查运行环境是否准备好了,如果没有准备好,那我们可以直接使用this.skip()
来跳过。
下面的例子表明正是由于环境有问题,需要跳过测试,最后跳过的测试会被标记为pending
。
'use strict';
const assert = require('assert');
let checkEnv = false;
describe('my suite 2', function(){
it('my test case 4', function(){
console.log('this is test case 4');
if (checkEnv === true){
console.log('Env is ready, lets do sth');
} else {
this.skip();
};
});
it('my test case 5', function(){
console.log('this is test case 5');
});
});
➜ mocha_demo mocha test.js
my suite 2
this is test case 4
- my test case 4
this is test case 5
✓ my test case 5
1 passing (8ms)
1 pending
this.skip()
后面一般不会有其他代码。
一次性跳过多个测试的话,我们可以在’before’ hook里使用this.skip()
下面例子中的beforeEach()
就使用了this.skip()
来跳过测试
'use strict';
const assert = require('assert');
let checkEnv = false;
describe('my suite 2', function(){
beforeEach(function(){
if (checkEnv === true){
console.log('Env is ready, lets do sth');
} else {
this.skip();
};
});
it('my test case 4', function(){
console.log('this is test case 4');
});
it('my test case 5', function(){
console.log('this is test case 5');
});
});
➜ mocha_demo mocha test.js
my suite 2
- my test case 4
- my test case 5
0 passing (6ms)
2 pending
重跑测试
当一个测试失败的时候,我们可以使用this.retries(number)
来重新执行失败的测试。这个功能是为了处理E2E功能测试失败而设计的,所以不推荐在单元测试里面使用。
下面例子表明在describe()
里面使用this.retries(number)
会作用于全部的测试用例,对于Hooks,只会作用于beforeEach()
和afterEach()
describe('retries', function() {
// Retry all tests in this suite up to 4 times
this.retries(4);
beforeEach(function () {
browser.get('http://www.yahoo.com');
});
it('should succeed on the 3rd try', function () {
// Specify this test to only retry up to 2 times
this.retries(2);
expect($('.foo').isDisplayed()).to.eventually.be.true;
});
});
动态生成测试
Mocha
使用Function.prototype.call
和函数表达式来定义测试套件和测试用例,这样能够很方便的动态生成测试用例。不需要特别的语法,只需要js就可以实现类似参数化测试的功能。
var assert = require('chai').assert;
function add() {
return Array.prototype.slice.call(arguments).reduce(function(prev, curr) {
return prev + curr;
}, 0);
}
describe('add()', function() {
var tests = [
{args: [1, 2], expected: 3},
{args: [1, 2, 3], expected: 6},
{args: [1, 2, 3, 4], expected: 10}
];
tests.forEach(function(test) {
it('correctly adds ' + test.args.length + ' args', function() {
var res = add.apply(null, test.args);
assert.equal(res, test.expected);
});
});
});
上面代码会产生三个测试用例
➜ mocha_demo mocha test.js
add()
✓ correctly adds 2 args
✓ correctly adds 3 args
✓ correctly adds 4 args
3 passing (7ms)