使用 Mocha 的 NodeJS EventEmitter 测试
NodeJS EventEmitter test with Mocha
我在测试 ldapjs 客户端搜索操作时遇到问题。它 returns 一个 EventEmitter,你必须让它监听一些特定的事件。我包装了这个操作以对其进行承诺并定义我的逻辑,我想对其进行单元测试。
findUser(username) {
return new Promise((resolve, reject) => {
logger.debug('Searching user: ', username);
this.ldapClient.bind(user.name, .user.password, err => {
if (err) return reject(err);
else
this.ldapClient.search(root, {filter: `(cn=${username})`}, (errSearch, resSearch) => {
if (errSearch) return reject(errSearch);
const entries = [];
resSearch.on('searchEntry', entry => entries.push(entry.object));
resSearch.on('searchReference', referral => reject(new Error(`Received search referall: ${referral}`)));
resSearch.on('error', err => reject((err));
resSearch.on('end', result => {
if (result.status === 0 && entries.length === 1) {
return resolve({
cn: entries[0].cn,
objectclass: entries[0].objectclass,
password: entries[0].password
});
} else {
return reject(new Error(`Wrong search result: ${result}`));
}
});
});
});
});
}
我正在使用 mockery and Sinon 替换模块中的 ldapjs 依赖项:
beforeEach(function () {
searchEM = new EventEmitter();
sandbox = sinon.createSandbox();
ldapClientStub = Stubs.getLdapClientStub(sandbox);
ldapClientStub.bind.yields(null);
ldapClientStub.search.withArgs('o=ldap', {filter: `(cn=${findParam})`}).yields(null, searchEM);
mockery.registerMock('ldapjs', Stubs.getLdapStub(ldapClientStub));
mockery.registerAllowable('../src/client');
UserClientCls = require('../src/client').default;
userClient = new UserClientCls(config.get());
});
it('should return user with given username', function (done) {
setTimeout(() => {
searchEM.emit('searchEntry', users[1]);
searchEM.emit('end', {status: 0});
console.log('emitted');
}, 500);
searchEM.on('end', res => console.log(res));
userClient.findUser(findParam)
.then(user => {
user.cn.should.equal(users[1].attributes.cn);
user.objectclass.should.equal(users[1].attributes.objectclass);
user.password.should.equal(users[1].attributes.password);
return done();
})
.catch(err => done(err));
});
问题在于 findUser
中定义的侦听器从未被调用(但函数本身被调用)。我在测试中定义的侦听器(只是为了调试行为)被正确调用。
我不明白我是否遗漏了 EventEmitters 的工作原理,或者我是否以错误的方式进行了测试。或者我写了一段无法测试的错误代码。
我找到了解决问题的方法。我扩展了基础 EventEmitter
:我添加了逻辑来存储我想要发出的事件,并用发出我的假事件的逻辑覆盖了它的 on
方法。
class TestEventEmitter extends EventEmitter {
constructor() {
super();
}
setFakeEmit(fakeEmit) {
this.fakeEmit = fakeEmit;
}
on(eventName, cb) {
super.on(eventName, cb);
if (super.eventNames().length === 4)
this.fakeEmit.forEach(f => this.emit(f.name, f.obj));
}
}
所以,在 beforeEach
中,我可以存根 ldapClientStub.search
使其成为 return 我的 TestEventEmitter
:
beforeEach(function() {
searchEM = new TestEventEmitter();
searchEM.setFakeEmit([{
name: 'searchEntry',
obj: { object: users[1].attributes }
}, {
name: 'end',
obj: { status: 0 }
}]);
...
ldapClientStub.search.withArgs('o=ldap', { filter: `(&(cn=${findParam})(objectclass=astsUser))` }).yields(null, searchEM);
})
这个解决方案可能不是很优雅,但它确实有效。如果有人可以 post 更好的解决方案,我将很乐意看看。
我在测试 ldapjs 客户端搜索操作时遇到问题。它 returns 一个 EventEmitter,你必须让它监听一些特定的事件。我包装了这个操作以对其进行承诺并定义我的逻辑,我想对其进行单元测试。
findUser(username) {
return new Promise((resolve, reject) => {
logger.debug('Searching user: ', username);
this.ldapClient.bind(user.name, .user.password, err => {
if (err) return reject(err);
else
this.ldapClient.search(root, {filter: `(cn=${username})`}, (errSearch, resSearch) => {
if (errSearch) return reject(errSearch);
const entries = [];
resSearch.on('searchEntry', entry => entries.push(entry.object));
resSearch.on('searchReference', referral => reject(new Error(`Received search referall: ${referral}`)));
resSearch.on('error', err => reject((err));
resSearch.on('end', result => {
if (result.status === 0 && entries.length === 1) {
return resolve({
cn: entries[0].cn,
objectclass: entries[0].objectclass,
password: entries[0].password
});
} else {
return reject(new Error(`Wrong search result: ${result}`));
}
});
});
});
});
}
我正在使用 mockery and Sinon 替换模块中的 ldapjs 依赖项:
beforeEach(function () {
searchEM = new EventEmitter();
sandbox = sinon.createSandbox();
ldapClientStub = Stubs.getLdapClientStub(sandbox);
ldapClientStub.bind.yields(null);
ldapClientStub.search.withArgs('o=ldap', {filter: `(cn=${findParam})`}).yields(null, searchEM);
mockery.registerMock('ldapjs', Stubs.getLdapStub(ldapClientStub));
mockery.registerAllowable('../src/client');
UserClientCls = require('../src/client').default;
userClient = new UserClientCls(config.get());
});
it('should return user with given username', function (done) {
setTimeout(() => {
searchEM.emit('searchEntry', users[1]);
searchEM.emit('end', {status: 0});
console.log('emitted');
}, 500);
searchEM.on('end', res => console.log(res));
userClient.findUser(findParam)
.then(user => {
user.cn.should.equal(users[1].attributes.cn);
user.objectclass.should.equal(users[1].attributes.objectclass);
user.password.should.equal(users[1].attributes.password);
return done();
})
.catch(err => done(err));
});
问题在于 findUser
中定义的侦听器从未被调用(但函数本身被调用)。我在测试中定义的侦听器(只是为了调试行为)被正确调用。
我不明白我是否遗漏了 EventEmitters 的工作原理,或者我是否以错误的方式进行了测试。或者我写了一段无法测试的错误代码。
我找到了解决问题的方法。我扩展了基础 EventEmitter
:我添加了逻辑来存储我想要发出的事件,并用发出我的假事件的逻辑覆盖了它的 on
方法。
class TestEventEmitter extends EventEmitter {
constructor() {
super();
}
setFakeEmit(fakeEmit) {
this.fakeEmit = fakeEmit;
}
on(eventName, cb) {
super.on(eventName, cb);
if (super.eventNames().length === 4)
this.fakeEmit.forEach(f => this.emit(f.name, f.obj));
}
}
所以,在 beforeEach
中,我可以存根 ldapClientStub.search
使其成为 return 我的 TestEventEmitter
:
beforeEach(function() {
searchEM = new TestEventEmitter();
searchEM.setFakeEmit([{
name: 'searchEntry',
obj: { object: users[1].attributes }
}, {
name: 'end',
obj: { status: 0 }
}]);
...
ldapClientStub.search.withArgs('o=ldap', { filter: `(&(cn=${findParam})(objectclass=astsUser))` }).yields(null, searchEM);
})
这个解决方案可能不是很优雅,但它确实有效。如果有人可以 post 更好的解决方案,我将很乐意看看。