如何使用 React 测试库通过包含 html 标签的文本字符串进行查询?

Bea*_*ith 34 unit-testing reactjs jestjs react-testing-library

当前工作解决方案

使用这个 html:

<p data-testid="foo">Name: <strong>Bob</strong> <em>(special guest)</em></p>
Run Code Online (Sandbox Code Playgroud)

我可以使用React 测试库 getByTestId方法来查找textContent

expect(getByTestId('foo').textContent).toEqual('Name: Bob (special guest)')

Run Code Online (Sandbox Code Playgroud)

有没有更好的办法?

我想简单地使用这个 html:

expect(getByTestId('foo').textContent).toEqual('Name: Bob (special guest)')

Run Code Online (Sandbox Code Playgroud)

并像这样使用React Testing LibrarygetByText方法:

expect(getByText('Name: Bob (special guest)')).toBeTruthy()
Run Code Online (Sandbox Code Playgroud)

但这不起作用。

所以,这个问题……

有没有一种更简单的方法来使用 React 测试库来查找带标签的文本内容字符串?

ken*_*ley 58

对于子字符串匹配,您可以传递{ exact: false }

https://testing-library.com/docs/dom-testing-library/api-queries#textmatch

const el = getByText('Name:', { exact: false })
expect(el.textContent).toEqual('Name: Bob (special guest)');
Run Code Online (Sandbox Code Playgroud)

  • 这是迄今为止最简单的解决方案。在这种情况下,类似于:expect(getByText('Name:', {exact: false }).textContent).toEqual('Name: Bob (special guest)'); (3认同)

Bea*_*ith 29

更新 2

使用了很多次后,我创建了一个助手。以下是使用此帮助程序的示例测试。

测试助手:

// withMarkup.ts
import { MatcherFunction } from '@testing-library/react'

type Query = (f: MatcherFunction) => HTMLElement

const withMarkup = (query: Query) => (text: string): HTMLElement =>
  query((content: string, node: HTMLElement) => {
    const hasText = (node: HTMLElement) => node.textContent === text
    const childrenDontHaveText = Array.from(node.children).every(
      child => !hasText(child as HTMLElement)
    )
    return hasText(node) && childrenDontHaveText
  })

export default withMarkup
Run Code Online (Sandbox Code Playgroud)

测试:

// app.test.tsx
import { render } from '@testing-library/react'
import App from './App'
import withMarkup from '../test/helpers/withMarkup'

it('tests foo and bar', () => {
  const { getByText } = render(<App />)
  const getByTextWithMarkup = withMarkup(getByText)
  getByTextWithMarkup('Name: Bob (special guest)')
})
Run Code Online (Sandbox Code Playgroud)

更新 1

这是一个getByTextWithMarkup创建新匹配器的示例。请注意,此函数getByText在测试中扩展,因此必须在那里定义。(当然可以更新该函数以接受getByText作为参数。)

import { render } from "@testing-library/react";
import "jest-dom/extend-expect";

