Efficiently composing promise tests - javascript

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;
});
});

Related

How to write a test using Mocha+Chai to expect an exception from setTimeout?

I have following:
it('invalid use', () => {
Matcher(1).case(1, () => {});
});
The case method is supposed to throw after some delay, how can I describe it for Mocha/Chai that's what I want - the test should pass (and must not pass when exception is not thrown)?
Consider case method off limits, it cannot be changed.
For testing purposes it should be equivalent to:
it('setTimeout throw', _ => {
setTimeout(() => { throw new Error(); }, 1); // this is given, cannot be modified
});
I tried:
it('invalid use', done => {
Matcher(1).case(1, () => {});
// calls done callback after 'case' may throw
setTimeout(() => done(), MatcherConfig.execCheckTimeout + 10);
});
But that's not really helping me, because the test behavior is exactly reverted - when an exception from case (setTimeout) is not thrown, it passes (should fail) and when an exception is thrown the test fails (should succeed).
I read somewhere someone mentioning global error handler, but I would like to solve this cleanly using Mocha and/or Chai, if it is possible (I guess Mocha is already using it in some way).
You cannot handle exceptions from within a asynchronous callback, e.g. see Handle error from setTimeout. This has to do with the execution model ECMAScript uses. I suppose the only way to catch it is in fact to employ some environment-specific global error handling, e.g. process.on('uncaughtException', ...) in Node.js.
If you convert your function to Promises, however, you can easily test it using the Chai plugin chai-as-promsied:
import * as chai from 'chai';
import chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
const expect = chai.expect;
it('invalid use', async () => {
await expect(Matcher(1).case(1, () => {})).to.eventually.be.rejected;
});
Any Mocha statements like before, after or it will work asynchronously if you return a promise. I generally use something like the below for async tests.
Also don't forget to set timeout this.timeout(...) if you expect the async function to take more than 2 seconds.
it('some test', () => {
return new Promise(function(resolve,reject){
SomeAsyncFunction(function(error,vals) {
if(error) {
return reject(error);
} else {
try {
//do some chai tests here
} catch(e) {
return reject(e);
}
return resolve();
}
});
});
});
Specifically for your case, since we expect some error to be thrown after a period of time (assuming the empty callback you have provided to .case should not run due to the exception being thrown) then you can write it something like:
it('invalid use', () => {
//define the promise to run the async function
let prom = new Promise(function(resolve,reject){
//reject the promise if the function does not throw an error
//note I am assuming that the callback won't run if the error is thrown
//also note this error will be passed to prom.catch so need to do some test to make sure it's not the error you are looking for.
Matcher(1).case(1, () => {return reject(new Error('did not throw'))});
});
prom.catch(function(err){
try {
expect(err).to.be.an('error');
expect(err.message).to.not.equal('did not throw');
//more checks to see if err is the error you are looking for
} catch(e) {
//err was not the error you were looking for
return Promise.reject(e);
}
//tests passed
return Promise.resolve();
});
//since it() receives a promise as a return value it will pass or fail the test based on the promise.
return prom;
});
From Chai documentation :
When no arguments are provided, .throw invokes the target function and asserts that an error is thrown.
So you could something like
expect(Matcher(1).case(1, () => {})).to.throw
If your tested code calls setTimeout with a callback that throws and no-one is catching this is exception then:
1) this code is broken
2) the only way to see that problem is platform global exception handler like process.on('uncaughtException' mentioned by user ComFreek
The last resort chance is to stub setTimeout for duration of test (for example using sinon.stub) or just manually.
In such stubbed setTimeout you can decorate timeout handler, detect exception and call appropriate asserts.
NOTE, this is last resort solution - your app code is broken and should be fixed to properly propagate errors, not only for testing but ... well, to be good code.
Pseudocode example:
it('test', (done) => {
const originalSetTimeout = setTimeout;
setTimeout = (callback, timeout) => {
originalSetTimeout(() => {
try {
callback();
} catch(error) {
// CONGRATS, you've intercepted exception
// in _SOME_ setTimeout handler
}
}, timeout)
}
yourTestCodeThatTriggersErrorInSomeSetTimeoutCallback(done);
})
NOTE2: I intentionally didn't wrote proper async cleanup code, it's a homework. Again, see sinon.js and its sandbox
NOTE3: It will catch all setTimeout calls during test duration. Beware, there are dragons.

Can't test effects of resolved promise

I have the following code in user:
import { redirectTo } from 'urlUtils';
export function create(user) {
api.postUser(user).then((response) => {
redirectTo(response.userUrl);
})
}
And I have the following test:
import * as api from 'api'
import * as user from 'user'
sinon.stub(api, 'postUser').returns(
Promise.resolve({ userUrl: 'a-url' })
);
sinon.spy(urlUtils, 'redirectTo');
const userData = {id: 2};
user.create(userData);
expect(api.postUser.calledWith(userData)).toBe(true); // This passes
expect(urlUtils.redirectTo.calledOnce).toBe(true); // This fails
I have been able to test it on the browser, and the redirect is happening. What am I missing here? I have stubbed the request call to resolve the promise synchronously, so that shouldn't be a problem.
Promises are asynchronous, so when you're doing expect(urlUtils.redirectTo.calledOnce).toBe(true) the promise hasn't been resolved yet.
The easiest way to get around it is to wrap that expectation in a short timeout and then use whatever utility the testing framework you're using provides to signal that an asynchronous test is complete. Something like this:
setTimeout(() => {
expect(urlUtils.redirectTo.calledOnce).toBe(true);
done();
}, 5);
Another, nicer solution is to actually use the promise that your stub returns. First, keep a reference to that promise:
Replace:
sinon.stub(api, 'postUser').returns(
Promise.resolve({ userUrl: 'a-url' })
);
with:
const postUserPromise = Promise.resolve({ userUrl: 'a-url' });
sinon.stub(api, 'postUser').returns(postUserPromise);
then write your expectation like this:
postUserPromise.then(() => {
expect(urlUtils.redirectTo.calledOnce).toBe(true);
done();
});
done() is the function most test frameworks (at least Jasmine and Mocha as far as I know) provide to signal that an asynchronous test is completed. You get it as the first argument to the function your test is defined in and by specifying it in your function signature you're telling the test framework that your test is asynchronous.
Examples:
it("is a synchronous test, completed when test function returns", () => {
expect(true).to.equal(true);
});
it("is an asynchronous test, done() must be called for it to complete", (done) => {
setTimeout(() => {
expect(true).to.equal(true);
done();
}, 5000);
});

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();
});

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

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));

