How do I use userEvent to test a component that renders a MUI Select?

2.1k Views Asked by At

I'm writing unit tests for a component that renders a Material UI Select. But userEvent.selectOption does not seem to be working. In the example below, both getByRole calls seem to be selecting the correct components, but the validation with screen.getByText fails to find the text, and the test output shows that the placeholder option is still selected. Testing manually in Storybook shows my component works as expected. I've tried some variations of using fireEvent but no luck with that either.

TestSelect.tsx:

import { Box, Select } from '@mui/material';
import { useState } from 'react';

export function TestSelect() {
  const [thing, setThing] = useState('');

  return (
    <Box>
      <Select
        placeholder="hi"
        onChange={(evt) => {
          setThing(evt.target.value);
        }}
        value={thing}
      >
        <option key="" value="">hi</option>
        <option value="One">One</option>
        <option value="Two">Two</option>
      </Select>
      <div>{thing && `thing: ${thing}`}</div>
    </Box>
  );
}

TestSelect.test.tsx:

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

it('select option', async () => {
  const { click, selectOptions } = userEvent.setup();

  render(<TestSelect />);
  await click(screen.getByRole('button'));
  await selectOptions(
    screen.getByRole('listbox'),
    screen.getByRole('option', { name: 'One' })
  );

  // next line throws "Unable to find an element with the text: thing: One."
  expect(screen.getByText('thing: One')).toBeInTheDocument();
});

according to the test output, here is the relevant section of the DOM at the time the assertion happens (after the selectOptions promise has resolved):

    <ul
      class="MuiList-root MuiList-padding MuiMenu-list css-6hp17o-MuiList-root-MuiMenu-list"
      role="listbox"
      tabindex="-1"
    >
      <option
        aria-selected="true"
        data-value=""
        role="option"
        tabindex="0"
      >
        hi
      </option>
      <option
        aria-selected="false"
        data-value="One"
        role="option"
      >
        One
      </option>
      <option
        aria-selected="false"
        data-value="Two"
        role="option"
      >
        Two
      </option>
    </ul>

package versions:

"@mui/material": "5.10.5",
"@testing-library/react": "13.4.0",
"@testing-library/user-event": "14.4.3",
"vitest": "0.23.4"
1

There are 1 best solutions below

3
Cathal Mac Donnacha On

Generally, after you perform an action, you want to wait for it to complete. The best way to do this is to use screen.findBy which uses waitFor() under the hood. So something like this:

expect(await screen.findByText('thing: One')).toBeInTheDocument();

Also, another assertion could be to check if the option is selected:

expect(await screen.findByRole('option', { name: 'One' }).selected).toBe(true)