I have a problem testing ldapjs client search operation. It returns an EventEmitter that you have to make listen for some specific event. I wrapped this operations to promisify it and to define my logic and I would like to unit-test it.
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}`));
}
});
});
});
});
}
I am using mockery and Sinon to replace ldapjs dependency inside my module:
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));
});
The problem is that listeners defined inside findUser are never called (but the function itself is called). The listener I defined in the test (just to debug the behaviour) is correctly called.
I do not understand if I miss something about how EventEmitters works or if I am doing the test in a wrong way. Or maybe I wrote a bad piece of code that cannot be tested.
I found a solution to my problem. I extended the base EventEmitter: I added the logic to store which event I want to emit and overrode its on method with a logic to emit my fake event.
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));
}
}
So, in beforeEach I can stub ldapClientStub.search to make it return my 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);
})
This solution may be not very elegant, but it works. If someone can post a better solution I'll be glad to have a look.
Related
i have this code:
function isAdmin(idOfChat, IdOfUser, ctx) {
//function
return isAdminBoolean
}
bot.command("test", ctx => {
if (isAdmin(ctx.message.chat.id, ctx.message.from.id) == true) {
ctx.reply("Admin")
}else{
ctx.reply("._.")
}
})
how to make it work?
sorry for my bad English)))
You should re-write your method as a promise (because Telegraf queries from Telegram API, so, your isAdmin method could be like this)
function isAdmin(idOfChat, IdOfUser, ctx) {
return new Promise((resolve, reject) => {
//Get user information first
ctx.telegram.getChatMember(idOfChat, IdOfUser).then((user) => {
//Then check if user is admin (or creator)
resolve(user.status == "administrator" || user.status == "creator");
})
.catch((error) => {
//Reject if it's an error
reject(error);
});
});
}
Then, for use it into your main function, you should have to handle it like this:
bot.command("test", ctx => {
isAdmin(ctx.message.chat.id, ctx.message.from.id, ctx).then((result) => {
if (result) {
ctx.reply("Admin");
} else {
ctx.reply("._.");
}
})
.catch((error) => {
ctx.reply("An error has ocurred trying to get user rank: " + JSON.stringify(error));
});
});
I want to refactor code for chain of promises by async, await. I have sequelize ORM for DB management and the code is written in AWS Lambda function having multiple middleware. In such cases I have to traverse code for multiple entries using sequelize transactions. It is easy to manage using promise.all() but need to change it to async await syntax for cleaner code.
Here are my demo code.
/* get all invoice where user_id === current logged in user, and where status != "paid" */
db.Invoice.findAll({
where: {
user_id: currentLoggedInUser,
status: {
$ne: "paid"
}
}
}).then(invoices => {
if (!invoices || invoices === null) {
return false;
}
function addScheduledTransactionAttempts(invoice, tryPayOnDate, t) {
return new Promise((resolve, reject) => {
/* check If any ScheduledTransactionAttempts exists for this invoice.id */
db.ScheduledTransactionAttempts.find({
where: {
invoice_id: invoice.id
}
})
.then(function(attempts) {
if (attempts) {
attempts
.destroy({}, {
transaction: t
})
.then(deletedAttempts => {
console.log("Attempts Record Deleted: ", deletedAttempts);
})
.catch(error => {
reject(error);
t.rollback();
});
}
return db.ScheduledTransactionAttempts.create({
invoice_id: invoice.id,
payment_source_id: PaymentMethodId,
try_pay_on_date: tryPayOnDate,
stripe_customer_id: currentLogInStripeCustomerId
}, {
transaction: t
})
.then(function(attempt) {
resolve(attempt.id);
})
.catch(error => {
reject(error);
t.rollback();
});
})
.catch(error => {
reject(error);
t.rollback();
});
});
}
//Run transaction to addScheduledTransactionAttempts
return db.sequelize.transaction().then(function(t) {
let promiseArr = [];
var i = 0;
invoices.forEach(function(invoice) {
var schedulePaymentDate = moment(paymentDate);
if (invoice) {
let tryPayOnDate = schedulePaymentDate
.add(i, "month")
.format("YYYY-MM-DD");
promiseArr.push(
addScheduledTransactionAttempts(invoice, tryPayOnDate, t) //calling above function
);
i++;
}
});
//now execute promise all
Promise.all(promiseArr)
.then(function(result) {
t.commit();
return true;
})
.catch(function(err) {
t.rollback();
return false;
});
});
});
In the above code I want to change
Promise.all(promiseArr)
which is calling
addScheduledTransactionAttempts
function to do DB queries to simple async function await process to make it easy simpler understandable without having multiple .then or .then inside then promises.
Any help regarding would be appreciated,
Thanks.
It's quite simple. await is valid when invoking methods that return a Promise. All of your SDK methods already return a promise, so refactoring should be quite straight forward. Here's something to get you off ground:
const processInvoices = async currentLoggedInUser {
const invoices = await db.Invoice.findAll({
where: {
user_id: currentLoggedInUser,
status: {
$ne: 'paid',
},
},
});
if (yourOwnLogicForInvoicesObject) {
for (const invoice of invoices) {
const potentiallyFoundInvoice = await db.ScheduledTransactionAttempts.find({
where: {
invoice_id: invoice.id,
},
});
if (potentiallyFoundInvoice) {
await addScheduledTransactionAttempts(potentiallyFoundInvoice)
}
}
}
}
const addScheduledTransactionAttempts = async invoice => {
console.log('Do something with your invoice', invoice)
}
Long story short: refactor the code inside your functions into smaller functions and make these new functions async, just like I did with addScheduledTransactionAttempts and processInvoices
More on async/await
So I have a file, user-database, that looks something like this :
export function foo(id: number): Promise<boolean> {
return new Promise<boolean>((resolve, reject) => {
findSomething(id)
.then((data) => {
//do something with data
})
}
}
export function findSomething(id: number): Promise<Object> {
return new Promise<Object> ((resolve, reject) => {
let query = 'SELECT * FROM user';
db.executeQuery(query);
.then(data) => {
if(data.length < 1) { reject(new Error('whoops')); }
resolve(data);
}, (err) => {
reject(err);
})
})
}
So I am writing unit tests using Sinon for the exterior function, foo, and therefore I want to stub the function it calls, findSomething. I do this as follows:
import * as user_db from '../../src/user-database';
describe('POST /someEndpoint', () => {
describe('when successful', () => {
let stub;
beforeEach(function() {
stub = sinon.stub(user_db, 'findSomething');
});
afterEach(function() {
stub.restore();
});
it('should respond with 200', function(done) {
stub.returns(anObjectIPredefine);
request(server)
.post(basePath)
.send(aPayloadIPredefine)
.expect(200, done);
});
}
}
When I run the test, I don't see the object I am telling the stub to return with this stub.returns(anObjectIPredefine). I instead actually have the function findSomething execute as normal and grab data from the dB. Is there anything obvious I am doing wrong? My only guess is that stub = sinon.stub(user_db, 'findSomething') is not the proper syntax for stubbing a function defined in the same scope as the function being tested. I can't find what an alternative syntax would be though.
So what I ended up doing was moving the functions I wished to stub to a different file. When this is done, stubbing works as intended. Probably not the best solution, but definitely a quick band-aid for anyone in a similar situation.
I've got a simple function such as;
module.exports = {
fetchUser:function(myUserId) {
return new Promise((resolve, reject) => {
this.getUser(myUserId)
.then(user => {
// do logic // then return user
return user;
})
.then(resolve)
.catch(err => {
// whoops there has been an error
let error = { error: 'My Error' };
reject(error);
});
});
}
};
I want to unit test both the resolve and reject result.
A simple chai test would be;
var expect = require('chai').expect;
var user = require('./user');
describe('User module', function() {
it('test fetchUser', function() {
let _user = user.fetchUser('abc123');
return _user
.then(user => {
expect(data).to.be.an('object');
});
});
Using sinon or another library, how can I for the fetchUser function to throw that reject error?
With Mocha, Chai and Sinon it can be implemented with stubbed method getUser.
const User = require("./fetchUserModule");
describe('User module', () => {
beforeEach(() => User.getUser = sinon.stub());
afterEach(() => User.getUser.reset());
it('returns user if `getUser` returns data', () => {
const user = {name: 'John'};
User.getUser.withArgs("abc123").returns(Promise.resolve(user));
return User.fetchUser("abc123").then(result => {
expect(result).to.equal(user)
}).catch(error => {
expect(error).to.be.undefined;
})
});
it('throws error if `getUser` is rejected', () => {
User.getUser.withArgs("abc123").returns(Promise.reject());
return User.fetchUser("abc123").then(result => {
expect(result).to.be.undefined;
}).catch(err => {
expect(err).to.eql({error: 'My Error'})
})
});
});
Start with anything in your "logic" that can throw an error.
If not you would need to stub this.getUser to reject or throw an error instead of returning data. sinon-as-promised patches sinon.stub to include the .resolves and .rejects promise helpers.
const sinon = require('sinon')
require('sinon-as-promised')
Setup the stub for the failure tests.
before(function(){
sinon.stub(user, 'getUser').rejects(new Error('whatever'))
})
after(function(){
user.getUser.restore()
})
Then either catch the .fetchUser error or use chai-as-promised for some sugar.
it('test fetchUser', function() {
return user.fetchUser('abc123')
.then(()=> expect.fail('fetchUser should be rejected'))
.catch(err => {
expect(err.message).to.eql('whatever')
})
})
it('test fetchUser', function() {
return expect(user.fetchUser('abc123')).to.be.rejectedWith(Error)
})
or async if you live in the new world
it('test fetchUser', async function() {
try {
await user.fetchUser('abc123')
expect.fail('fetchUser should be rejected'))
} catch(err) {
expect(err.message).to.eql('whatever')
}
})
As a side note, you don't need to wrap something that already returns a promise in new Promise and be careful about losing error information when chaining multiple .catch handlers.
fetchUser: function (myUserId) {
return this.getUser(myUserId)
.then(user => {
//logic
return user
})
.catch(err => {
let error = new Error('My Error')
error.original = err
reject(error)
});
}
Using promises with NodeJS, I load a model that can then be re-used by susequent calls to the NodeJS app. How can I prevent the same object/model being loaded twice from a database if a second request arrives while the first is still being loaded?
I set a "loading flag" to say that the object is being retrieved from the database and "loaded" when done. If there is a second request that attempts to load the same object, it needs to wait until the initial model is filled and then both can use the same object.
Sample Code (simplified, ES6, Node 0.10 [old for a reason]).
It's the TODO that needs solving.
App:
import ClickController from './controllers/ClickController'
import express from 'express'
const app = express()
app.get('/click/*', (req, res) => {
// Get the parameters here
let gameRef = "test";
ClickController.getGameModel(gameRef)
.then(() => {
console.log('Got game model')
return this.handleRequest()
}, (err) => {
throw err
})
}
Controller:
import gameModel from '../models/GameModel'
class ClickController {
constructor(config) {
// Stores the objects so they can be re-used multiple times.
this.loadedGames = {}
}
// getGameModel() as a promise; return model (or throw error if it doesn't exist)
getGameModel(gameRef) {
return new Promise((resolve, reject) => {
let oGame = false
if(typeof this.loadedGames[gameRef] === 'undefined') {
oGame = new gameModel()
this.loadedGames[gameRef] = oGame
} else {
oGame = this.loadedGames[gameRef]
}
oGame.load(gameRef)
.then(function() {
resolve()
}, (err) => {
reject(err)
})
})
}
}
Model / Object:
class GameModel {
constructor {
this.loading = false
this.loaded = false
}
load(gameRef) {
return new Promise((resolve, reject) => {
if (this.loading) {
// TODO: Need to wait until loaded, then resolve or reject
} else if (!this.loaded) {
this.loading = true
this.getActiveDBConnection()
.then(() => {
return this.loadGame(gameRef)
}, (err) => {
console.log(err)
reject(err)
})
.then(() => {
this.loading = false
this.loaded = true
resolve()
})
} else {
// Already loaded, we're fine
resolve()
}
})
}
// As this uses promises, another event could jump in and call "load" while this is working
loadGame(gameRef) {
return new Promise((resolve, reject) => {
let sql = `SELECT ... FROM games WHERE gameRef = ${mysql.escape(gameRef)}`
this.dbConnection.query(sql, (err, results) => {
if (err) {
reject('Error querying db for game by ref')
} else if (results.length > 0) {
// handle results
resolve()
} else {
reject('Game Not Found')
}
})
})
}
}
I don't follow exactly which part of you're code you are asking about, but the usual scheme for caching a value with a promise while a request is already "in-flight" works like this:
var cachePromise;
function loadStuff(...) {
if (cachePromise) {
return cachePromise;
} else {
// cache this promise so any other requests while this one is stil
// in flight will use the same promise
cachePromise = new Promise(function(resolve, reject) {
doSomeAsyncOperation(function(err, result) {
// clear cached promise so subsequent requests
// will do a new request, now that this one is done
cachePromise = null;
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
return cachePromise;
}
}
// all these will use the same result that is in progress
loadStuff(...).then(function(result) {
// do something with result
});
loadStuff(...).then(function(result) {
// do something with result
});
loadStuff(...).then(function(result) {
// do something with result
});
This keeps a cached promise and, as long as request is "in-flight", the cachePromise value is in place and will be returned by subsequent requests.
As soon as the request actually finishes, the cachePromise will be cleared so that the next request that comes later will issue a new request.