在过去的几个月里,我一直在使用JavaScript并使用SinonJS来存储某些行为。我已经设法使它工作,我已经存在许多方法,一切都很好。
但我仍然对Sinon如何在桌面下工作有一些疑问。我想我说的是Sinon,但是这个问题可能适用于所有其他用于模拟/存根/间谍的库。
过去几年我最常用的语言是Java。在Java中,我使用Mockito来模拟/存根依赖项和依赖项注入。我曾经导入类,使用@Mock
注释该字段,并将此模拟作为参数传递给被测试的类。我很容易看到我在做什么:嘲笑一个类并将模拟作为参数传递。
当我第一次开始使用SinonJS时,我看到了类似的东西:
moduleUnderTest.spec.js
const request = require('request')
describe('Some tests', () => {
let requestStub
beforeEach(() => {
requestStub = sinon.stub(request, 'get')
})
afterEach(() => {
request.get.restore()
})
it('A test case', (done) => {
const err = undefined
const res = { statusCode: 200 }
const body = undefined
requestStub
.withArgs("some_url")
.yields(err, res, body)
const moduleUnderTest = moduleUnderTest.someFunction()
// some assertions
})
})
moduleUnderTest.js
const request = require('request')
// some code
request
.get("some_url", requestParams, onResponse)
它有效。当我们运行测试时,实现request
内的moduleUnderTest.js
会调用request
模块的存根版本。
我的问题是:为什么会这样?
当测试调用实现执行时,实现需要并使用request
模块。如果我们没有将存根对象作为param(注入它)传递,Sinon(以及其他模拟/存根/间谍库)如何设法使实现调用存根? Sinon在测试执行期间替换整个request
模块(或部分模块),通过require('request')
使存根可用,然后在测试完成后恢复它?
我试图遵循Sinon repo中stub.js
代码中的逻辑,但我还不熟悉JavaScript。
对不起,这篇文章很长,很遗憾。 :)
答案 0 :(得分:8)
如果我们没有将存根对象作为param(注入它)传递,Sinon(以及其他模拟/存根/间谍库)如何设法使实现调用存根?
让我们编写自己的简单存根工具,是吗?
为简洁起见,它非常有限,不提供存根API,每次只返回42。但这足以说明诗乃是如何运作的。
function stub(obj, methodName) {
// Get a reference to the original method by accessing
// the property in obj named by methodName.
var originalMethod = obj[methodName];
// This is actually called on obj.methodName();
function replacement() {
// Always returns this value
return 42;
// Note that in this scope, we are able to call the
// orignal method too, so that we'd be able to
// provide callThrough();
}
// Remember reference to the original method to be able
// to unstub (this is *one*, actually a little bit dirty
// way to reference the original function)
replacement.originalMethod = originalMethod;
// Assign the property named by methodName to obj to
// replace the method with the stub replacement
obj[methodName] = replacement;
return {
// Provide the stub API here
};
}
// We want to stub bar() away
var foo = {
bar: function(x) { return x * 2; }
};
function underTest(x) {
return foo.bar(x);
}
stub(foo, "bar");
// bar is now the function "replacement"
// foo.bar.originalMethod references the original method
underTest(3);
Sinon在测试执行期间替换整个请求
module
(或部分请求),通过require('request')
使存根可用,然后在测试完成后恢复它?
require('request')
将返回在" request"中创建的相同(对象)引用。模块,每次被调用。
模块在第一次加载后进行缓存。这意味着(除其他外)每次调用
require('foo')
将获得完全相同的返回对象,如果它将解析为同一文件。多次调用
require('foo')
可能不会导致模块代码多次执行。这是一个重要的特征。有了它,"部分完成"可以返回对象,从而允许传递依赖性,即使它们会导致循环。
如果还没有变得清晰:它只替换了从"返回的"返回的对象引用的单个方法。模块,它不会取代模块。
这就是你不打电话的原因
stub(obj.method)
因为这只会传递对函数method
的引用。 Sinon将无法更改对象obj
。
文件进一步说:
如果你想让模块多次执行代码,那么导出一个函数,并调用该函数。
这意味着,如果模块看起来像这样:
<强> foo.js 强>
module.exports = function() {
return {
// New object everytime the required "factory" is called
};
};
<强> main.js 强>
// The function returned by require("foo") does not change
const moduleFactory = require("./foo"),
// This will change on every call
newFooEveryTime = moduleFactory();
此类模块工厂函数无法存根,因为您无法从模块中替换require()
导出的内容。
在Java中,我使用Mockito来模拟/存根依赖项和依赖注入。我曾经导入类,使用
@Mock
注释该字段,并将此模拟作为参数传递给被测试的类。我很容易看到我正在做的事情:嘲笑一个类并将模拟作为参数传递。
在Java中,您(没有)无法将方法重新分配给新值,这是不可能的。而是生成新的字节码,使模拟提供与模拟类相同的接口。与Sinon相比,Mockito所有方法都被嘲笑,应该explictly instructed来调用真正的方法。
Mockito将有效地调用mock()
并最终将结果分配给带注释的字段。
但是你仍然需要将mock替换/分配给被测试类中的字段,或者将其传递给测试方法,因为该模拟本身没有帮助。 < / p>
@Mock
Type field;
或
Type field = mock(Type.class)
实际上相当于Sinons mocks
var myAPI = { method: function () {} };
var mock = sinon.mock(myAPI);
mock.expects("method").once().throws();
该方法首先是replaced with the expects()
call:
wrapMethod(this.object, method, function () {
return mockObject.invokeMethod(method, this, arguments);
});