Mocha testing inside async callbacks - node.js

I have simplified the example to be able to explain it well. I have an array which I want to iterate on. For each element of the array I want to execute a test with async/await functions, so I have this code:
const chai = require('chai');
const expect = chai.expect;
describe('Each film', async() => {
await Promise.all([1, 2, 3].map(async(n) => {
await new Promise(resolve => setTimeout(() => resolve(), 1000));
console.log('N:', n);
it('test', async() => {
expect(true).to.be.true;
});
}));
});
Executing this results in the following output:
0 passing (1ms)
N: 1
N: 2
N: 3
However, if I don't use async/await it is executed as I would expect, so it generates three tests that are resolved correctly.
What could be happening here?
UPDATE
I finally discovered that setTimeout can be used to load data asynchronously and then generate tests dinamically. This is the explanation from mocha page:
If you need to perform asynchronous operations before any of your suites are run, you may delay the root suite. Run mocha with the --delay flag. This will attach a special callback function, run(), to the global context:
So I finally wrote the code this way:
const chai = require('chai');
const expect = chai.expect;
setTimeout(async() => {
await Promise.all([1, 2, 3].map(async(n) => {
describe(`Element number ${n}`, () => {
it('test', async() => {
await new Promise(resolve => setTimeout(() => resolve(), 1000));
expect(true).to.be.true;
});
});
}));
run();
}, 500);
which generates the following output:
➜ node_modules/.bin/mocha --delay test.js
Element number 1
✓ test (1005ms)
Element number 2
✓ test (1001ms)
Element number 3
✓ test (1002ms)
3 passing (3s)

Mocha does not support asynchronous describe functions. You can generate tests dynamically, as described here, but that generation must still be synchronous.
Any tests that aren't created synchronously won't be picked up by the runner. Hence the 0 passing line at the top of your output. Mocha has decided there are no tests well before your promise resolves.
This isn't to say testing your stuff is impossible, just that you need to rethink how you're using Mocha to test it. The following, for example, would be similar to loading all of your things up front and making an assertion on each one in various tests:
const chai = require('chai');
const expect = chai.expect;
describe('Each item', () => {
let items;
before(async () => {
items = [];
await Promise.all([1, 2, 3].map(async(n) => {
await new Promise(resolve => setTimeout(() => resolve(), 1000));
items.push(n);
}));
})
it('is a number', () => {
for (item of items) {
expect(item).to.be.a('number');
}
});
it('is an integer', () => {
for (item of items) {
expect(item % 1).to.equal(0)
}
});
it('is between 1 and 3', () => {
for (item of items) {
expect(item).to.be.within(1, 3)
}
});
});
Unfortunately you won't be able to make a fully separate test displaying in your output for each item. If you want this, you may check out another test runner. I don't really have enough experience with others to say whether or not any of them support this. I'd be surprised if they do, though, since it's quite unusual.

Related

How to use wrapper with beforeEach and afterEach?

