Reputation: 29375
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?
Upvotes: 179
Views: 244887
Reputation: 18456
In Jest 29+, await jest.runAllTimersAsync();
will wait for all timeouts and promises to resolve!
Upvotes: 15
Reputation: 7788
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 useEffect
s that change state (in response to fetch
es, etc.) that cause more useEffect
s to run that do more fetch
es or whatever -- All the promises will be resolved and ready for assertions without asynchronous mechanisms like testing library's waitFor
, findBy*
, etc.
Upvotes: 0
Reputation: 5450
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.
Upvotes: 306
Reputation: 714
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
Upvotes: 12
Reputation: 15596
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);
});
});
Upvotes: 4
Reputation: 539
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
Upvotes: 4
Reputation: 6566
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();
});
});
Upvotes: 1
Reputation: 2768
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.
Upvotes: 2