Why does mocha timeout when an assertion fails inside a Q future? You also don't get to see the assertion failure when this happens. This does not happen if I just use callbacks. How should I write this while still using futures but get to see the assertion error instead of a timeout?
var Q = require('q');
function hack() {
var ret = Q.defer();
ret.resolve(true);
return ret.promise;
}
it('test', function(done) {
hack().then(function(bool) {
assert(false);
done();
});
});
The assertion call throws an exception, which is caught by Q in order to properly conform to the promises spec. So mocha never reaches done(), nor does it see an exception thrown. You could do something like this:
it('test', function(done) {
hack().then(function(bool) {
assert(false);
done();
}).catch(function(err){
done(err);
});
});
[edit] Alternatively, you can omit the done argument altogether and just return the promise directly from the test function, in which case mocha will pass/fail the test based on the outcome of the returned promise:
it('test', function() {
return hack().then(function(bool) {
assert(false);
});
});
...which is a nice way to simplify your test functions. Props to Taytay elsewhere in this thread for pointing this out.
Mocha now supports promises in unit tests, so you can just return the promise instead of relying upon calling (done) from a then handler. It's easier and safer (because you won't forget to call done)
So you could just write:
it('test', function() {
return hack().then(function(bool) {
assert(false);
});
});
That would fail because the promise would fail, and Mocha would detect it.
This is from the Mocha docs in the section "Working with Promises": https://mochajs.org/
Improving on greim's answer including what callumacrae added in a comment, you can do it like this:
it('test', function(done) {
hack().then(function(bool) {
assert(false);
done();
}).catch(done);
});
Related
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();
}
I'm writing test cases with mocha and its hook beforeEach which deletes and re-creates all tables using sequelize.drop and sequelize.sync.
lib/testutils.js
exports.deleteAll = function () {
return sequelize.drop({logging: false, cascade: true}).then(function() {
return sequelize.sync({logging: false});
});
};
test/controllers/controllers.js
(A) This did work:
var testutils = require("../../lib/testutils");
describe("CRUD Controller", function() {
beforeEach(function(done) {
testutils.deleteAll().then(function(){
done();
}).catch(function(err) {
return done(err);
});
});
describe("#read()", function(){
it("should....", function(done) {
});
});
}
(B) This did not work:
var testutils = require("../../lib/testutils");
describe("CRUD Controller", function() {
beforeEach(function(done) {
testutils.deleteAll().then(done).catch(function(err) {
return done(err);
});
});
describe("#read()", function(){
it("should....", function(done) {
});
});
}
I don't understand why testutils.deleteAll().then(done) did not work and the first test case it("should....", function(done) did not wait for the beforeEach hook finished. I was getting TypeError: Converting circular structure to JSON. However, testutils.deleteAll().then(function(){ done(); }) did work.
My question is why (B) is not working while (A) is working? Any idea? Is there something wrong?
Mocha supports promises in beforeEach so just write below code:
beforeEach(function(){
return testutils.deleteAll();
});
Your deleteAll() function returns sequelize.sync() which returns a promise passing it this. I think the context will be the last model created, so when you pass a function to the returned promise, the function will be passed the last table created. So in case (A) you specified the function()
and called done() manually without passing any args to done(). But in case (B) when you pass done to the promise.then(), the last model created will be passed to done.
To get what I mean consider the following:
User.create({name: 'aName'}).then(function(user){console.log(user)});
is equivalent to
User.create({name: 'aName'}).then(console.log);
The user will be automatically passed to console.log().
So done was being passed the last model created which I think might cause a problem.
When you pass done directly it receive object returned from deleteAll, and mocha treat it as an error, so it try apply JSON.stringify to this object and show it as an error, but somehow JSON.stringify can't do it, so it throw exception which you can see.
You can clearly see it in Mocha::Runnable#run
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
I'm having trouble with how to properly structure a test for my Promise-returning API with Vows, e.g.
topic:function() { return myfunc() { /* returns a Bluebird Promise */ } },
'this should keep its promise':function(topic) {
var myfunc = topic;
myfunc()
.then(function(result) {
assert(false);
})
.catch(function(error) {
assert(false);
})
.done();
}
My vow never fails. This is my first attempt at using vows to test promises. Hoping someone familiar with this will lend a hand.
In advance, thank you.
Enrique
Since unlike libraries like Mocha - Vows does not yet have support for testing promises, we use its regular asynchronous test format that takes callbacks:
topic:function() { return myfunc() { /* returns a Bluebird Promise */ } },
'this should keep its promise':function(topic) {
var myfunc = topic;
myfunc() // call nodeify to turn a promise to a nodeback, we can chain here
.nodeify(this.callback); // note the this.callback here
}
Here is how it would look with mocha:
describe("Promises", function(){
it("topics", function(){
return myfunc(); // chain here, a rejected promise fails the test.
});
})
The following example is using a when js style promise with vows. You should be able to adapt it to whatever flavor of promise you are using. The key points are:
1) Make sure you call this.callback when your promise resolves. I assign 'this' to a variable in the example below to make sure it is properly available when the promise resolves.
2) Call this.callback (see below how this is done with the variable) with an err object and your result. If you just call it with your result, vows will interpret it as an error.
vows.describe('myTests')
.addBatch({
'myTopic': {
topic: function() {
var vow = this;
simpleWhenPromise()
.then(function (result) {
vow.callback(null, result);
})
.catch(function (err) {
vow.callback(result, null);
});
},
'myTest 1': function(err, result) {
// Test your result
}
},
})
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.