作者:bugall微信:bugallF邮箱:769088641@qq.com项目地址:https://github.com/bugall/nod...为什么我们需要Sinon在开发前端项目中,经常需要根据后端返回的数据来渲染页面。我们通常使用AJAX向服务器发送请求。我们在开发后端逻辑的时候,有时需要连接数据库,根据从数据库中获取的数据来执行后续的逻辑代码或者其他依赖,甚至更加复杂和棘手。这些开发都有一个共同的局限性,就是会依赖于其他服务,需要其他系统的支持。比如我们使用Ajax来请求网络,就需要有一个服务器来响应相应的请求。对于数据库,您需要设置一个测试数据库以进行测试。所有这些都意味着编写和运行测试更加困难,因为您需要做额外的工作来准备和设置测试成功的环境。值得庆幸的是,我们可以使用sinon.js避免所有这些麻烦。我们可以利用它的特性将上面的例子减少到几行代码。然而,第一次遇到间谍、存根、模拟可能会很棘手。很难选择何时使用哪个功能。他们也有一些问题,所以你需要知道你应该使用什么功能来解决什么样的问题。在本文中,我将向您展示何时以及如何使用间谍、存根和模拟,并为您提供一组最佳实践来帮助您避免常见的陷阱。什么是SinonSinon拥有独立的spies、stubs、mock功能,Sinon并不是一个独立的测试框架,它只是在测试中提供了以上三个功能,比如我们常用的测试框架Mocha,Sinon并不能完全替代Mocha的功能。Sinon通过所谓的测试替身轻松消除了测试的复杂性。顾名思义,Test-double是测试中使用的真实代码逻辑的替代品。回过头来看Ajax的例子,我们不需要搭建服务器,而是使用Ajax的替换代码。我们用不请求服务器返回预先设置的数据来代替Ajax的逻辑。这听起来不可思议,但基本概念非常简单。因为JavaScript是动态的,我们可以在调用方法时用任何函数替换它。在Sinon中,我们可以用测试逻辑替换任何JavaScript函数,然后使测试复杂的事情变得更加容易。spies的概念顾名思义,我们简单的称spies为spy函数,spy函数是Sinon中最简单的部分,其他的函数都建立在spies之上,spies的主要目的是收集函数调用的信息.你也可以用它们来帮助验证一些事情,比如某个函数是否被调用等。就像电影《窃听风云》里一样,监控室里有人进进出出,做了什么,监控过程不被房间里的人察觉。同样,spies监听的实现基本不会影响函数本身的正常调用(不会影响被监听函数的上下文)。当然,我们需要在房间里偷偷安装窃听器,那么间谍窃听器如何实现呢?后面会介绍存根的概念。它们具有间谍的所有功能,而不是监视某个功能的调用,它们完全取代了这个功能。换句话说,当使用spies时,原来的函数还在运行,但是当使用stubs时,函数将没有原来的功能,而是被替换的函数。mock的概念mock和stubfunction都是用来替换指定的函数,如果要替换一个对象中的多个方法,那么mock可以发挥作用,但是如果只是替换对象中的一个函数,那么stub就更简单了使用。我们在使用mocks的时候要非常小心,因为对原有代码逻辑的大量替换会导致测试变得脆弱。有关函数调用的信息。例如,间谍可以告诉我们一个函数被调用的次数,每次调用的参数,返回值,抛出的错误等等。所以当测试的目的是验证发生了什么时,间谍就是一个好的选择。结合诗乃所说的,我们可以用一个简单的间谍来检查不同的结果。最常见的间谍场景包括:检查一个函数被调用了多少次('shouldcallsaveonce',function(){varsave=sinon.spy(Database,'save');setupNewUser({name:'test'},function(){});save.restore();sinon.assert.calledOnce(save);});检查传递给函数的参数it('shouldpassobjectwithcorrectvaluestosave',function(){varsave=sinon.spy(Database,'save');varinfo={name:'test'};varexpectedUser={name:info.name,nameLowercase:info.name.toLowercase()};setupNewUser(info,function(){});save.restore();sinon.assert.calledWith(save,expectedUser);});存根就像间谍,只是它们取代了目标功能。它们还可以包含自定义行为,例如返回值或抛出异常。他们甚至可以自动调用作为参数提供的任何回调函数。存根有几种常见用途:您可以使用它们来替换有问题的代码部分您可以使用它们来触发不会触发的代码路径,例如错误处理您可以使用它们来帮助更轻松地测试异步代码存根可用于替换有问题的代码代码,即使写出来也很难测试。这通常是由外部网络连接、数据库或其他非JavaScript引起的。这些的问题是它们通常需要手动设置。例如,在运行测试之前,我们需要将测试数据填充到一个数据库中,这使得运行和编写变得更加复杂。it('应该将具有正确值的对象传递给保存',function(){varsave=sinon.stub(Database,'save');varinfo={name:'test'};varexpectedUser={name:info.name,nameLowercase:info.name.toLowercase()};setupNewUser(info,function(){});save.restore();sinon.assert.calledWith(save,expectedUser);});通过用存根替换数据库,对于相关功能,我们不再需要实际的数据库来进行测试。几乎在任何情况下,类似的方法都可以用于难以测试的代码。存根也可用于触发不同的代码路径。如果我们正在测试的代码调用另一个函数,我们有时需要测试它在异常情况下的行为,我们可以使用存根从代码中触发错误:它('如果保存失败,应该将错误传递到回调中',function(){varexpectedError=newError('oops');varsave=sinon.stub(Database,'save');save.throws(expectedError);varcallback=sinon.spy();setupNewUser({name:'foo'},callback);save.restore();sinon.assert.calledWith(callback,expectedError);});mock主要用在stub的时候想验证多个特定的行为例如下面是我们使用mock验证一个更具体的数据库保存场景:it('shouldpassobjectwithcorrectvaluestosaveonlyonce',function(){varinfo={name:'test'};varexpectedUser={name:info.name,nameLowercase:info.name.toLowercase()};vardatabase=sinon.mock(数据库);database.expects('保存').once().withArgs(expectedUser);setupNewUser(info,function(){});database.verify();database.restore();});Sinon的实现原理spiesconstsinon={spyObjs:{},spy:function(obj,method){constself=this;this.spyObjs['spy#:'+(Object.keys(self.spyObjs).lengthh+1)]={}this.proxy(obj,方法);},proxy:function(obj,method){constdescriptor=Object.getOwnPropertyDescriptor(obj,method);constdelegateFlag='spy#:'+Object.keys(sinon.spyObjs).length;this.spyObjs[delegateFlag]={delegateValue:descriptor.value,delegateObject:obj}Object.defineProperty(obj,method,Object.getOwnPropertyDescriptor(this,'invoke'))},invoke:function(name){console.log('参数%s,被调用了',name)constdelegateFlag='spy#:'+Object.keys(sinon.spyObjs).length;sinon.spyObjs[delegateFlag].delegateValue.apply(sinon.spyObjs[delegateFlag].delegateObject)}}vartestFlag={sayHello:function(name){console.log('Hello:%s',name)},whoAmI:function(){this.sayHello('bugall')console.log('我是谁')}}sinon.spy(testFlag,'whoAmI');testFlag.whoAmI('bugall')
