Javascript 监视用作构造函数的函数

Javascript 监视用作构造函数的函数,javascript,node.js,unit-testing,sinon,Javascript,Node.js,Unit Testing,Sinon,在我的一个单元测试中,我需要监视一个函数,该函数被另一个具有Sinonlibrary的函数用作构造函数。根据他们的文件 …sinon.spy(object,“method”)创建一个封装现有函数object.method的spy。spy的行为与原始方法完全相同(包括用作构造函数时)。。。 但到目前为止,即使我试图监视在测试函数中调用的构造函数,更不用说由另一个函数调用的构造函数,我也无法使它工作 单元测试: it('constructor was called.', () => {

在我的一个单元测试中,我需要监视一个函数,该函数被另一个具有
Sinon
library的函数用作构造函数。根据他们的文件

sinon.spy(object,“method”)
创建一个封装现有函数object.method的spy。spy的行为与原始方法完全相同(包括用作构造函数时)。。。 但到目前为止,即使我试图监视在测试函数中调用的构造函数,更不用说由另一个函数调用的构造函数,我也无法使它工作

单元测试:

   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;
当我使用调试器时,我可以看到函数位于
constfoospy=sinon.spy(fooModule,'exports')
被spy替换(添加了所有的
sinon
属性,如
calledOnce
等…),但是当启用
new Foo(5)时似乎
Foo
是一个非间谍对象

我认为这可能是一个作用域或引用错误,但我似乎找不到除了在
模块中定义
Foo
以外的其他地方。children
。它既不在
全局
上,也不在
窗口
上,因为它在
节点
上运行

目前,测试当然失败,原因是:

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,而是关于节点模块如何导入函数。调用
sinon.spy
时,除非我们正在测试回调函数,否则我们通常需要一个对象作为我们想要监视特定方法的上下文。这就是为什么您的示例尝试访问
foo.js
模块的
exports
对象。我不知道节点允许我们访问这样的对象

然而,为了让您的示例发挥作用,我们不需要访问Foo模块的
导出
,我们只需创建自己的上下文即可。例如:

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);
    });
});
当然,上面的测试是示例中提供的问题的有效解决方案,但是,作为测试,它不是很有用,因为断言只是验证上面的行

当SPIE是被测系统(SUT)的依赖项时,它们更有用。换句话说,如果我们有一个模块应该构造一个
Foo
,我们想让
Foo
构造函数成为一个间谍,这样它就可以向我们的测试报告该模块确实调用了它

例如,假设我们有
foodfactory.js
模块:

const Foo = require("./foo");

module.exports = {
  createFoo(num) {
    return new Foo(num);
  },
};
现在,我们想创建一个单元测试,以确认调用
fooFactory.js
模块的
createFoo
函数调用带有指定参数的
Foo
构造函数。我们需要用间谍覆盖
fooFactory.js
Foo
依赖关系

这让我们回到了最初的问题,即当导入的(构造函数)函数不是上下文对象上的方法,因此无法用
sinon.spy(context,'method')
覆盖它时,如何将其转换为spy

幸运的是,我们不是第一个遇到这个问题的人。NPM模块允许覆盖所需模块中的依赖项。Sinon.js提供了一个关于做这类事情的工具,他们使用一个名为

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);
    });
});

我认为你做不到。需要/导入的模块与正在测试的代码处于不同的级别。测试代码运行时,模块已加载。您对
模块没有相同的访问权限。导出
与您对当前执行上下文中引用的对象所做的操作相同,例如
Foo
f
。非常感谢<代码>proxyquire
正是我所需要的。