menu

Testing Library

Simple and complete DOM testing utilities that encourage good testing practices.

Channels
Chat
view-forward
# All channels
view-forward
# General
view-forward
# General Help
view-forward
# Angular Help
view-forward
# Cypress Help
view-forward
# DOM Help
view-forward
# React Help
view-forward
# Svelte Help
view-forward
# TestCafe Help
view-forward
# Vue Help
view-forward
Team

Material-UI Textfield Select (non-native)

March 28, 2019 at 3:18pm

Material-UI Textfield Select (non-native)

March 28, 2019 at 3:18pm
Has anyone manage to successfully use RTL to test a Material UI dropdown, and if so, how?
Problem: Cannot successfully use any of the RTL getBy* queries to identify and then 'click' the correct child of a Textfield component, so as to choose an option from the dropdown.
This means that the <ul> dropdown of selectable options is never rendered and I have nothing on which to assert.
Any ideas will be very gratefully received! 🙏
ps. I've thoroughly checked the related post with a similar concern, using React-Select and have not found that it solved the problem

March 28, 2019 at 3:27pm
Hey, so using jest.mock and render a native select doesn't work for your case?
  • reply
  • like
Thanks for getting back so rapidly, Giorgio 😄. It was only your suggestion I haven't tried, mainly because I'm not (yet) convinced it's what I'd want to do. Why mock the UI that I expect my user to interact with? I'm all ears for being convinced though!
like-fill
2
  • reply
  • like
:D
  • reply
  • like
Because you have no control over that UI. It's defined in a 3rd party module.
So, you have two options:
You can figure out what HTML the material library creates and then use container.querySelector to find its elements and interact with it. It takes a while but it should be possible. After you have done all of that you have to hope that at every new release they don't change the DOM structure too much or you might have to update all your tests.
The other option is to trust that Material-UI is going to make a component that works and that your users can use. Based on that trust you can simply replace that component in your tests for a simpler one.
Yes, option one tests what the user sees but option two is easier to maintain.
In my experience the second option is just fine but of course your use-case might be different and you might have to test the actual component.
like-fill
1
  • reply
  • like
Thanks ever so much for expanding 🤙 I've certainly expended far too much energy on the former!
I think the line that is most convinced me is 'trust material ui and replace it with something simpler'. I think that is probably the core of this problem, although I don't think it necessarily presents as such at first glance!
like-fill
1
  • reply
  • like
Hi Robert, the code of your select component would help to know what exactly is your problem. Ideally with sandbox.
Following solution definitely feels hacky, but it's the best I came up with:
// my component
<Select
SelectDisplayProps={{
'data-testid': 'vehicleSelect'
}}
>
<MenuItem value="car">
<em>Car</em>
</MenuItem>
<MenuItem value="ship">
<em>Ship</em>
</MenuItem>
</Select>
then in my test:
const { getByTestId, getByText } = render(<MyComponent/>)
const selectButton = getByTestId('vehicleSelect')
fireEvent.click(selectButton)
getByText(/car/i)
getByText(/ship/i)
like-fill
3
  • reply
  • like

April 17, 2019 at 6:50am
But won't such a mock beat the purpose of me writing those tests altogether? For example, say a future version of this Material UI component renamed a prop called placeholder to defaultText. I upgrade and run my tests to see if things are fine, the tests would pass but in reality my component would be breaking.
  • reply
  • like
Yes the test would break but for those kind of checks I prefer to rely on TypeScript/Flow/PropTypes.
I also check the release notes whenever I update a dependency so that I know what could break. It's especially true in your example because you're talking about a breaking change that would be introduced in a major release.
In the end it's a tradeoff between having a 100% test coverage and having tests that are easy to maintain. In my experience 100% coverage is seldom a good thing but your requirements might be different.
  • reply
  • like

