Jest test promise resolution and event loop tick - javascript

I have a failing Jest test case for code where I'm using promises. It looks like the resolution of the promise is happening after the test has completed, meaning I can't check that my promise resolution code has been executed.
It feels like I need to make the event loop tick so the promise is resolved and the resolution code is executed, but haven't found anything that can do that in Jest.
Here's a sample case. The code to be tested:
const Client = require('SomeClient');
module.exports.init = () => {
Client.load().then(() => {
console.log('load resolved');
setTimeout(() => {
console.log('load setTimeout fired, retrying init');
module.exports.init();
}, 1000);
});
};
Test code:
jest.useFakeTimers();
const mockLoad = jest.fn().mockImplementation(() => Promise.resolve());
jest.mock('SomeClient', () => {
return {
load: mockLoad
};
}, { virtual: true });
const promiseTest = require('./PromiseTest');
describe('SomeClient Promise Test', () => {
it('retries init after 10 secs', () => {
promiseTest.init();
expect(mockLoad).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenCalledTimes(1); // <-- FAILS - setTimeout has not been called
jest.runAllTimers();
expect(mockLoad).toHaveBeenCalledTimes(2);
});
});
The expect(setTimeout).toHaveBeenCalledTimes(1); assertion fails (setTimeout has not been called at all), I think because the promise has not yet been resolved.
Am I doing something wrong here? Can I cause the event loop to tick inside the test?

To tick the event loop inside your test, you should make it asynchronous.
A nice workaround was suggested on GitHub.
Having flushPromises as suggested there
function flushPromises() {
return new Promise(resolve => setImmediate(resolve));
}
your test will look like
describe('SomeClient Promise Test', () => {
it('retries init after 10 secs', () => {
promiseTest.init();
expect(mockLoad).toHaveBeenCalledTimes(1);
// notice return so jest knows that the test is asynchronous
return flushPromises()
.then(() => {
expect(setTimeout).toHaveBeenCalledTimes(1);
jest.runAllTimers();
expect(mockLoad).toHaveBeenCalledTimes(2);
});
});
});
Or the same using async/await:
describe('SomeClient Promise Test', () => {
it('retries init after 10 secs', async () => {
promiseTest.init();
expect(mockLoad).toHaveBeenCalledTimes(1);
await flushPromises();
expect(setTimeout).toHaveBeenCalledTimes(1);
jest.runAllTimers();
expect(mockLoad).toHaveBeenCalledTimes(2);
});
});

Not entirely sure why our React setup was different but Sergey's answer nearly got us there.
We needed these two bits in our test:
export function flushPromises(): Promise<void> {
return new Promise(jest.requireActual("timers").setImmediate);
}
await flushPromises();

Related

Jest mock setTimeout - getting timeout error

I'm writing a unit test which tests a function which waits a timeout before continuing, I've tried using
jest.useFakeTimers();
jest.advanceTimersByTime(20000);
but I keep getting an error message:
: Timeout - Async callback was not invoked within the 20000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.Error:
Module file:
async function main()
{
const promisesArray: Promise<void>[] = [];
// lots of logic and async calls //
for (const userRecord of records) {
promisesArray.push(verifyUserRecord(userRecord.userId));
}
await Promise.all(promisesArray);
}
function waitTimeout(ms: number) {
return new Promise((resolve) => setTimeout(() => resolve(true), ms));
}
async function verifyUserRecord(userId) {
await waitTimeout(CHECK_EXECUTION_STATUS_TIMEOUT);
await <some other method call>
/////////////// more logic /////////////
}
Test File
describe('main test', () => {
beforeAll(() => {
// calls to jest spy on //
});
beforeEach(() => {
jest.useFakeTimers();
// various mocks //
});
afterEach(() => {
jest.resetAllMocks();
});
afterAll(() => {
jest.useRealTimers();
jest.restoreAllMocks();
});
it('Should successfully run', async () => {
const p = main();
jest.advanceTimersByTime(60000);
jest.runAllTimers(); // <- explicitly tell jest to run all setTimeout, setInterval
jest.runAllTicks(); // <- explicitly tell jest to run all Promise callback
await p;
// validations //
});
});
But when I call main method from the test file, I get the error above.
When debugging I see that it arrives to waitTimeout and then either waits or moves on to next it in the test module.
It does not mock the timeout, and continues the module file logic.
How can I properly test the file and mock the timeout?
Thanks in advance.
You use wrong API when mock timer
jest.setTimeout(20000); is used to set the maximum timeout that jest will wait for a test to finish. If a test take to long jest will throw errors
Back to your problem, since you use jest.useFakeTimers(); you have to tell jest explicitly to exhaust all macro tasks (setTimeout) and micro tasks (Promise callback).
I modified your code a little so the expect works
const CHECK_EXECUTION_STATUS_TIMEOUT = 2000;
const records = [{ userId: 1 }, { userId: 2 }];
async function main() {
const promisesArray: Promise<number>[] = [];
for (const userRecord of records) {
promisesArray.push(verifyUserRecord(userRecord.userId));
}
return await Promise.all(promisesArray);
}
function waitTimeout(ms: number) {
return new Promise((resolve) => setTimeout(() => resolve(true), ms));
}
async function verifyUserRecord(userId: number) {
await waitTimeout(CHECK_EXECUTION_STATUS_TIMEOUT);
// await <some other method call>
/////////////// more logic /////////////
return userId;
}
describe("main", () => {
beforeEach(() => {
jest.useFakeTimers(); // <- use fake timer
});
afterEach(() => {
jest.useRealTimers();
});
it("should work", async () => {
const p = main();
jest.runAllTimers(); // <- explicitly tell jest to run all setTimeout, setInterval
jest.runAllTicks(); // <- explicitly tell jest to run all Promise callback
expect(await p).toEqual([1, 2]);
});
});
Output:
You can use runOnlyPendingTimers, this flushes all pending timers.
Usage
jest.useFakeTimers();
// call your function here
jest.runOnlyPendingTimers();

