16

I have a normal select list. I need to test handleChoice gets called when I choose an option. How can I do this with React Testing Library?

  <select
    onChange={handleChoice}
    data-testid="select"
  >
    <option value="default">Make your choice</option>
    {attributes.map(item => {
      return (
        <option key={item.key} value={item.key}>
          {item.label}
        </option>
      );
    })}
  </select>

getByDisplayValue with the value of item.label doesn't return anything, perhaps this is because it's not visible on the page?

Evanss
  • 17,152
  • 66
  • 217
  • 397

2 Answers2

18

Add data-testid to the options

  <option data-testid="select-option" key={item.key} value={item.key}>
      {item.label}
  </option>

Then, in the test call fireEvent.change, get all the options by getAllByTestId and check the selected option:

import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import App from './App';

test('Simulates selection', () => {
  const { getByTestId, getAllByTestId } = render(<App />);
  //The value should be the key of the option
  fireEvent.change(getByTestId('select'), { target: { value: 2 } })
  let options = getAllByTestId('select-option')
  expect(options[0].selected).toBeFalsy();
  expect(options[1].selected).toBeTruthy();
  expect(options[2].selected).toBeFalsy();
  //...
})

For your question, the getByDisplayValue works only on displayed values

Guy
  • 59,547
  • 93
  • 241
  • 306
misolo
  • 405
  • 6
  • 11
2

This solution didn't work for me, what did work, was userEvent. https://testing-library.com/docs/ecosystem-user-event/

    import React from 'react';
    import { render } from '@testing-library/react';
    import userEvent from '@testing-library/user-event';
    import App from './App';
    
    test('Simulates selection', () => {
      const { getByTestId } = render(<App />);
      // where <value> is the option value without angle brackets!
      userEvent.selectOptions(getByTestId('select'), '<value>');
      expect((getByTestId('<value>') as HTMLOptionElement).selected).toBeTruthy();
      expect((getByTestId('<another value>') as HTMLOptionElement).selected).toBeFalsy();
      //...
    })

You can also forgo having to add a data-testid to the select element if you have a label (which you should!), and simply use getByLabelText('Select')

Further still, you can get rid of the additional data-testid on each option element, if you use getByText.

      <label for="selectId">
            Select
      </label>
      <select
        onChange={handleChoice}
        id="selectId"
      >
        <option value="default">Make your choice</option>
        {attributes.map(item => {
          return (
            <option key={item.key} value={item.key}>
              {item.label}
            </option>
          );
        })}
      </select>

Then:

    import React from 'react';
    import { render } from '@testing-library/react';
    import userEvent from '@testing-library/user-event';
    import App from './App';
    
    test('Simulates selection', () => {
      const { getByLabelText, getByText } = render(<App />);
      // where <value> is the option value without angle brackets!
      userEvent.selectOptions(getByLabelText('<your select label text>'), '<value>');
      expect((getByText('<your selected option text>') as HTMLOptionElement).selected).toBeTruthy();
      expect((getByText('<another option text>') as HTMLOptionElement).selected).toBeFalsy();
      //...
    })

This seems a more optimal way to do this type of test.

Marek Szkudelski
  • 867
  • 2
  • 11
Matthew Trow
  • 2,204
  • 1
  • 10
  • 13