Javascript NodeJS:如何断言是否使用sinon调用了事件回调函数

Javascript NodeJS:如何断言是否使用sinon调用了事件回调函数,javascript,node.js,unit-testing,sinon,sinon-chai,Javascript,Node.js,Unit Testing,Sinon,Sinon Chai,如何测试是否调用了来自事件侦听器的回调函数?例如,我有以下代码,其中app.js通过init.js控制器初始化应用程序 main.js文件有一个扩展和事件发射器的类,使对象成为事件发射器 app.js main.js ui.js init.js 因此,当我运行命令node app.js时,我看到以下输出 启动应用程序 HTTP初始化序列 HTTP成功序列 HTTP错误序列 这意味着侦听器处于活动状态,并且函数被调用 ui.tests.js 但是,当我运行上述测试时,即使我得到了输出,即调用了函数

如何测试是否调用了来自事件侦听器的回调函数?例如,我有以下代码,其中app.js通过init.js控制器初始化应用程序

main.js文件有一个扩展和事件发射器的类,使对象成为事件发射器

app.js

main.js

ui.js

init.js

因此,当我运行命令
node app.js
时,我看到以下输出

启动应用程序 HTTP初始化序列 HTTP成功序列 HTTP错误序列

这意味着侦听器处于活动状态,并且函数被调用

ui.tests.js

但是,当我运行上述测试时,即使我得到了输出,即调用了函数,但是sinon assert始终无法说出以下内容:

  UI Tests
    Testing Eventlisteners
HTTP INIT SEQUENCE
      1) should register an eventlistener on 'http-init' to onHttpInit
HTTP SUCCESS SEQUENCE
      2) should register an eventlistener on 'http-success' to onHttpSuccess
HTTP ERROR SEQUENCE
      3) should register an eventlistener on 'http-error' to onHttpError


  0 passing (16ms)
  3 failing

 1) UI Tests
       Testing Eventlisteners
         should register an eventlistener on 'http-init' to onHttpInit:
     AssertError: expected onHttpInit to have been called at least once but was never called
      at Object.fail (node_modules/sinon/lib/sinon/assert.js:106:21)
      at failAssertion (node_modules/sinon/lib/sinon/assert.js:65:16)
      at Object.assert.(anonymous function) [as called] (node_modules/sinon/lib/sinon/assert.js:91:13)
      at Context.it (test/ui.tests.js:25:30)
  2) UI Tests
       Testing Eventlisteners
         should register an eventlistener on 'http-success' to onHttpSuccess:
     AssertError: expected onHttpSuccess to have been called at least once but was never called
      at Object.fail (node_modules/sinon/lib/sinon/assert.js:106:21)
      at failAssertion (node_modules/sinon/lib/sinon/assert.js:65:16)
      at Object.assert.(anonymous function) [as called] (node_modules/sinon/lib/sinon/assert.js:91:13)
      at Context.it (test/ui.tests.js:25:30)

  3) UI Tests
       Testing Eventlisteners
         should register an eventlistener on 'http-error' to onHttpError:
     AssertError: expected onHttpError to have been called at least once but was never called
      at Object.fail (node_modules/sinon/lib/sinon/assert.js:106:21)
      at failAssertion (node_modules/sinon/lib/sinon/assert.js:65:16)
      at Object.assert.(anonymous function) [as called] (node_modules/sinon/lib/sinon/assert.js:91:13)
      at Context.it (test/ui.tests.js:25:30)

即使函数至少被调用了一次,我也不知道为什么测试失败,这可以从运行测试时的输出
HTTP INIT SEQUENCE
HTTP SUCCESS SEQUENCE
HTTP ERROR SEQUENCE
中看出

我试着做
stub.should.have.been.call。这样,测试就通过了,但是,它并没有真正通过测试,因为
stub.should.have.been.called
stub.should.not.have.been.called无论如何都通过测试,而不是后者未通过测试


有人知道这次考试不及格的原因吗?谢谢您的帮助。

您正在存根之前注册主回调,因此存根函数不是调用的函数,而是原始函数。尝试在it功能中反转顺序:

        eventMap.forEach((value, key) => {
            it(`should register an eventlistener on '${key}' to ${value}`, () => {
                const stub = sinon.stub(controller, value);
                controller.init(main);
                main.emit(key);
                sinon.assert.called(stub);
            })
        })


您似乎还需要main而不使用proxyquire。这样它就永远不会捡起存根了。有两种解决方案:1)返工main,将UI作为参数(即依赖项注入),在这种情况下,您的测试可以将存根传递给main;或者2)使用proxyquire要求main,以便您可以强制它要求stubbed版本。如果您需要更多详细信息,请告诉我。

好的,我不知道有关sinon的信息,但jest具有相同的功能,名为

由于出口,jest也面临同样的问题。因为您在main.js中导出了
getMain
init
start
,并在
init
内部使用了
getMain
start


