Preact: act not waiting for resolved promises from useEffect

Created on 8 Apr 2020  路  2Comments  路  Source: preactjs/preact

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).

Reproduction

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.

Steps to reproduce

$ git clone https://github.com/lohfu/act && cd act
$ yarn
$ yarn test

Expected Behavior

The assertions after the act() call should not run until after the component has re-rendered.

Actual Behavior

Assertions after act are called before the component has re/rendered.

EDIT: Currrent solution

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

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 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.

All 2 comments

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!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jescalan picture jescalan  路  3Comments

Zashy picture Zashy  路  3Comments

KnisterPeter picture KnisterPeter  路  3Comments

youngwind picture youngwind  路  3Comments

matthewmueller picture matthewmueller  路  3Comments