user-event: Error thrown when using react hooks

Description

Given a react component using hooks When a you attempt to use a user event e.g. type Then you receive the error below

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

Reason

As far as I understand this is because we are using the fireEvent from @testing-library/dom directly and not from @testing-library/react which means that it is not being in act.

Solution

A possible solution would be to allow for the user to provide a fireEvent implementation or allow for this to be configured in test setup

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 2
  • Comments: 40 (20 by maintainers)

Most upvoted comments

This issue is still happening. Why is this lib still using the fireEvent from the @testing-library/dom? Any idea if that’s going to be changed/fixed?

If I strip it right back, all is fine:

BasicForm.js:

import React from 'react';

const BasicForm = () => {
  const onSubmit = e => {
    e.preventDefault();
  };
  return (
    <form onSubmit={onSubmit}>
      <input type='submit' />
    </form>
  );
};

export default BasicForm;

BasicForm.test.js:

import React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import BasicForm from './BasicForm';

it("doesn't fall over on userEvent.click", async () => {
  render(<BasicForm />);
  userEvent.click(screen.getByRole('button'));
});

The problem is only present when I use handleSubmit from react-hook-form:

BasicForm.js:

import React from 'react';
import { useForm } from 'react-hook-form';

const BasicForm = () => {
  const { handleSubmit } = useForm();
  const onSubmit = data => {};
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input type='submit' />
    </form>
  );
};

export default BasicForm;

npm run test BasicForm:

 PASS  src/BasicForm.test.js
  ✓ doesn't fall over on userEvent.click (170ms)

  console.error node_modules/react-dom/cjs/react-dom.development.js:88
    Warning: An update to BasicForm 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 */

    This ensures that you're testing the behavior the user would see in the browser. Learn more at https://fb.me/react-wrap-tests-with-act
        in BasicForm (at BasicForm.test.js:7)

I think there’s a use case that still necessitates events to be wrapped in act.

If using a third-party library that triggers async logic that you have no method of awaiting, the act warning seems to show up.

As a concrete example, I’m referring to the validate function of react-hook-form. Since the state update that eventually occurs when the library validates an input is asynchronous, the state update eventually happens outside of an act call (or at least that’s what my guess is).

I’ve tested it and this works with no errors:

fireEvent.click(checkbox)

And this console.errors the act warning:

userEvent.click(checkbox)

If there’s a RTL async utility that I should be using and am just missing here, I’m all ears 🙂

I’m not sure how I feel about option 3. user-event only exposes 3 methods. I think I’d rather see it more mature and cover more use cases.

I’ll add that if you’re seeing act warnings, it’s not because user-event doesn’t handle act. Actually, the fact that fireEvent is wrapped by act is technically unnecessary because React already automatically flushes effects after it calls event handlers.

For example: https://github.com/kentcdodds/user-event-act (no act warnings happen in those tests).

So as long as you’re using RTL’s async utilities then you should be solid. Often I find that these warnings show up when someone has a test that doesn’t wait for side-effects to finish (like having one test that verifies that a loading spinner shows up, but doesn’t wait for the loading to actually complete and does that in another test. These tests should be combined… I think I’ll blog about this today).

So the urgency for me on this issue is very low because I don’t think it should be even necessary.

I’m waiting for https://github.com/testing-library/react-testing-library/issues/281 to be closed to decided what to do here. Also, following #119 we might add support to Vue which would prevent us from importing from @testing-library/react.

For now, I prefer not to address this issue, but it’s something I want to get solved in the near future.

Thanks for bringing it up!

@testing-library/react wraps fireEvent in act. See https://github.com/testing-library/react-testing-library/pull/685 So if the change happens synchronously when triggering the event, the change will happen inside an act.

But if the event handler returns and executes the change per timeout/promise you need to wait for the change. And then you need to wrap that change in act yourself.

// This probably works, too. But a change might occur between the act calls inside userEvent.
userEvent.whatever(element)
act(() => { /*wait for some change */ })

you should not rely on that.

Your example does not really test anything in your system under test. So it is hard to tell if you should just wrap it in act or take a different approach.

@matburnham I’m creating a new issue with your example, hope you don’t mind.

I’m hitting this when using react-hook-forms and userEvent.click too, since this issue is closed and is not exactly the same (not specific about react-hook-forms) should we create a new one?

Can you simplify it further so it’s not using any dependencies?

It’s likely that react-bootstrap has a side effect that’s causing this issue.

Note also, userEvent.click is synchronous, so you shouldn’t have to await it at all.

This should be passing without needing act now 😃

I have same issue of act warnings when using both user-event and react-hook-form with it’s async form validation. The only workaround I’ve found is wrapping userEvent.type() with act() calls. I am not sure whether the issue is actually with user-event or react-hook-forms.

My general observation is that if I use userEvent.type(input, 'some text) early in the test, and later there are couple of other async actions (like mocked network calls, but not other user-event actions) & related await wait() calls then the act warning does NOT appear. However in shorter tests the act warning appears and I actually have to wrap my user-event actions with act() calls.

I believe there are three (main) paths here. Some food for thought:

  1. Keep user-event as is, trying to provide as much compatibility to different frameworks as possible. They are not that different after all.
  2. As @calebeby suggested, provide a way for RTL/VTL/ATL and others to integrate their own fireEvent version into user-event.
  3. Bundle user-event with DOM Testing Library. DTL has become the source of truth for the whole Testing Library ecosystem (and that’s great), so this option would keep things as consistent as possible.

That looks reasonable to me.

I’d rather not have any configuration if possible. I prefer to wait for the two issues I mention to be resolved before doing anything here