即使正在调用被监视的函数,Sinon 间谍也会失败

Sinon spy fails even though function spied on is being called

我无法理解为什么 sinon 间谍对我来说失败了,即使我正在监视的函数在我的测试期间确实被调用了(我用一些简单的控制台日志记录证明了这一点)。

所以说我有如下内容:

index.js

let MyModule = require('./src/index.js');

MyModule = new MyModule();

module.exports = {
  DoStuff: MyModule.DoStuff,
  doOtherStuff: MyModule.doOtherStuff,
};

src/index.js

const MyModule = function MyModule() {

  const self = this;

  self.doOtherStuff = function doOtherStuff() {
    console.log('doOtherStuff called!!!')
  }

  self.DoStuff = async function DoStuff() {
    const xhr = self.axiosInstance();
    await xhr.post()
      .then((res) => {
        self.doOtherStuff(res.data);
      })
      .catch((_err) => {
        console.log(_err);
      });
  };
}

module.exports = MyModule;

我的测试如下:

const nock = require('nock');
const sinon = require('sinon');
const MyModule = require('../index.js');
describe('When calling DoStuff succeeds in making the xhr call', () => {
        before(() => {
          nock(apiHostName)
            .post('/some-path')
            .reply(200, { foo: 'bar' });
        });
        it('should call doOtherStuff', async () => {
          const spy = sinon.spy(MyModule, 'doOtherStuff');
          await MyModule.DoStuff();
          sinon.assert.calledOnce(spy);
        });
      });

我在我的测试运行器输出中看到我的 doOtherStuff 函数输出中的控制台日志,但测试失败说间谍被调用零次。

我想知道这是否归因于我正在测试的代码的异步性质,但我已确保在我的测试中使用 async/await。我一定是在做傻事,我哪里错了?

谢谢

更新

所以我尝试将函数剥离回更基本的东西,现在有以下内容:

const MyModule = function MyModule() {

  const self = this;

  self.doOtherStuff = function doOtherStuff() {
    console.log('doOtherStuff called!!!')
  }

  self.DoStuff = function DoStuff() {
    self.doOtherStuff();
  };
}

module.exports = MyModule;

所以这将排除我可能遇到的任何 async/await 问题。

但即使 运行 下面的简单测试,间谍也永远不会被调用:

const MyModule = require('../index.js');

it('should call doOtherStuff', () => {
  const spy = sinon.spy(MyModule, 'doOtherStuff');
  MyModule.DoStuff();
  sinon.assert.calledOnce(spy);
});

如果我监视 console.log 然而它就会通过。我一定是误解了这里的一个非常基本的原则,但我不知道它是什么!

这与我的 module.exports 的申报方式有关吗?因此,即使我试图监视 index.jsdoOtherStuff: MyModule.doOtherStuff)中的顶级导出,这不是在我的测试中调用 DoStuff 时内部实际调用的内容吗?

问题

包裹在 spy 中的 属性 不是被调用的 属性。

详情

sinon.spy 获取一个对象和一个 属性 名称并将函数包装在该 属性 名称处的间谍中。

在这种情况下,对象是 index.js 的模块导出。

模块导出是一个具有两个属性的对象,这些属性指向在 index.js 中创建的内部 MyModule 实例上的方法。所以那个对象的 doOtherStuff 属性 现在是 spyDoStuff 属性 仍然只是对 DoStuff [=65 的引用=] 内部 MyModule 实例。

当测试随后调用 MyModule.DoStuff() 时,它调用内部 MyModule 实例的 DoStuff 属性,后者调用 doOtherStuff 属性记录到控制台的内部 MyModule 实例。

关键是直接调用了内部MyModule实例的doOtherStuff属性和导出对象的doOtherStuff属性 index.js 从未调用过。

doOtherStuff 属性 上的 doOtherStuff index.js 导出的对象的 spy 然后正确断言它被调用了 0 次。

解决方案

确保 spy 是在实际调用的 属性 上创建的。

在这种情况下,最简单的方法是直接从 index.js:

导出 MyModule 实例
let MyModule = require('./src/index.js');

MyModule = new MyModule();

module.exports = MyModule;

现在,当创建 spy 时,它是直接在内部 MyModule 实例的 doOtherStuff 属性 上创建的,并且会正确地报告它被调用了一次。