React-testing-library: on change for Material UI Select component not triggered

Created on 15 Mar 2019  路  37Comments  路  Source: testing-library/react-testing-library

  • react-testing-library version: 4.1.3
  • react version: 16.4.1
  • node version: 11.10.1
  • npm (or yarn) version: 6.7.0

Relevant code or config:

    const select = await waitForElement(() =>
      getByTestId("select-testid")
    );

    select.value = "testValue";
    fireEvent.change(select);
   <Select
       className={classes.select}
       onChange={this.handleSelectChange}
       value={selectedValue}
       inputProps={{
         id: "select-id",
         "data-testid": "select-id"
       }}
   >

What you did:

I am trying to fire the onChange method of the Material UI Select.

What happened:

onChange won't fire.
Also tried with

select.dispatchEvent(new Event('change', { bubbles: true }));

Most helpful comment

I had the problem that the text I want to select is elsewhere on the page, so I needed to target the 'dropdown' directly. Also I wanted it as a separate function, ideally not using the getByText etc. returned by render().

// myTestUtils.js
import {within, waitForElementToBeRemoved} from '@testing-library/react';
import UserEvent from '@testing-library/user-event';

export const selectMaterialUiSelectOption = async (element, optionText) =>
    new Promise(resolve => {
        // The the button that opens the dropdown, which is a sibling of the input
        const selectButton = element.parentNode.querySelector('[role=button]');

        // Open the select dropdown
        UserEvent.click(selectButton);

        // Get the dropdown element. We don't use getByRole() because it includes <select>s too.
        const listbox = document.body.querySelector('ul[role=listbox]');

        // Click the list item
        const listItem = within(listbox).getByText(optionText);
        UserEvent.click(listItem);

        // Wait for the listbox to be removed, so it isn't visible in subsequent calls
        waitForElementToBeRemoved(() => document.body.querySelector('ul[role=listbox]')).then(
            resolve,
        );
    });

Edit: added async, since material-ui can take a tic to remove the listbox.

All 37 comments

The same occurs with the Boostrap(reactstrap) Input Checkbox

According to this issue (https://github.com/kentcdodds/react-testing-library/issues/275), you should use the onClick event, not the onChange Event, but on reactstrap its not supported right now :/ I hope react-testing-library can implement the onChange function to it like browsers has it.

Edit: While inspecting this, I found out, that onClick isn't supported by react-testing-library as well.

that onClick isn't supported by react-testing-library as well.

That's not true. fireEvent.click exists.

If a "checkbox" component doesn't handle a click event then that component is inaccessible. This would be a bug in that component and should be fixed.

As for the original post, do this:

    const select = await waitForElement(() =>
      getByTestId("select-testid")
    );

    fireEvent.change(select, {target: {value: 'testValue'}});

Good luck!

As for the original post, do this:

    const select = await waitForElement(() =>
      getByTestId("select-testid")
    );

    fireEvent.change(select, {target: {value: 'testValue'}});

Good luck!

This by itself did not work. That being said, both of the following do work:

    select.value = "testValue";
    fireEvent.change(select, {target: {value: "testValue"}});

or

    select.value = "testValue";
    fireEvent.change(select);

I personally prefer the second option.

_Something to note for future readers_: If you are using a Material UI select, none of the above will work. You'll have to use the native version of the Select element.
For future reference: https://stackoverflow.com/questions/55184037/react-testing-library-on-change-for-material-ui-select-component

Hey @kentcdodds, Can you please brief us on how we can efficiently use react-testing-library with React Material-UI components ? I faced a similar issue of while testing the Material UI select(Not native select) onChange function.

This is the codeSandBox link: https://codesandbox.io/s/q94q9z1849

In the codeSandBox code I have similar Select component which I am using. For your info, the dropdown which appears in Select component is a Paper component. Best regards!!!

Hi @kentcdodds

Any help on how can we make testing select possible for onchange event when native is not true?

Sure, happy to have a look at it but your CodeSandbox is missing any unit tests at the moment

Hey @weyert, please feel free to add unit test cases in the Codesandbox example. Thank you.

I assume you already have some unit test cases you tried? The code sandbox you linked doesn't even have react-testing-library as a dependency. I would also suggest to look at https://stackoverflow.com/a/55576660

Hey @weyert , I have updated the codesandbox example and have wrote unit test for it. I would like to thank you for taking a look at this issue.

Here is the link for updated codesandbox: https://codesandbox.io/s/q94q9z1849

Please feel free to edit it or suggest improvements on it.

Try this: https://codesandbox.io/embed/94pm1qprmo
I have moved the data-testid to the inputProps-prop of the Select of ControlledOpenSelect-component also I have written a unit test for you which clicks on Thirty and ensures a onChange-props got triggered (I added the onChange to your comp).

Not sure, why it doesn't work in the browser but when you download it and test locally the unit test is successful. I hope it helps!

Thanks @weyert @anandthanki1
My tests are getting passed through your suggested way of selecting button and then clicking on element found by text. perfect.

Thank you @weyert

The same issue occurs for Input components as well.

fireEvent.change(input, { target: { value: "Valid input value" } }); does nothing.

What if you actually type it with something like UserEvents type-helper method?

Maybe @oliviertassinari can help with this

It would probably be good to have a few examples for testing MaterialUI components because we regularly get reports about difficulties with that library and I don't use it so I don't know why it's so uniquely difficult.

The solution shared by @weyert looks great to me: https://codesandbox.io/s/94pm1qprmo, thanks! The non-native variation of the select component relies on click/keyboard interactions. You can't fire a DOM change event. The input element present is only here to support HTML POST forms.

@TidyIQ for the Input component, let's move to #359.

Similar to what is in codesandbox worked for me:

    const { getByText, getAllByRole, getByTestId, container } = render(
      <Form />,
    );
    const selectNode = getByTestId('select-button-text');
    const selectButton = getAllByRole('button')[0];
    expect(selectButton).not.toBeNull();
    expect(selectNode).not.toBeNull();
    UserEvent.click(selectButton);
    await waitForElement(() => getByText('Custom Text'), { container });
    const itemClickable = getByText('Custom Text');
    UserEvent.click(itemClickable);
    getByTestId('custom1');

I had the problem that the text I want to select is elsewhere on the page, so I needed to target the 'dropdown' directly. Also I wanted it as a separate function, ideally not using the getByText etc. returned by render().

// myTestUtils.js
import {within, waitForElementToBeRemoved} from '@testing-library/react';
import UserEvent from '@testing-library/user-event';

export const selectMaterialUiSelectOption = async (element, optionText) =>
    new Promise(resolve => {
        // The the button that opens the dropdown, which is a sibling of the input
        const selectButton = element.parentNode.querySelector('[role=button]');

        // Open the select dropdown
        UserEvent.click(selectButton);

        // Get the dropdown element. We don't use getByRole() because it includes <select>s too.
        const listbox = document.body.querySelector('ul[role=listbox]');

        // Click the list item
        const listItem = within(listbox).getByText(optionText);
        UserEvent.click(listItem);

        // Wait for the listbox to be removed, so it isn't visible in subsequent calls
        waitForElementToBeRemoved(() => document.body.querySelector('ul[role=listbox]')).then(
            resolve,
        );
    });

Edit: added async, since material-ui can take a tic to remove the listbox.

Hey @davidgilbertson,

Cool! This would be awesome for https://github.com/kentcdodds/react-testing-library-examples

Okey doke I'll do a PR tonight. FYI I updated the above to use querySelector instead of getByRole - it seems that getByRole('listbox') will include a <select> on the page without that role, does that sound right?

will include a