test("pass functions to matchers", () => {
  const Hello = () => (
    <div>
      Hello <span>world</span>
    </div>
  );
  const { getByText } = render(<Hello />);

  const getByTextWithMarkup = (text: string) => {
    getByText((content, node) => {
      const hasText = (node: HTMLElement) => node.textContent === text
      const childrenDontHaveText = Array.from(node.children).every(
        child => !hasText(child as HTMLElement)
      )
      return hasText(node) && childrenDontHaveText
    })
  }

  getByTextWithMarkup('Hello world')
Run Code Online (Sandbox Code Playgroud)

以下是来自Giorgio Polvara 博客的关于测试库五件事中的第四件事的可靠答案:


查询也接受函数

您可能已经看到过这样的错误:

无法找到带有文本的元素:Hello world。这可能是因为文本被多个元素分解了。在这种情况下,您可以为您的文本匹配器提供一个功能,使您的匹配器更加灵活。

通常,发生这种情况是因为您的 HTML 如下所示:

// withMarkup.ts
import { MatcherFunction } from '@testing-library/react'

type Query = (f: MatcherFunction) => HTMLElement

const withMarkup = (query: Query) => (text: string): HTMLElement =>
  query((content: string, node: HTMLElement) => {
    const hasText = (node: HTMLElement) => node.textContent === text
    const childrenDontHaveText = Array.from(node.children).every(
      child => !hasText(child as HTMLElement)
    )
    return hasText(node) && childrenDontHaveText
  })

export default withMarkup
Run Code Online (Sandbox Code Playgroud)

解决方案包含在错误消息中:“[...] 您可以为您的文本匹配器提供一个函数 [...]”。

那是怎么回事?结果证明匹配器接受字符串、正则表达式或函数。

为您渲染的每个节点调用该函数。它接收两个参数:节点的内容和节点本身。您所要做的就是根据节点是否是您想要的节点返回 true 或 false。

一个例子将阐明它:

import { render } from "@testing-library/react";
import "jest-dom/extend-expect";

test("pass functions to matchers", () => {
  const Hello = () => (
    <div>
      Hello <span>world</span>
    </div>
  );
  const { getByText } = render(<Hello />);

  // These won't match
  // getByText("Hello world");
  // getByText(/Hello world/);

  getByText((content, node) => {
    const hasText = node => node.textContent === "Hello world";
    const nodeHasText = hasText(node);
    const childrenDontHaveText = Array.from(node.children).every(
      child => !hasText(child)
    );

    return nodeHasText && childrenDontHaveText;
  });
});
Run Code Online (Sandbox Code Playgroud)

我们忽略了这个content参数,因为在这种情况下,它要么是“Hello”、“world”,要么是一个空字符串。

我们正在检查的是当前节点是否具有正确的textContenthasText是一个小辅助函数来做到这一点。我宣布它是为了保持清洁。

但这还不是全部。我们div并不是唯一一个包含我们正在寻找的文本的节点。例如,body在这种情况下具有相同的文本。为了避免返回比需要更多的节点,我们确保没有任何子节点与其父节点具有相同的文本。通过这种方式,我们确保我们返回的节点是最小的——换句话说,靠近 DOM 树底部的节点。


阅读关于测试库你(可能)不知道的其余五件事

  • 考虑一下 RTL 不会查找子级的子级,因为否则 getAllByText(`&lt;div&gt;&lt;div&gt;Hello&lt;/div&gt;&lt;/div&gt;`, 'Hello') 将返回两个结果。这说得通 (2认同)
  • 很好的答案。我还必须捕获“getByText”抛出的异常,并用“text”重新抛出另一条消息,因为使用自定义匹配器时它不包含在错误消息中。我认为在“@testing-library”中默认包含这个助手会很棒。 (2认同)

Cor*_*use 9

现有的答案已经过时。新的*ByRole查询支持:

getByRole('button', {name: 'Bob (special guest)'})
Run Code Online (Sandbox Code Playgroud)

  • 我想知道在OP的背景下,没有明显的作用。除非 `p` 有默认角色? (5认同)
  • @CoryHouse - 如果没有具有可访问角色的元素并且只有这样的元素怎么办: &lt;div&gt;&lt;b&gt;[AL]&lt;/b&gt; Albania&lt;/div&gt; &lt;div&gt;&lt;b&gt;[DZ]&lt;/b&gt;阿尔及利亚&lt;/div&gt; 如何通过文本查询第一个元素? (2认同)

for*_*nos 8

如果你testing-library/jest-dom在你的项目中使用。您也可以使用toHaveTextContent.

expect(getByTestId('foo')).toHaveTextContent('Name: Bob (special guest)')
Run Code Online (Sandbox Code Playgroud)

如果您需要部分匹配,您还可以使用正则表达式搜索模式

expect(getByTestId('foo')).toHaveTextContent(/Name: Bob/)
Run Code Online (Sandbox Code Playgroud)

这是的链接


Gio*_*Gpx 5

更新

下面的解决方案有效,但在某些情况下,它可能会返回多个结果。这是正确的实现:

getByText((_, node) => {
  const hasText = node => node.textContent === "Name: Bob (special guest)";
  const nodeHasText = hasText(node);
  const childrenDontHaveText = Array.from(node.children).every(
    child => !hasText(child)
  );

  return nodeHasText && childrenDontHaveText;
});
Run Code Online (Sandbox Code Playgroud)

您可以将方法传递给getbyText

getByText((_, node) => {
  const hasText = node => node.textContent === "Name: Bob (special guest)";
  const nodeHasText = hasText(node);
  const childrenDontHaveText = Array.from(node.children).every(
    child => !hasText(child)
  );

  return nodeHasText && childrenDontHaveText;
});
Run Code Online (Sandbox Code Playgroud)

您可以将代码放入辅助函数中,这样您就不必一直输入它:

getByText((_, node) => node.textContent === 'Name: Bob (special guest)')
Run Code Online (Sandbox Code Playgroud)

  • 同意,解决方案实际上取自我的博客:D (2认同)