注意:我会回答我自己的问题,但如果您有更好的答案,我会接受它。
我有一个如下所示的函数:
function systemUnderTest(func, param) {
param.firstProperty = !param.firstProperty;
func();
param.secondProperty = !param.secondProperty;
}
我想存根
param
并断言 param.firstProperty
在 func()
之前分配。我还想检查 func()
在设置 param.secondProperty
之前被调用。
spy.calledBefore(anotherSpy)
,这似乎是完成这项工作的正确工具,但我不确定如何设置 param
(一个对象文字)来记录其属性/访问器的使用顺序.
如何设置
param
以便我可以在其属性上调用 x.calledBefore(y)
?
我知道有两种方法可以实现这一目标,并且都需要权衡。一种解决方案更冗长但更灵活。另一种解决方案更简洁,但应用有限。
function systemUnderTest(func, param) {
param.firstProperty = !param.firstProperty;
func();
param.secondProperty = !param.secondProperty;
}
it('tests order of setters', () => {
// SETUP
const param = {
firstProperty: true, // 1
secondProperty: true,
};
const firstPropertySetter = sinon.stub(); // 2
sinon.stub(param, 'firstProperty').set(firstPropertySetter); // 3
const secondPropertySetter = sinon.spy(); // 4
sinon.stub(param, 'secondProperty').set(secondPropertySetter); // 5
const aFunction = sinon.stub(
{
aFunction: () => {},
},
'aFunction'
);
// SUBJECT
systemUnderTest(aFunction, param);
// ASSERT
expect(firstPropertySetter.calledBefore(aFunction)).toBeTrue(); // pass, as it should
expect(firstPropertySetter.calledOnce).toBeTrue(); // pass, as it should
expect(secondPropertySetter.calledBefore(aFunction)).toBeTrue(); // fail, as it should
});
备注:
即使
param
具有属性(与 getter/setter 相对),测试仍可按预期运行。
如果
param
有 getter/setter,则此测试仍将按预期运行。
sinon.stub()
需要保存在变量中。不幸的是,const firstPropertySetter = sinon.stub(param, 'firstProperty').set(sinon.stub());
不会正常工作:firstPropertySetter.called*
将始终返回 false/0。 (我很想知道为什么。)
如前所述,即使对象字面量没有 setter,对
.set(...)
的调用仍然有效。
如果您愿意,可以在此行使用
sinon.spy()
代替 sinon.stub()
。
但是你不能在这里使用
sinon.spy()
:.set(...)
在spy()
上不存在。
我从这个 GitHub 评论了解到这个解决方案。
function systemUnderTest(func, param) {
param.firstAccessor = !param.firstAccessor; // 1
func();
param.secondAccessor = !param.secondAccessor;
}
it('tests order of setters/getters', () => {
// SETUP
const param = {
set firstAccessor(value) {}, // 2
get firstAccessor() {
return true;
},
set secondAccessor(value) {},
get secondAccessor() {
return true;
},
};
const firstAccessor = sinon.spy(param, 'firstAccessor', ['get', 'set']); // 3
const secondAccessor = sinon.spy(param, 'secondAccessor', ['get', 'set']);
const aFunction = sinon.stub(
{
aFunction: () => {},
},
'aFunction'
);
// SUBJECT
systemUnderTest(aFunction, param);
// ASSERT
expect(firstAccessor.set.calledBefore(aFunction)).toBeTrue(); // 4 // pass, as it should
expect(firstAccessor.set.calledOnce).toBeTrue(); // pass, as it should
expect(secondAccessor.set.calledBefore(aFunction)).toBeTrue(); // fail, as it should
});
备注:
我想提请注意,我将这些键从
*Property
重命名为 *Accessor
。
param
此解决方案必须使用 getter 和 setter...
...例如,如果
firstAccessor
是一个属性,则此行将出错:TypeError: Attempted to wrap undefined property firstAccessor as function
。我不知道为什么这个错误消息说它是一个未定义的属性。这可能是错误消息中的错误?
此外,该行必须使用
sinon.spy()
。 sinon.stub()
的第三个参数不接受字符串数组。
我想提请注意这样一个事实:spy 会调用
.set
/ .get
来访问 sinon API。例如, firstAccessor.set.calledBefore()
存在,firstAccessor.calledBefore
不存在。
我从官方的Sinon间谍文档学到了这个解决方案。在该页面上搜索“使用间谍包装属性 getter 和 setter”以查找另一个示例。
这是用Sinon v17测试的。