April 23, 2019 at 1:44pm
If I'm understanding your question correctly, this might help. <Textfield /> is a combination of several other components, presented in an easily used way. In order to get a data-testid onto the semantic html rendered by a child component (which is consumed in RTL tests), Textfield exposes a few props for child component prop injection The following is how to inject a data-testid onto a semantic input html element:
<Textfield
inputProps={{
data-testid="foo-bar"
}}
/>
renders to:
<input data-testid="foo-bar" />
See https://material-ui.com/api/text-field/ for other ways to inject props into child components in a way that renders into the semantic HTML.
Update: There's a prop called SelectProps on <Textfield /> that might be the thing you're looking for! 😃
Edited
  • reply
  • like

May 28, 2019 at 8:52am
Thanks for your comment it helped me look in the right direction, for a select that would be SelectDisplayProps. And from the TextField with the select prop it would look something like:
SelectProps={{
SelectDisplayProps: {
'data-testid': 'language-select',
},
}}
Edited
like-fill
3
  • reply
  • like

May 31, 2019 at 12:23pm
You're welcome, and thank you for the better example that foobar! :)
  • reply
  • like

July 31, 2019 at 8:28pm
after I get the language-select using getByTestId("language-select"), how do I fire an event to select the 1st, or 2nd in the options? I am having this issue now. I fired click event and it causes error. I fired change event but it says "The given element does not have a value setter" How do I fire an event to choose one from the list of MenuItem
  • reply
  • like
<TextField id="myid" select label="Languages" value="" required={true} onChange={(e) => handleChange("fcm", e)} error={state.fcmError} helperText={state.error ? "This field is required" : null} SelectProps={{ MenuProps: { className: classes.menu }, SelectDisplayProps: { 'data-testid': 'language-select', } }} margin="normal" > {props.list.map(option => ( <MenuItem key={option.id} value={option.id}> {option.name} </MenuItem> ))} </TextField>
  • reply
  • like

September 8, 2019 at 8:40am
<TextField id="myid" select label="Languages" value="" required={true} onChange={(e) => handleChange("fcm", e)} error={state.fcmError} helperText={state.error ? "This field is required" : null} SelectProps={{ MenuProps: { className: classes.menu }, SelectDisplayProps: { 'data-testid': 'language-select', } }} margin="normal" > {props.list.map(option => ( <MenuItem key={option.id} value={option.id}> {option.name} </MenuItem> ))} </TextField>
I have the same problem, how to test a change in selector. Did you solved it finally?
  • reply
  • like
great approach, but how to test when changing menuItems ?
  • reply
  • like
Hi Robert, the code of your select component would help to know what exactly is your problem. Ideally with sandbox.
Following solution definitely feels hacky, but it's the best I came up with:
// my component
<Select
SelectDisplayProps={{
'data-testid': 'vehicleSelect'
}}
>
<MenuItem value="car">
<em>Car</em>
</MenuItem>
<MenuItem value="ship">
<em>Ship</em>
</MenuItem>
</Select>
then in my test:
const { getByTestId, getByText } = render(<MyComponent/>)
const selectButton = getByTestId('vehicleSelect')
fireEvent.click(selectButton)
getByText(/car/i)
getByText(/ship/i)
great hack!, but how to test when changing menuItems ?
  • reply
  • like

January 2, 2020 at 4:53pm
I've managed to test using the examples above but unfortunately, after upgrading to MaterialUI to version 4 it stopped working. Now it does not find the MenuItem to click
Test Code
fireEvent.click(getByTestId("id-country"));
const countryOption = await waitForElement(() => getByText("Brazil"));
fireEvent.click(countryOption);
React Component Code
<Grid item xs={12} sm={4}>
<TextField
id="select-country"
name="country"
select
helperText={touched.country ? errors.country : ""}
error={touched.country && Boolean(errors.country)}
required
label="Country"
onChange={handleChange}
value={values.country}
className={classes.selectField}
SelectProps={{
SelectDisplayProps: {
"data-testid": "id-country"
}
}}
>
{CountryEnum.map(country => (
<MenuItem key={country.type} value={country.type}>
{country.label}
</MenuItem>
))}
</TextField>
</Grid>
  • reply
  • like

January 6, 2020 at 8:33pm
Solved using: fireEvent.mouseDown(getByTestId("id-country")); instead offireEvent.click(countryOption);
  • reply
  • like