Every test I made with jest it's begining with await app.transaction(async(trx) => { and ends with ..rollback..
await app.transaction(async(trx) => {
const a = await update();
expect(a).toBe(something);
await trx.rollback();
});
The actual test is:
const a = await update();
expect(a).toBe(something);
And I want instead of write this wrapper for every test function, just to write within the beforeEach and afterEach.
Since the test is inside of the parameter to transaction you can't really do this in a beforeEach since it will differ based on the test. However you can avoid duplicating the code in each test by writing a helper function like this:
async function wrapper(testFn) {
return app.transaction(async(trx) => {
await testFn();
return trx.rollback();
}
}
// then do this in each test:
it('should work', () => {
await wrapper(async () => {
const a = await update();
expect(a).toBe(something);
});
});

Async beforeAll() does not finish before beforeEach() is called

In Jest, beforeAll() is supposed to run before beforeEach().
The problem is that when I use an async callback for beforeAll(), Jest doesn't wait for the callback to finish before going on to beforeEach().
How can I force Jest to wait for an async beforeAll() callback to finish before proceeding to beforeEach()?
Minimal reproducible example
tests/myTest.test.js
const connectToMongo = require('../my_async_callback')
// This uses an async callback.
beforeAll(connectToMongo)
// This is entered before the beforeAll block finishes. =,(
beforeEach(() => {
console.log('entered body of beforeEach')
})
test('t1'), () => {
expect(1).toBe(1)
}
test('t2'), () => {
expect(2+2).toBe(4)
}
test('t3'), () => {
expect(3+3+3).toBe(9)
}
my_async_callback.js
const connectToMongo = async () => {
try {
await mongoose.connect(config.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: false,
useCreateIndex: true
})
console.log('Connected to MongoDB')
} catch (err) {
console.log(`Error connecting to MongoDB: ${err.message}`)
}
}
module.exports = connectToMongo
UPDATE: As the accepted answer helpfully points out, Jest actually does wait for beforeAll to finish first, except in the case of a broken Promise chain or a timeout. So, the premise of my question is false. My connectToMongo function was timing out, and simply increasing the Jest timeout solved the problem.
The problem is that when I use an async callback for beforeAll(), Jest doesn't wait for the callback to finish before going on to beforeEach().
How can I force Jest to wait for an async beforeAll() callback to finish before proceeding to beforeEach()?
TLDR
The short answer is that Jest does wait for an async beforeAll() callback to finish before proceeding to beforeEach().
This means that if beforeEach() is running before something that should run in beforeAll() then the Promise chain must be broken or the beforeAll function is timing out.
Queue Runner in Jest
All of the beforeAll, beforeEach, test, afterEach, afterAll functions associated with a test are collected in queueableFns and are chained on these lines in queueRunner.ts:
const result = options.queueableFns.reduce(
(promise, fn) => promise.then(() => mapper(fn)),
Promise.resolve(),
);
So Jest starts with a resolved Promise and chains every function, in order, to the Promise chain with .then.
This behavior can be seen with the following test:
const order = [];
// first beforeAll with async function
beforeAll(async () => {
order.push(1);
await new Promise((resolve) => { setTimeout(resolve, 1000); });
order.push(2);
});
// first beforeEach with done callback
beforeEach(done => {
order.push(4);
setTimeout(() => {
order.push(6);
done();
}, 1000);
order.push(5);
});
// second beforeEach
beforeEach(() => {
order.push(7);
});
// second beforeAll
beforeAll(() => {
order.push(3);
});
it("should run in order", () => {
expect(order).toEqual([1, 2, 3, 4, 5, 6, 7]); // SUCCESS!
});
Broken Promise Chain
If beforeEach is running before something that should run in beforeAll then it is possible the Promise chain is broken:
const order = [];
// does not return Promise and will break the Promise chain
const func = () => {
setTimeout(() => { order.push(2); }, 1000);
}
const asyncFunc = async () => {
order.push(1);
await func(); // doesn't actually wait for 2 to be pushed
order.push(3);
}
beforeAll(asyncFunc);
beforeEach(() => {
order.push(4);
});
it("should run in order", () => {
expect(order).toEqual([1, 2, 3, 4]); // FAIL: [1, 3, 4]
});
Timeout
...or there is a timeout (note that the timeout will be reported by Jest in the output):
const order = [];
jest.setTimeout(100); // 100ms timeout
const asyncFunc = async () => {
order.push(1);
await new Promise(resolve => { setTimeout(resolve, 1000); }); // times out
order.push(2);
}
beforeAll(asyncFunc);
beforeEach(() => {
order.push(3);
});
it("should run in order", () => {
expect(order).toEqual([1, 2, 3]); // FAIL: [1, 3] and Timeout error
});
If there is async function and callback you can call done. if you want to pass callback inside the async function you are free!
Let me show you;
beforeAll(async (done) => {
await connectToMongo().catch(done) // if there is error it finish with error payload
done(); // it says i am finish. also you can use it on your callback function to say i am done.
})
This happened to me when upgrading to angular 14. Solution was to upgrade zone.js to 0.11.8. Found solution here: https://github.com/angular/angular/issues/45476#issuecomment-1195153212
connectToMongo function is an async function, not a async callback (async function with a function as a parameter ???)
beforeEach will be called when beforeAll is finished, and it still works well.
beforeAll(connectToMongo) will be done right after you call it, this means, it will not wait until the db connect success.
Just wait until connectToMongo done and continue:
beforeAll(async () => { // async function
await connectToMongo() // wait until connected to db
})

mocha unit test not working within callback

I am trying to define my mocha tests in json and then parse them and run my tests. However, I find that I am having difficulty running the tests in the callback functions
function addTwoNumbers(a,b){
return a+b;
}
function testAddNumbers(){
describe("testing the addition of 2 numbers",() => {
it('should return the addition',(done) => {
addTwoNumbers(2,2).should.be.equal(4)
done()
})
})
}
//Obtain all the test jsons
glob(__dirname +'/../**/*.doc.json', {} , (err,fileNames) => {
fileNames.forEach(fileName => {
let file = fs.readFileSync(fileName)
let tests = JSON.parse(file)
tests.forEach(test => {
testAddNumbers()
})
})
})
Which returns:
Backend listening on port 3000
0 passing (1ms)
Moving testAddNumbers() outside the glob block works, so I am not too sure of the problem. Eg:
function addTwoNumbers(a,b){
return a+b;
}
function testAddNumbers(){
describe("testing the addition of 2 numbers",() => {
it('should return the addition',(done) => {
addTwoNumbers(2,2).should.be.equal(4)
done()
})
})
}
//Obtain all the test jsons
glob(__dirname +'/../**/*.doc.json', {} , (err,fileNames) => {
fileNames.forEach(fileName => {
let file = fs.readFileSync(fileName)
let tests = JSON.parse(file)
tests.forEach(test => {
})
})
})
testAddNumbers()
returns
Backend listening on port 3000
testing the addition of 2 numbers
√ should return the addition
1 passing (238ms)
Thanks for the help!
I have fixed it in a way that I find rather disgraceful, but if you can do better do enlighten me
Problem:
Mocha was exiting when it reached the end of the file
Solution:
Loop and wait till file has completed running. This must be done within a mocha test or mocha will blatantly ignore you as if you were its mom
describe('Wait for dynamically added tests to complete', function () {
this.timeout(999999999)
it('Waiting...', function (done) {
completed = (function wait() {
// console.log(completed)
// console.log('boomdiyadah')
if (!completed) {
setTimeout(wait, 1000)
} else {
done()
}
})();
})
})
Probably this could be implemented with promises but my life needs to move forward

