Converting some components and theirs tests from react to preact I have noticed a difference in behavior between react-dom's act and preact's.
If mocking async requests with resolved promises react-dom's act will wait for those to be resolved correctly if calling await act(async () => render).
I have created a repo with two simple test files, one for react and one for preact: https://github.com/lohfu/act. The test files are virtually identical.
$ git clone https://github.com/lohfu/act && cd act
$ yarn
$ yarn test
The assertions after the act() call should not run until after the component has re-rendered.
Assertions after act are called before the component has re/rendered.
Changing my tests from
await act(async () => {
render(<Component />, container)
})
// assertions
to
render(<Component />, container)
await act(() => new Promise(resolve => setTimeout(resolve)))
// assertions
fixes the issue
Looking at this code in your example, neither React nor Preact have any idea about the fact that async activity is happening within an effect inside your component, since you're not passing the Promise from fetch back to the framework. There isn't any way to do that either.
It might however be the case that React's implementation of act happens to wait an extra micro-task or two before it resolves, effectively doing what your workaround does. If my guess is correct here, then this is accidental and your test could break in future.
What I would recommend doing instead in this scenario is behaving more like a real user: Just wait until the output changes to what you expect it to be and give up after a timeout. In our tests, we have a waitFor utility that we use like so:
// Render widget and trigger some async effects (eg. fetching data via `fetch`).
act(() => {
render(<Widget />, container);
});
// Check that initially we're showing a loading state.
assert.include(container.textContent, 'Loading data...');
// Wait for the load to complete and show the loaded data or reject
// if that never happens.
await waitFor(
() => container.textContent.includes('Loaded the data'),
10 /* timeout */
)
This way your test is not coupled to how many turns of the event loop the async task happens to complete in.
Testing Library provides some very similar utilities.
I'm going to close this for now because I can't see anything in Preact that is not working as expected, but I'm happy to re-open if it turns out there is an issue.
@robertknight ok thanks for looking into it! i am already using react-testing-library so i will fiddle around with their waitFor. thanks for the suggestions!
Most helpful comment
Looking at this code in your example, neither React nor Preact have any idea about the fact that async activity is happening within an effect inside your component, since you're not passing the Promise from
fetchback to the framework. There isn't any way to do that either.It might however be the case that React's implementation of
acthappens to wait an extra micro-task or two before it resolves, effectively doing what your workaround does. If my guess is correct here, then this is accidental and your test could break in future.What I would recommend doing instead in this scenario is behaving more like a real user: Just wait until the output changes to what you expect it to be and give up after a timeout. In our tests, we have a
waitForutility that we use like so:This way your test is not coupled to how many turns of the event loop the async task happens to complete in.
Testing Library provides some very similar utilities.
I'm going to close this for now because I can't see anything in Preact that is not working as expected, but I'm happy to re-open if it turns out there is an issue.