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();
Related
We have a method in our CLI which uses method returning a promise to print message to user.
exports.handler = (argv) => {
let customUtils = new Utils(argv);
Utils.deploy()
.then(res => console.log(`Ressource was deployed`))
.catch(e => {
console.error(`Ressource was not deployed`);
console.error(e);
process.exit(1);
});
}
We are looking for a way to test console errors and process exit in case of deploy() promise rejection.
We tried using sandbox stub then assert in an async test:
describe('when promise is errored', () => {
beforeEach(() => {
sandbox = sinon.createSandbox();
utilsStub = sandbox.stub(Utils.prototype, 'deploy').rejects('rejected');
processStub = sandbox.stub(process, 'exit');
consoleStub = sandbox.stub(console, 'error');
});
afterEach(() => {
sandbox.restore();
});
it('should call deploy and log the error before exiting', async () => {
await handler({});
expect(utilsStub).to.have.been.called;
expect(console.error).to.have.been.called;
});
});
This test doesn't work: AssertionError: expected error to have been called at least once, but it was never called.
The same happens when we expect(process.exit).to.have.been.called;. It's never called.
We successfuly tested the then part in a similary way:
describe('when promise is resolved', () => {
beforeEach(() => {
sandbox = sinon.createSandbox();
utilsStub = sandbox.stub(Utils.prototype, 'deploy').callsFake(() => Promise.resolve('some text'));
consoleStub = sandbox.stub(console, 'log');
});
afterEach(() => {
sandbox.restore();
});
it('should call deploy and print success message', async () => {
await handler({});
expect(utilsStub).to.have.been.called;
expect(console.log).to.have.been.calledWith('Ressource was deployed');
});
});
There are some things to fix the source and test file.
For source file, we must use customUtils to call deploy() function. Since, you can use async/await, convert it from Promise can produce better code.
exports.handler = async argv => { // put async
let customUtils = new Utils(argv);
try {
await customUtils.deploy(); // change to await and use customUtils
console.log(`Ressource was deployed`);
} catch (e) {
console.error(`Ressource was not deployed`);
console.error(e);
process.exit(1);
}
};
For test file, nothing changes
describe('when promise is errored', () => {
beforeEach(() => {
sandbox = sinon.createSandbox();
utilsStub = sandbox.stub(Utils.prototype, 'deploy').rejects('rejected');
processStub = sandbox.stub(process, 'exit');
consoleStub = sandbox.stub(console, 'error');
});
afterEach(() => {
sandbox.restore();
});
it('should call deploy and log the error before exiting', async () => {
await handler({});
expect(utilsStub).to.have.been.called;
expect(console.error).to.have.been.called;
expect(process.exit).to.have.been.called; // add it
});
});
UPDATED:
In case want to still use promise, we have to make sure we return the promise.
exports.handler = (argv) => {
let customUtils = new Utils(argv);
return customUtils.deploy() // <== specify return here
.then(res => console.log(`Ressource was deployed`))
.catch(e => {
console.error(`Ressource was not deployed`);
console.error(e);
process.exit(1);
});
};
Hope it helps
You need to be able await the result of exports.handler before you test your assertions. You are awaiting it, but exports.handler is not returning the promise, so there's nothing to await in the test — exports.handler returns undefined immediately so the test runs the assertions in the same event loop before console.error can be called.
I'm not sure why you aren't seeing similar problems in the test where the promise resolves. (Maybe worth checking that that test fails properly)
This should help:
exports.handler = (argv) => {
let customUtils = new Utils(argv);
//Utils.deploy() // <- is that a typo?
return customUtils.deploy()
.then(res => console.log(`Ressource was deployed`))
.catch(e => {
console.error(`Ressource was not deployed`);
console.error(e);
process.exit(1);
});
}
Also in your tests you are creating a spy with:
consoleStub = sandbox.stub(console, 'error');
But writing the assertion directly on console.error. I don't think this should work:
expect(console.error).to.have.been.called;
// maybe expect(consoleStub)...
With those changes the test passes for me and (more importantly) fails when I don't call console.error in the catch.
Consider testing the following simplified function
const functionToBeTested = async (val) => {
await otherModule.otherFunction(val/2);
}
In my jest test I want to make sure that the otherModule.otherFunction is not only called but also waited on. In other words, I want to write a test that will fail if someone removes the await from in front of the otherFunction call.
I have so far this
test('should wait on otherFunction', () => {
await functionToBeTested(6)
expect(otherModule.otherFunction).toHaveBeenCalledWith(3);
}
But the expect(otherModule.otherFunction).toHaveBeenCalledWith(3); check does not verify that functionToBeTested has waited on otherFunction.
Here's what i came up with:
const delay = duration => new Promise(resolve => setTimeout(resolve, duration));
test('should wait on otherFunction', async () => {
let resolve;
const mockPromise = new Promise((res) => {resolve = res;});
otherModule.otherFunction.mockReturnValue(mockPromise);
const resolution = jest.fn();
functionToBeTested(6).then(resolution);
expect(otherModule.otherFunction).toHaveBeenCalledWith(3);
await delay(0);
expect(resolution).not.toHaveBeenCalled();
resolve();
await delay(0);
expect(resolution).toHaveBeenCalled();
}
So, i mock otherFunction to return a promise which starts unresolved, but i can resolve it at will during the test. Then i call the function i want to test, and give it a callback for when its complete.
I then want to assert that it did not call the callback, but since promise resolution is always asynchronous i need to add in a timeout 0 to give the promise a chance to resolve. I chose to do this with a promis-ified version of setTimeout.
And finally, i resolve the mockPromise, do a timeout 0 (again, to make sure the promise gets a chance to call its callbacks), and assert that now the resolution has been called.
If you cannot check against otherModule.otherFunction resolved value or on any side-effects, there is no need to test wether it resolves.
Otherwise, removing await in following examples will cause the tests to fail.
describe('check for side effect', () => {
let sideEffect = false;
const otherModule = {
otherFunction: x =>
new Promise(resolve => {
setTimeout(() => {
sideEffect = true;
resolve();
}, 0);
}),
};
const functionToBeTested = async val => {
await otherModule.otherFunction(val / 2);
};
test('should wait on otherFunction', async () => {
const spy = jest.spyOn(otherModule, 'otherFunction');
await expect(functionToBeTested(6)).resolves.toBeUndefined();
expect(spy).toHaveBeenCalledWith(3);
expect(sideEffect).toBe(true);
});
});
describe('check returned value', () => {
const otherModule = {
otherFunction: x =>
new Promise(resolve => {
setTimeout(() => {
resolve('hello');
}, 0);
}),
};
const functionToBeTested = async val => {
const res = await otherModule.otherFunction(val / 2);
return `*** ${res} ***`;
};
test('should wait on otherFunction', async () => {
const spy = jest.spyOn(otherModule, 'otherFunction');
const promise = functionToBeTested(6);
expect(spy).toHaveBeenCalledWith(3);
await expect(promise).resolves.toBe('*** hello ***');
});
});
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
Hi I am write a unit test case of this function. When I run this function from the unit test case then it covers all statements but setInterval complete lines are not covered.
Does anyone know how to cover it in javascript? I am using mocha.
const open = async function (config) {
...... set of lines..
let retryIn = setInterval(async () => {
try {
client = await connect(config);
clearInterval(retryIn);
return client;
} catch (err) {
//error
}
}, 20000);
};
I am simply calling it like this
it("###test", async () => {
open(config);
});
});
First of all, you should never use setInterval in the case where you want to retry a task that has failed. You should use setTimeout instead.
Besides that, you cannot return a value from a classical callback base function like setInterval or setTimeout. So in its current form, the promise returned when calling open will be resolved before any connection is made.
With await/async you can create a clean and simple setup for such a situation:
function wait(seconds) {
return new Promise((resolve, _) => setTimeout(resolve, seconds))
}
async function connect() {
throw new Error('failed')
}
async function open(config) {
let client;
while (client === undefined /* || retries > maxRetries*/ ) {
try {
client = await connect(config);
} catch (err) {
// if connection failed due to an error, wait n seconds and retry
console.log('failed wait 2 seconds for reconnect');
await wait(2000)
// ++retries
}
}
if (!client) {
throw new Error('connection failed due to max number of retries reached.')
}
return client
}
async function main() {
let connection = await open()
}
main().catch(err => console.log(err))
You can further extend this snippet by adding a retry limit. See the comments for a rough idea on how that can be achieved.
To test the above code, you would write:
it("###test", function() {
return open(config);
});
Someone posted an answer about fake timers and then deleted it , The answer was correct so I re-posted again.
You can use sinonjs to create fake timers
Fake timers are synchronous implementations of setTimeout and friends
that Sinon.JS can overwrite the global functions with to allow you to
more easily test code using them
But from your code, it seems you are trying to test async code, in mocha, this can be achieved like this
describe('somthing', () => {
it('the result is 2', async () => {
const x = await add(1, 1)
expect(x).to.equal(4);
});
});
With something closer to your code
async function open() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('done')
}, 1000);
});
};
describe('somthing', () => {
it('###test', async () => {
const x = await open()
chai.expect(x).to.equal("done");
});
});
Just wrap to Promise
const open = async function (config) {
...... set of lines..
return new Promise((resolve, reject) => {
let retryIn = setInterval(async () => {
client = await connect(asConfigParam);
clearInterval(retryIn);
return client;
}, 20000);
return resolve(retryIn);
});
};
it("###test", async () => {
const result = await open(config);
console.log('result', result)
});
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();