mocha学习笔记1

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)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,911评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,014评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,129评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,283评论 1 264
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,159评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,161评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,565评论 3 382
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,251评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,531评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,619评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,383评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,255评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,624评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,916评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,199评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,553评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,756评论 2 335

推荐阅读更多精彩内容