当前位置: 首页 > 后端技术 > Node.js

如何使用mocha和sinon集成单元测试——单元测试实例与解析(上)

时间:2023-04-03 16:57:01 Node.js

使用mocha集成单元测试(上)项目地址:https://github.com/Jay-tian/j...安装依赖yarnaddjquerymochamochawesomeistanbulsinonchaijsdomdecachebabel-clibabel-corebabel-preset-es2015babel-plugin-module-resolverbabel-istanbulmocha:测试框架mochawesome:visualreportistanbul:coveragesinon:replacedependencieschai:assertscriptscommandcommand"scripts":{"test":"mocha--timeout5000--递归--reportermochawesome--requirebabel-core/registertests/src&&打开mochawesome-report/mochawesome.html&&npmruntest:cover","test:cover":"babel-node./node_modules/.bin/babel-istanbulcover_mocha--tests/src/*-Rspec--recursive&&opencoverage/lcov-report/index.html","test:s":"mocha--recursive--requirebabel-core/注册--timeout5000"}test命令:执行单元测试,并打开测试报告页面和覆盖率页面test:cover执行生成单元测试覆盖率并打开test:s执行单个单元测试文件参数分析--timeout5000超时设置--recursive包含子目录--reportermochawesome通过mochawesome生成报告--requirebabel-core/register通过babel单元测试目录路径openmochawe翻译es6语法tests/srcsome-report/mochawesome.html打开页面测试包含jQuery的代码初始化Jquery环境let{JSDOM}=require('jsdom');letdom=newJSDOM(``,{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');测试点击事件const{demo1}=require('../../src/demo1.js');constassert=require('chai').assert;describe('demo1',function(){it('jqueryclicktest',function(){demo1($('??body'));assert.equal($('body').hasClass('hide'),false);$('body').trigger('click');assert.equal($('body').hasClass('hide'),true);});});运行结果上面的测试,当点击一个元素的时候,给元素添加一个'hide'类方法来模拟jquery环境并触发click事件来测试post事件由于jquery环境的初始化比较常见,我们把它在工具类中引用utils.jsconstdecache=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,},参数);letdom=newJSDOM(`${html}`,参数);global.window=dom.window;decache('jquery');global.$=require('jquery');}因为在node环境下,require会有缓存,导致不同单元测试之间初始环境不一致,需要手动清除缓存decache('jquery');test.demo2.js从'../../src/demo2.js'导入帖子;constutils=require('./../utils');constsinon=require('sinon');require('./../utils');describe('demo2',function(){before(function(){utils.initJquery('');});it('jquerypost',function(){让stubPost=sinon.stub($,'post');让expectedUrl='/demo2';让expectedParams={'a':'abc'};post();sinon.assert.calledWith(stubPost,expectedUrl,expectedParams);tubPost.restore();});});restore()操作将恢复被替换的对象mocha有四个钩子方法beforerunoncebeforeallunittestsrunafterrunonceafterallunittestrunsbeforeEachRunoncebeforeeachunittestrunafterEachjsexport默认函数(){$.ajax({type:'GET',url:null,async:true,promise:true,dataType:'json',beforeSend(request){}});}test.demo3.jsimport来自'../../src/demo3.js'的ajax;constutils=require('./../utils');constsinon=require('sinon');要求('./../utils');describe('demo3',function(){before(function(){utils.initJquery('');});it('jqueryajax',function(){letstubAjax=sinon.stub($,'ajax');letexpectedParams={type:'GET',url:null,async:true,promise:true,dataType:'json'};ajax();sinon.assert.calledWithMatch(stubAjax,expectedParams);stubAjax.restore();});});这里我们使用calledWithMatch断言参数,可以断言输入参数是否正确,不需要传入所有参数来测试异步代码demoe4.jsexportdefaultfunction(){$('#demo4').hide();setTimeout(function(){$('#demo4').show();},1000);}从'../../src/demo4.js'导入demo4;constutils=require('./../utils');constsinon=require('sinon');constassert=require('chai').assert;require('./../utils');describe('异步代码',function(){letclock;before(function(){utils.initJquery('

');});it('testbysetTimeout',function(done){let$demo=$('#demo4');demo4();assert.equal($demo.css('display'),'none');lettest=function(){assert.equal($demo.css('display'),'block');//donehere告诉本次单元测试结束,保证不影响其他单元测试done();};setTimeout(test,1001);});it('testbysinon',function(){//当使用useFakeTimers时,事件会停止clock=sinon.useFakeTimers();let$d情绪=$('#demo4');//在运行demo4之前,元素仍然显示assert.equal($demo.css('display'),'block');演示4();//运行demo4后,元素隐藏assert.equal($demo.css('display'),'none');//时间穿梭101ms,定时器代码还没有执行,所以元素还是隐藏的clock.tick(101);assert.equal($demo.css('display'),'none');//时间又经过了900ms,到达1001ms后,执行定时器代码,所以元素现在显示clock.tick(900);断言。等于($demo.css('显示'),'块');//恢复时间clock.restore();});});第一次单元测试使用setTimeout测试异步代码第二次单元测试使用Sinon的时空穿梭测试异步代码结果如图,第一次单元测试耗时1035ms,第二次单元测试几乎没有耗时。因此,在写异步代码的单元测试时,第二个单元测试多写在需要测试的代码中包含其他业务逻辑时demo5.jsconstdemo5require=require('./demo5.require.js');exportdefaultfunction(){if(demo5require.a()=='a'){返回1;}else{返回2;}}test.demo5.js从'../../src/demo5.js'导入demo5;constutils=require('./../utils');constsinon=require('sinon');constassert=require('chai').assert;describe('demo5',function(){before(function(){utils.initJquery('');});it('test',function(){assert.equal(demo5(),1);constdemo5require=require('../../src/demo5.require.js');letstub=sinon.stub(demo5require,'a').returns('b');assert.equal(demo5(),2);stub.restore();});});这时候demo5依赖了其他模块,我们可以替换掉demo5require的方法并指定返回值,这样就不需要和依赖它的模块做任何业务了。测试结束后,将替换的对象恢复到webpack环境中。在编写单元测试时,webpack中会有别名。这样,单元测试引入的模块的路径可能是错误的。这里我们可以使用babel-plugin-module-resolver来代替alias.babelrc{"presets":["es2015"],"plugins":[["module-resolver",{"root":["./"],"alias":{"common":""}}]]}运行结果执行命令npmruntest,如图,最佳实践总结文件名和路径定义如下,这个定义规范了路径的写法,方便文件查找/a/b/c/demo.js//待测文件/tests/a/b/c/test.demo.js//单元测试文件A单元测试文件测试一个js文件,一个describe测试一个方法,一个it测试一个方法中的逻辑,这样一个测试只验证一个行为使用sinon隔离外部调用使用before或beforeEach初始环境使用after或afterEach清除或还原环境,不同单元测试互不影响,状态不共享