How do I properly test promises with mocha and chai? - node.js

The following test is behaving oddly:
it('Should return the exchange rates for btc_ltc', function(done) {
var pair = 'btc_ltc';
shapeshift.getRate(pair)
.then(function(data){
expect(data.pair).to.equal(pair);
expect(data.rate).to.have.length(400);
done();
})
.catch(function(err){
//this should really be `.catch` for a failed request, but
//instead it looks like chai is picking this up when a test fails
done(err);
})
});
How should I properly handle a rejected promise (and test it)?
How should I properly handle a failed test (ie: expect(data.rate).to.have.length(400);?
Here is the implementation I'm testing:
var requestp = require('request-promise');
var shapeshift = module.exports = {};
var url = 'http://shapeshift.io';
shapeshift.getRate = function(pair){
return requestp({
url: url + '/rate/' + pair,
json: true
});
};

The easiest thing to do would be to use the built in promises support Mocha has in recent versions:
it('Should return the exchange rates for btc_ltc', function() { // no done
var pair = 'btc_ltc';
// note the return
return shapeshift.getRate(pair).then(function(data){
expect(data.pair).to.equal(pair);
expect(data.rate).to.have.length(400);
});// no catch, it'll figure it out since the promise is rejected
});
Or with modern Node and async/await:
it('Should return the exchange rates for btc_ltc', async () => { // no done
const pair = 'btc_ltc';
const data = await shapeshift.getRate(pair);
expect(data.pair).to.equal(pair);
expect(data.rate).to.have.length(400);
});
Since this approach is promises end to end it is easier to test and you won't have to think about the strange cases you're thinking about like the odd done() calls everywhere.
This is an advantage Mocha has over other libraries like Jasmine at the moment. You might also want to check Chai As Promised which would make it even easier (no .then) but personally I prefer the clarity and simplicity of the current version

As already pointed out here, the newer versions of Mocha are already Promise-aware. But since the OP asked specifically about Chai, it's only fair to point out the chai-as-promised package which provides a clean syntax for testing promises:
using chai-as-promised
Here's how you can use chai-as-promised to test both resolve and reject cases for a Promise:
var chai = require('chai');
var expect = chai.expect;
var chaiAsPromised = require("chai-as-promised");
chai.use(chaiAsPromised);
...
it('resolves as promised', function() {
return expect(Promise.resolve('woof')).to.eventually.equal('woof');
});
it('rejects as promised', function() {
return expect(Promise.reject('caw')).to.be.rejectedWith('caw');
});
without chai-as-promised
To make it really clear as to what's getting tested, here's the same example coded without chai-as-promised:
it('resolves as promised', function() {
return Promise.resolve("woof")
.then(function(m) { expect(m).to.equal('woof'); })
.catch(function(m) { throw new Error('was not supposed to fail'); })
;
});
it('rejects as promised', function() {
return Promise.reject("caw")
.then(function(m) { throw new Error('was not supposed to succeed'); })
.catch(function(m) { expect(m).to.equal('caw'); })
;
});

Here's my take:
using async/await
not needing extra chai modules
avoiding the catch issue, #TheCrazyProgrammer pointed out above
A delayed promise function, that fails, if given a delay of 0:
const timeoutPromise = (time) => {
return new Promise((resolve, reject) => {
if (time === 0)
reject({ 'message': 'invalid time 0' })
setTimeout(() => resolve('done', time))
})
}
// ↓ ↓ ↓
it('promise selftest', async () => {
// positive test
let r = await timeoutPromise(500)
assert.equal(r, 'done')
// negative test
try {
await timeoutPromise(0)
// a failing assert here is a bad idea, since it would lead into the catch clause…
} catch (err) {
// optional, check for specific error (or error.type, error. message to contain …)
assert.deepEqual(err, { 'message': 'invalid time 0' })
return // this is important
}
assert.isOk(false, 'timeOut must throw')
log('last')
})
Positive test is rather simple. Unexpected failure (simulate by 500→0) will fail the test automatically, as rejected promise escalates.
Negative test uses the try-catch-idea. However: 'complaining' about an undesired pass happens only after the catch clause (that way, it does not end up in the catch() clause, triggering further but misleading errors.
For this strategy to work, one must return the test from the catch clause. If you want't to test anything else, use another it()-block.

Thre is a better solution. Just return the error with done in a catch block.
// ...
it('fail', (done) => {
// any async call that will return a Promise
ajaxJson({})
.then((req) => {
expect(1).to.equal(11); //this will throw a error
done(); //this will resove the test if there is no error
}).catch((e) => {
done(e); //this will catch the thrown error
});
});
this test will fail with following message: AssertionError: expected 1 to equal 11

Related

Catch multiple nested asynchronous function errors within a single catch block

The code below is an example of what may take place during development.
With the current code, the outer function may throw an error but in this case wont. However, the nested function WILL throw an error (for examples sake). Once it throws the error it cannot be caught as it is asynchronous function.
Bungie.Get('/Platform/Destiny2/Manifest/').then((ResponseText)=>{
//Async function that WILL throw an error
Bungie.Get('/Platform/Destiny2/Mnifest/').then((ResponseText)=>{
console.log('Success')
})
}).catch((error)=>{
//Catch all errors from either the main function or the nested function
doSomethingWithError(error)
});
What I want is for the outer most function to catch all asynchronous function error's but with this code I cannot. I have tried awaiting the nested function but there may be certain circumstances where it will be quicker to not wait for the function. I also tried to include a .catch() with each nested function but this would require a .catch() for each function that would allhandle the error in the same way e.g. doSomethingWithError().
you only needs return the inner function in the outside function.
see example below:
const foo = new Promise((resolve,reject) =>{
setTimeout(() => resolve('foo'), 1000);
});
foo.then((res)=>{
console.log(res)
return new Promise((resolve,reject)=>{
setTimeout(() => reject("bar fail"), 1000);
})
}).catch((e)=>{
// your own logic
console.error(e)
});
this is called promise chaining. see this post for more info https://javascript.info/promise-chaining
if you have multiple promises can do something like:
const foo1 = new Promise((resolve,reject) =>{
setTimeout(() => resolve('foo1'), 1000);
});
const foo2 = new Promise((resolve,reject) =>{
setTimeout(() => resolve('foo2'), 2000);
});
const foo3 = new Promise((resolve,reject) =>{
setTimeout(() => reject('foo3'), 3000);
});
const bar = new Promise((resolve,reject) =>{
setTimeout(() => resolve('bar'), 4000);
});
foo1
.then((res)=>{
console.log(res)
return foo2
})
.then((res)=>{
console.log(res)
return foo3 // throws the error
})
.then((res)=>{
console.log(res)
return bar
})
.catch((e)=>{
// every error will be cached here
console.error(e)
});
I would aim to use async / await unless you have very particular reasons, since it avoids callback hell and makes your code simpler and more bug free.
try {
const response1 = await Bungie.Get('/Platform/Destiny2/Manifest/');
const response2 = await Bungie.Get('/Platform/Destiny2/Mnifest/');
console.log('Success');
} catch (error) {
doSomethingWithError(error);
}
Imagine each Bungie call takes 250 milliseconds. While this is occurring, NodeJS will continue to execute other code via its event loop - eg requests from other clients. Awaiting is not the same as hanging the app.
Similarly, this type of code is used in many browser or mobile apps, and they remain responsive to the end user during I/O. I use the async await programming model in all languages these days (Javascript, Java, C#, Swift etc).
Try this:
let getMultiple = function(callback, ... keys){
let result = [];
let ctr = keys.length;
for(let i=0;i<ctr;i++)
result.push(0);
let ctr2 = 0;
keys.forEach(function(key){
let ctr3=ctr2++;
try{
Bungie.Get(key, function(data){
result[ctr3] = data;
ctr--;
if(ctr==0)
{
callback(result);
}
});
} catch(err) {
result[ctr3]=err.message;
ctr--;
if(ctr==0)
{
callback(result);
}
}
});
};
This should get all your data requests and replace relevant data with error message if it happens.
getMultiple(function(results){
console.log(results);
}, string1, string2, string3);
If the error causes by requesting same thing twice asynchronously, then you can add an asynchronous caching layer before this request.

Mocha test with SuperTest always passes (even when wrong)

I'm using Mocha and SuperTest to test my Express API. However my first test always seems to pass when inside the .then() of my request().
I'm passing in a String to a test that is expecting an Array. So should definitely fail the test.
It fails outside of the then() as expected, but I won't have access to the res.body there to perform my tests.
Here is my code:
const expect = require('chai').expect;
const request = require('supertest');
const router = require('../../routes/api/playlist.route');
const app = require('../../app');
describe('Playlist Route', function() {
// before((done) => {
// }
describe('Get all playlists by user', function() {
it('Should error out with "No playlists found" if there are no Playlists', function() {
request(app).get('/api/playlists/all')
.then(res => {
const { body } = res;
// Test passes if expect here
expect('sdfb').to.be.an('array');
})
.catch(err => {
console.log('err: ', err);
});
// Test fails if expect here
expect('sdfb').to.be.an('array');
})
})
});
I found this article but I'm not using a try catch block, but I thought maybe it could have something to do with the promise.
Quick reponse
it('decription', function(done) {
asyncFunc()
.then(() => {
expect(something).to.be(somethingElse)
done()
})
})
Detailed response in the comment of #jonrsharpe
Rather than using done, simply return request(app).get('/api/playlists/all') since request() returns a promise. Since you have expect('sdfb').to.be.an('array'); twice, remove the one that's not in the .then callback. When using asynchronous code, remember that synchronous code that appears to come after the async chain will execute before the promise .then handlers. This is counterintuitive.
Here's the .then approach:
it('should ...', () => {
return request(app)
.get('/api/playlists/all')
.then(res => {
const {body} = res;
// assert here
});
});
The other approach is to await the promise yourself in the test case function, then make assertions on the resolved response object. In this case, drop the then chain. This approach is generally preferred as it reduces nesting.
it('should ...', async () => {
const res = await request(app).get('/api/playlists/all');
const {body} = res;
// assert here
});
If you don't let Mocha know you're working with asynchronous code by returning a promise, awaiting the promises, or adding and calling the done parameter, the assertions occur asynchronously after the test is over and disappear into the void, creating a false positive.
Skip .catch either way. Since you've informed Mocha of the promise, if it rejects, it'll let you know.

Catching thrown errors with SinonJS, UnhandledPromiseRejectionWarning

I have the following async function that checks the returned value from a promise and I having trouble writing
async function fetchData(pageLocation) {
const data = await
apiService.fetchPage(pageLocation);
if (!data || !data.mapping) {
const error = new Error(`Unknown channel ${pageLocation}`);
error.code = 404;
throw (error);
}
return data.mapping;
}
Test case
describe.only('fetchData', () => {
let fetchPage;
beforeEach(() => {
fetchPage =
sinon.stub().returns(Promise.resolve(mockMapping));
csfPageService.__set__({
apiService: {
fetchPage,
},
});
});
it('should throw an error when there is no available Data', () => {
channeData', async function() {
const fetchChannelSectionData = pageService.__get__('fetchData');
expect(async () => { await fetchData('pageLocation'); }).to.throw();
expect(fetchPage).to.be.calledWith('pageLocation');
console.log('----------------------2');
});
What causing the main issue is having an async function and a promise I am able to use the same approach when it is not an async function and there is no await I have looked into the following links
Catching thrown errors with SinonJS
https://www.chaijs.com/api/bdd/#method_throw
enter link description here
but I haven't been successful
please advise on how should this be done ...
That is one of the reasons I don't like async, await, they are just syntactic sugar over promises, but they uses normal/sync semantics but just in appearance.
Async functions never throws, no matter how bad is the error you throw inside it, they will just return a rejected promise. In your case, your function is not throwing at all, it is returning a rejected promise, and you are not attaching any catch hanlder to that promise, hence the warning. When you use async function or promises, forget about normal handling of errors, promises catches any error automatically and encapsulates them on a rejected promise.
So, in your case the correc way of doing this will vary depending on your testing framework, but it could be something like this:
it('should throw an error when there is no available Data', () => {
channeData', async function() {
const fetchChannelSectionData = pageService.__get__('fetchData');
fetchData('pageLocation').catch(err => {
expect(err).to.be.an.error();
expect(fetchPage).to.be.calledWith('pageLocation');
console.log('----------------------2');
})
});

Sinon.JS stub that resolves a promise returns "{}"

I have a Node.js function that returns a promise. I am using Sinon.JS stubs to resolve the promise. My console.log statements in the code show that the stub is working. However, what is returned is {} instead of what the Promise resolves to.
I reviewed these other SO posts, but neither were exactly the problem I am running into:
Sinon.JS stub a function that resolves a promise
When to reject/resolve a promise
Here is the function:
function publishMessage(pubsub, topicName, data) {
const topic = pubsub.topic(topicName);
const publisher = topic.publisher();
return publisher.publish(data)
.then((results) => {
const messageId = results[0];
return messageId;
})
.catch((error) => {
console.log('Error ', error);
return error;
}); };
Here is the test:
describe('publishMessage', function() {
describe('Success', function() {
it('should return the messageId', function(done) {
var publishMessage = index.__get__('publishMessage');
var promise = sinon.stub().resolves(['1111']);
var publisher = {
publish: promise
};
var topic = {
publisher: sinon.stub().returns(publisher)
};
var pubsub = {
topic: sinon.stub().returns(topic)
};
assert.equal('1111', publishMessage(pubsub, 'st', 'ds'));
assert.isTrue(topic.publisher.calledWith());
done();
});
});
});
And when I execute the test, the output from the console.log shows the resolve value is printed:
publishMessage
Success
1) should return the messageId
1111
0 passing (256ms)
1 failing
1) publishMessage
Success
should return the messageId:
AssertionError: expected '1111' to equal {}
at Context.<anonymous> (test/index.spec.js:63:14)
There's a few potential problem areas that I noticed.
First, I don't see where index is defined, so I can't confirm whether or not the function you expect is being returned from index.__get__('publishMessage');. You can confirm that the right function is returned by visually inspecting the result of
publishMessage.toString();
The other problem I see (and more likely the cause of your problem) is that you are returning a Promise from publishMessage(), but comparing the result of a call to that function to the value to which the Promise will eventually resolve. In other words, your comparing a Promise to a String. Unless your assertion library waits for the Promise to resolve before checking the result (similar to Jasmine), you are comparing a String to a Promise. To remedy this, simply wait for the Promise to resolve:
it('should return the messageId', function(done) {
// Set up the test case by defining publishMessage, etc.
publishMessage(pubsub, 'st', 'ds').then((result) => {
assert.equal(result, '1111');
assert.isTrue(topic.publisher.calledWith());
done();
}).catch(done);
}
Notice I added a .catch() on the Promise. This makes sure that any errors thrown in the Promise will show the appropriate error as opposed to just a timed out error.
If you're using a testing framework like Mocha or Karma/Jasmine, you can improve this a little more by directly returning the Promise instead of using done(). In my experience, returning the Promise results in much better stack traces and more helpful and accurate error messages when trying to debug a test case that uses Promises. As an example:
it('should return the messageId', function() {
// Set up the test case by defining publishMessage, etc.
return publishMessage(pubsub, 'st', 'ds').then((result) => {
assert.equal(result, '1111');
assert.isTrue(topic.publisher.calledWith());
});
}
Notice that I don't accept an argument in the test case anymore. In Mocha and Karma, this is how the framework determines how to treat the test case.
You don't wait for your promise to be resolved.
Try
publishMessage(pubsub, 'st', 'ds').then(result => {
assert.equal('1111', result);
assert.isTrue(topic.publisher.calledWith());
done();
}

How to make assertions inside a promise when any errors thrown don't bubble up?

Running this with mocha results in timing out, rather than letting mocha catch the error so it could fail immediately..
var when = require('when');
var should = require('should');
describe('', function() {
it('', function(done) {
var d = when.defer();
d.resolve();
d.promise.then(function() {
true.should.be.false;
false.should.be.true;
throw new Error('Promise');
done();
}); }); });
http://runnable.com/me/U7VmuQurokZCvomD
Is there another way to make assertions inside the promise, such that when they fail they are caught by mocha causing it to fail immediately?
As per chai recommendation, I looked into it and it seems I have to have a direct access to the promise object, right? The problem is that I'm not using promise directly.. My bad if I simplified but This would be a more closer to reality example
function core_library_function(callback){
do_something_async(function which_returns_a(promise){
promise.then(function(){
callback(thing);
}); }); }
describe('', function() {
it('', function(done) {
core_library_function(function(thing){
...
done();
}); }); });
So I really have no control over the promise directly, it's abstracted far far away.
When using promises with Mocha, you'll have to return the promise in the test and will want to remove the done parameter since the callback isn't being used.
it('', function() {
var d = when.defer();
d.resolve();
return d.promise.then(function() {
throw new Error('Promise');
});
});
This is described in the docs under Working with Promises:
Alternately, instead of using the done() callback, you can return a promise.

Resources