sinon spy

本文由Mark Brown和MarcTowler进行了同行评审。 感谢所有SitePoint的同行评审员使SitePoint内容达到最佳状态!

编写单元测试时,最大的绊脚石之一就是当您拥有不平凡的代码时该怎么办。

在现实生活中的项目中,代码通常会执行各种使测试变得困难的事情。 Ajax请求,计时器,日期,访问其他浏览器功能…或者如果您使用的是Node.js,则数据库总是很有趣,网络或文件访问也是如此。

所有这些都很难测试,因为您无法用代码控制它们。 如果您使用的是Ajax,则需要服务器来响应该请求,以使测试通过。 如果使用setTimeout ,则测试必须等待。 对于数据库或网络,这是同一回事–您需要具有正确数据的数据库或网络服务器。

现实生活并不像许多测试教程所显示的那样容易。 但是您知道有解决方案吗?

通过使用Sinon,我们可以使测试非平凡的代码变得微不足道!

让我们看看如何。

是什么使Sinon如此重要和有用?

简而言之, Sinon允许您使用使测试变得简单的东西来替换测试中的困难部分。

测试一段代码时,您不想让它受到测试之外的任何影响。 如果外部因素影响测试,则测试会变得更加复杂,并且可能会随机失败。

如果要测试进行Ajax调用的代码,该怎么做? 您需要运行服务器,并确保它给出了测试所需的准确响应。 设置很复杂,并且使编写和运行单元测试变得困难。

如果您的代码取决于时间呢? 假设它需要等待一秒钟,然后再执行某项操作。 现在怎么办? 您可以在测试中使用setTimeout等待一秒钟,但这会使测试变慢。 想象一下间隔是否更长,例如五分钟。 我猜您可能不想每次运行测试都等待五分钟。

通过使用Sinon,我们可以解决这两个问题(以及许多其他问题),并消除复杂性。

Sinon如何工作?

Sinon通过允许您轻松创建所谓的test-double来帮助消除测试的复杂性。

顾名思义,测试双打是测试中使用的代码的替换。 回顾Ajax示例,而不是设置服务器,我们将用一个test-double代替Ajax调用。 对于时间示例,我们将使用测试双打来允许我们“及时向前移动”。

听起来可能有些奇怪,但是基本概念很简单。 因为JavaScript是动态的,所以我们可以采用任何函数并将其替换为其他函数。 测试双打使这个想法更进一步。 使用Sinon,我们可以用test-double替换任何JavaScript函数,然后可以将其配置为执行多种操作以简化复杂的测试。

Sinon将测试倍数分为三种类型:

  • 间谍 ,提供有关函数调用的信息,而不会影响其行为
  • 存根(Stub)类似于间谍,但完全替代了该功能。 这样就可以使存根函数执行您喜欢的任何操作-引发异常,返回特定值等
  • 伪装 ,通过组合间谍和存根使替换整个对象更加容易

此外,Sinn还提供了一些其他帮助器,尽管它们不在本文讨论范围之内:

  • 伪计时器 ,可用于及时向前移动,例如触发setTimeout
  • 伪XMLHttpRequest和server ,可用于伪造Ajax请求和响应

借助这些功能,Sinon可以帮助您解决外部依赖性在测试中引起的所有难题。 如果您了解有效使用Sinon的技巧,则不需要任何其他工具。

安装锡诺

首先,我们需要安装Sinon。

免费学习PHP!

全面介绍PHP和MySQL,从而实现服务器端编程的飞跃。

原价$ 11.95 您的完全免费

免费获得这本书

对于Node.js测试:

  1. 使用npm install sinon通过npm安装Sinon
  2. 使用var sinon = require('sinon');在测试中需要Sinon

对于基于浏览器的测试:

  1. 您可以通过npm的npm install sinon来安装Sinon,使用CDN或从Sinon的网站下载它
  2. 在测试运行器页面中包含sinon.js

入门

Sinon具有很多功能,但是其中很多功能都是基于自身。 您了解了一部分,而您已经知道了下一部分。 一旦您学习了基础知识并知道每个不同部分的功能,Sinan便可以轻松使用。

当我们的代码调用一个给我们带来麻烦的函数时,我们通常需要Sinon。

