When I've tested complicated JavaScript or TypeScript projects, I've run into the problem of async code from one test case resolving in another test case, and causing intermittent failures (particularly in React projects). This indicates a structural problem in the test suite. Ideally, I think you find a way to restructure the test cases so this doesn't happen. You don't always have the time or ability to do this (for any number of practical or technical reasons). One sure-fire way to address this is by flushing the micro-task queue in an afterEach
block.
const flushMicroTaskQueue = () =>
new Promise((resolve) => setTimeout(resolve, 0));
afterEach(async () => {
await flushMicroTaskQueue();
});
setTimeout
enqueues the resolve
function on the main queue, and it can only resolve after the micro-queue has emptied. Promises interact with the micro-queue, so this ensure that all promises resolve. (I think this also ensures all promises settle since promise-chains would continue adding to the micro-queue, but I'm not confident on that.)
Structurally, javascript runtimes have two queues, the task queue and the micro-task queue. Here is a simple logging example on MDN demonstrating how micro-tasks run before regular tasks.