NodeJS:如何断言是否使用 sinon 调用了事件回调函数
NodeJS: How to assert if event callback function was called using sinon
如何测试事件侦听器的回调函数是否被调用?例如,我有以下代码,其中 app.js 通过 init.js 控制器初始化应用程序。
main.js 文件有一个 class 扩展和事件发射器,使对象成为事件发射器。
app.js
const initController = require('./init');
async function init() {
initController.startMain();
}
init();
main.js
const events = require('events'),
ui = require('./ui');
module.exports.getMain = function () {
class Main extends events.EventEmitter {
constructor() {
super();
this.status = null;
}
}
return new Main();
};
module.exports.init = () => {
const main = this.getMain();
ui.init(main);
this.start(main);
}
module.exports.start = (main) => {
ui.start(main);
main.emit('http-init');
main.emit('http-success');
main.emit('http-error');
};
ui.js
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 start (main) {};
function onAppReady() {
console.log('APP READY');
};
function onHttpInit() {
console.log('HTTP INIT SEQUENCE');
};
function onHttpError(error) {
console.log('HTTP ERROR SEQUENCE');
};
function onHttpSuccess() {
console.log('HTTP SUCCESS SEQUENCE');
};
module.exports = exports = {
init,
start,
onHttpInit,
onHttpError,
onHttpSuccess,
};
init.js
exports.startMain = () => {
console.log('Start application');
// Load application modules
const main = require('./main');
// Start the application
main.init();
};
因此,当我 运行 命令 node app.js
时,我看到以下输出
开始申请
HTTP 初始化序列
HTTP 成功序列
HTTP 错误序列
这意味着侦听器处于活动状态并且调用了函数。
ui.tests.js
const sinon = require('sinon'),
main = require('../main').getMain(),
proxyquire = require('proxyquire').noPreserveCache().noCallThru();
describe('UI Tests', () => {
const sandbox = sinon.createSandbox();
let controller = null;
before(() => {
controller = proxyquire('../ui', {});
})
describe('Testing Eventlisteners', ()=> {
afterEach(() => {
main.removeAllListeners();
});
const eventMap = new Map([
[ 'http-init', 'onHttpInit' ],
[ 'http-success', 'onHttpSuccess' ],
[ 'http-error', 'onHttpError']
]);
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);
})
})
})
})
然而,当我 运行 上面的测试时,即使我得到了输出,即函数被调用,然而,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)
我不知道为什么即使函数至少被调用一次,测试也会失败,当我 运行 测试。
我试过 stub.should.have.been.called;
。然而,通过这个测试,它并没有真正通过测试,因为 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) require main with proxyquire 这样你就可以强制它要求存根版本。如果您需要更多详细信息,请告诉我。
好的,我不关心 sinon,但这个笑话具有相同的功能,称为 mock functions。
jest 由于导出 https://medium.com/@DavideRama/mock-spy-exported-functions-within-a-single-module-in-jest-cdf2b61af642 也面临同样的问题。由于您在 main.js 中导出 getMain
、init
和 start
并使用 getMain
和 start
init
.
里面
改为尝试移动 getMain
和 start
以分离模块并导出和测试它。如果问题仍然出现请告诉我
您 运行 const stub = sinon.stub(controller, value);
存根 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
赋了相同的值
此更改应该可以解决您 运行 遇到的直接问题。但是,我会在这里修改测试方法。您的测试标题为 should register an eventlistener on '${key}' to ${value}
但实际上您正在测试的不仅仅是事件侦听器已注册,而且事件传播有效。实际上,您正在测试 EventEmitter
负责提供的功能。我会更改测试以存根 main
object 的 on
方法,并验证是否已使用适当的值调用它。然后测试将测试它实际宣传的内容。
经过一周的提问和测试,我找到了解决办法。这是 DDupont 和 Louis 解决方案的组合。首先更改如下,在ui.js文件中,在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));
};
正如 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);
})
})
感谢大家的帮助。
如何测试事件侦听器的回调函数是否被调用?例如,我有以下代码,其中 app.js 通过 init.js 控制器初始化应用程序。
main.js 文件有一个 class 扩展和事件发射器,使对象成为事件发射器。
app.js
const initController = require('./init');
async function init() {
initController.startMain();
}
init();
main.js
const events = require('events'),
ui = require('./ui');
module.exports.getMain = function () {
class Main extends events.EventEmitter {
constructor() {
super();
this.status = null;
}
}
return new Main();
};
module.exports.init = () => {
const main = this.getMain();
ui.init(main);
this.start(main);
}
module.exports.start = (main) => {
ui.start(main);
main.emit('http-init');
main.emit('http-success');
main.emit('http-error');
};
ui.js
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 start (main) {};
function onAppReady() {
console.log('APP READY');
};
function onHttpInit() {
console.log('HTTP INIT SEQUENCE');
};
function onHttpError(error) {
console.log('HTTP ERROR SEQUENCE');
};
function onHttpSuccess() {
console.log('HTTP SUCCESS SEQUENCE');
};
module.exports = exports = {
init,
start,
onHttpInit,
onHttpError,
onHttpSuccess,
};
init.js
exports.startMain = () => {
console.log('Start application');
// Load application modules
const main = require('./main');
// Start the application
main.init();
};
因此,当我 运行 命令 node app.js
时,我看到以下输出
开始申请 HTTP 初始化序列 HTTP 成功序列 HTTP 错误序列
这意味着侦听器处于活动状态并且调用了函数。
ui.tests.js
const sinon = require('sinon'),
main = require('../main').getMain(),
proxyquire = require('proxyquire').noPreserveCache().noCallThru();
describe('UI Tests', () => {
const sandbox = sinon.createSandbox();
let controller = null;
before(() => {
controller = proxyquire('../ui', {});
})
describe('Testing Eventlisteners', ()=> {
afterEach(() => {
main.removeAllListeners();
});
const eventMap = new Map([
[ 'http-init', 'onHttpInit' ],
[ 'http-success', 'onHttpSuccess' ],
[ 'http-error', 'onHttpError']
]);
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);
})
})
})
})
然而,当我 运行 上面的测试时,即使我得到了输出,即函数被调用,然而,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)
我不知道为什么即使函数至少被调用一次,测试也会失败,当我 运行 测试。
我试过 stub.should.have.been.called;
。然而,通过这个测试,它并没有真正通过测试,因为 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) require main with proxyquire 这样你就可以强制它要求存根版本。如果您需要更多详细信息,请告诉我。
好的,我不关心 sinon,但这个笑话具有相同的功能,称为 mock functions。
jest 由于导出 https://medium.com/@DavideRama/mock-spy-exported-functions-within-a-single-module-in-jest-cdf2b61af642 也面临同样的问题。由于您在 main.js 中导出 getMain
、init
和 start
并使用 getMain
和 start
init
.
改为尝试移动 getMain
和 start
以分离模块并导出和测试它。如果问题仍然出现请告诉我
您 运行 const stub = sinon.stub(controller, value);
存根 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
赋了相同的值
此更改应该可以解决您 运行 遇到的直接问题。但是,我会在这里修改测试方法。您的测试标题为 should register an eventlistener on '${key}' to ${value}
但实际上您正在测试的不仅仅是事件侦听器已注册,而且事件传播有效。实际上,您正在测试 EventEmitter
负责提供的功能。我会更改测试以存根 main
object 的 on
方法,并验证是否已使用适当的值调用它。然后测试将测试它实际宣传的内容。
经过一周的提问和测试,我找到了解决办法。这是 DDupont 和 Louis 解决方案的组合。首先更改如下,在ui.js文件中,在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));
};
正如 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);
})
})
感谢大家的帮助。