sinon.stub
/ sandbox.stub
已成为厨房水槽
带有问题的可配置行为,这些问题通常很难在没有回归的情况下找到和修复。
我认为困难的根本原因是它stub
的责任太多了。
此外, stub
的使用也有问题,因为行为是在创建后设置的,并且可以多次重新定义。
var myStub;
beforeEach(function(){
myStub = sinon.stub().resolves('apple pie :)');
});
// several hundred lines of tests later
myStub = sinon.stub().rejects('no more pie :(');
// several hundred lines of tests later
// what behaviour does myStub currently have? Can you tell without
// reading the entire file?
// can you safely change the behaviour without affecting tests further
// down in the file?
然后是更令人困惑的场景
var myStub = sinon.stub()
.withArgs(42)
.onThirdCall()
.resolves('apple pie')
.rejects('no more pie')
那还能做什么?
与其继续为stub
增加更多职责,我建议改为向sinon
引入新成员,其范围要窄得多。
我能想到的最重要的是函数的不可变替代。
然后我们可以稍后弄清楚我们将对属性做什么(作为一个单独的、新的、单一职责的成员)。
sinon.fake
fake
(调用sinon.fake
的返回值)是纯粹且不可变的Function
。 它只做一件事,而且只做一件事。 它在每次调用时都具有相同的行为。 与stub
不同,它的行为不能被重新定义。 如果您需要不同的行为,请创建一个新的fake
。
假货可以承担这些责任之一
Promise
解析为一个值Promise
到Error
Error
如果您想要/需要副作用,并且仍然想要 spy 接口,那么要么使用真实函数,使用stub
或制作自定义函数
sinon.replace(myObject, myMethod, sandbox.spy(function(args) {
someFunctionWithSideEffects(args);
});
当用户尝试以不受支持的方式创建/使用错误时,会慷慨地抛出错误。
// will throw TypeError when `config` argument has more than one property
const fake = sinon.fake({
resolves: true,
returns: true
});
除了.withArgs
,因为这违反了不变性
// will return a Promise that resolves to 'apple pie'
var fake = sinon.fake({resolves: 'apple pie'})
// will return a Promise that rejects with the provided Error, or
// creates a generic Error using the input as message
var fake = sinon.fake({rejects: new TypeError('no more pie')});
var fake = sinon.fake({rejects: 'no more pie'});
// returns the value passed
var fake = sinon.fake({returns: 'apple pie'});
// throws the provided Error, or creates a generic Error using the
// input as message
var fake = sinon.fake({throws: new RangeError('no more pie')});
var fake = sinon.fake({throws: 'no more pie'});
// replace a method with a fake
var fake = sinon.replace(myObject, 'methodName', sandbox.fake({
returns: 'apple pie'
}))
// .. or use the helper method, which will use `sandbox.replace` and `
// sinon.fake`
var fake = sinon.setFake(global, 'methodName', {
returns: 'apple pie'
});
// create an async fake
var asyncFake = sinon.asyncFake({
returns: 'apple pie'
});
我不知道fake
是否是这里使用的最佳名词,但我认为我们应该尽量坚持使用名词的惯例,不要误入形容词或动词。
这是我一直在考虑的事情,我们为什么不做一个默认的沙箱呢? 如果人们需要单独的沙箱,他们仍然可以创建它们。
我们应该创建一个默认沙箱,用于通过sinon.*
公开的所有方法。
这意味着sinon.stub
将与sandbox.stub
相同,这将消除能够使用sinon.stub
存根属性的限制。
sandbox.replace
创建sandbox.replace
并将其用于在任何地方替换任何内容的所有操作。 将其公开为sinon.replace
并在以这种方式使用时使用默认沙箱。
这可能应该有一些严肃的输入验证,所以它只会用函数替换函数,用访问器替换访问器等。
平 @sinonjs/核心
好建议,摩根。 感谢您提出这个问题。 我也认为stub
API 很混乱,我喜欢你的所有建议。 以下是一些想法:
sinon.fake
我同意不变性是这里的关键。 不过,我们可以允许一些目前可以使用存根的“理智”用例。
例如,yield 和 return 可能是一个有效的用例:
sinon.fake({
yields: [null, 42],
returns: true
})
我们可以检查什么是有意义的,什么是没有意义的。
此外,如果我们支持callsThrough: true
作为配置(与任何行为属性结合使用无效),也可以使用新的伪造品代替“间谍”API。 这将比学习诗浓语中的“间谍”和“存根”的含义更自我解释😄
使用默认沙箱
虽然我喜欢这个想法,但这意味着在测试之后调用sinon.restore()
可能会恢复其他测试的一些剩余部分并导致令人惊讶的结果 - 或者之前发生的测试失败。 这样做的好处是重置beforeEach
中的全局沙箱以改善测试隔离。 👍
沙盒.替换
我非常喜欢这个。 我将其理解为“让我把这个东西粘在那里”实用程序,对吧?
此外,如果我们支持 callThrough: true 作为配置(与任何行为属性组合无效),也可以使用新的伪造品代替“间谍”API。 这将比学习诗浓语中的“间谍”和“存根”的含义更自我解释😄
这是否意味着我们根本不需要spy
或stub
?
沙盒.替换
我非常喜欢这个。 我将其理解为“让我把这个东西粘在那里”实用程序,对吧?
是的,就是这个想法。 与其重载相同的方法( sinon.stub
)来做很多很多事情,不如使用只做一件事的显式方法
正如您所强调的, fake
API 可能不会支持目前使用间谍和存根可能实现的所有功能。 但是,是的,我认为fake
API 是一个统一stub
和spy
功能的机会。
虽然我喜欢这个想法,但这意味着在测试之后调用 sinon.restore() 可以恢复其他测试的一些剩余部分并导致令人惊讶的结果 - 或者之前发生的测试失败。 这样做的好处是在 beforeEach 中重置全局沙箱以改进测试隔离。 👍
这无疑是一个突破性的变化,不应轻易引入。
创建fake
时,如果您不向其传递行为配置,则它将等效于spy
。
// ~spy, records all calls, has no behaviour
const fake = sinon.fake();
// ~stub, records all calls, returns 'apple pie'
const fake = sinon.fake({
returns: 'apple pie'
});
你将如何创建一个什么都不做的存根呢?
你将如何创建一个什么都不做的存根呢?
我不确定我是否完全理解你的问题......但是这里有
// a fake that has no behaviour
const fake = sinon.fake();
// put it in place of an existing method
sandbox.replace(myObject, 'someMethod', fake);
啊,我想我现在明白你的意思了: fake
始终是stub
。 当您说~spy, records all calls
时,我理解“调用原始函数”。 然而, fake
并不知道它要替换的函数——这就是sandbox.replace
所做的。
因此,考虑到这一点,这是另一个建议,我们如何将当前的spy
功能(如在调用中)折叠到新的假货中:
const fake = sinon.fake(function () {
// Any custom function
});
给定的函数将被伪造者调用。 此 API 使其无法与其他行为混合使用。 实际上,配置对象会创建一个实现指定行为的函数,然后将其传递给 fake。
然后sandbox.spy(object, method)
实现可以变成这样:
const original = object[method];
const fake = sinon.fake(original);
sandbox.replace(object, method, fake);
基本上是单线🤓
是的。 一旦你简化了事情,那么你就可以开始重新混合以获得乐趣🎉和利润💰
但是,如果我们想倾向于只使用fake
而不再使用spy
和stub
,那么我们可能应该不理会这两个。
我正在考虑这里的“下一个”API。 您需要sandbox.spy
才能在某处拥有替换逻辑。 据我了解,这应该是向后兼容的。 然后可以弃用stub
实现。
您需要 sandbox.spy 在某处拥有替换逻辑。 据我了解,这应该是向后兼容的。 然后可以弃用存根实现。
我不确定我是否遵循。 你能详细说明一下吗?
当然。 据我了解您的建议,您希望替换过于复杂的stub
API。 当前实现存根的方式是通过创建具有实现行为的函数的spy
。 我的建议是用fake
API 做同样的事情并在内部创建一个spy
,但我们不会再返回一个行为,因为我们想摆脱链接. 我们只要把间谍还回去。 这使得fake
实现成为stub
的替代方案,返回的函数与所有当前的 Sinon API 兼容。 这有意义还是我错过了什么?
好吧,我想我们有类似的理解👍
只是为了重复一遍,以防我们遗漏了什么,以便其他贡献者有同样的理解。
sandbox.replace
(目前位于stub
中)sinon
将有一个默认沙箱,允许sinon.reset
和sinon.restore
(我们应该合并它们吗?)sinon.fake
— 一个不可变的、可编程的替代记录所有调用的函数sinon.spy
sinon.stub
// effectively a spy that has no target
const fake = sinon.fake()
// spy on a function
const fake = sinon.fake(console.log);
const fake = sinon.fake(function() { return 'apple pie'; });
// a shorthand construction of fake with behaviour
const fake = sinon.fake({
returns: 'apple pie'
});
// replacing an existing function with a fake
var fakeLog = sinon.fake();
sandbox.replace(console, 'log', fakeLog);
在这种状态下,提案仅处理Function
。 我们需要考虑如何处理非函数属性和访问器。 至少,我们应该看看我们是否可以限制sandbox.replace
只允许理智的替换。
这是否意味着sinon.stub()
和sinon.spy()
都将在未来被弃用以支持sinon.fake()
,或者只是在内部重做? 如果是这样,那么我们本质上是在朝着TestDouble 的思路前进。 恕我直言,不一定是坏事,但可能值得考虑的是,如果很多人发现他们无论如何都需要为sinon.fake()
替换所有 Sinon api 调用,他们不妨只使用另一个库(尽管这意味着他们将失去所有现有的 Sinon API 知识)。
这是否意味着 sinon.stub() 和 sinon.spy() 都将在未来被弃用以支持 sinon.fake(),或者只是在内部重做? 如果是这样,那么我们基本上是在朝着 TestDouble 的思路前进。
我想它确实有些重叠。 我提出这个建议的主要动机是拥有具有不可变行为的假函数。
恕我直言,不一定是坏事,但可能值得考虑的是,如果很多人发现他们无论如何都需要为 sinon.fake() 替换所有 Sinon api 调用,他们不妨只使用另一个库(尽管将意味着他们将失去所有现有的 Sinon API 知识)。
如果人们发现另一个图书馆可以更好地满足他们的需求,那么我很高兴我们帮助他们了解了这一点:)
但是我们会保留 spy 和 stub 方法还是弃用它们,并可能会减少一些功能? 我不清楚。
但是我们会保留 spy 和 stub 方法还是弃用它们,并可能会减少一些功能?
一旦fake
看起来稳定,那么我会弃用spy
和stub
,然后给它一年的时间让人们有时间升级。
我认为我们应该尽最大努力提供 codemods 和优秀的文档,以帮助人们移动他们的代码
我正在为此的第一部分(默认沙箱)开发一个分支。 我重构了代码,使sandbox
和collection
现在合二为一。 我已经让默认的沙箱工作了。
我将在接下来的几天内整理提交,然后在此存储库上为更新的 API 创建一个分支。
这是个好主意,顺便说一句,写得很好。
我还会向存根和间谍添加弃用通知。
我也在考虑通过传递函数来改变传递带有键的对象。
这将增加以下好处:
typescript
或其他类型的静态检查器的用户添加type
到那些功能因此,API 看起来像这样:
// It would be cool to allow users to import these using destructuring to make code more concise
import { resolves, rejects, returns } from 'sinon/behaviors';
var fake = sinon.fake(resolves('apple pie'))
var fake = sinon.fake(rejects(new TypeError('no more pie')));
var fake = sinon.fake(rejects('no more pie'));
var fake = sinon.fake(returns('apple pie'));
var fake = sinon.fake(throws(new RangeError('no more pie'));
var fake = sinon.fake(throws('no more pie'));
在实现这一点时,可能只是返回非常简单的对象,例如您提议的对象。 然后,如果我们有多个行为,我们可以合并它们。
此外,当涉及到混合onThirdCall
和withArgs
之类的东西时,我认为应该记录在这些情况下发生的事情。
很抱歉这么晚才审查这个。 过去几个月非常忙碌。
@lucasfcosta查看 PR #1586
此问题已自动标记为过时,因为它最近没有活动。 如果没有进一步的活动,它将被关闭。 感谢你的贡献。
5.0.0 以前的版本在 package.json 中与后来的 5.0.0-next.* 预发布版本造成问题,因为 5.0.0 比任何预发布版本都大。
由于 5.0.0 已经发布,我认为next
预发布数字可能需要提高到5.0.1-next.1
?
我注意到了这一点,因为我正在使用的另一个包得到了一个弃用的 msg,它的 package.json 依赖于"sinon": "^5.0.0-next.4"
npm WARN deprecated [email protected]: this version has been deprecated
我不确定这是否值得为预发布问题打开一个新问题,所以这里的评论似乎最安全。
另一个解决方案是发布下一个主要版本。 你觉得@sinonjs/core 怎么样?
@mroderick我无法再告诉 v5 的所有更改是什么。 从我上次的测试来看,它运行良好,我期待使用新的假货。 这是一个新的专业,所以嘿,把它寄出去😄
在我们发布下一个主要版本之前,我只想合并一个 PR #1764。
我已经发布[email protected]
,希望这会让人们的生活更轻松。
谢谢,我已经测试了 package.json 中的依赖项(总是很好地仔细检查),并且"sinon": "^5.0.1"
给出了一个错误,因为没有找到匹配项(还没有发布),并且"sinon": "^5.0.1-next.1"
可以正常获取该版本。
这从来都不是什么大问题,我只是觉得值得让你知道,特别是当我看到 v5 已经开发了一段时间,所以我不知道它要发布多久。 我认为在不久的将来发布听起来是个好主意。
fake
已与 #1768 一起引入,变为[email protected]
最有用的评论
好吧,我想我们有类似的理解👍
只是为了重复一遍,以防我们遗漏了什么,以便其他贡献者有同样的理解。
TL;博士
sandbox.replace
(目前位于stub
中)sinon
将有一个默认沙箱,允许sinon.reset
和sinon.restore
(我们应该合并它们吗?)sinon.fake
— 一个不可变的、可编程的替代记录所有调用的函数sinon.spy
sinon.stub
在这种状态下,提案仅处理
Function
。 我们需要考虑如何处理非函数属性和访问器。 至少,我们应该看看我们是否可以限制sandbox.replace
只允许理智的替换。