使用Ajax,可以是$.getXMLHttpRequest 。 随着时间的推移,该函数可能是setTimeout 。 对于数据库,它可能是mongodb.findOne

为了使讨论此功能更加容易,我将其称为dependency 。 我们正在测试的功能取决于另一个功能的结果。

可以说,Sinon的基本使用模式是用test-double替换有问题的依赖关系

  • 在测试Ajax时,我们用一个test-double替换XMLHttpRequest ,假装发出一个Ajax请求
  • 测试时间时,我们用假装计时器替换setTimeout
  • 在测试数据库访问时,我们可以用test-double替换mongodb.findOne ,它会立即返回一些假数据

让我们看看它在实践中是如何工作的。

间谍

间谍是Sinon最简单的部分,而其他功能则建立在它们之上。

间谍的主要用途是收集有关函数调用的信息。 您还可以使用它们来帮助验证事情,例如是否调用了函数。

var spy = sinon.spy();
//We can call a spy like a function
spy('Hello', 'World');
//Now we can get information about the call
console.log(spy.firstCall.args); //output: ['Hello', 'World']

函数sinon.spy返回一个Spy对象,可以像调用函数一样调用它,但还包含带有有关对其进行任何调用的信息的属性。 在上面的示例中, firstCall属性具有有关第一次调用的信息,例如firstCall.args是传递的参数列表。

尽管您可以通过sinon.spy带任何参数调用sinon.spy来如上所述创建匿名间谍,但更常见的模式是用间谍替换另一个函数。

var user = {
...
setName: function(name){
this.name = name;
}
}
//Create a spy for the setName function
var setNameSpy = sinon.spy(user, 'setName');
//Now, any time we call the function, the spy logs information about it
user.setName('Darth Vader');
//Which we can see by looking at the spy object
console.log(setNameSpy.callCount); //output: 1
//Important final step - remove the spy
setNameSpy.restore();

用间谍代替另一个功能的方式与上一个示例相似,但有一个重要的区别:使用完间谍之后,务必记住要还原原始功能,就像上面示例的最后一行一样。 否则,您的测试可能会发生错误。

间谍具有许多不同的属性,它们提供了有关如何使用它们的不同信息。 Sinon的间谍文档包含所有可用选项的完整列表。

实际上,您可能不会经常使用间谍。 您更可能需要存根,但是间谍可以很方便地例如验证回调是否被调用:

function myFunction(condition, callback){
if(condition){
callback();
}
}
describe('myFunction', function() {
it('should call the callback function', function() {
var callback = sinon.spy();
myFunction(true, callback);
assert(callback.calledOnce);
});
});

在此示例中,我使用Mocha作为测试框架,并使用Chai作为声明库。 如果您想了解有关这两种方法的更多信息,请查阅我以前的文章: 使用Mocha和Chai对JavaScript进行单元测试 。

请参阅PenSinn教程: CodePen上的SitePoint ( @SitePoint )进行的具有嘲笑,间谍和存根的JavaScript测试 。

诗乃的断言

在继续讨论存根之前,让我们先走一下弯路,看看Sinon的主张 。

在带有间谍(和存根)的大多数测试情况下,您需要某种方式来验证测试结果。

我们可以使用任何类型的断言来验证结果。 在上一个带有回调的示例中,我们使用了Chai的assert函数来确保该值是真实的。

assert(callback.calledOnce);

问题是失败中的错误消息不清楚。 您将简单地被告知“假不是真”,或者对它的某种变体。 您可能会想像到,找出问题所在并不是很有帮助,您需要查看源代码以进行测试。 不好玩。

为了解决该问题,我们可以在断言中包含一个自定义错误消息。

assert(callback.calledOnce, 'Callback was not called once');

但是,当我们可以使用Sinon自己的主张时,为什么还要打扰呢?

describe('myFunction', function() {
it('should call the callback function', function() {
var callback = sinon.spy();
myFunction(true, callback);
sinon.assert.calledOnce(callback);
});
});

像这样使用Sinon的断言可以使我们获得更好的错误信息。 当您需要验证更复杂的条件(例如函数的参数)时,这将非常有用。

