在这种情况下,react-testing-library 找不到元素

react-testing-library doesn't find element in this case

这是我的测试文件:

import List from '../List';
import { render, screen } from '@testing-library/react';

let data = ['one', 'two', 'three'];

describe('Component: List', () => {
  let { container } = render(<List items={data} />);

  test('(1) list length', () => {
    let li = container.querySelectorAll('li');
    expect(li.length).toBe(3);
  });

  test('(2) values in list', () => {
    expect(screen.getByText('one')).toBeInTheDocument();
    expect(screen.getByText('two')).toBeInTheDocument();
    expect(screen.getByText('three')).toBeInTheDocument();
  });
});

我写了这个组件:

const List = ({ items }) => {
  return (
    <ul>
      {
        items.map((item, index) => <li key={index}>{item}</li>)
      }
    </ul>
  )
}

export default List;

我的测试没有通过,我收到 (2) values in list 的错误:

TestingLibraryElementError: Unable to find an element with the text: one. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.

如果我评论或删除测试 (1) 时测试 (2) 通过:

describe('Component: List', () => {
  let { container } = render(<List items={data} />);

  // test('(1) list length', () => {
  //   let li = container.querySelectorAll('li');
  //   expect(li.length).toBe(3);
  // });

  test('(2) values in list', () => {
    expect(screen.getByText('one')).toBeInTheDocument();
    expect(screen.getByText('two')).toBeInTheDocument();
    expect(screen.getByText('three')).toBeInTheDocument();
  });
});

container对其他选择器有影响吗?

因为 RTL 将在每次测试后卸载使用 render 挂载的 React 树。这叫做cleanup.

Please note that this is done automatically if the testing framework you're using supports the afterEach global and it is injected to your testing environment (like mocha, Jest, and Jasmine). If not, you will need to do manual cleanups after each test.

您只在 describe 块中渲染一次 List 组件。当第一个测试完成后,RTL 将卸载 List 组件,“屏幕”中没有任何内容,这就是为什么你的第二个测试失败。

有两种方法可以解决这个问题:

1. Skipping Auto Cleanup

为了使这更容易,您也可以简单地导入 @testing-library/react/dont-cleanup-after-each which 将做同样的事情。只需确保在导入 @testing-library/react 之前执行此操作。您可以使用 Jest 的 setupFiles 配置来做到这一点:

如果您使用 jestjs 作为测试框架

{
  // ... other jest config
  setupFiles: ['@testing-library/react/dont-cleanup-after-each']
}

但是多个测试用例使用同一个组件,测试的执行顺序乱了,会导致测试相互影响。

简单的说:测试用例不是相互隔离的,依赖同一个测试数据.

2。为每个测试用例创建新的测试数据和测试替身,隔离测试。

import List from './';
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import React from 'react';

let data = ['one', 'two', 'three'];

describe('Component: List', () => {
  // let { container } = render(<List items={data} />);

  test('(1) list length', () => {
    let { container } = render(<List items={data} />);
    let li = container.querySelectorAll('li');
    expect(li.length).toBe(3);
  });

  test('(2) values in list', () => {
    render(<List items={data} />);
    expect(screen.getByText('one')).toBeInTheDocument();
    expect(screen.getByText('two')).toBeInTheDocument();
    expect(screen.getByText('three')).toBeInTheDocument();
  });
});

或者,render beforeEach() 挂钩中的组件。

更新:RTL 清理如何工作?

并回复您的评论:清理是否取决于每个 test() 或 it() 块?

每次使用render函数将组件挂载到容器中,mountedContainers集会添加组件容器。

mountedContainers 设置在模块范围内定义。

每次测试后,cleanup 函数将从容器中卸载组件,从文档中删除容器,并从 mountedContainers 集合中删除容器。

所以你在describe函数体中渲染组件,describe函数在整个测试过程中只执行一次。 mountedContainers 添加您的组件一次,当执行第一个测试用例时,mountedContainers set 将删除它。然后“屏幕”什么也没有,第二次测试失败