React、Jest 和 Material-UI:如何测试在模态或弹出窗口中呈现的内容

IT技术 reactjs testing material-ui jestjs enzyme
2021-05-12 22:05:03

有一些 material-ui 组件不会在其父组件放置组件的同一位置呈现其结果。其中,我们有DialogMenu等等。

这使得在 jest.js 包装器中测试它们的内容是否存在显然是不可能的,并且其中安装了一些父组件。

例如给定以下组件:

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>
    );
  }
}

即使它应该直观地工作,这个测试也会失败:

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

这是 Jest 的失败输出:

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>

如您所见,就像渲染的所有内容都是<Button>. 事实上,当您在浏览器中呈现上述组件,并展开菜单并检查它的菜单项元素时,它们会在 DOM 中的其他地方呈现,而不是在按钮出现的位置内甚至附近。它们实际上是在<body><div data-mui-portal="true"> ... </div>文档<body>元素正下方的div 中呈现

那么如何测试这个菜单内容呢?

2个回答

是的,这可能很棘手。问题有两方面:

  1. 触发点击事件意味着您需要使您的测试成为异步函数
  2. 菜单项不在您的包装元素中<AppMenu />- 正如您所指出的,它们在 DOM 中的其他地方。

对于菜单项,您需要将它们定位到它们实际所在的位置。打开菜单的按钮位于您的包装元素中,但菜单和菜单项不会,因此您需要按角色获取菜单,然后您可以通过文本获取菜单中的项目。

这是我如何使用 React 测试库执行此操作的示例。

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();
});

Menu状态更改之前不会呈现 ,因此您可以模拟对 的单击Button,让其处理程序setState,触发重新呈现,并找到特定的MenuItem.

此外,这可能无需完全安装即可完成:

it('renders some menu items', () => {
  const wrapper = shallow(<AppMenu />);

  // find the Menu Button
  const button = wrapper.findWhere(node => node.is(Button) && n.prop('children') === 'Menu');

  // simulate a click event so that state is changed
  button.simulate('click');

  // find the Home MenuItem
  const menuItem = wrapper.findWhere(node => node.is(MenuItem) && n.prop('label') === 'Home');

  // make sure it was rendered
  expect(menuItem.exists()).toBe(true);
});