reactjsmaterial-uireact-testing-library

React testing library on change for Material UI Select component


I am trying to test the onChange event of a Select component using react-testing-library.

I grab the element using getByTestId which works great, then set the value of the element and then call fireEvent.change(select); but the onChange is never called and the state is never updated.

I have tried using both the select component itself and also by grabbing a reference to the underlying input element but neither works.

Any solutions? Or is this a know issue?


Solution

  • This turns out to be super complicated when you are using Material-UI's Select with native={false} (which is the default). This is because the rendered input doesn't even have a <select> HTML element, but is instead a mix of divs, a hidden input, and some svgs. Then, when you click on the select, a presentation layer (kind of like a modal) is displayed with all of your options (which are not <option> HTML elements, by the way), and I believe it's the clicking of one of these options that triggers whatever you passed as the onChange callback to your original Material-UI <Select>

    All that to say, if you are willing to use <Select native={true}>, then you'll have actual <select> and <option> HTML elements to work with, and you can fire a change event on the <select> as you would have expected.

    Here is test code from a Code Sandbox which works:

    import React from "react";
    import { render, cleanup, fireEvent } from "react-testing-library";
    import Select from "@material-ui/core/Select";
    
    beforeEach(() => {
      jest.resetAllMocks();
    });
    
    afterEach(() => {
      cleanup();
    });
    
    it("calls onChange if change event fired", () => {
      const mockCallback = jest.fn();
      const { getByTestId } = render(
        <div>
          <Select
            native={true}
            onChange={mockCallback}
            data-testid="my-wrapper"
            defaultValue="1"
          >
            <option value="1">Option 1</option>
            <option value="2">Option 2</option>
            <option value="3">Option 3</option>
          </Select>
        </div>
      );
      const wrapperNode = getByTestId("my-wrapper")
      console.log(wrapperNode)
      // Dig deep to find the actual <select>
      const selectNode = wrapperNode.childNodes[0].childNodes[0];
      fireEvent.change(selectNode, { target: { value: "3" } });
      expect(mockCallback.mock.calls).toHaveLength(1);
    });
    

    You'll notice that you have to dig down through the nodes to find where the actual <select> is once Material-UI renders out its <Select>. But once you find it, you can do a fireEvent.change on it.

    The CodeSandbox can be found here:

    Edit firing change event for material-ui select