ES6 Promises in Mocha

I'm using this polyfill for ES6 promises and Mocha / Chai.
My assertions for the promises are not working. The following is a sample test:
it('should fail', function(done) {
new Promise(function(resolve, reject) {
resolve(false);
}).then(function(result) {
assert.equal(result, true);
done();
}).catch(function(err) {
console.log(err);
});
});
When I run this test it fails due to timeout. The assertion failure that was thrown in the then block is caught in the catch block. How can I avoid this and just throw it straight to Mocha?
I could just throw it from the catch function, but then how would I make assertions for the catch block?
If your Promise has a failure, it will only call your catch callback. As a result, Mocha's done callback is never called, and Mocha never figures out that the Promise failed (so it waits and eventually times out).
You should replace console.log(err); with done(err);. Mocha should automatically display the error message when you pass an error to the done callback.
I ended up solving my problem by using Chai as Promised.
It allows you to make assertions about the resolution and rejections of promises:
return promise.should.become(value)
return promise.should.be.rejected
A pattern I use in my Mocha/Chai/es6-promise tests is the following:
it('should do something', function () {
aPromiseReturningMethod(arg1, arg2)
.then(function (response) {
expect(response.someField).to.equal("Some value")
})
.then(function () {
return anotherPromiseReturningMethod(arg1, arg2)
})
.then(function (response) {
expect(response.something).to.equal("something")
})
.then(done).catch(done)
})
The last line is odd looking, but calls Mocha's done on success or on error.
One problem is if the last then returns something, I then need to noop()* before both the then and the catch:
it('should do something', function () {
aPromiseReturningMethod(arg1, arg2)
.then(function (response) {
expect(response.someField).to.equal("Some value")
})
.then(function () {
return anotherPromiseReturningMethod(arg1, arg2)
})
.then(_.noop).then(done).catch(done)
})
*Lodash's noop().
Would love to hear any critiques of this pattern.

Categories

Resources