testing-library / react-testing-library

🐐 Simple and complete React DOM testing utilities that encourage good testing practices.
https://testing-library.com/react
MIT License
18.98k stars 1.1k forks source link

Using fireEvent.change() on a select element fires the event handler, but doesn't update state. #908

Open Taelkir opened 3 years ago

Taelkir commented 3 years ago

Relevant code or config:

  const selectOne = screen.getByRole("combobox", { name: "My select" });
  fireEvent.change(selectOne, {
    target: { value: "OPTION1" }
  });

  expect(screen.getByText("OPTION1")).toBeInTheDocument();

What you did:

Attempting to change a <select> element and to check that different content is displayed based on what is selected. This works in a browser, but I can't get the test to recognise that.

What happened:

console.log() statements in the event handler of my component shows that the state is not changing; the fact these statements are logging at all show that the handler is being fired.

Reproduction:

Codesandbox here: https://codesandbox.io/s/react-testing-library-demo-forked-v09xi?file=/src/App.js

I've stripped down my TS project here to try and pin down the problem and am getting the same failing test behaviour.

Problem description:

I would expect the tests to change the app state in the same way as the browser does.

Suggested solution:

I'm not sure what's wrong; the event is happening, it's just the state then seems to reset to its original value.

eps1lon commented 3 years ago

Thanks for the report.

This is probably an old issue. It's caused due to the timing of when the state updater function runs.

If you capture the value, the test works as expected:


handleSelectChange = (event) => {
+   const value = event.target.value;
    this.setState(
      () => {
-       return { selectValue: event.target.value };
+       return { selectValue: value };
      },
      () => {
        console.log("State updated to: ", this.state.selectValue);
      }
    );
  };
Taelkir commented 3 years ago

Yep, that workaround is good for me in my project. Thanks!

eps1lon commented 3 years ago

Just to be clear: The code is working fine with manual testing in a browser. It only fails with our fireEvent.change implementation. So we do want to keep an eye on this bug.

davelosert commented 3 years ago

So I am having a (maybe) similar issue both with fireEvent as well as with userEvent (if it is something else I am happy to open another ticket 🙂 )

When mocking out the callback to an onChange-handler that receives the event as first argument, the event.target seems to be reset to the actual input field after the onChange call occurs but before fireEvent returns.

See this code example:

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

it("works on normal input.", async () => {
  const callback = jest.fn();

  render(
    <input
      type="text"
      value="2"
      onChange={(event) => {
        // Here, event.target.value is "29" as expected
        console.log(`Value in Handler: ${event.target.value}`);
        callback(event);
      }}
    />
  );

  const inputElement = screen.getByDisplayValue("2");

  userEvent.type(inputElement, "9");

  const event = callback.mock.calls[0][0];

  // Here, event.target.value is only "2"
  console.log(`Value after userEvent.type returns: ${event.target.value}`);

  expect(callback).toHaveBeenCalled();
  expect(event.target.value).toBe("29");
});

});

https://codesandbox.io/s/react-testing-library-demo-forked-m8wgx?file=/src/__tests__/App.js

Within the callback itself, I still have the new value. But in the test assertion, it is the old value. I guess this has something to do with how react handles events and I am happy to investiage why this happens - but I thought I'd asked first if this is a known issue.

gugu commented 2 years ago

Same error, happened after upgrade from react 17 to react 18. adding {delay: 0} fixed the problem and I have no idea, why

gugu commented 2 years ago

Oh, I found, why. Both of us are missing await after userEvent.type

eps1lon commented 2 years ago

For issues with user-event please file an issue with testing-library/user-event. This issue is just about fireEvent.change.

gugu commented 2 years ago

This issue is because issue author forgot to add await in their code (me as well)

On Mon, Jun 6, 2022, at 00:01, Sebastian Silbermann wrote:

For issues with user-event please file an issue with testing-library/user-event. This issue is just about fireEvent.change.

— Reply to this email directly, view it on GitHub https://github.com/testing-library/react-testing-library/issues/908#issuecomment-1146882633, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAASLIMXPKBAFYQ5FI35U63VNUITVANCNFSM44IFHHRQ. You are receiving this because you commented.Message ID: @.***>

JuanjoOrt commented 2 years ago

@gugu can you share in with part we have to put the await?

I have the same issue of author.

Thanks

gugu commented 2 years ago

const selectOne = screen.getByRole("combobox", { name: "My select" }); await fireEvent.change(selectOne, { target: { value: "OPTION1" } });

expect(screen.getByText("OPTION1")).toBeInTheDocument();

On Tue, Jun 14, 2022, at 11:18, Juanjo Ortiz wrote:

@gugu https://github.com/gugu can you share in with part we have to put the await?

I have the same issue of author.

Thanks

— Reply to this email directly, view it on GitHub https://github.com/testing-library/react-testing-library/issues/908#issuecomment-1154866217, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAASLIKU3V43BIIYPQXWPIDVPA54LANCNFSM44IFHHRQ. You are receiving this because you were mentioned.Message ID: @.***>

demo-Ghost commented 2 years ago

Hi. I'm using react-select in my project and can't seem to find a way to capture the event properly. This happens due to the fact I don't have access to the event in the scope of the onChange function.

Is there another way for this to work, other than grabbing the event in the handleChange funtion?

Edit: I'm really new to react-testing-library, so if there is another way to override this behavior from fireEvent.change on a select component, please let me know. I do have to use react-select because of a restriction on the project, but any react-testing-library API will do.

Thanks in advance!

demo-Ghost commented 2 years ago

Imported the react-select-event library and it magically worked. It's also mentioned in the react testing library docs in the ecosystem section.

https://testing-library.com/docs/ecosystem-react-select-event

fattylee2021 commented 2 years ago

@eps1lon thanks, this workaround works for me

sanathp commented 1 year ago

Using userEvent instead of fireEvent worked for me.