怎样运用 mocha 和 sinon 集成单元测试--单元测试示例及剖析(上)

运用 mocha 集成单位测试(上)

项目地点:
https://github.com/Jay-tian/j…

装置依靠

yarn add jquery mocha  mochawesome  istanbul  sinon chai jsdom decache babel-cli babel-core babel-preset-es2015 babel-plugin-module-resolver babel-istanbul  

mocha:测试框架
mochawesome:可视化报表
istanbul:覆蓋率
sinon:替代依靠
chai:断言

scripts 敕令

敕令

  "scripts": {
    "test": "mocha --timeout 5000 --recursive --reporter mochawesome --require babel-core/register tests/src && open mochawesome-report/mochawesome.html && npm run test:cover",
    "test:cover": "babel-node ./node_modules/.bin/babel-istanbul cover _mocha -- tests/src/* -R spec --recursive && open coverage/lcov-report/index.html",
    "test:s": "mocha --recursive --require babel-core/register  --timeout 5000"
  }

test 敕令:实行单位测试,并翻开测试报告页面和覆蓋率页面
test:cover 实行天生单位测试覆蓋率并翻开
test:s 实行单个单位测试文件

参数剖析

–timeout 5000 超时设置
–recursive 包括子目次
–reporter mochawesome 经由过程mochawesome天生报表
–require babel-core/register 经由过程babel转译es6语法
tests/src 单位测试目次途径
open mochawesome-report/mochawesome.html 翻开页面

测试含有jQuery的代码

初始化Jquery环境

let { JSDOM } = require('jsdom');
let dom = new JSDOM(`<!DOCTYPE html><html><body></body></html>`,{
    url: 'http://127.0.0.1',
    referrer: 'http://127.0.0.1',
    contentType: 'text/html',
    userAgent: 'Mellblomenator/9000',
    includeNodeLocations: true,
});
global.window = dom.window;
global.$ = require('jquery');

测试click事宜

const { demo1 } = require('../../src/demo1.js');
const assert = require('chai').assert;
describe('demo1', function() {
  it('jquery click test', function() {
    demo1($('body'));
    assert.equal($('body').hasClass('hide'), false);
    $('body').trigger('click');
    assert.equal($('body').hasClass('hide'), true);
  });
});

运转效果

以上测试了,点击元素时,给该元素增加一个‘hide’类的要领
模仿jquery环境和触发click事宜
《怎样运用 mocha 和 sinon 集成单元测试--单元测试示例及剖析(上)》

测试post事宜

由于初始化jquery环境比较通用,我们把它放到东西类去援用

utils.js

const decache = require('decache');
let { JSDOM } = require('jsdom');
exports.initJquery = function(html, params = {}){
    params = Object.assign({
        url: 'http://127.0.0.1',
        referrer: 'http://127.0.0.1',
        contentType: 'text/html',
        userAgent: 'Mellblomenator/9000',
        includeNodeLocations: true,
    }, params);
    let dom = new JSDOM(`<!DOCTYPE html><html><body>${html}</body></html>`, params);

    global.window = dom.window;
    decache('jquery');
    global.$ = require('jquery');
}

由于node环境中,require会有缓存,致使差别的单位测试间的初始环境不一致,须要手动消灭缓存

 decache('jquery');

test.demo2.js

import post from '../../src/demo2.js'; 
const utils = require('./../utils');
const sinon = require('sinon');
require('./../utils');
describe('demo2', function() {
    before(function() {
        utils.initJquery('');
    });
    
    it('jquery post', function() {
        let stubPost = sinon.stub($, 'post');
        let expectedUrl = '/demo2';
        let expectedParams = {'a': 'abc'};
        post();
        sinon.assert.calledWith(stubPost, expectedUrl, expectedParams);
        stubPost.restore();
    });
});

restore()操纵 将会回复被替代的对象
mocha 有四个钩子要领
before 在一切的单位测试运转前运转一次
after 在一切的单位测试运转完毕运转一次
beforeEach 在每个的单位测试运转前运转一次
afterEach 在每个的单位测试运转后运转一次

测试ajax

demo3.js

export default function() {
    $.ajax({
    type: 'GET',
    url: null,
    async: true,
    promise: true,
    dataType: 'json',
    beforeSend(request) {
    }
  });
}

test.demo3.js

import ajax from '../../src/demo3.js'; 
const utils = require('./../utils');
const sinon = require('sinon');
require('./../utils');
describe('demo3', function() {
    before(function() {
        utils.initJquery('');
    });
    
    it('jquery ajax', function() {
        let stubAjax = sinon.stub($, 'ajax');
        let expectedParams = {
            type: 'GET',
            url: null,
            async: true,
            promise: true,
            dataType: 'json'
        };
        ajax();
        sinon.assert.calledWithMatch(stubAjax, expectedParams);
        stubAjax.restore();
    });
});

