How to 'reverse' the rejection/fulfillment of a promise? - javascript

For a mocha test, I want to assert that a promise will eventually reject.
I don't want to use chai-as-promised. I'd prefer to just use Node's standard assert module, and to just use standard ES6 promises.
The best I've come up with is this, but it feels slightly hacky:
it('foo should reject given bad data', function () {
var rejected;
return foo('bad data').catch(function (err) {
rejected = true;
}).then(function () {
assert(rejected);
});
});
Can anyone suggest a neater, more expressive way to 'reverse' a promise, so rejection becomes fulfillment and vice versa?

You could just assert on both the resolution and rejection callbacks passing true or false directly.
it('foo should reject given bad data', function () {
return foo('bad data').then(function () {
assert(false);
}, function () {
assert(true);
});
});

You could do it using a single .done() like so:
it('foo should reject given bad data', function () {
return foo('bad data')
.done(assert.equal.bind(null, 'false', 'true', null), assert);
});
I've used values for assert.equal that will provide the equivalent of assert(false), but you can obviously remove the last null if you want to print the actual result.
Edit: You could actually make this cleaner for multiple tests by defining your own assertFail function:
function assertFail () { assert(false); }
it('foo should reject given bad data', function () {
return foo('bad data')
.done(assertFail, assert);
});

You may add a reverse method to Promise prototype and then just use it.
Promise.prototype.reverse = function() {
return new Promise((resolve, reject) => {
this.then(reject).catch(resolve);
});
}
foo('bad data')
.reverse()
.then(e => assert(true))
.catch(e => assert(false));

Related

Jest tests of Promises

I need to write a Jest test for a function using Promises, which is working perfectly in the browser.
My code is as follows: a JQuery AJAX call is done, which returns a promise; when this promise is resolved, I call another function, which is also using another Promises internally!
So the code of my Jest test is globally like this:
function post(url) {
return $.ajax(url).then((result) => {
resolve(result);
});
}
function showAlert(message) {
return new Promise((resolve, reject) => {
require('anotherPackage').then(() => { // require returns a Promise
anotherPackage.showAlert(message); // will result in DOM update
}).then(resolve, reject);
});
}
function handleResponse(result) {
const promises = [];
...
promises.push(showAlert(message));
...
return Promise.all(promises);
}
test("Promises test", () => {
let response = null,
url = 'http://example.com/result.json';
return post(url).then([result, status, request] => {
response = parseResponse(result);
}).then(() => {
return handleResponse(response).then(() => {
return $('.alert').length;
}).then(value => {
expect(value).toBe(1); // doesn't work, value is 0 !!!
});
});
});
The handleResponse function is using AJAX request response, and in this use case, the function is calling another function which is using a promise internally which, when resolved, is creating an alert inside the DOM.
Actually, the test given as example doesn't work, because the expect call is done before the inner handleResponse promise is "completely resolved"!
So my question is: how can I handle this use case with Jest?
Best regards,
Thierry

Test error from async function using async/await [duplicate]

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

Properly call mocha done callback and promises

I have several integration tests in my app:
it('creating vehicle', function (done) {
createVehicle()
.then(() => {
done();
})
.catch((err) => {
done(err);
});
});
createVehicle makes post request and returns promise:
return request.json('post', '/api/vehicle/')
.send(obj)
.expect(200)
.then((res) => {
expect(res.body).toExist("Body is empty");
expect(res.body.id).toExist("Id is empty");
return res.body;
});
Now all works fine, but if I rewrite first snippet in the following way:
it('creating vehicle', function (done) {
createVehicle()
.then(done) //*
.catch(done); //*
});
I get error from Mocha
done() invoked with non-Error
I understand why. The createVehicle return res.body and it's passed to then callback, in result done run as done(arg) and I get the error, because mocha done callback has to be called without arg when there is no error and with argument when there is error.
Is it possible to use this variant:
.then(done)
.catch(done);
How to achieve this?
Of course, I can delete return statement, but createVehicle is used in several places and I need returned value:
it('creating vehicle with media', function (done) {
createVehicle()
.then(createMedia) //createMedia will get returned value
//....
});
The easiest solution would be to just return the promise instead of having to deal with callbacks, because Mocha supports promises out-of-the-box:
it('creating vehicle', function() {
return createVehicle();
});

