Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test material ui autocomplete with react testing library

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.

like image 266
sruthi Avatar asked Mar 27 '20 08:03

sruthi


People also ask

How do you test a material UI autocomplete?

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.

How do you test material UI components?

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

Why react testing library is the best for material UI?

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.

How do I test a React component?

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.

Is autocomplete's onchange event called correctly with react-testing-library?

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.

What is react test library (RTL)?

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.


Video Answer


1 Answers

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.

like image 61
Eugen Sunic Avatar answered Sep 18 '22 11:09

Eugen Sunic