使用React Testing Library如何获取HTML元素?

98

我正在使用React Testing Library中的getByTestId函数:

const button = wrapper.getByTestId("button");
expect(heading.textContent).toBe("something");

是否有可能/建议搜索HTML元素?就像这样:

const button = wrapper.getByHTML("button");
const heading = wrapper.getByHTML("h1");
4个回答

170

我不确定在这种情况下wrapper是什么。但是为了回答你的两个问题:是的,可以通过HTML元素实现,但并不推荐。

以下是如何实现:

// Possible but not advisable
const { container } = render(<MyComponent />)
// `container` is just a DOM node
const button = container.querySelector('button')

由于您得到的是DOM节点,因此可以使用所有常规的DOM API,例如querySelector

那么,为什么这不可取呢? react-testing-library的一个重要卖点是您像用户一样测试组件。这意味着不依赖于实现细节。例如,您无法直接访问组件状态。

以这种方式编写测试有点困难,但允许您编写更健壮的测试。

在您的情况下,我会认为底层HTML是一项实现细节。如果您更改HTML结构,使h1成为h2div,测试将会失败。相反,如果通过文本查看这些元素,则标记变得无关紧要。

在某些情况下,普通查询助手可能不足够。对于这些事件,您可以使用data-testid并使用getByTestId


28
这确实使得某些测试变得更加困难。比如说,当页面加载时,我有一个加载器(loader),我想确保它在数据表(table)出现之前就渲染出来。这个旋转图标(spinner)并没有与其相关联的任何文本信息。 - Gilbert Nwaiwu
11
当HTML元素标签恰好是你希望防止回归的对象时怎么办?也许出于可访问性考虑,您想要确保它是h2,并且测试应该失败。 - Pure Function
然后测试它是否为 h2,但通常这是例外而不是规则。 - Gio Polvara
8
我不理解的是为什么React测试框架不支持 getByDataAttribute("data-custom-value") 方法。这些并不是实现细节,它们可以存在于任何元素上,并且可以更改而不会破坏测试。为什么要将事情限制在特定于测试的数据属性上呢? - Jeremy
因为大多数情况下,这不是您想要测试的内容。您仍然可以通过RTL进行测试,但对于大多数应用程序来说,这不是最佳方法。 - Gio Polvara
显示剩余2条评论

37

根据您要查询的元素类型,您可能还会发现byRole API很有用:

https://testing-library.com/docs/queries/byrole/

例如,level对我来说特别有用,可用于测试默认的<h1>元素是否正确地被覆盖:

it('correctly renders override header level', () => {
  const { getByRole } = render(<Heading overrideHeadingLevel="h6" />)

  expect(getByRole('heading', { level: 6 })).toBeInTheDocument()
})

当使用React Testing Library时,肯定的是,尽可能使用*ByRole查询是更符合惯用法的解决方案。 - juliomalves
+1 这个答案比被接受的那个好多了——它测试了真正重要且被屏幕阅读器、网络爬虫等捕捉到的语义,而另一个则测试了不重要的实现细节。getByRole('button')将匹配<button><a role="button">,上面例子将匹配<h6><a role="heading" aria-level="6">——这很好,因为关心这些角色的工具(如果构建得正确…)将把它们视为相同。 - user56reinstatemonica8

5

另一个可能的解决方案是

考虑渲染您的组件。

render(<ReactComponent />);

const button = screen.getByText((content, element) => element.tagName.toLowerCase() === 'button');

如果您有多个按钮,请使用getAllByText并引用您需要选择的目标元素。


1
如前所述,建议使用行为中心的选择器(aria、文本等),但我遇到了一个需要按标记名称查询的情况(我有一个包含段落标记的 HTML 字符串,并希望在组件中截断它以查看 HTML 标记是否保持不变 - 而 getByRole('paragraph') 不起作用)。
话虽如此,我想展示如何为这种情况创建自定义查询,而不是使用 container.querySelector。虽然对于大多数情况来说这样做没问题,但自定义查询将允许更多地控制当找不到、找到过多等情况时该怎么做,还可以自定义错误消息。 文档仅提供了基于属性的自定义查询示例,因此我们可以这样做:
import { queryHelpers } from '@testing-library/dom';
import { queries as libraryQueries } from '@testing-library/react';

function getAllByTagName(
  container: HTMLElement,
  tagName: keyof JSX.IntrinsicElements,
) {
  return Array.from(container.querySelectorAll<HTMLElement>(tagName));
}

function getByTagName(
  container: HTMLElement,
  tagName: keyof JSX.IntrinsicElements,
) {
  const result = getAllByTagName(container, tagName);

  if (result.length > 1) {
    throw queryHelpers.getElementError(
      `Found multiple elements with the tag ${tagName}`,
      container,
    );
  }
  return result[0] || null;
}

export const queries = {
  ...libraryQueries,
  getAllByTagName,
  getByTagName,
};


现在我们可以在渲染方法的选项中使用这些查询。
import { queries } from '../helpers/queries'

// it('...')
const { getAllByTagName } = render(<MyComponent />, { queries } )
const paragraphs = getAllByTagName('p')

查询也可以通过自定义渲染方法全局添加(其中可能包括提供程序等)。
import { render } from '@testing-library/react';
import { queries } from '../helpers/queries'

function renderWithProviders(node: JSX.Element) {
  return render(
    <SomeProvider>{node}</SomeProvider>, { queries }
  )
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接