这是锡诺提供的其他有用断言的一些示例:

  • sinon.assert.calledWith可用于验证使用特定参数调用的函数(这可能是我最常使用的函数)
  • sinon.assert.callOrder可以验证以特定顺序调用了函数

与间谍一样, Sinn的断言文档具有所有可用选项。 如果你喜欢用柴,也有兴农钗插件可用,它可以让你通过柴的使用兴农断言expectshould接口。

存根

存根具有灵活性和便利性,因此是测试的两倍。 它们具有间谍的所有功能,但存根完全取代了它,而不仅仅是监视函数的功能。 换句话说,使用间谍程序时,原始功能仍会运行,但使用存根程序则不会。

这使存根非常适合许多任务,例如:

  • 替换使测试缓慢且难以编写的Ajax或其他外部调用
  • 根据函数输出触发不同的代码路径
  • 测试异常条件,例如引发异常时会发生什么?

我们可以以类似于间谍的方式创建存根...

var stub = sinon.stub();
stub('hello');
console.log(stub.firstCall.args); //output: ['hello']

我们可以像创建间谍一样创建匿名存根,但是当您使用存根替换现有功能时,它们将变得非常有用。

例如,如果我们有一些使用jQuery Ajax功能的代码,则很难进行测试。 该代码将请求发送到我们已配置的任何服务器,因此我们需要使其可用,或在代码中添加特殊情况以在测试环境中不执行该请求-这是一个很大的禁忌。 您几乎永远都不会在代码中包含特定于测试的案例。

除了使用不良做法,我们可以使用Sinon并用存根替换Ajax功能。 这使得测试变得微不足道。

这是我们将测试的示例功能。 它以对象作为参数,然后通过Ajax将其发送到预定义的URL。

function saveUser(user, callback) {
$.post('/users', {
first: user.firstname,
last: user.lastname
}, callback);
}

通常,由于Ajax调用和预定义的URL,测试起来很困难,但是如果我们使用存根,则变得很容易。

假设我们要确保在请求完成后正确调用传递给saveUser的回调函数。

describe('saveUser', function() {
it('should call callback after saving', function() {
//We'll stub $.post so a request is not sent
var post = sinon.stub($, 'post');
post.yields();
//We can use a spy as the callback so it's easy to verify
var callback = sinon.spy();
saveUser({ firstname: 'Han', lastname: 'Solo' }, callback);
post.restore();
sinon.assert.calledOnce(callback);
});
});

请参阅PenSinn教程: CodePen上的SitePoint ( @SitePoint )进行的具有嘲笑,间谍和存根的JavaScript测试 。

在这里,我们用存根替换Ajax函数。 这意味着该请求永远不会发送,并且我们不需要服务器或其他任何东西-我们可以完全控制测试代码中发生的一切!

因为我们要确保传递给saveUser的回调被调用,所以我们将指示存根让步 。 这意味着存根会自动调用作为参数传递给它的第一个函数。 这模仿了$.post的行为,一旦请求完成,它将调用回调。

除了存根,我们还在此测试中创建间谍。 我们可以使用普通函数作为回调,但是使用间谍可以使用Sinon的sinon.assert.calledOncesinon.assert.calledOnce断言轻松验证测试结果。

在大多数情况下,需要存根时,可以遵循相同的基本模式:

  • 查找有问题的函数,例如$.post
  • 看一下它是如何工作的,以便您可以在测试中模仿它
  • 创建存根
  • 设置存根,使其具有测试所需的行为

存根不需要模仿所有行为。 仅需要测试所需的行为,其他任何东西都可以忽略。

存根的另一种常见用法是验证是否使用一组特定的参数调用了函数。

例如,对于我们的Ajax功能,我们要确保发送正确的值。 因此,我们可能会遇到以下情况:

describe('saveUser', function() {
it('should send correct parameters to the expected URL', function() {
//We'll stub $.post same as before
var post = sinon.stub($, 'post');
//We'll set up some variables to contain the expected results
var expectedUrl = '/users';
var expectedParams = {
first: 'Expected first name',
last: 'Expected last name'
};
//We can also set up the user we'll save based on the expected data
var user = {
firstname: expectedParams.first,
lastname: expectedParams.last
}
saveUser(user, function(){} );
post.restore();
sinon.assert.calledWith(post, expectedUrl, expectedParams);
});
});

