I am using material-ui autocomplete component and am trying to test it using react-testing-library
Component:
/* eslint-disable no-use-before-define */ import TextField from '@material-ui/core/TextField'; import Autocomplete from '@material-ui/lab/Autocomplete'; import React from 'react'; export default function ComboBox() { const [autocompleteInputValue, setAutocompleteInputValue] = React.useState(''); const [isAutocompleteOpen, setIsAutocompleteOpen] = React.useState(false); const renderInput = (params: any) => <TextField {...params} label='openOnFocus: false' variant='outlined' />; const getTitle = (option: any) => option.title; const handleAutocompleteInputChange = (event: any, value: string) => { setAutocompleteInputValue(value); }; const updateAutocompletePopper = () => { setIsAutocompleteOpen(!isAutocompleteOpen); }; return ( <Autocomplete id='autocompleteSearch' data-testid='autocomplete-search' disableClearable={true} renderOption={getTitle} getOptionLabel={getTitle} renderInput={renderInput} options={top100Films} clearOnEscape={true} onInputChange={handleAutocompleteInputChange} inputValue={autocompleteInputValue} open={isAutocompleteOpen} onOpen={updateAutocompletePopper} onClose={updateAutocompletePopper} style={{ width: 300 }} ListboxProps={{ 'data-testid': 'list-box' }} /> ); } // Top 100 films as rated by IMDb users. http://www.imdb.com/chart/top export const top100Films = [ { title: 'The Shawshank Redemption', year: 1994 }, { title: 'The Godfather', year: 1972 }, { title: 'The Godfather: Part II', year: 1974 }, { title: 'The Dark Knight', year: 2008 }, { title: '12 Angry Men', year: 1957 }, { title: 'Schindlers List', year: 1993 }, { title: 'Pulp Fiction', year: 1994 }, { title: 'The Lord of the Rings: The Return of the King', year: 2003 }, { title: 'The Good, the Bad and the Ugly', year: 1966 }, { title: 'Fight Club', year: 1999 }, { title: 'The Lord of the Rings: The Fellowship of the Ring', year: 2001 }, { title: 'Star Wars: Episode V - The Empire Strikes Back', year: 1980 }, { title: 'Forrest Gump', year: 1994 }, { title: 'Inception', year: 2010 }, { title: 'The Lord of the Rings: The Two Towers', year: 2002 }, { title: 'One Flew Over the Cuckoos Nest', year: 1975 }, { title: 'Goodfellas', year: 1990 }, { title: 'The Matrix', year: 1999 }, { title: 'Seven Samurai', year: 1954 }, { title: 'Star Wars: Episode IV - A New Hope', year: 1977 }, { title: 'City of God', year: 2002 }, { title: 'Se7en', year: 1995 }, { title: 'The Silence of the Lambs', year: 1991 }, { title: 'Its a Wonderful Life', year: 1946 }, { title: 'Life Is Beautiful', year: 1997 }, { title: 'The Usual Suspects', year: 1995 }, { title: 'Léon: The Professional', year: 1994 }, { title: 'Spirited Away', year: 2001 }, { title: 'Saving Private Ryan', year: 1998 }, { title: 'Once Upon a Time in the West', year: 1968 }, { title: 'American History X', year: 1998 }, { title: 'Interstellar', year: 2014 }, { title: 'Casablanca', year: 1942 }, { title: 'City Lights', year: 1931 }, { title: 'Psycho', year: 1960 }, { title: 'The Green Mile', year: 1999 }, { title: 'The Intouchables', year: 2011 }, { title: 'Modern Times', year: 1936 }, { title: 'Raiders of the Lost Ark', year: 1981 }, { title: 'Rear Window', year: 1954 }, { title: 'The Pianist', year: 2002 }, { title: 'The Departed', year: 2006 }, { title: 'Terminator 2: Judgment Day', year: 1991 }, { title: 'Back to the Future', year: 1985 }, { title: 'Whiplash', year: 2014 }, { title: 'Gladiator', year: 2000 }, { title: 'Memento', year: 2000 }, { title: 'The Prestige', year: 2006 }, { title: 'The Lion King', year: 1994 }, { title: 'Apocalypse Now', year: 1979 }, { title: 'Alien', year: 1979 }, { title: 'Sunset Boulevard', year: 1950 }, { title: 'Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb', year: 1964, }, { title: 'The Great Dictator', year: 1940 }, { title: 'Cinema Paradiso', year: 1988 }, { title: 'The Lives of Others', year: 2006 }, { title: 'Grave of the Fireflies', year: 1988 }, { title: 'Paths of Glory', year: 1957 }, { title: 'Django Unchained', year: 2012 }, { title: 'The Shining', year: 1980 }, { title: 'WALL·E', year: 2008 }, { title: 'American Beauty', year: 1999 }, { title: 'The Dark Knight Rises', year: 2012 }, { title: 'Princess Mononoke', year: 1997 }, { title: 'Aliens', year: 1986 }, { title: 'Oldboy', year: 2003 }, { title: 'Once Upon a Time in America', year: 1984 }, { title: 'Witness for the Prosecution', year: 1957 }, { title: 'Das Boot', year: 1981 }, { title: 'Citizen Kane', year: 1941 }, { title: 'North by Northwest', year: 1959 }, { title: 'Vertigo', year: 1958 }, { title: 'Star Wars: Episode VI - Return of the Jedi', year: 1983 }, { title: 'Reservoir Dogs', year: 1992 }, { title: 'Braveheart', year: 1995 }, { title: 'M', year: 1931 }, { title: 'Requiem for a Dream', year: 2000 }, { title: 'Amélie', year: 2001 }, { title: 'A Clockwork Orange', year: 1971 }, { title: 'Like Stars on Earth', year: 2007 }, { title: 'Taxi Driver', year: 1976 }, { title: 'Lawrence of Arabia', year: 1962 }, { title: 'Double Indemnity', year: 1944 }, { title: 'Eternal Sunshine of the Spotless Mind', year: 2004 }, { title: 'Amadeus', year: 1984 }, { title: 'To Kill a Mockingbird', year: 1962 }, { title: 'Toy Story 3', year: 2010 }, { title: 'Logan', year: 2017 }, { title: 'Full Metal Jacket', year: 1987 }, { title: 'Dangal', year: 2016 }, { title: 'The Sting', year: 1973 }, { title: '2001: A Space Odyssey', year: 1968 }, { title: 'Singin in the Rain', year: 1952 }, { title: 'Toy Story', year: 1995 }, { title: 'Bicycle Thieves', year: 1948 }, { title: 'The Kid', year: 1921 }, { title: 'Inglourious Basterds', year: 2009 }, { title: 'Snatch', year: 2000 }, { title: '3 Idiots', year: 2009 }, { title: 'Monty Python and the Holy Grail', year: 1975 }, ];
Depending on the option chosen from the autocomplete, I am doing some other stuff, like rendering a chip, another component etc. But to make matters simple, initially I am only testing that, when user focuses in the input field, the pop up is shown so that later, I can click on an option in this popup and test that everything else is working as expected. I am verifying for the popup using data-testid
I assigned to list box through ListboxProps
prop of autocomplete:
Test:
import { fireEvent, getByRole as globalGetByRole, getByText as globalGetByText, render, } from '@testing-library/react'; import React from 'react'; import ComboBox, { top100Films } from './AutoComplete'; test('that autocomplete works', async () => { const { getByTestId, getByRole, queryByRole } = render(<ComboBox />, {}); const AutoCompleteSearch = getByTestId('autocomplete-search'); const Input = globalGetByRole(AutoCompleteSearch, 'textbox'); expect(queryByRole('listbox')).toBeNull(); fireEvent.mouseDown(Input); const ListBox = getByRole('listbox'); expect(ListBox).toBeDefined(); const menuItem1 = globalGetByText(ListBox, top100Films[0].title); fireEvent.click(menuItem1); expect(queryByRole('listbox')).toBeNull(); fireEvent.mouseDown(Input); const ListBoxAfter = getByRole('listbox'); expect(ListBoxAfter).toBeDefined(); const menuItem2 = globalGetByText(ListBoxAfter, top100Films[1].title); fireEvent.click(menuItem2); expect(queryByRole('listbox')).toBeNull(); });
But this is failing with: Unable to find an element by: [data-testid="list-box"]
. What am I doing wrong?
EDIT: I fired mouseDown
on Input
and was successfully able to test that the popup is opened. I used listbox
role instead of a data-testid
to verify that the popup has opened. The same can be done with data-testid
as well. Then, I chose an item from autocomplete options and the popup closed. Now, I tried to open the popup again for the 2nd time and here, it fails again. Not able to open in for the 2nd time using mouseDown
event.
You have to locate the autocomplete and input DOM elements on which you will trigger events. You need to insert a value on the input element then trigger the change. After that the listbox opens which permits selecting the first value by firing the enter key.
This is how MUI components are tested internally. A library that has a first-class API for this approach is @testing-library/react . For example, when rendering a TextField your test should not need to query for the specific MUI instance of the TextField but rather for the input , or [role="textbox"] .
It enables us to change codes more speedy with less bugs. When it comes to frontend, React Testing Library is becoming popular because it’s easier to test React hooks compared with enzyme. However, because Material UI (MUI) styled component use HOC (Higher Order Component), setup is bit complicated.
This is an example of a test: We are testing a function called hello (), which is in another file, so we import it into hello.spec.js and call it. This is a really nice lightweight testing library for testing React components by querying and interacting with DOM nodes. It has very easy utility methods to simulate an end-user.
While Autocomplete's onChange event is called correctly during normal usage, when testing with the react-testing-library it is not. The following test fails: The issue is present in the latest release. I have searched the issues of this repository and believe that this is not a duplicate.
You can test the UI of React components just using the Jest testing framework, of course, but React Test Library (RTL) provides extra lightweight utility functions for Jest to work with React components, saving your time and encouraging best testing practices - it forces your application to be more accessible.
Since the list items are not "visible" in the DOM itself you need to use a different approach.
You have to locate the autocomplete and input DOM elements on which you will trigger events.
The autocomplete is usually found in the DOM by the role attribute e.g. role="combobox"
but it's best to give it a unique identifier such as data-testid="autocomplete"
The following code shows how to test item selection in autocomplete:
const autocomplete = getByTestId('autocomplete'); const input = within(autocomplete).querySelector('input') autocomplete.focus() // assign value to input field fireEvent.change(input, { target: { value: value } }) await wait() // navigate to the first item in the autocomplete box fireEvent.keyDown(autocomplete, { key: 'ArrowDown' }) await wait() // select the first item fireEvent.keyDown(autocomplete, { key: 'Enter' }) await wait() // check the new value of the input field expect(input.value).toEqual('some_value')
You need to insert a value on the input element then trigger the change. After that the listbox opens which permits selecting the first value by firing the enter key. The selected value will replace the entered initial value used to search/open the autocomplete.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With