Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 992
Asker Avatar asked Feb 14 '21 08:02

Asker


1 Answers

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
});
like image 116
Brian Adams Avatar answered Oct 04 '22 14:10

Brian Adams