Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React/Jest/Enzyme - await not waiting long enough

I have a function that awaits multiple promises

const function = async () => {
    await function1()
    await function2()
    await function3()
}

I want to test that function3 is called:

it(('calls function3', async () => {
    jest.spyOn(api, 'function1').mockResolvedValue({})
    jest.spyOn(api, 'function2').mockResolvedValue({})
    spy = jest.spyOn(api, 'function3')
    await function()
    expect(spy).toBeCalledTimes(1)
})

and this test fails, but when I call await a lot of times:

it(('calls function3', async () => {
    jest.spyOn(api, 'function1').mockResolvedValue({})
    jest.spyOn(api, 'function2').mockResolvedValue({})
    spy = jest.spyOn(api, 'function3')
    await await await await await function()
    expect(spy).toBeCalledTimes(1)
})

the test will pass. Why is this? Shouldn't await function() resolve all of the promises before moving onto the next expect line?

edit: The deeper the awaited function is i.e. a function4, the more await statements I need, but its not 1 to 1.

like image 578
peter Avatar asked Dec 13 '22 11:12

peter


2 Answers

Shouldn't await function() resolve all of the promises before moving onto the next expect line?

Yes, await will wait for the returned Promise before continuing.

Here is a simple working example:

const function1 = jest.fn().mockResolvedValue();
const function2 = jest.fn().mockResolvedValue();
const function3 = jest.fn().mockResolvedValue();

const func = async () => {
  await function1();
  await function2();
  await function3();
}

it('calls function3', async () => {
  await func();
  expect(function3).toHaveBeenCalled();  // Success!
})

If await is not waiting as long as expected, then the Promise chain is likely broken at some point.

Here is an example of a broken Promise chain:

const function1 = jest.fn().mockResolvedValue();
const function2 = jest.fn().mockResolvedValue();
const function3 = jest.fn().mockResolvedValue();

const func = async () => {
  await function1();
  await function2();
  await function3();
}

const func2 = async () => {
  func();  // <= breaks the Promise chain
}

it('calls function3', async () => {
  await func2();
  expect(function3).toHaveBeenCalled();  // <= FAILS
})

Calling await multiple times will queue the rest of the test function at the back of the PromiseJobs queue multiple times which can give pending Promise callbacks a chance to run...

...so the broken test above will pass if it is changed to this:

it('calls function3', async () => {
  await await await await func2();  // <= multiple await calls
  expect(function3).toHaveBeenCalled();  // Success...only because of multiple await calls
})

...but the real solution is to find and fix where the Promise chain is broken:

const func2 = async () => {
  await func();  // <= calling await on func fixes the Promise chain
}

it('calls function3', async () => {
  await func2();
  expect(function3).toHaveBeenCalled();  // Success!
})
like image 112
Brian Adams Avatar answered Dec 24 '22 04:12

Brian Adams


It is a matter of the order that the promises are enqueued in the micro-task queue, I'm using flush-promises to resolve the same issue.

It uses nodes setImmediate that pushes to the queue a callback that will be called when the micro-task queue is empty.

const flushPromises = require('flush-promises');

test('flushPromises', async () => {
  let a;
  let b;

  Promise.resolve().then(() => {
    a = 1;
  }).then(() => {
    b = 2;
  })

  await flushPromises();

  expect(a).toBe(1);
  expect(b).toBe(2);
});
like image 40
felixmosh Avatar answered Dec 24 '22 04:12

felixmosh