How should I test React Hook "useEffect" making an api call with Typescript?
Solution
It both works and gets rid of the test was not wrapped in act(...)
warning.
const waitForComponentToPaint = async (wrapper) => {
await act(async () => {
await new Promise(resolve => setTimeout(resolve, 0));
wrapper.update();
});
};
Usage:
it('should do something', () => {
const wrapper = mount(<MyComponent ... />);
await waitForComponentToPaint(wrapper);
expect(wrapper).toBlah...
})
Thanks to...
This is a work-around suggested by edpark11 in the issue @Brian_Adams mentioned in his answer.
Original post: https://github.com/enzymejs/enzyme/issues/2073#issuecomment-565736674
I copied the post here with a few modifications for archiving sake.
There are two issues at play here
Asynchronous call to setData
setData
gets called in a Promise
callback.
As soon as a Promise
resolves, any callbacks waiting for it get queued in the PromiseJobs queue. Any pending jobs in the PromiseJobs queue run after the current message has completed and before the next one begins.
In this case the currently running message is your test so your test completes before the Promise
callback has a chance to run and setData
isn't called until after your test completes.
You can fix this by using something like setImmediate
to delay your assertions until after the callbacks in PromiseJobs have a chance to run.
Looks like you'll also need to call component.update()
to re-render the component with the new state. (I'm guessing this is because the state change happens outside of an act
since there isn't any way to wrap that callback code in an act
.)
All together, the working test looks like this:
it('should render a proper table data', done => {
const mock = new MockAdapter(axios);
mock.onGet('/path/to/api').reply(200, response.data);
const component = mount(<SalesPlanTable />);
setImmediate(() => {
component.update();
console.log(component.debug());
done();
});
});
Warning: An update to ... was not wrapped in act(...)
The warning is triggered by state updates to the component that happen outside of an act
.
State changes caused by asynchronous calls to setData
triggered by a useEffect
function will always happen outside of an act
.
Here is an extremely simple test that demonstrates this behavior:
import React, { useState, useEffect } from 'react';
import { mount } from 'enzyme';
const SimpleComponent = () => {
const [data, setData] = useState('initial');
useEffect(() => {
setImmediate(() => setData('updated'));
}, []);
return (<div>{data}</div>);
};
test('SimpleComponent', done => {
const wrapper = mount(<SimpleComponent/>);
setImmediate(done);
});
As I was searching for more info I stumbled on enzyme
issue #2073 opened just 10 hours ago talking about this same behavior.
I added the above test in a comment to help the enzyme
devs address the issue.