Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React, Jest and Material-UI: How to test for content rendered in a modal or popover

There are a few material-ui components that do not render their results in the same place as the component is placed by their parent. Among these we have the Dialog, Menu, etc.

This makes it apparently impossible to test for their content's presence in a jest.js wrapper with some parent component mounted in it.

For example given the following component:

class DropdownMenu extends React.Component {
  onButtonClick = (e) => {
    this.setState({ open: true, anchorEl: e.currentTarget });
  }

  render() {
    return (
      <div>
        <Button onClick={this.onButtonClick}>Menu</Button>
        <Menu
          open={this.state.open}
          onRequestClose={() => this.setState({ open: false })}
        >
          <MenuItem label="Home" />
          <MenuItem label="Sign in" />
        </Menu>
      </div>
    );
  }
}

This test fails even though it should intuitively work:

it('renders some menu items', () => {
  const wrapper = mount(<AppMenu />);
  expect(wrapper).toContainReact(<MenuItem label="Home" />);
});

And this is Jest's output of the failure:

renders some menu items

Expected <AppMenu> to contain <withStyles(MenuItem) className="MenuItem" component={{...}} to={{...}}>Home</withStyles(MenuItem)> but it was not found.
HTML Output of <AppMenu>:
 <div><button tabindex="0" class="MuiButtonBase-root-3477017037 MuiButton-root-3294871568 MuiButton-flatContrast-53993421" type="button" role="button" aria-owns="simple-menu" aria-haspopup="true"><span class="MuiButton-label-49836587">Menu</span><span class="MuiTouchRipple-root-3868442396"></span></button><!-- react-empty: 5 --></div>

As you can see, it's like if all that was rendered was the <Button>. And indeed, when you render the above component in a browser, and you expand the menu and inspect it's menu item elements, they are rendered elsewhere in the DOM, not within or even near the place where the button appears. They are in fact rendered inside a div <body><div data-mui-portal="true"> ... </div> directly under the document's <body> element.

So how can this menu contents be tested?

like image 858
Ernesto Avatar asked Aug 07 '17 16:08

Ernesto


People also ask

How do you test a popover?

Test file code: import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import SimpleModal from './SimpleModal'; test('should close when esc key is pressed', async () => { render(<SimpleModal />); userEvent. click(screen. getByText('Open Modal')); expect(screen.

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

Can Jest be used for UI testing?

React's official documentation recommends Jest as a test runner and React UI testing framework.

What is difference between Jest and enzyme?

Both Jest and Enzyme are meant to test the react applications. Jest can be used with any other Javascript framework, but Enzyme is meant to run on react only. Jest can be used without Enzyme, and snapshots can be created and tested perfectly fine. But the Enzyme adds additional functionality to it.


1 Answers

Yes this can be tricky. The issue is two-fold:

  1. firing a click event means you need to make your test an async function
  2. the menu items are not in your wrapper element <AppMenu /> - as you noted they are elsewhere in the DOM.

For the menu items, you'll need to locate them where they actually are. The button that opens the menu is in your wrapper element, but the menu and the menu items won't be, so you'll need to get the menu by role then you can get the items within menu by their text.

Here's an example of how I'd do this using React Testing Library.

import React, { ReactElement } from "react";
import { render, screen } from "@testing-library/react";
import AppMenu from "./AppMenu";
import { getByText, fireEvent, getByLabelText } from "@testing-library/react";


test("It renders some menu items", async () => {
  const { container } = render(<AppMenu />);
  const button = getByText(container, "Menu");

  fireEvent.click(button);

  const menuItem = screen.getByRole("menu");

  expect(await getByLabelText(menuItem, "Home")).toBeTruthy();
  expect(await getByLabelText(menuItem, "Sign in")).toBeTruthy();
});
like image 192
Carolyn Avatar answered Sep 25 '22 19:09

Carolyn