romgain/react-select-event

v5.5.1 introduces act-type warning in tests

Opened this issue · 11 comments

Hey! I just wanted to respectfully point out that the v5.5.1 patch might have actually brought along a breaking change, depending on your definition of "breaking change" ;)

We use Mind Renovate to manage our package upgrades automatically. We also use jest-fail-on-console to help keep on top of both, what I call, our "test barf" and to help us be a bit more careful about what non-critical warnings and errors might be raised and subsequently ignored by us during development.

Anyway, when Renovate tried to upgrade to react-select-event to v5.5.1 we started getting a failing build on that branch due to a new act-style warning being thrown... you know the one:

    Warning: An update to WhateverComponentLol inside a test was not wrapped in act(...).

    When testing, code that causes React state updates should be wrapped into act(...):

    act(() => {
      /* fire events that update state */
    });
    /* assert on the output */

... snip ...

I just wanted to point this out because it was unexpected in a patch release and the removal of the act call seems like the entire reason why this patch was released in the first place.

We're on react 17.0.2 and @testing-library/react 12.1.5 for what it's worth which seems to meet this project's devDependencies requirements.

We are seeing the same issue with React 17.0.2 and @testing-library/react 12.1.5.

Do you happen to be able to provide a reproducible test case? The tests in this package still use React 17 and there's no such warning, so it might only be occurring for specific usage?

@curtvict

We get a similar issue when using Formik together with react-select (React 18, though I don't know if this happens on React 17 too):

import selectEvent from 'react-select-event';
// ...

// In test:
await selectEvent.select(
  select,
  ['TagA', 'TagB', 'TagC']
);
expect(screen.getByText('TagA')).toBeInTheDocument();

The test passes, but we get the annoying act warning:

    Warning: An update to Formik inside a test was not wrapped in act(...).
    
    When testing, code that causes React state updates should be wrapped into act(...):
    
    act(() => {
      /* fire events that update state */
    });
    /* assert on the output */
    

The react-select component we are testing uses Formik under the hood and changes its state using the helpers returned by the Formik's useField hook:

  // In react-select component
  const [field, meta, helpers] = useField(props);

  const handleChange = (newValue) => {
    helpers.setValue(newValue);
  };
  
  // ...
  return (
    <Select
      onChange={handleChange}
      ...
     />
  );

@romgain Is there a solution to this? Thank you!

@curtvict

I have found a workaround for now, I'm sharing it here so hopefully it helps you as well.

The following code doesn't cause the dreaded act warning:

import selectEvent from 'react-select-event';
import { act, render, screen, waitFor } from '@testing-library/react';
// ...

// In test:
await act(async () => 
  await selectEvent.select(
    select,
    ['TagA', 'TagB', 'TagC']
  );
);
expect(screen.getByText('TagA')).toBeInTheDocument();
expect(screen.getByText('TagB')).toBeInTheDocument();
expect(screen.getByText('TagC')).toBeInTheDocument();

With this code, there's no act warning for me.

☝️ But... The test above didn't pass. I noticed that when you pass an array of options to selectEvent.select like I did with ['TagA', 'TagB', 'TagC'], only the last options ends up being added to the select for some reason (in my case as I'm using React 18, I suppose that it has to do with React 18's Auto Batching, but I'm not sure as I didn't dig deeper).

So what I did was selecting one option at a time, and ended up with this working code:

import selectEvent from 'react-select-event';
import { act, render, screen, waitFor } from '@testing-library/react';
// ...

// In test:
const select = screen.getByRole('textbox', {
  name: /Tags/i
});

// Select one option at a time, reopening the react-select's dropdown each time.
// After all, that's how our users will end up using this component...
selectEvent.openMenu(select);
await act(async () => 
  await selectEvent.select(
    select,
    'TagA'
  );
);

selectEvent.openMenu(select);
await act(async () => 
  await selectEvent.select(
    select,
    'TagB'
  );
);

selectEvent.openMenu(select);
await act(async () => 
  await selectEvent.select(
    select,
    'TagC'
  );
);

expect(screen.getByText('TagA')).toBeInTheDocument();
expect(screen.getByText('TagB')).toBeInTheDocument();
expect(screen.getByText('TagC')).toBeInTheDocument();

A bit of boilerplate, but at least it works and doesn't give any error/warning in my case.

Hope it helps someone who's facing a similar issue.

@antonvialibri1 in your code you missed the async
await act( ASYNC HERE () => await selectEvent.select( select, 'TagC' ); );

Talking about the warning messages
I tried using the await waitFor(() => selectEvent.select(element, 'value')), and it works here.

@nntdias Thanks for pointing that out, I updated the code snippets.

All my tests are passing, but after using react-select-event I've started seeing the following warning in unrelated tests:

console.error
      Warning: The current testing environment is not configured to support act(...)

This works but prints "Warning: An update to Select inside a test was not wrapped in act(...)."

await selectEvent.select(element, 'label');

This works but prints "Warning: You called act(async () => ...) without await. This could lead to unexpected testing behaviour, interleaving multiple act calls and mixing their scopes. You should - await act(async () => ...);"

act(async () => {
  await selectEvent.select(element, 'label');
});

This does NOT work at all

await act(async () => {
  await selectEvent.select(element, 'label');
});

This does NOT work too (with or without the waitFor)

await waitFor(async () => {
  await selectEvent.select(element, 'label');
});

Not even opening the select before selecting works.

Any help here?

We are seeing the same problems and need to change lots of tests in order to be able to update. Is this really necessary or a regression that we can expect to get fixed at some point?

This worked for me:

  await act(() => selectEvent.select(selectWrapper, 'Name'));