Efficiently composing promise tests

I find myself often writing tests for async APIs like this:
beforeEach(function(done) {
setup()
.then(function(val) {
return setup2();
})
.done(done, done);
});
it('should test something', function(done) {
promiseFunction
.then(function(val) {
expect(val).to.be.something;
})
.done(done, done);
});
Which seems pretty straightforward, except for the beforeEach function: if setup2 returns a promise, then done will be called with a value, which it doesn't like. So I end up having to write:
.done(function() { done(); }, done)
which is all well and good, but I'm wondering if there's a more compact way to do this (yes, I'm lazy!). Like
.catch(done) //handle fail
.then(null) //strip the promise value
.then(done) //handle success
but then requires a function. I know a trivial solution is to write myself another function:
function noParam(done) { done(); }
and use:
.done(noParam(done), done);
I'm just wondering if there's a better way to compose this generally or a way to use existing API functions.
If done uses node callback conventions, you'll want to have a look at the .asCallback method. You might do
beforeEach(function(done) {
setup()
.then(function(val) {
return setup2();
})
.asCallback(done);
});
And mocha (unless you are using very old version) supports returning promises so your test code can be written as:
it('should test something', function() {
return promiseFunction
.then(function(val) {
expect(val).to.be.something;
});
});

Test if a promise is resolved or rejected with Jasmine in Nodejs

I know how to do it in Mocha but want to know how to do it with Jasmine.
I tried this
describe('test promise with jasmine', function() {
it('expects a rejected promise', function() {
var promise = getRejectedPromise();
// return expect(promise).toBe('rejected');
return expect(promise.inspect().state).toBe('rejected');
});
});
However, the state is always pending and, of course, the test fails. I couldn't find any example online that I could make it work.
Can someone please help me with this?
Thanks.
you can now use expectAsync()
Expecting success:
it('expect result', async () => {
...
await expectAsync(someAsyncFunction(goodInput)).toBeResolved(expectedResponse)
})
Expecting failure:
it('expect result', async () => {
...
await expectAsync(someAsyncFunction(badInput)).toBeRejectedWith(expectedResponse)
})
To test asynchronous code with jasmine you should use its async syntax, e.g.:
describe('test promise with jasmine', function(done) {
var promise = getRejectedPromise();
promise.then(function() {
// Promise is resolved
done(new Error('Promise should not be resolved'));
}, function(reason) {
// Promise is rejected
// You could check rejection reason if you want to
done(); // Success
});
});
jasmine 2.7 onwards supports returning promises, and would have its fulfilled state tested.
To test for rejection:
it('test promise with jasmine', async () => {
try {
await getRejectedPromise();
} catch (err) {
return;
}
throw new Error('Promise should not be resolved');
});
or yet:
it('test promise with jasmine', async () => {
await getRejectedPromise()
.then(
() => Promise.reject(new Error('Promise should not be resolved')),
() => {});
});
To verify the actual message, besides the usual instanceof/toBe(), place inside the catch:
expect(() => { throw err }).toThrow(new MyCustomError('Custom error message'));
The benefit from this approach is to have a nicer fail message on the test output.
Expected function to throw MyCustomError: Custom error message, but it threw Another error message.
Somewhat better than the usual output.
To test for resolved (can't be simpler):
it('test promise with jasmine', async () => {
await getRejectedPromise();
});
You can use finally block to test promise state:
it('should resolve if auth succeed', (done)=>{
var p = server.login('user', 'password');
p.finally(()=>{
expect(p.isFulfilled()).toBeTruthy();
done();
});
});
You can use isFulfilled to check if promise was fulfilled and value method to check the fulfillment value. Corresponding methods for rejection are isRejected and reason.
#Leonid's answer is correct, but you can simplify like so, and use only promises:
it('test promise with jasmine', function() {
return getRejectedPromise().then(function() {
// Promise should not be resolved, so we reject it
return Promise.reject(new Error('Promise should not be resolved'));
})
.catch(function(err){
if(!/Promise should not be resolved/.test(err && err.message)){
return Promise.reject(err);
}
})
})

Categories

Resources