94

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?

3
  • Have you tried fireEvent.change(getByTestId("select"), { target: { value: '<item label>' } }); Commented Sep 15, 2019 at 18:24
  • It seems to not like getByTestId("select"), I get an error: TypeError: container.querySelectorAll is not a function Commented Sep 15, 2019 at 18:39
  • Keep scrolling below for a most idiomatic answer, provided by @helen8287 : stackoverflow.com/a/69558645/72351 Commented Mar 6, 2024 at 14:08

8 Answers 8

83

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

Sign up to request clarification or add additional context in comments.

6 Comments

fireEvent.change doesn't work for me; I used fireEvent.click and it worked fine.
instead of getByTestId('select') you can use getByRole('combobox')
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.
But the same code not working with Angular Material. is it require any additional workaround?
@incarnate 's comment should be the answer
|
52

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.

1 Comment

I'm running "@testing-library/user-event": "^13.2.1" and your solution worked for me. Much appreciated!
42

**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.

Comments

9

This seems simpler to me:

userEvent.selectOptions(screen.getByLabelText('County'), 'Aberdeenshire');

1 Comment

This answer is much simpler and should be accepted
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();

Comments

5

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();
});

Comments

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();

Comments

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>.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.