Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jest mock for react-select or react-testing-library on change not firing on target change

I'm currenty trying to mock the react-select component and no matter what i do the firevent.change from react-testing-library seems to only fire on the first call of firevent.change.

I was wondering why the change was only firing once and how to fix it.

My jest mock is as follows:

jest.mock("react-select", () => ({ options, value, onChange }) => {
  function handleChange(event) {
    console.log("called");

    const option = options.find(option => {
      return option.value == event.currentTarget.value;
    });

    onChange(option);
  }
  return (
    <select
      id="uc"
      data-testid="select"
      value={value}
      onChange={event => handleChange(event)}
    >
      {options?.map(({ label, value }) => (
        <option key={value} value={value}>
          {label}
        </option>
      ))}
    </select>
  );
});

As you can see its a fairly simple select block and i thought i should be able to call.

fireEvent.change(selectOptions[0], { target: { value: "0" } });

And this will work however if i then chain them together as in the following example:

test("Should render add another button and function", () => {
  const mockSetOpen = jest.fn();

  const { queryAllByTestId, getByTestId, getByLabelText, debug } = renderWithRedux(
    <TabByRestraints setOpen={mockSetOpen} hidden={false} />,
    {
      userCatagories: userCategoriesMock,
      permissionGroups: permissionGroupsMock,
      roles: rolesMock
    }
  );

  // Check that the user catagories exist and user catagories values
  const selectOptions = queryAllByTestId("select");
  expect(selectOptions).toHaveLength(2);

  expect(getByTestId("user-category-row")).toBeTruthy();

  fireEvent.change(selectOptions[0], { target: { value: "0" } });
  fireEvent.change(selectOptions[1], { target: { value: "132" } });

  expect(getByLabelText("Add a new user category restraint")).toBeTruthy();
});

I can see that the console.log("called") is only fired once. The second select on the page is never has its value selected therfore the test fails.

I would like both change to dispatch a change event.

fireEvent.change(selectOptions[0], { target: { value: "0" } });
fireEvent.change(selectOptions[1], { target: { value: "132" } }); 

However it only ever sets of the first.

Any help greatly appeciated.

like image 744
Morphasis Avatar asked Feb 18 '20 10:02

Morphasis


2 Answers

You should do another query for the select elements. I would go further and name them differently to make the test easier to read/debug.

  // it's easier to have separate ids for each select, will be easier to read/maintain the test in the future
  const firstSelect = queryByTestId("select1");
  fireEvent.change(firstSelect, { target: { value: "0" } });

  // a rerender might have happened so you need to query for the second select after the first event.
  const secondSelect = queryByTestId("select2");
  fireEvent.change(secondSelect, { target: { value: "132" } });

  expect(getByLabelText("Add a new user category restraint")).toBeTruthy();
like image 191
tudor.gergely Avatar answered Nov 01 '22 09:11

tudor.gergely


The fact that render works but renderWithRedux doesn't suggests the problem lies in the redux state management.

One thing to consider -- setting state queues a rerender within React, while dispatching an action to Redux queues the change in Redux's own update queue. React testing tools may not know how to deal with that.

Although it's closer to the code, you may want to consider mocking dispatch and verifying that it is receiving the expected results of the select changes. That way you can validate that your code is working, without worrying about what third party code is doing. (Kind of ironic that you were trying to avoid using react-select when renderWithRedux was the actual third-party problem. :D )

You may also want to consider directly calling your onChange handler, plucking it from the selects in your component instead of firing the events. This would be another way to reduce the amount of third-party code you are testing and mocking.

like image 42
Michael Landis Avatar answered Nov 01 '22 09:11

Michael Landis