Unsure about usage of async and callbacks in javascript

Description:
I have a Mocha Test within my Node-App that should test whether a DB-Export of Mongo-DB-JSON-Documents is done correctly.
In my test I besides other tests also test if the download-directory is not empty.
Expected result:
The test should await the downloads and only then check whether the directory is empty.
Actual Result:
The test returns always green.
My Question:
I understood that we have callbacks but promises are better.
I understood that async await is some syntactic sugar to promises.
And I understood that there is even RxJS (which I do not use here)
Somehow I have to deal with the callback from mogodb-backup.
See https://www.npmjs.com/package/mongodb-backup
I do not understand what I am doing wrong so that the tests always turn green (running in parallel to the download)
mocha-test.js
describe('Database.downloadDocumentsOfType_KEYS()', function () {
it('should result in data/exportFromCosmos/KEYS/admin/ag-data/ not being empty', function () {
const config = {
documents: ['DEFAULT', 'KEYS'],
exportpathDEFAULT: 'data/exportFromCosmos/DEFAULT/',
exportpathKEYS: 'data/exportFromCosmos/KEYS/',
uploadpath: 'data/uploadToAzureBlob/',
crosscheckFile: 'data/crosscheckFile.txt'
}
async function f() {
await Database.downloadDocumentsOfType_KEYS().then(expect(dir(config.exportpathKEYS + 'admin/ag-data/')).to.not.be.empty)
}
f()
})
})
Databasemodule-to-be-tested.js
const mongodbbackup = require('mongodb-backup')
const Database = {
downloadDocumentsOfType_DEFAULT: () => {
new Promise((resolve) => mongodbbackup({
uri: process.env.DB_CONNECTION_STRING_READ,
root: 'data/exportFromCosmos/DEFAULT',
parser: 'json',
callback: function(err) {
if (err) {
reject()
} else {
resolve()
}
}
)}
}
async function f() {
await Database.downloadDocumentsOfType_KEYS().then(e)
}
f()
This fires off the asynchronous function immediately and
it('...', function (){})
finishes immediately.
So you need to use
describe('...',async function(){
it('...',async function(){
const f = async function(){
await Database.downloadDocumentsOfType_KEYS();
expect(dir(config.exportpathKEYS + 'admin/ag-data/')).to.not.be.empty);
};
await f();
});
});
Also,
new Promise((resolve) => mongodbbackup({...
should be
new Promise((resolve,reject) => mongodbbackup({
Otherwise reject is undefined

Promise test timing out even after returning promise

I researched and found out that when testing promises in mocha, you have to return the promise.
I tried to do the following but the test keeps timing out. What the correct way to do this?
describe('A promise', () => {
it('should not timeout', () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hi!');
}, 3000);
}).then((msg) => {
expect(msg).to.equal('hi!');
});
});
});
Output:
$ ./node_modules/mocha/bin/mocha test.js
A promise
1) should not timeout
0 passing (2s)
1 failing
1) A promise
should not timeout:
Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
Edit: I tried adding the done in the it line and calling it in my then block but it doesn't work
Try this (only change: ".timeout(5000)" was added to "it"). This works for me. Basically you have to change the default timeout of 2sec for async call - if you expect your async call will take more than 2sec.
describe('A promise', () => {
it('should not timeout', () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hi!');
}, 3000);
}).then((msg) => {
expect(msg).to.equal('hi!');
});
}).timeout(5000);
});
2nd option (no need to change test in this case) :
./node_modules/mocha/bin/mocha --timeout 5000 test-mocha-spec.js
Does this work ?
it('should not timeout', done => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hi!');
}, 1000);
}).then((msg) => {
expect(msg).to.equal('hi!');
done();
});
});
First you need to add the done parameter in the callback
it('should not timeout', (done) => {
and call it at the end,
}).then((msg) => {
expect(msg).to.equal('hi!');
done()
});
You have 3 options:
return a promise
use done (if you also return promise error will be
thrown)
use async/await
In all these cases you have to set timeout threshold, because mocha can't be sure will you resolve your async call or not. It is a protection from endless tests.
Usually, you need to fake you promised calls with some immediately resolved promise with some fake value, so you will not have such problems.

