使用 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事件

测试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 的时空穿梭器去测试异步代码
结果如图所示

第一个单元测试花了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

如图


最佳实践总结

  • 文件名以及路径的定义如下,这样定义规范了路径的书写,便于文件的查找
/a/b/c/demo.js //待测试文件
/tests/a/b/c/test.demo.js //单元测试文件
  • 一个单元测试文件测试一个js文件
  • 一个describe测试一个方法
  • 一个it 测试一个方法中一个逻辑,这样保证一个测试只验证一个行为
  • 使用sinon隔离外部调用
  • 使用before或beforeEach 初始环境
  • 使用after或afterEach 清空或还原环境,不同单元测试互不影响,状态不共享

如何使用 mocha 和 sinon 集成单元测试--单元测试示例及分析(上)相关推荐

  1. 前端新手使用karma+mocha+chai+sinon 进行angularjs 单元测试

    这里只谈如何搭建环境. 选型:在jasmine 和 mocha+chai中间比较了一下,根据项目需要选择了后者. 安装依赖包和环境 直接执行下列命令安装或在项目目录下 运行npm install(pa ...

  2. cobertura覆盖率_Cobertura和Maven:集成和单元测试的代码覆盖率

    cobertura覆盖率 在姜黄项目中,我们每晚维护一个仪表板. 在仪表板上,我们收集有关项目的统计信息,包括代码覆盖率,findbugs分析和其他指标. 我们一直在使用Maven EMMA插件来提供 ...

  3. Cobertura和Maven:集成和单元测试的代码覆盖率

    在姜黄项目中,我们每晚维护一个仪表板. 在仪表板上,我们收集有关项目的统计信息,包括代码覆盖率,findbugs分析和其他指标. 我们一直在使用Maven EMMA插件来提供代码覆盖,但是遇到了EMM ...

  4. 持续集成之单元测试篇——WWH(讲讲我们做单元测试的故事)

    持续集成之单元测试篇--WWH(讲讲我们做单元测试的故事) 前言 临近上线的几天内非重大bug不敢进行发版修复,担心引起其它问题(摁下葫芦浮起瓢) 尽管我们如此小心,仍不能避免修改一些bug而引起更多 ...

  5. JUnit与Mockito单元测试典型示例

    单元测试的重要性无需赘述,但单元测试也会遇到困难,其中之一就是如何创建依赖.试想我们常见的server端分层架构,数据访问层Dao,业务层,以及Web层,想要单元测试业务层,我们需要依赖Dao层提供数 ...

  6. spring 项目中集成 Protocol Buffers 示例

    http://blog.csdn.net/fangzhangsc2006/article/details/8687388 本文适用于了解spring框架,同时想在spring项目中使用Protocol ...

  7. MKS上游和下游集成式压力控制器的技术分析及其替代解决方案

    摘要:目前的MKS系列集成式压力控制器本质上是一种流量调节和测量装置,无法直接用来进行准确的压力控制,而且MKS压力控制器还存在测量精度不高.压力控制范围有限和对工作介质洁净度要求很高的不足.为此,为 ...

  8. 【前端测试与集成】使用mocha和sinon进行单元测试

    目录 简介 编写测试 Spy Mock Stub Test a Promise xhr Hooks Coverage 简介 Mocha是流行的JavaScript测试框架之一,通过它添加和运行测试,从 ...

  9. Django 4.x Test 单元测试使用示例和配置方法

    大家好,我是Mr数据杨,来到项目配置的部分,这就像在打仗之前要做充足的筹备,就如同周瑜在赤壁之战前,准备连环计,火烧曹操的战船.而创建项目和应用就像建立起各自的军队,一如刘备.曹操.孙权他们分别建立起 ...

最新文章

  1. JavaScript中函数作为另一个函数的参数的时候它存在于哪个作用域
  2. 前端一HTML:十八:元素的显示方式
  3. SAP数据表(一)商品表
  4. Linux 内核中的 cdev_alloc和cdev_add
  5. Nemuria UML架构图 第2次迭代. 添加了缓冲层
  6. LeetCode之Find All Numbers Disappeared in an Array
  7. 前端学习(2748):uniapp创建项目和演示
  8. LeetCode 1845. 座位预约管理系统(set)
  9. 操作系统中的文件系统和访问方法
  10. jpa存储byte到postgresql
  11. 管理后台--2.分类列表
  12. FAQ系列 | mysqldump选项之skip-opt
  13. 【爬虫】批量下载B站收藏夹视频 - Python
  14. NLP自然语言处理CRF详解
  15. 前台传入数据后台被转义问题解决
  16. 【控制工程】啥是控制工程?拉开控制工程的帷幕
  17. 转载新闻 你应该知道的一些 Linux 技巧
  18. Java使用itextpdf根据关键词插入图片
  19. uni-app二维码生成,点击按钮弹框展示二维码
  20. 深度学习提高泛化能力的技术

热门文章

  1. 英文论文如何看?转自知乎
  2. 2012年国内薪资最高的IT公司排行
  3. 845透色android10,和平精英TCA845透色
  4. 畅写Office加持公软件云协作特性,让企业办公效率飞起来
  5. HTTP协议与HTTPS协议的区别
  6. c++:警告:warning: catching polymorphic type ‘class std::exception’ by value
  7. 解决:win10搜狗输入法突然无效
  8. 计算机的硬盘维修,计算机硬盘的维修方法和技巧
  9. 风险中性的深度学习选股策略
  10. 算法提高 盾神与积木游戏