How to select an option from a select list with React Testing Library
Asked Answered
L

8

85

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?

Leveille answered 15/9, 2019 at 18:8 Comment(3)
Have you tried fireEvent.change(getByTestId("select"), { target: { value: '<item label>' } });Buster
It seems to not like getByTestId("select"), I get an error: TypeError: container.querySelectorAll is not a functionLeveille
Keep scrolling below for a most idiomatic answer, provided by @helen8287 : https://mcmap.net/q/239411/-how-to-select-an-option-from-a-select-list-with-react-testing-librarySisto
F
79

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

Flatter answered 25/4, 2020 at 20:24 Comment(7)
fireEvent.change doesn't work for me; I used fireEvent.click and it worked fine.Hedger
instead of getByTestId('select') you can use getByRole('combobox')Sickener
NOTE: "value: 2" is not referring to the index of values but chooses the option with the value set to "2". If your values are not a number, make sure to do: "value: <your value>" on the fireEvent.Danyluk
Thanks, was looking for a way to test for selectedKeller
But the same code not working with Angular Material. is it require any additional workaround?Beberg
I created an updated answer using the suggestions by Tomasz Mularczyk and Logan Cundiff for an easier exampleApproximation
@Hedger 's comment should be the answerSlipcase
T
49

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.

Tuckie answered 12/4, 2021 at 8:52 Comment(1)
I'm running "@testing-library/user-event": "^13.2.1" and your solution worked for me. Much appreciated!Maui
V
39

**In 2021, you can use userEvent which comes as part of the React Testing Library ecosystem. It allows you to write tests which are closer to real user behaviour. Example


    it('should correctly set default option', () => {
      render(<App />)
      expect(screen.getByRole('option', {name: 'Make a choice'}).selected).toBe(true)
    })


    it('should allow user to change country', () => {
      render(<App />)
      userEvent.selectOptions(
        // Find the select element
        screen.getByRole('combobox'),
        // Find and select the Ireland option
        screen.getByRole('option', {name: 'Ireland'}),
      )
      expect(screen.getByRole('option', {name: 'Ireland'}).selected).toBe(true)
    })

Have a look at this article for more info on how to test select elements using React Testing Library.

Venge answered 7/10, 2021 at 9:45 Comment(0)
A
7

You can also use getByText which will save you from adding the data-testid attribute

In my case I did

import { screen } from '@testing-library/react';

....
<select>
  <option value="foo">Foo</option>
  <option selected value="bar">Bar</option>
</select>

....

expect((screen.getByText('Foo') as HTMLOptionElement).selected).toBeFalsy();
expect((screen.getByText('Bar') as HTMLOptionElement).selected).toBeTruthy();
Aerialist answered 22/7, 2021 at 7:29 Comment(0)
R
7

This seems simpler to me:

userEvent.selectOptions(screen.getByLabelText('County'), 'Aberdeenshire');
Rodroda answered 13/10, 2021 at 15:56 Comment(1)
This answer is much simpler and should be acceptedSisto
B
4

1: Install user-event V14 and above

npm:

npm install --save-dev @testing-library/user-event

yarn:

yarn add --dev @testing-library/user-event


2: Import and set in up: (it's a default import so make sure you didn't import named one)

import userEvent from '@testing-library/user-event';

test('should change data', async () => {
    const user = userEvent.setup();
});

3: Change selected option and test:

import userEvent from '@testing-library/user-event';

test('should change data', async () => {
    const user = userEvent.setup();
    
    // Check if default value in correct:
    expect(screen.getByRole('option', { name: /default option/i }).selected).toBeTruthy();
    
    // Change option: (Make sure to use Async/Await syntax)
    await user.selectOptions(screen.getByRole("combobox"), 'optionToSelect');
    
    // Check the result:
    expect(screen.getByRole('option', { name: /optionToSelect/i }).selected).toBeTruthy();
});
Byblow answered 27/10, 2022 at 20:58 Comment(0)
F
1

In my case I was checking a number against a string, which failed. I had to convert the number with toString() to make it work.

The HTML:

<select>
<option value="1">Hello</option>
<option value="2">World</option>
</select>

And in my test:

await fireEvent.change(select, { target: { value: categories[0].id.toString() } });

expect(options[1].selected).toBeTruthy();
Felucca answered 10/1, 2023 at 12:4 Comment(0)
A
1

I combined the answers here to create a short way to achieve this using fireEvent. For this example I'm using a selector with 6 color options.

fireEvent.change(screen.getByRole('combobox'), { target: { value: "green" } });
const options = screen.getAllByRole('option');
expect(options).toHaveLength(6); 
expect(options[1].selected).toBeTruthy(); // green is the second option in this case

Here, "green" refers to the value of the option, so change this for your own use ie. value: <your value>.

Approximation answered 18/6, 2023 at 2:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.