jest - resolve promise before tests

I want to write tests for promise result and I dot'n want to resolve promise in each it/pit section.
I need smth like this:
describe('getData() results test', () => {
return getData().then(response => {
it('foo', () => expect(response.foo).toEqual(1));
it('bar', () => expect(response.bar).toEqual(2));
it('bar', () => expect(response.bar).toEqual(3));
});
});
If use beforeEach - promise will be resolved as many times as number of it sections. I need to resolve it once and then test response. There are a lot of test cases so I want to split all tests into it sections
The beforeAll function is called only once before all the specs in describe are run.
When return promise, Jest will wait for the promise to resolve before letting the test run.
describe('getData() results test', () => {
let data = null;
beforeAll(() => getData().then(response => {
data = response;
}));
it('foo', () => expect(data.foo).toEqual(1));
it('bar', () => expect(data.bar).toEqual(2));
it('bar', () => expect(data.bar).toEqual(3));
});
Have a look at the Async tutorial from the Jest documentation. I believe you need something like this:
describe('getData() results test', () => {
var response;
beforeEach(() => {
response = getData();
});
it('foo', () => { return response.then(r => expect(r.foo).toEqual(1))});
it('bar', () => { return response.then(r => expect(r.bar).toEqual(2))});
it('bar', () => { return response.then(r => expect(r.bar).toEqual(3))});
});
Key bit of the docs:
The promise that is being tested should be returned.
This is not an answer to your question but it may help people comming from Google
Jest will await the promises returned from beforeAll or beforeEach as #tuchk4 mentioned.
In my case, I forgot the it function and implemented the test right into describe. Surprisingly enough, this "works" but the promises from beforeAll and beforeEach will not be awaited and this makes all sense
I said "works" on quote because failed expect() will indeed fail but the Promise they return will not be awaited which will cause a UnhandledPromiseRejectionWarning.
You can simply do the following
describe('some test suite title', () => {
let response;
beforeAll(async (done) => {
// Do some Async. logic before running any test
await require('./somefile');
response = await getData();
done();
});
// Now run tests => response have the data
it('foo', () => expect(response.foo).toEqual(1));
it('bar', () => expect(response.bar).toEqual(2));
it('bar', () => expect(response.bar).toEqual(3));
});

Is there a way to get Chai working with asynchronous Mocha tests?