相反,尝试移动
getMain
start
以分离模块并导出和测试它。如果您运行的
const stub=sinon.stub(控制器,值)仍然出现问题,请告诉我
存根由
ui
模块导出的值。这确实会更改模块导出的值,但问题在于
ui
模块中的此代码:

function init(main) {
    main.on('http-init', onHttpInit.bind(this));
    main.on('http-success', onHttpSuccess.bind(this));
    main.on('http-error', onHttpError.bind(this));
    main.once('app-ready', onAppReady.bind(this));
}
从这个代码的角度来看,
module.exports
被您的调用
sinon.stub(controller,value)
变异,但是这不会改变符号
onHttpInit
onHttpSuccess
,的值,等等。上面代码中的符号,因为这些符号是
ui
模块
范围内的本地符号。您可以对
module.exports
进行任意更改:它仍然对上面的代码没有影响

您可以将代码更改为:

function init(main) {
    main.on('http-init', exports.onHttpInit.bind(this));
    main.on('http-success', exports.onHttpSuccess.bind(this));
    main.on('http-error', exports.onHttpError.bind(this));
    main.once('app-ready', exports.onAppReady.bind(this));
}
您可以直接使用
exports
,因为您将相同的值分配给
module.exports
exports
以及
module.exports=exports=…


此更改将解决您遇到的直接问题。但是,我会在这里修改测试方法。您的测试标题为
应该将“${key}”上的eventlistener注册到${value}
,但实际上您测试的不仅仅是事件侦听器已注册,而是事件传播工作正常。实际上,您正在测试
EventEmitter
负责提供的功能。我会将测试更改为在
main
对象的
方法上存根
,并验证是否使用适当的值调用了它。然后,测试将测试它实际宣传的内容。

经过一周的提问和测试,我找到了解决方案。这是DDupont和路易斯的解决方案的结合。第一个更改是在ui.js文件中,将
this.
添加到绑定中

function init(main) {
    main.on('http-init', this.onHttpInit.bind(this));
    main.on('http-success', this.onHttpSuccess.bind(this));
    main.on('http-error', this.onHttpError.bind(this));
    main.once('app-ready', this.onAppReady.bind(this));
  };
正如DDupont所说,在单元测试中,在存根之后移动
controller.init(main)

eventMap.forEach((value, key) => {
            it(`should register an eventlistener on '${key}' to ${value}`, () => {
                const stub = sinon.stub(controller, value);
                controller.init(main);
                main.emit(key);
                sinon.assert.called(stub);
            })
        })

感谢您提供的所有帮助。

这也不起作用,仍然会遇到与上述相同的问题和相同的输出。解决方案1不太可能,因为应用程序有更多类似于UI的控制器,对于解决方案2,您将如何代理主单例?我不完全确定。我确实理解你说的话,但我不确定我会怎么做。我会为你准备一个完整的测试示例-今天被淹没了hi@DDupont,你能研究一下吗?谢谢。你是说这里的问题是所有的init、start和getMain都在同一个模块中,这会干扰单元测试吗?是的,但也是因为一个模块在另一个模块中调用。那么,很难解决这个问题,至少在应用程序的源代码没有做很多更改的情况下是这样的,由于它相当大,而且不经过讨论就无法真正更改代码,但还是要感谢您的洞察力。:)是的,它正在测试,但是,当我执行函数init(main){main.on('http-init',exports.onHttpInit.bind(this));main.on('http-success',exports.onHttpSuccess.bind(this));main.once('app-ready',exports.onAppReady.bind(this));}我得到了以下错误:TypeError:无法读取Object.init(ui.js:5:45)上未定义的属性“bind”,但是,显然当我将其更改为this.onHttpInit.bind(this)时,测试似乎
        eventMap.forEach((value, key) => {
            it(`should register an eventlistener on '${key}' to ${value}`, () => {
                const stub = sinon.stub(controller, value);
                controller.init(main);
                main.emit(key);
                sinon.assert.called(stub);
            })
        })
function init(main) {
    main.on('http-init', onHttpInit.bind(this));
    main.on('http-success', onHttpSuccess.bind(this));
    main.on('http-error', onHttpError.bind(this));
    main.once('app-ready', onAppReady.bind(this));
}
function init(main) {
    main.on('http-init', exports.onHttpInit.bind(this));
    main.on('http-success', exports.onHttpSuccess.bind(this));
    main.on('http-error', exports.onHttpError.bind(this));
    main.once('app-ready', exports.onAppReady.bind(this));
}
function init(main) {
    main.on('http-init', this.onHttpInit.bind(this));
    main.on('http-success', this.onHttpSuccess.bind(this));
    main.on('http-error', this.onHttpError.bind(this));
    main.once('app-ready', this.onAppReady.bind(this));
  };
eventMap.forEach((value, key) => {
            it(`should register an eventlistener on '${key}' to ${value}`, () => {
                const stub = sinon.stub(controller, value);
                controller.init(main);
                main.emit(key);
                sinon.assert.called(stub);
            })
        })