How to make Jest wait for all asynchronous code to finish execution before expecting an assertion
Asked Answered
A

8

171

I am writing an integration test for for a React application, i.e. a test that tests many components together, and I want to mock any calls to external services.

The issue is that the test seems to execute before the async callback is executed causing my tests to fail.

Is there anyway around this? Can I somehow wait for call async code to finish?

Here is some bad pseudo code to illustrate my point.

I would like to test that when I mount Parent, its Child component render the content that came back from an external service, which i will mock.

class Parent extends component {
  render() {
    <div>
      <Child />
    </div>;
  }
}
class Child extends component {
  DoStuff() {
    aThingThatReturnsAPromise().then((result) => {
      Store.Result = result;
    });
  }

  render() {
    DoStuff();
    return <div>{Store.Result}</div>;
  }
}
function aThingThatReturnsAPromise() {
  return new Promise((resolve) => {
    eternalService.doSomething(function callback(result) {
      resolve(result);
    });
  });
}

When I do this in my test, it fails because the It gets executed before the callback gets fired.

jest.mock('eternalService', () => {
  return jest.fn(() => {
      return { doSomething: jest.fn((cb) => cb('fakeReturnValue');
  });
});

describe('When rendering Parent', () => {
  var parent;

  beforeAll(() => {
      parent = mount(<Parent />)
  });

  it('should display Child with response of the service', () => {
      expect(parent.html()).toMatch('fakeReturnValue')
  });
});

How do I test this? I understand angular resolves this with zonejs, is there an equivalent approach in React?

Apologist answered 24/6, 2017 at 21:23 Comment(1)
@Gegenwind Isn't jestjs.io/docs/en/asynchronous.html related? It's just the expect.assertions() thing. I won't dump an example from doc in an answer...Balkhash
R
287

Updated for Jest 27+

For jest 27+, you can also use process.nextTick:

await new Promise(process.nextTick);

(Thanks to Adrian Godong in the comments)

Original Answer

Here's a snippet that waits until pending Promises are resolved:

const flushPromises = () => new Promise(setImmediate);

Note that setImmediate is a non-standard feature (and is not expected to become standard). But if it's sufficient for your test environment, should be a good solution. Its description:

This method is used to break up long running operations and run a callback function immediately after the browser has completed other operations such as events and display updates.

Here's roughly how to use it using async/await:

it('is an example using flushPromises', async () => {
    const wrapper = mount(<App/>);
    await flushPromises();
    wrapper.update(); // In my experience, Enzyme didn't always facilitate component updates based on state changes resulting from Promises -- hence this forced re-render

    // make assertions 
});

I used this a lot in this project if you want some working real-world examples.

Rhodia answered 26/6, 2018 at 14:47 Comment(16)
Works great! If you want to make this a little more terse you can simplify that funciton even more: const flushPromises = () => new Promise(setImmediate);Kamal
whats the difference between this flushPromises and await Promise.resolve()? How do they wait for the microtask queue differently? Asking because I can use Promise.resolve when there's only one Promise that needs to be fullfilled, but flushPromises is neccessary otherwise.Aruabea
This function did help but I soon after figured out why it wasn't waiting. The line that was processing the promise didn't have await at the beginning. In my case this didn't affect program correctness but this may not be the best solution for everyone.Domitian
wow! I've been looking for this solution for two days! Thanks a lot!Hazel
This doesn't actually do what the question asked for. This (or even just await Promise.resolve()) will effectively put the remainder of the test on the back of the stack behind promises that are already fulfilled, but it's not going to wait for any promises that still need time to complete.Diamagnetic
Worth noting that setImmediate was removed from jsdom in jest 27 (github.com/facebook/jest/pull/11222), so this will not work any longer in jest tests.Homiletics
It is not working in my case. I use jest version 24.9.0 and react testing librarySciuroid
await Promise.resolve() will be resolved in the current event loop iteration while setImmediate or setTimeout with 0 will be run in the next tick. There is no difference in flushing promises but the later will also flush any pending timer that timed-out, useful to test with jest.useFakeTimers(). Additionally based on my unit test setTimeout with 0 perform consistent to flush timers compare to setImmediate due to non-deterministic order of execution of both (again flushing promises if fine from any of these)Ripe
For jest 27+, you can also use process.nextTick, e.g. await new Promise(process.nextTick);.Culp
@EricHaynes It worked for me though...Frump
@AdrianGodong this should be the accepted answer! Thank you so much! Please consider posting this as an answer to the question. Or someone else might ;)Askwith
@Frump Then your code under test had all of the promises already fulfilled. This essentially says "go run all of the .then for promises that are already complete, then resume". It does not "wait for all asynchronous code to finish", which is very different.Diamagnetic
I still need to wait 500ms before calling await new Promise(process.nextTick); otherwise nextTick happens before the onclick handler calls my async handler. Is there any way to avoid that?Savagism
@EricHaynes's comment is definitely overlooked with this solution. You're not guaranteed your promise will be fulfilled at the time of calling await new Promise(process.nextTick);Savagism
@Savagism In general, it's better to wait for some condition to be met rather than waiting for some length of time, as that length of time must be the greatest possible value. It really depends on the type of tests, but for browser-based testing testing-library.com/docs/dom-testing-library/api-async It's not too hard to implement something similar yourself if those aren't a good fit. Essentially just check for a condition, await the next tick if it fails, & repeat until it's either met or some amount of time has passed.Diamagnetic
Nice, thanks for this. This worked perfectly for my use case, where I had resolved a mock fetch, but then the "then" fn wasn't resolving in time, and thus, I had false negatives. Using the Jest 27 answer solved my problem -- the "then" fn is now properly resolving in time for the assertions.Expugnable
G
10

the flushPromises approach is in some scenario's broken.

Simply use await Promise.resolve() instead:

const component = mount(<App/>);

component.find('<button>').simulate('click');

// State changes

await Promise.resolve();

// Assert changes that occurred on the component

Gormandize answered 3/8, 2021 at 12:50 Comment(2)
It sounds like, from the comments in the accepted solution, that this works, but only if you're only waiting for one promise. Is it correct that, if you're waiting for multiple, you need to use await new Promise(process.nextTick)?Steffin
As @Steffin pointed out, this only works for a single promise. A better solution would be await new Promise(process.nextTick) - or await new Promise(setImmediate) for Jest < 27.Bunnybunow
H
6

In Jest 29+, await jest.runAllTimersAsync(); will wait for all timeouts and promises to resolve!

Haunch answered 21/9, 2023 at 0:31 Comment(0)
H
4

I would suggest you export aThingThatReturnsAPromise() from its module or file and then import it into your test case.

Since aThingThatReturnsAPromise() returns a promise, you can make use of the asynchronous testing features of Jest. Jest will wait for your promise to resolve and then you can make your assertions.

describe('When rendering Parent', () => {
    var parent;

    beforeAll(() => {
        parent = mount(<Parent />)
    });

    it('should display Child with response of the service', () => {
        expect.assertions(1);

        return aThingThatReturnsAPromise().then( () => {
            expect(parent.html()).toMatch('fakeReturnValue');
        });
    });
});

For more info, read how Jest handles test cases with Promises in the Jest Docs here

Hackworth answered 27/6, 2018 at 13:53 Comment(0)
S
4

As an alternate to some of the techniques listed in other answers, you can use also use the npm module flush-promises. A sample test suite with two tests is shown below (as is also shown at the referenced url):

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

describe('Async Promise Test Suite', () => {

    it('A test involving flushPromises', async () => {
        const wrapper = mount(<App/>);
        await flushPromises();
        // more code
    });

    it('Will not work correctly without flushing promises', async () => {
        let a;
        let b;

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

        await flushPromises();

        expect(a).toBe(1);
        expect(b).toBe(2);

    });

});
Scheel answered 23/1, 2021 at 1:6 Comment(2)
Note that the flush-promises module simply uses the const flushPromises = () => new Promise(setImmediate) technique from the accepted answer (with a fallback to setTimeout if setImmediate doesn't exist).Gambrinus
ahh yes, a dependency for 4 lines of code... love itLorin
G
2

I'm unaware of anything native to React to accomplish what you're looking for.

However, I was able to accomplish this in similar code by calling beforeAll()'s @done after setup was complete. See changes to your code below:

let setupComplete;
jest.mock('eternalService', () => {
    return jest.fn(() => {
        return { doSomething: jest.fn((cb) => { cb('fakeReturnValue'); setupComplete(); }) };
});
.
.
.
    beforeAll(done => {
        parent = mount(<Parent />)
        setupComplete = done;
    });
});

I've never used them, but of potential interest is Jest's runAllTicks and runAllImmediates.

Gastroenteritis answered 7/10, 2017 at 4:14 Comment(0)
I
1

while the pseudo code can be refactored to follow React lifecycle (using componentWillMount() componentDidMount(), it would be much easier to test. However, below is my untested pseudo code to the changes for your test codes, feel free to test it out and update it. Hope it'll help you!

describe('When rendering Parent', () => {
    it('should display Child with the response of the service', function(done) => {
        const parent = mount(<Parent />);
        expect(parent.html()).toMatch('fakeReturnValue');
        done();
    });
});
Incompetence answered 21/6, 2018 at 2:20 Comment(0)
W
-1

The other answers that basically boil down to: "Create a new promise and wait for it to settle" are no good. They only wait for currently-pending promises to settle, but what if the settling of the currently-pending promises creates new pending promises!? You can only cover one round of promises using something like

await Promise.resolve();

A more generic solution is to wrap your render function with await act from the testing library like this:

let component;
await act(()=>{
    component = render(<Whatever />);
});

after which all promises that are created in any useEffect chains, even useEffects that change state (in response to fetches, etc.) that cause more useEffects to run that do more fetches or whatever -- All the promises will be resolved and ready for assertions without asynchronous mechanisms like testing library's waitFor, findBy*, etc.

Wellnigh answered 9/9, 2022 at 19:59 Comment(1)
Sounds good to me! I just don't know why I'm getting linter errors when trying to wrap the render on act. It used to work before, but I don't know if something changed in the new versions...Microclimate

© 2022 - 2024 — McMap. All rights reserved.