How to use jest.each asynchronously

I am having problems loading filenames into jest.each asynchronously.
My code:
let files: string[][]
function getFilesWorking() {
files = [["test1"], ["test2"]]
}
async function getFilesAsync() {
files = await Promise.resolve([["test1"], ["test2"]])
}
beforeAll(() => {
console.log("before")
})
describe.only("Name of the group", () => {
getFilesAsync()
test.each(files)("runs", f => {})
})
beforeAll is executed before each test but NOT before initialization of test.each, so I end up with undefined.
How can I load files before using test.each?
You can pass an async callback to beforeAll and await getFilesAsync within it
beforeAll(async () => {
await getFilesAsync();
})
As of Jest 28.1.3 and prior, this is not possible. There is an open issue documenting this behavior.
The best thing you can do for now is put your tests in a regular it() test and do a deep value comparison:
it('tests an array of cases', async () => {
const data = await getSomeAsyncData()
const expectedData = [ ... ]
expect(data).toEqual(expectedData)
})
You can use beforeEach to set up code that will run prior to tests for any given scope, https://jestjs.io/docs/setup-teardown:
beforeEach(() => {
console.log('before every test');
});
describe.only(('Name of the group') => {
beforeEach(() => {
console.log('before tests in this describe block');
})
})
Jest is only going to run the tests in your describe.only block. If you want to use beforeEach in other blocks and run those tests as well, change describe.only to describe.
(Edit: I know this is a year late, I'm just trying to look for a similar problem/solution set and thought I could answer this.)

Put time limit on Jest test?

I'm running a set of tests with Jest to demonstrate Big O using two different methods for Fibonacci.
const fastFib = require('./../fastFib');
const slowFib = require('./../slowFib');
test('Fast way of getting Fibonacci of 44', () => {
expect(fastFib(44)).toBe(701408733);
});
test('Slow way of getting Fibonacci of 44', () => {
expect(slowFib(44)).toBe(701408733);
});
I'm wondering if there is a way to specify the maximum length of a test? I saw you can pass a third variable for an async timeout but it doesn't seem to have any effect on normal functions:
test('Slow way of getting Fibonacci of 44', () => {
expect(slowFib(44)).toBe(701408733);
}, 5000);
Is there a way I can specify the maximum execution time for a function with Jest?
I will share slowFib.js for reference:
function fib(n) {
return (n<=1) ? n : fib(n - 1) + fib(n - 2);
}
module.exports = fib;
so your test pauses because of sync execution - there is no way to interrupt that by timeout. You need to "split execution". Next version fails to me:
test('Slow way of getting Fibonacci of 44', (done) => {
expect(slowFib(44)).toBe(701408733);
setTimeout(done, 10); // smallest timeout possible to make different macrotask
}, 5000);
PS I also believe this should be achievable by marking test async but have not yet figured how exactly.
[UPD] you actually may achieve your goal without using test's timeout:
test('Slow way of getting Fibonacci of 44', () => {
const start = new Date();
expect(slowFib(44)).toBe(701408733);
expect(new Date() - start).toBeLessThan(5000);
});
In your test file you can set
jest.setTimeout(5000); which overrides the default timeout for a test case in jest
I think you would need to implement your own timer (with setTimeout or using Promises). One alternative would be to use the async keyword for your function to make it work with the built-in parameter :
test('Slow way of getting Fibonacci of 44', async () => {
expect(slowFib(44)).toBe(701408733);
}, 5000);
Here's a (typescript friendly) functions inspired by #skyboyer's suggestion.
(tested using Jest 24.8.0, but should be relevant to any version)
// Takes a threshold and a callback.
// if the callback didn't run within the threshold, the returned function will evaluate to a rejected promise.
// else, the returned function will evaluate to a resolved promise with the value returned by 'cb' (T)
const resolveWithin = <T>(threshold: number, cb: () => T): () => Promise<T> => {
return () => {
const start = Date.now();
return Promise.resolve(cb()).then((t: T) => {
const elapsed = Date.now() - start;
if (elapsed > threshold) {
return Promise.reject(elapsed);
}
return Promise.resolve(t);
});
}
};
// Uses "resolveWithin" to ensure a test has run within the threshold.
const withIn = <T>(threshold: number, fn: () => T): () => Promise<T> => {
const cb = resolveWithin(threshold, fn);
// #ts-ignore
return () => {
return cb().catch((elapsed) => {
expect(elapsed).toBeLessThanOrEqual(threshold);
})
}
};
it("example", withIn(1000, () => { ... }));
it("example", withIn(1000, async () => { ... }));
A note regarding #Khaled Osman's / #HRK44 answers.
From what I could tell, using either approach will not be reflected as a test failure, and will not appear in reports generated by Jest.

Resources