监视用作构造函数的函数
Spying on a function used as a constructutor
在我的一个单元测试中,我需要监视一个函数,该函数被另一个具有 Sinon
库的函数用作构造函数。根据他们的文档
...sinon.spy(object, "method")
creates a spy that wraps the existing function object.method. The spy will behave exactly like the original method (including when used as a constructor)...
But so far I have failed to make it work even when trying to spy on a constructor called within the test function let alone called by another function.
单元测试:
it('constructor was called.', () => {
const Foo = require('../app/foo');
const fooModule = module.children.find(m => m.id.includes('foo.js'));
const fooSpy = sinon.spy(fooModule, 'exports');
const f = new Foo(5);
expect(fooSpy).calledOnce;
});
要实例化的函数:
const Foo = function(param) {
console.log('Foo called with: ' + param);
};
Foo.prototype.bar = function(x) {
console.log(`Foo.prototype.bar() called with x: ` + x);
};
module.exports = Foo;
当我使用调试器时,我可以看到 const fooSpy = sinon.spy(fooModule, 'exports');
处的函数被间谍替换(添加了所有 sinon
属性,如 calledOnce
等等...)然而,当 new Foo(5);
时,Foo
似乎不是间谍对象。
我认为这可能是范围界定或引用错误,但我似乎无法找到 module.children
之外的其他地方定义 Foo
。它不在 global
上,也不在 window
上,因为它在 node
上 运行。
目前测试当然失败了:
Foo called with: 5
AssertionError: expected exports to have been called exactly once, but it was called 0 times
at Context.it (test/fooTest.js:18:23)
在此先感谢您的帮助!
您的问题不在于 sinon.spy
API,而在于 Node 模块导入函数的方式。在调用 sinon.spy
时,除非我们正在测试回调函数,否则我们通常需要一个 Object 作为我们想要监视特定方法的上下文。这就是您的示例尝试访问 foo.js
模块的 exports
对象的原因。我不知道 Node 允许我们访问这样的对象。
但是,为了使您的示例正常工作,我们不需要访问 Foo 模块的 exports
,我们可以简单地创建一个我们自己的上下文。例如:
const chai = require("chai");
const sinon = require("sinon");
const sinonChai = require("sinon-chai");
const expect = chai.expect;
chai.use(sinonChai);
describe("foo", function () {
it('constructor was called.', function () {
const context = {
Foo: require("../app/foo"),
};
const fooSpy = sinon.spy(context, "Foo");
new context.Foo(5);
expect(fooSpy).to.be.calledOnceWith(5);
});
});
当然,上面的测试是针对您的示例中提供的问题的有效解决方案,但是,作为测试,它不是很有用,因为断言只是验证它上面的行。
间谍当它们是被测系统 (SUT) 的 依赖项 时更有用。换句话说,如果我们有一些应该构造 Foo
的模块,我们希望使 Foo
构造函数成为 Spy,以便它可以向我们的测试报告该模块确实调用了它。
例如,假设我们有 fooFactory.js
个模块:
const Foo = require("./foo");
module.exports = {
createFoo(num) {
return new Foo(num);
},
};
现在我们要创建一个单元测试,确认调用 fooFactory.js
模块的 createFoo
函数会调用带有指定参数的 Foo
构造函数。我们需要用 Spy 覆盖 fooFactory.js
的 Foo
依赖。
这 returns 我们解决了我们最初的问题,即当导入的(构造函数)函数不是上下文对象上的方法时,我们如何将其变成间谍,因此我们不能用 [=26 覆盖它=].
幸运的是,我们不是第一个遇到这个问题的人。 NPM 模块允许覆盖所需模块中的依赖项。 Sinon.js 提供了一个 How-To on doing this sort of thing and they use a module called proxyquire.
proxyquire 将允许我们将 fooFactory.js
模块导入到我们的单元测试中,而且(更重要的是)覆盖它所依赖的 Foo
。这将允许我们的单元测试使 fooFactory.js
使用 sinon.spy
代替 Foo
构造函数。
测试文件变为:
const chai = require("chai");
const proxyquire = require("proxyquire");
const sinon = require("sinon");
const sinonChai = require("sinon-chai");
const expect = chai.expect;
chai.use(sinonChai);
describe("fooFactory", function () {
it("calls Foo constructor", function () {
const fooSpy = sinon.spy();
const { createFoo } = proxyquire("../app/fooFactory", {
"./foo": fooSpy,
});
createFoo(5);
expect(fooSpy).to.be.calledOnceWith(5);
});
});
在我的一个单元测试中,我需要监视一个函数,该函数被另一个具有 Sinon
库的函数用作构造函数。根据他们的文档
...
sinon.spy(object, "method")
creates a spy that wraps the existing function object.method. The spy will behave exactly like the original method (including when used as a constructor)... But so far I have failed to make it work even when trying to spy on a constructor called within the test function let alone called by another function.
单元测试:
it('constructor was called.', () => {
const Foo = require('../app/foo');
const fooModule = module.children.find(m => m.id.includes('foo.js'));
const fooSpy = sinon.spy(fooModule, 'exports');
const f = new Foo(5);
expect(fooSpy).calledOnce;
});
要实例化的函数:
const Foo = function(param) {
console.log('Foo called with: ' + param);
};
Foo.prototype.bar = function(x) {
console.log(`Foo.prototype.bar() called with x: ` + x);
};
module.exports = Foo;
当我使用调试器时,我可以看到 const fooSpy = sinon.spy(fooModule, 'exports');
处的函数被间谍替换(添加了所有 sinon
属性,如 calledOnce
等等...)然而,当 new Foo(5);
时,Foo
似乎不是间谍对象。
我认为这可能是范围界定或引用错误,但我似乎无法找到 module.children
之外的其他地方定义 Foo
。它不在 global
上,也不在 window
上,因为它在 node
上 运行。
目前测试当然失败了:
Foo called with: 5
AssertionError: expected exports to have been called exactly once, but it was called 0 times
at Context.it (test/fooTest.js:18:23)
在此先感谢您的帮助!
您的问题不在于 sinon.spy
API,而在于 Node 模块导入函数的方式。在调用 sinon.spy
时,除非我们正在测试回调函数,否则我们通常需要一个 Object 作为我们想要监视特定方法的上下文。这就是您的示例尝试访问 foo.js
模块的 exports
对象的原因。我不知道 Node 允许我们访问这样的对象。
但是,为了使您的示例正常工作,我们不需要访问 Foo 模块的 exports
,我们可以简单地创建一个我们自己的上下文。例如:
const chai = require("chai");
const sinon = require("sinon");
const sinonChai = require("sinon-chai");
const expect = chai.expect;
chai.use(sinonChai);
describe("foo", function () {
it('constructor was called.', function () {
const context = {
Foo: require("../app/foo"),
};
const fooSpy = sinon.spy(context, "Foo");
new context.Foo(5);
expect(fooSpy).to.be.calledOnceWith(5);
});
});
当然,上面的测试是针对您的示例中提供的问题的有效解决方案,但是,作为测试,它不是很有用,因为断言只是验证它上面的行。
间谍当它们是被测系统 (SUT) 的 依赖项 时更有用。换句话说,如果我们有一些应该构造 Foo
的模块,我们希望使 Foo
构造函数成为 Spy,以便它可以向我们的测试报告该模块确实调用了它。
例如,假设我们有 fooFactory.js
个模块:
const Foo = require("./foo");
module.exports = {
createFoo(num) {
return new Foo(num);
},
};
现在我们要创建一个单元测试,确认调用 fooFactory.js
模块的 createFoo
函数会调用带有指定参数的 Foo
构造函数。我们需要用 Spy 覆盖 fooFactory.js
的 Foo
依赖。
这 returns 我们解决了我们最初的问题,即当导入的(构造函数)函数不是上下文对象上的方法时,我们如何将其变成间谍,因此我们不能用 [=26 覆盖它=].
幸运的是,我们不是第一个遇到这个问题的人。 NPM 模块允许覆盖所需模块中的依赖项。 Sinon.js 提供了一个 How-To on doing this sort of thing and they use a module called proxyquire.
proxyquire 将允许我们将 fooFactory.js
模块导入到我们的单元测试中,而且(更重要的是)覆盖它所依赖的 Foo
。这将允许我们的单元测试使 fooFactory.js
使用 sinon.spy
代替 Foo
构造函数。
测试文件变为:
const chai = require("chai");
const proxyquire = require("proxyquire");
const sinon = require("sinon");
const sinonChai = require("sinon-chai");
const expect = chai.expect;
chai.use(sinonChai);
describe("fooFactory", function () {
it("calls Foo constructor", function () {
const fooSpy = sinon.spy();
const { createFoo } = proxyquire("../app/fooFactory", {
"./foo": fooSpy,
});
createFoo(5);
expect(fooSpy).to.be.calledOnceWith(5);
});
});