请参阅PenSinn教程: CodePen上的SitePoint ( @SitePoint )进行的具有嘲笑,间谍和存根的JavaScript测试 。

同样,我们为$.post()创建一个存根,但是这次我们不将其设置为yield。 该测试不关心回调,因此没有必要让它产生。

我们设置了一些变量以包含预期的数据-URL和参数。 设置这样的变量是一个好习惯,因为这样一眼就可以轻松看出测试的要求。 它还可以帮助我们设置user变量,而无需重复这些值。

这次我们使用了sinon.assert.calledWith()断言。 我们将存根作为其第一个参数传递,因为这一次我们要验证存根是否使用正确的参数调用。

还有一种在Sinon中测试Ajax请求的方法。 这是通过使用Sinon伪造的XMLHttpRequest功能。 我们在这里不做详细介绍,但是如果您想了解它的工作原理,请参阅我的关于使用Sinon的伪XMLHttpRequest进行Ajax测试的文章 。

cks

嘲笑是存根的另一种处理方法。 如果您听说过“模拟对象”一词,那是同一回事– Sinon的模拟可用于替换整个对象并更改其行为,类似于存根函数。

如果您需要在单个对象中存根多个函数,它们主要有用。 如果只需要替换一个功能,则存根更易于使用。

使用模拟程序时应多加注意! 由于它们的强大功能,很容易使测试过于具体(测试太多和太具体的事物),这会使测试无意间变得脆弱。

与间谍和存根不同,模拟具有内置的断言。 通过告诉模拟对象需要做什么,然后在测试结束时调用验证函数,可以预先定义期望的结果。

假设我们正在使用store.js将内容保存到localStorage,并且我们想测试与此相关的功能。 我们可以使用模拟来帮助测试它,如下所示:

describe('incrementStoredData', function() {
it('should increment stored value by one', function() {
var storeMock = sinon.mock(store);
storeMock.expects('get').withArgs('data').returns(0);
storeMock.expects('set').once().withArgs('data', 1);
incrementStoredData();
storeMock.restore();
storeMock.verify();
});
});

请参阅PenSinn教程: CodePen上的SitePoint ( @SitePoint )进行的具有嘲笑,间谍和存根的JavaScript测试 。

使用模拟时,我们使用流畅的调用样式定义预期的调用及其结果,如上所示。 这与使用断言来验证测试结果相同,不同之处在于我们storeMock.verify()定义了它们,并在测试结束时调用storeMock.verify()来验证它们。

在兴农的模拟对象的术语,将mock.expects('something')带来的期望mock.something() ,方法mock.something()预计会被调用。 除了特定于模拟的功能外,每个期望都支持与间谍和存根相同的功能。

您可能会发现,使用存根比使用模拟常容易得多,这很好。 子应小心使用。

有关特定于模拟的功能的完整列表,请查看Sinon的模拟文档 。

重要的最佳实践:使用sinon.test()

Sinon有一项重要的最佳实践,每当使用间谍,存根或嘲笑时都应记住。

如果用test-double替换现有函数,请使用sinon.test()

在前面的示例中,我们在使用它们之后使用stub.restore()mock.restore()进行清理。 这是必要的,否则测试重复项将保留在原处,并且可能对其他测试产生负面影响或导致错误。

但是直接使用restore()函数是有问题的。 被测试的函数可能会导致错误并在调用restore()之前终止测试函数!

我们有两种方法可以解决此问题:我们可以将整个内容包装在try catch块中。 这使我们可以将restore()调用放在finally块中,以确保无论它如何运行。

或者,更好的方法是,我们可以用sinon.test()包装测试函数。