January 13, 2020 at 1:56pm
Hello, I'm noob to react-testing-library.
first, thanks to . I'm solving my problem with your solution, but that solution have a typescript error.
(property) "data-testid": string
No overload matches this call.
Overload 1 of 2, '(props: TextFieldProps, context?: any): ReactElement<any, string | ((props: any) => ReactElement<any, string | ... | (new (props: any) => Component<any, any, any>)>) | (new (props: any) => Component<...>)> | Component<...>', gave the following error.
Type '{ "data-testid": string; }' is not assignable to type 'HTMLAttributes<HTMLDivElement>'.
Object literal may only specify known properties, and '"data-testid"' does not exist in type 'HTMLAttributes<HTMLDivElement>'.
Overload 2 of 2, '(props: PropsWithChildren<TextFieldProps>, context?: any): ReactElement<any, string | ((props: any) => ReactElement<any, string | ... | (new (props: any) => Component<any, any, any>)>) | (new (props: any) => Component<...>)> | Component<...>', gave the following error.
Type '{ "data-testid": string; }' is not assignable to type 'HTMLAttributes<HTMLDivElement>'.
Object literal may only specify known properties, and '"data-testid"' does not exist in type 'HTMLAttributes<HTMLDivElement>'.ts(2769)
Select.d.ts(23, 3): The expected type comes from property 'SelectDisplayProps' which is declared here on type 'Partial<SelectProps>'
Select.d.ts(23, 3): The expected type comes from property 'SelectDisplayProps' which is declared here on type 'Partial<SelectProps>'
example component
import React from "react";
// Type
import { AccessFormProps, CaseType } from "../InfoForm.type";
// CSS-In-JS
import { TextField, MenuItem } from "@material-ui/core";
import useFormStyle from "../useFormStyle";
const accessCase: Array<CaseType> = [{ value: `PUBLIC` }, { value: `PRIVATE` }];
const AccessForm: React.FC<AccessFormProps> = ({ info, handleFunc }) => {
const classes = useFormStyle({});
return (
<TextField
className={classes.textField}
select
id={`course-access`}
label={access}
value={info.access}
onChange={(evt: React.ChangeEvent<HTMLTextAreaElement>) => handleFunc("access")(evt)}
// SelectProps={{
// SelectDisplayProps: {
// "data-testid": "accessSelect", // this has a typescript error.
// },
// }}
InputProps={{
classes: {
input: classes.inputStyle,
},
}}
>
{accessCase.map(({ value }) => (
<MenuItem key={value} value={value}>
{value}
</MenuItem>
))}
</TextField>
);
};
export default AccessForm;
So, I removed SelectProps, and testing like this
const waitForNextTick = () => new Promise(resolve => setTimeout(resolve));
it("can change the selector value with click selector", async () => {
const mocks = [COURSE_EDIT_DATA_MOCK]; //this is data mock
let utils: RenderResult = render(<MakeCourseEditorComponent mocks={mocks} />);
await act(async () => {
await waitForNextTick();
const selectButtons = utils.getAllByRole("button"); // get all button list
const accessButton = selectButtons.find(button => button.id === "course-access"); // find by id
fireEvent.mouseDown(accessButton);
const accessOption = await waitForElement(() => utils.getByText("PUBLIC"));
fireEvent.mouseDown(accessOption);
utils.getByText("PUBLIC");
});
});
I hope this comment helps someone
Edited
  • reply
  • like

February 29, 2020 at 3:27pm
I hope the following helps any tormented soul, trying to test material-ui forms. What worked for me is the following:
My codebase:
<Select
labelId="demo-simple-select-label"
id="demo-simple-select"
value={age}
onChange={handleChange}
>
<MenuItem value={10}>Ten</MenuItem>
<MenuItem value={20}>Twenty</MenuItem>
<MenuItem value={30}>Thirty</MenuItem>
</Select>
</Select>
And then on my test code:
const selector = document.querySelector('#demo-simple-select');
fireEvent.mouseDown(selector);
const choice=getByText('Twenty');
// or choice=document.querySelector('li[data-value]=30');
fireEvent.click(choice);
I tested even with Formik wrapped around it. It was able to update the value of Formik.
Edited
  • reply
  • like