I'm running some asynchronous tests in Mocha using the Browser Runner and I'm trying to use Chai's expect style assertions:
window.expect = chai.expect;
describe('my test', function() {
it('should do something', function (done) {
setTimeout(function () {
expect(true).to.equal(false);
}, 100);
}
}
This doesn't give me the normal failed assertion message, instead I get:
Error: the string "Uncaught AssertionError: expected true to equal false" was thrown, throw an Error :)
at Runner.fail (http://localhost:8000/tests/integration/mocha/vendor/mocha.js:3475:11)
at Runner.uncaught (http://localhost:8000/tests/integration/mocha/vendor/mocha.js:3748:8)
at uncaught (http://localhost:8000/tests/integration/mocha/vendor/mocha.js:3778:10)
So it's obviously catching the error, it's just not displaying it correctly. Any ideas how to do this? I guess I could just call "done" with an error object but then I lose all the elegance of something like Chai and it becomes very clunky...
Your asynchronous test generates an exception, on failed expect()ations, that cannot be captured by it() because the exception is thrown outside of it()'s scope.
The captured exception that you see displayed is captured using process.on('uncaughtException') under node or using window.onerror() in the browser.
To fix this issue, you need to capture the exception within the asynchronous function called by setTimeout() in order to call done() with the exception as the first parameter. You also need to call done() with no parameter to indicate success, otherwise mocha would report a timeout error because your test function would never have signaled that it was done:
window.expect = chai.expect;
describe( 'my test', function() {
it( 'should do something', function ( done ) {
// done() is provided by it() to indicate asynchronous completion
// call done() with no parameter to indicate that it() is done() and successful
// or with an error to indicate that it() failed
setTimeout( function () {
// Called from the event loop, not it()
// So only the event loop could capture uncaught exceptions from here
try {
expect( true ).to.equal( false );
done(); // success: call done with no parameter to indicate that it() is done()
} catch( e ) {
done( e ); // failure: call done with an error Object to indicate that it() failed
}
}, 100 );
// returns immediately after setting timeout
// so it() can no longer catch exception happening asynchronously
}
}
Doing so on all your test cases is annoying and not DRY so you might want to provide a function to do this for you. Let's call this function check():
function check( done, f ) {
try {
f();
done();
} catch( e ) {
done( e );
}
}
With check() you can now rewrite your asynchronous tests as follows:
window.expect = chai.expect;
describe( 'my test', function() {
it( 'should do something', function( done ) {
setTimeout( function () {
check( done, function() {
expect( true ).to.equal( false );
} );
}, 100 );
}
}
Here are my passing tests for ES6/ES2015 promises and ES7/ES2016 async/await. Hope this provides a nice updated answer for anyone researching this topic:
import { expect } from 'chai'
describe('Mocha', () => {
it('works synchronously', () => {
expect(true).to.equal(true)
})
it('works ansyncronously', done => {
setTimeout(() => {
expect(true).to.equal(true)
done()
}, 4)
})
it('throws errors synchronously', () => {
return true
throw new Error('it works')
})
it('throws errors ansyncronously', done => {
setTimeout(() => {
return done()
done(new Error('it works'))
}, 4)
})
it('uses promises', () => {
var testPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Hello')
}, 4)
})
testPromise.then(result => {
expect(result).to.equal('Hello')
}, reason => {
throw new Error(reason)
})
})
it('uses es7 async/await', async (done) => {
const testPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Hello')
}, 4)
})
try {
const result = await testPromise
expect(result).to.equal('Hello')
done()
} catch(err) {
done(err)
}
})
/*
* Higher-order function for use with async/await (last test)
*/
const mochaAsync = fn => {
return async (done) => {
try {
await fn()
done()
} catch (err) {
done(err)
}
}
}
it('uses a higher order function wrap around async', mochaAsync(async () => {
const testPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Hello')
}, 4)
})
expect(await testPromise).to.equal('Hello')
}))
})
If you like promised, try Chai as Promised + Q, which allow something like this:
doSomethingAsync().should.eventually.equal("foo").notify(done);
I asked the same thing in the Mocha mailing list. They basically told me this : to write asynchronous test with Mocha and Chai :
always start the test with if (err) done(err);
always end the test with done().
It solved my problem, and didn't change a single line of my code in-between (Chai expectations amongst other). The setTimout is not the way to do async tests.
Here's the link to the discussion in the mailing list.
I've published a package that resolves this issue.
First install the check-chai package:
npm install --save check-chai
Then in your tests, use chai.use(checkChai); and then use the chai.check helper function as shown below:
var chai = require('chai');
var dirtyChai = require('dirty-chai');
var checkChai = require('check-chai');
var expect = chai.expect;
chai.use(dirtyChai);
chai.use(checkChai);
describe('test', function() {
it('should do something', function(done) {
// imagine you have some API call here
// and it returns (err, res, body)
var err = null;
var res = {};
var body = {};
chai.check(done, function() {
expect(err).to.be.a('null');
expect(res).to.be.an('object');
expect(body).to.be.an('object');
});
});
});
Per Is there a way to get Chai working with asynchronous Mocha tests? I published this as an NPM package.
Please see https://github.com/niftylettuce/check-chai for more information.
Try chaiAsPromised! Aside from being excellently named, you can use statements like:
expect(asyncToResultingValue()).to.eventually.equal(true)
Can confirm, works very well for Mocha + Chai.
https://github.com/domenic/chai-as-promised
Very much related to and inspired by Jean Vincent's answer, we employ a helper function similar to his check function, but we call it eventually instead (this helps it match up with the naming conventions of chai-as-promised). It returns a function that takes any number of arguments and passes them to the original callback. This helps eliminate an extra nested function block in your tests and allows you to handle any type of async callback. Here it is written in ES2015:
function eventually(done, fn) {
return (...args) => {
try {
fn(...args);
done();
} catch (err) {
done(err);
}
};
};
Example Usage:
describe("my async test", function() {
it("should fail", function(done) {
setTimeout(eventually(done, (param1, param2) => {
assert.equal(param1, "foo"); // this should pass
assert.equal(param2, "bogus"); // this should fail
}), 100, "foo", "bar");
});
});
I know there are many repeat answers and suggested packages to solve this however I haven't seen the simple solutions above offer a concise pattern for the two use cases. I am posting this as a consolidated answer for other who wish to copy-pasta:
event callbacks
function expectEventCallback(done, fn) {
return function() {
try { fn(...arguments); }
catch(error) { return done(error); }
done();
};
}
node style callbacks
function expectNodeCallback(done, fn) {
return function(err, ...args) {
if (err) { return done(err); }
try { fn(...args); }
catch(error) { return done(error); }
done();
};
}
example usage
it('handles event callbacks', function(done) {
something.on('event', expectEventCallback(done, (payload) => {
expect(payload).to.have.propertry('foo');
}));
});
it('handles node callbacks', function(done) {
doSomething(expectNodeCallback(done, (payload) => {
expect(payload).to.have.propertry('foo');
}));
});
Based on this link provided by #richardforrester http://staxmanade.com/2015/11/testing-asyncronous-code-with-mochajs-and-es7-async-await/, describe can use a returned Promise if you omit the done parameter.
Only downside there has to be a Promise there, not any async function (you can wrap it with a Promise, thou). But in this case, code can be extremely reduced.
It takes into account failings from either in the initial funcThatReturnsAPromise function or the expectations:
it('should test Promises', function () { // <= done removed
return testee.funcThatReturnsAPromise({'name': 'value'}) // <= return added
.then(response => expect(response).to.have.property('ok', 1));
});
I solved it extracting try/catch to a function.
function asyncExpect(test, done){
try{
test();
done();
} catch(error){
done(error);
}
}
Then in it() I call:
it('shall update a host', function (done) {
testee.insertHost({_id: 'host_id'})
.then(response => {
asyncExpect(() => {
expect(response).to.have.property('ok', 1);
expect(response).to.have.property('nModified', 1);
}, done);
});
});
It's also debugable.
Timers during tests and async sounds pretty rough. There is a way to do this with a promise based approach.
const sendFormResp = async (obj) => {
const result = await web.chat.postMessage({
text: 'Hello world!',
});
return result
}
This async function uses a Web client (in this case it is Slacks SDK). The SDK takes care of the asynchronous nature of the API call and returns a payload. We can then test the payload within chai by running expect against the object returned in the async promise.
describe("Slack Logic For Working Demo Environment", function (done) {
it("Should return an object", () => {
return sdkLogic.sendFormResp(testModels.workingModel).then(res => {
expect(res).to.be.a("Object");
})
})
});
A simpler approach would be using wait-for-expect library.
const waitForExpect = require("wait-for-expect")
test("it waits for the number to change", async () => {
let numberToChange = 10;
setTimeout(() => {
numberToChange = 100;
}, randomTimeout);
await waitForExpect(() => {
expect(numberToChange).toEqual(100);
});
});
What worked very well for me icm Mocha / Chai was the fakeTimer from Sinon's Library.
Just advance the timer in the test where necessary.
var sinon = require('sinon');
clock = sinon.useFakeTimers();
// Do whatever.
clock.tick( 30000 ); // Advances the JS clock 30 seconds.
Has the added bonus of having the test complete quicker.
You can also use domain module. For example:
var domain = require('domain').create();
domain.run(function()
{
// place you code here
});
domain.on('error',function(error){
// do something with error or simply print it
});

Categories