it('should do something with stubs', sinon.test(function() {
var stub = this.stub($, 'post');
doSomething();
sinon.assert.calledOnce(stub);
});

在上面的示例中,请注意it()的第二个参数包装在sinon.test() 。 注意的第二件事是我们使用this.stub()而不是sinon.stub()

sinon.test()包装测试允许我们使用Sinon的沙盒功能,从而允许我们通过this.spy()this.stub()this.mock()创建间谍,存根和this.mock() 。 您使用沙箱创建的所有测试双打都会自动清除。

请注意,上面的示例代码没有stub.restore() ,这是不必要的,因为测试已被沙箱化。

如果在可能的情况下使用sinon.test() ,则可以避免测试开始随机失败的问题,因为较早的测试没有由于错误而清除其测试双精度。

诗乃不是魔术

Sinon做很多事情,有时候似乎很难理解它是如何工作的。 让我们看一下Sinon工作原理的一些简单JavaScript例子,以便我们更好地了解它的幕后工作。 这将帮助您在不同情况下更有效地使用它。

我们也可以手动创建间谍,存根和模拟。 我们使用Sinon的原因是它使任务变得微不足道-手动创建它们可能非常复杂,但是让我们看看它是如何工作的,以了解Sinon的工作。

首先,间谍本质上是一个函数包装器:

//A simple spy helper
function createSpy(targetFunc) {
var spy = function() {
spy.args = arguments;
spy.returnValue = targetFunc.apply(this, arguments);
return spy.returnValue;
};
return spy;
}
//Let's spy on a simple function:
function sum(a, b) { return a + b; }
var spiedSum = createSpy(sum);
spiedSum(10, 5);
console.log(spiedSum.args); //Output: [10, 5]
console.log(spiedSum.returnValue); //Output: 15

通过这样的自定义函数,我们可以轻松获得间谍功能。 但是请注意,Sinon的间谍提供了更广泛的功能-包括断言支持。 这使Sinon更加方便。

那存根呢?

要制作一个非常简单的存根,您可以简单地用一个新函数替换一个函数:

var stub = function() { };
var original = thing.otherFunction;
thing.otherFunction = stub;
//Now any calls to thing.otherFunction will call our stub instead

但同样,锡诺的存根具有几个优点:

  • 它们具有完整的间谍功能
  • 您可以使用stub.restore()轻松还原原始行为
  • 您可以针对锡农存根断言

嘲笑仅将间谍和存根的行为结合在一起,从而可以以不同的方式使用它们的功能。

尽管Sinon有时看起来确实很“神奇”,但在大多数情况下,使用您自己的代码也可以很容易地做到这一点。 Sinon比起编写自己的库要方便得多。

结论

测试现实生活中的代码有时看起来过于复杂,并且很容易完全放弃。 但是在Sinon的帮助下,几乎可以测试任何类型的代码。

只要记住主要原则:如果一个函数使您的测试难以编写,请尝试用test-double替换它。 无论该功能做什么,该原则都适用。

是否想了解更多有关如何使用自己的代码应用Sinon的信息? 转到我的网站,我将在真实世界指南中向您发送免费的Sinon ,其中包括Sinon最佳实践,以及三个在不同类型的测试情况下如何应用它的真实示例!

翻译自: https://www.sitepoint.com/sinon-tutorial-javascript-testing-mocks-spies-stubs/

sinon spy

sinon spy_Sinon教程:使用嘲弄,间谍和存根进行JavaScript测试相关推荐

  1. Sinon教程:使用嘲弄,间谍和存根进行JavaScript测试

    本文由Mark Brown和MarcTowler进行了同行评审. 感谢所有SitePoint的同行评审人员使SitePoint内容达到最佳状态! 编写单元测试时,最大的绊脚石之一就是当您拥有不平凡的代 ...

  2. JavaScript测试工具对决:Sinon.js vs testdouble.js

    在对真实代码进行单元测试时,有许多情况使测试难以编写. 您如何检查是否调用了函数? 您如何测试Ajax呼叫? 还是使用setTimeout编码? 就是在这种情况下,您使用测试倍数 -替换代码使难以测试 ...

  3. 助创cms众筹 php,【教程】助创cms众筹系统完整测试流程详解

    原标题:[教程]助创cms众筹系统完整测试流程详解 这两年提到互联网金融,不得不提的一个词语:众筹.的确相比飘忽不定的股市和频发跑路P2P,众筹具备低风险,收益高,周期短等各方面的优势.为了帮助更多朋 ...

  4. Unity5.3官方VR教程重磅登场-系列6 VR真机测试和产品发布

    不知道怎么开发VR游戏?Unity5.3官方VR教程重磅登场-系列6 VR真机测试和产品发布 王寒 · 3 个月前 在本教程中我们将学习如何将产品在两个真机平台上进行测试-DK2和Gear VR.当然 ...

  5. 【mmaction2 入门教程 01】 slowfast训练配置 日志分析 测试结果分析

    目录 0 参考资料 1 GPU平台 2 训练配置(Training setting) 2.1 官网的训练配置文档 2.2 官网的时空动作检测的配置文件系统解析(Config System for Sp ...

  6. QCC304x系列开发教程(实战篇) 之 QCC3040之RF测试

    查看全部教程开发请点击:高通蓝牙耳机QCC304x开发详解汇总(持续更新中) 查看本文全部文章请点击:QCC304x系列开发教程(实战篇) 之 QCC3040之RF测试 更新记录链接:QCC514x- ...

  7. python3.5.3下载安装教程_在Python3.5下安装和测试

    一. 安装Twisted 1. 同安装Lxml库 (参考<为编写网络爬虫程序安装Python3.5>3.1节)一样,通过下载对应版本的.whl文件先安装twisted库,下载地址:http ...

  8. STM32掌机教程3,工程模板与带灯按键测试

    我们需要"脚手架"   关于代码,我想体现出这么一个过程:我是如何一步一步修改代码的.我认为,从学习的角度来考虑,直接看最终的代码没有什么意义. 写代码就像工人盖房子,盖房子过程中 ...

  9. RabbitMQ+PHP 教程二(Work Queues)用yii2测试通过

    2019独角兽企业重金招聘Python工程师标准>>> 介绍 在上一个 Hello World 教程中,我们编写了从指定队列发送和接收消息的程序.在这篇文章中,我们将创建一个工作队列 ...

最新文章

  1. Java面对对象的核心是啥_Java面向对象核心技能
  2. 去某大厂三面总监面,因为迟到了5分钟,面试官当着我的面把简历扔垃圾桶了
  3. linux inotifywait脚本,使用inotify/fswatch构建自动监控脚本
  4. 转 abap中sy-index和sy-tabix使用的时候有什么区别
  5. Text determination debug
  6. 生成jsp验证码的代码详解(servlet版)
  7. RocketMQ-PushConsumer配置参数详解
  8. 比人高效10倍,3分钟就能评估帕金森!这是腾讯新推出的AI医生
  9. 从零开始学习python编程-从0开始的Python学习014面向对象编程(推荐)
  10. Async注解使用及源码分析
  11. qnap威联通作文件服务器,QNAP 威联通 453BT3 网络存储服务器 使用手记,Nas中的小钢炮...
  12. android 找不到 theme,android-找不到与给定名称'@ style / Theme.Holo.Light.DarkActionBar'匹配的资源...
  13. SAP中成本核算结构及构成组件分析
  14. ios下使用speex进行音频压缩
  15. 东大计算机考研录取分数线,东南大学2020初试考研录取分数线名单
  16. matlab 莫比乌斯曲面,『Rhino实例.2』莫比乌斯曲面
  17. 【Javascript基础语法】第五周预习博客
  18. 计算机网络实验--验证性实验
  19. 企业微信发送应用消息php,企业微信如何发送应用消息?及时发布重要通知
  20. Python开发系列课程(7) - 函数和模块的使用

热门文章

  1. 安全组设置IP段 -- 示列
  2. 小米笔记本电脑我的计算机图标不见了怎么办,毕业论文致谢结尾800字范文_本科毕业论文致谢结尾通用...
  3. 创维30周年庆典举行,中国制造业标杆向千亿目标加速冲刺
  4. Github+Facebook=?这家公司有望让程序猿的头秃得慢一点
  5. EF Power Tool 参数错误 HRESULT:0x80070057 (E_INVALIDARG)) 解决办法
  6. c语言两个字母如何比较,c如何比较两个字符 C语言中怎么判断两个字符相同
  7. MSF-02-木马捆绑
  8. 2018 大数据面试
  9. 值得学习Google的编程样式指南
  10. NotebookApp] 302 GET /?token=be0e8107dd84eab831a957b640602e5157b5336b15e7fa61 (127.0.0.1) 1.000000ms