mocha学习笔记

第一部分

Posted by DanteYu on September 26, 2017

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/*.jsMocha寻找的目标。

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.jsexpect.jschai等验证库来完成验证功能。推荐使用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)