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?
This is how MUI components are tested internally. A library that has a first-class API for this approach is @testing-library/react . For example, when rendering a TextField your test should not need to query for the specific MUI instance of the TextField but rather for the input , or [role="textbox"] .
You need to insert a value on the input element then trigger the change. After that the listbox opens which permits selecting the first value by firing the enter key. The selected value will replace the entered initial value used to search/open the autocomplete.
material-ui's select component uses the mouseDown event to trigger the popover menu to appear. If you use fireEvent.mouseDown
that should trigger the popover and then you can click your selection within the listbox that appears. see example below.
import React from "react"; import { render, fireEvent, within } from "react-testing-library"; import Select from "@material-ui/core/Select"; import MenuItem from "@material-ui/core/MenuItem"; import Typography from "@material-ui/core/Typography"; it('selects the correct option', () => { const {getByRole} = render( <> <Select fullWidth value={selectedTab} onChange={onTabChange}> <MenuItem value="privacy">Privacy</MenuItem> <MenuItem value="my-account">My Account</MenuItem> </Select> <Typography variant="h1">{/* value set in state */}</Typography> </> ); fireEvent.mouseDown(getByRole('button')); const listbox = within(getByRole('listbox')); fireEvent.click(listbox.getByText(/my account/i)); expect(getByRole('heading')).toHaveTextContent(/my account/i); });
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:
Here is a working example for MUI TextField with Select option.
Sandbox: https://codesandbox.io/s/stupefied-chandrasekhar-vq2x0?file=/src/__tests__/TextSelect.test.tsx:0-1668
Textfield:
import { TextField, MenuItem, InputAdornment } from "@material-ui/core";
import { useState } from "react";
export const sampleData = [
{
name: "Vat-19",
value: 1900
},
{
name: "Vat-0",
value: 0
},
{
name: "Vat-7",
value: 700
}
];
export default function TextSelect() {
const [selected, setSelected] = useState(sampleData[0].name);
return (
<TextField
id="vatSelectTextField"
select
label="#ExampleLabel"
value={selected}
onChange={(evt) => {
setSelected(evt.target.value);
}}
variant="outlined"
color="secondary"
inputProps={{
id: "vatSelectInput"
}}
InputProps={{
startAdornment: <InputAdornment position="start">%</InputAdornment>
}}
fullWidth
>
{sampleData.map((vatOption) => (
<MenuItem key={vatOption.name} value={vatOption.name}>
{vatOption.name} - {vatOption.value / 100} %
</MenuItem>
))}
</TextField>
);
}
Test:
import { fireEvent, render, screen } from "@testing-library/react";
import React from "react";
import { act } from "react-dom/test-utils";
import TextSelect, { sampleData } from "../MuiTextSelect/TextSelect";
import "@testing-library/jest-dom";
describe("Tests TextField Select change", () => {
test("Changes the selected value", () => {
const { getAllByRole, getByRole, container } = render(<TextSelect />);
//CHECK DIV CONTAINER
let vatSelectTextField = container.querySelector(
"#vatSelectTextField"
) as HTMLDivElement;
expect(vatSelectTextField).toBeInTheDocument();
//CHECK DIV CONTAINER
let vatSelectInput = container.querySelector(
"#vatSelectInput"
) as HTMLInputElement;
expect(vatSelectInput).toBeInTheDocument();
expect(vatSelectInput.value).toEqual(sampleData[0].name);
// OPEN
fireEvent.mouseDown(vatSelectTextField);
//CHECKO OPTIONS
expect(getByRole("listbox")).not.toEqual(null);
// screen.debug(getByRole("listbox"));
//CHANGE
act(() => {
const options = getAllByRole("option");
// screen.debug(getAllByRole("option"));
fireEvent.mouseDown(options[1]);
options[1].click();
});
//CHECK CHANGED
vatSelectInput = container.querySelector(
"#vatSelectInput"
) as HTMLInputElement;
expect(vatSelectInput.value).toEqual(sampleData[1].name);
});
});
/**
* HAVE A LOOK AT
*
*
* https://github.com/mui-org/material-ui/blob/master/packages/material-ui/src/Select/Select.test.js
* (ll. 117-121)
*
* https://github.com/mui-org/material-ui/blob/master/packages/material-ui/src/TextField/TextField.test.js
*
*
*/
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With