这里我们运用calledWithMatch断言参数,该要领能够断言传入的参数是不是准确,不须要传入一切的参数

测试异步代码

demoe4.js

export default function() { 
    $('#demo4').hide();
    setTimeout(
        function(){
            $('#demo4').show();
        }, 1000);
}
import demo4 from '../../src/demo4.js'; 
const utils = require('./../utils');
const sinon = require('sinon');
const assert = require('chai').assert;
require('./../utils');
describe('asynchronous code', function() {
    let clock;
    before(function () { 
        utils.initJquery('<div id="demo4"></div>');
       
    });

    it('test by setTimeout', function(done) {
        let $demo = $('#demo4');
        demo4();
        assert.equal($demo.css('display'), 'none');
        let test = function() {
            assert.equal($demo.css('display'), 'block');
            // 这里的done示知这个单位测试完毕了,保证不影响其他单位测试
            done();
        };

        setTimeout(test, 1001);
    });

    it('test by sinon', function() {
        //当利用了useFakeTimers后,事宜将会住手
        clock = sinon.useFakeTimers(); 
        let $demo = $('#demo4');
        //运转demo4前,元素照样显现的
        assert.equal($demo.css('display'), 'block');
        demo4();
        //运转demo4完,元素隐蔽了
        assert.equal($demo.css('display'), 'none');
        //时候穿越101ms秒,定时器代码还未实行,所以元素照样隐蔽的
        clock.tick(101);
        assert.equal($demo.css('display'), 'none');
        //时候再穿越900ms秒,就抵达了1001ms后,定时器代码实行了,所以元素如今显现了
        clock.tick(900);
        assert.equal($demo.css('display'), 'block');
        //恢复时候
        clock.restore();
    });
});

第一个单位测试利用了 setTimeout 去测试异步代码
第二个单位测试利用了 sinon 的时空穿越器去测试异步代码
效果如图所示
《怎样运用 mocha 和 sinon 集成单元测试--单元测试示例及剖析(上)》

第一个单位测试花了1035ms
而第二个单位测试几乎没有消费若干时候

所以异步代码编写单位测试时,第二个单位测试写法更优

须要测试的代码包括其他担任营业逻辑时

demo5.js

const demo5require = require('./demo5.require.js');

export default function() { 
    if(demo5require.a() == 'a') {
        return 1;
    } else {
        return 2;
    }
}

test.demo5.js

import demo5 from '../../src/demo5.js'; 
const utils = require('./../utils');
const sinon = require('sinon');
const assert = require('chai').assert;

describe('demo5', function() {
    before(function () { 
        utils.initJquery('');
    });
    it('test', function() {
        assert.equal(demo5(), 1);
        const demo5require = require('../../src/demo5.require.js');
        let stub = sinon.stub(demo5require, 'a').returns('b');
        assert.equal(demo5(), 2);
        stub.restore();
    });
});

此时demo5依靠其他模块,我们就能够替代demo5require的要领,并指定返回值,如许就不必关联依靠的模块做了什么营业。
测试完毕,回复被替代的对象

webpack环境编写单位测试

webpack中会有设置别号的状况,如许单位测试有能够引入的模块的途径有误,这里我们能够运用babel-plugin-module-resolver举行别号的替代

.babelrc

{
  "presets": ["es2015"],
  "plugins": [
    ["module-resolver", {
      "root": ["./"],
      "alias": {
         "common":""
      }
    }]
  ]
}

运转效果

实行敕令

npm run test

如图
《怎样运用 mocha 和 sinon 集成单元测试--单元测试示例及剖析(上)》
《怎样运用 mocha 和 sinon 集成单元测试--单元测试示例及剖析(上)》
《怎样运用 mocha 和 sinon 集成单元测试--单元测试示例及剖析(上)》

最好实践总结

  • 文件名以及途径的定义以下,如许定义范例了途径的誊写,便于文件的查找
/a/b/c/demo.js //待测试文件
/tests/a/b/c/test.demo.js //单位测试文件
  • 一个单位测试文件测试一个js文件
  • 一个describe测试一个要领
  • 一个it 测试一个要领中一个逻辑,如许保证一个测试只考证一个行动
  • 运用sinon断绝外部挪用
  • 运用before或beforeEach 初始环境
  • 运用after或afterEach 清空或复原环境,差别单位测试互不影响,状况不同享
    原文作者:jay_tian
    原文地址: https://segmentfault.com/a/1190000014062596
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