返回

Ant Design v4 与 React Testing Library 组件测试:Select & AutoComplete 解决方案

javascript

解决 Ant Design v4 与 React Testing Library 在 Select 和 Autocomplete 组件上的测试问题

Ant Design v4 引入了一些架构上的变动,这些变动会影响到使用 React Testing Library 进行组件测试的方式。 特别是在 SelectAutoComplete 组件上,开发者可能会遇到点击后下拉选项不出现,导致测试失败的情况。这并非代码本身的问题,而是因为 React Testing Library 在默认情况下可能无法完全捕获到 Ant Design v4 这些组件动态渲染的行为。 本文将针对这一问题分析原因并提供解决方案。

问题分析

问题的根本原因在于 Ant Design v4 对于某些组件(比如 Select, AutoComplete, Tooltip等)的下拉菜单或者弹窗采用的是 portal 的机制渲染,而非直接渲染在当前 DOM 树中。 而 React Testing Library 的 JSDOM 模拟环境默认不跟踪这种 portal 渲染行为,所以导致测试中我们无法查找到期望出现的下拉菜单 DOM 结构。也就是说,虽然我们在测试代码中模拟了点击事件,组件也的确执行了打开下拉的操作,但在 JSDOM 虚拟环境中并没有新的 DOM 元素生成并加入到我们所监控的测试范围内。

解决方案

针对上述问题,以下提供几种常见的解决方案。

1. waitFor 等待机制

最直接的方式是在点击操作之后使用 waitFor API 进行异步等待,让组件有足够时间完成 portal 元素的渲染。 waitFor 函数会循环检测我们设定的期望元素是否存在于文档中,当超时或找到元素时,返回对应的 Promise 状态。这种方案最通用也易于理解。

代码示例:

import "@testing-library/jest-dom/extend-expect";
import React from "react";
import { render, fireEvent, waitFor, getByText } from "@testing-library/react";
import App from "./App";

test("App Test", async () => {
    const { queryAllByText, getByText, container } = render(<App />);

    expect(queryAllByText("Lucy").length).toBe(1);
    expect(queryAllByText("Jack").length).toBe(0);
    fireEvent.click(getByText("Lucy"));
     // 必须使用 await  等待 waitFor 执行完成,因为他是异步的
    await waitFor(() => {
      expect(queryAllByText("Jack").length).toBe(1);
    });
  });

步骤:

  1. 安装 @testing-library/react
  2. 使用 render 渲染你的组件。
  3. 使用 fireEvent.click 触发 Select 组件的点击事件,使其下拉列表展开。
  4. 使用 waitFor 函数来等待目标下拉选项(例如 "Jack")的出现。 请注意,必须使用 async/await 保证 waitFor 函数执行完毕。
  5. 使用 expect 进行断言。

这种方式保证了代码在目标选项渲染完成之后才进行断言,从而解决下拉菜单 DOM 未渲染导致的测试失败。

2. 指定 container 参数查找 portal元素

React Testing Library 中的 render 函数可以接受一个container 参数,这允许我们将组件渲染到一个指定的 DOM 元素中。默认情况下 React Testing Library 创建的 container 位于 document.body 的外部, 如果某些组件创建 portal 默认使用 document.body 作为锚点,这时我们可以利用该参数来指定 render 到一个已经挂载在 document.body 上的 div 中,然后通过 document.body 查询相关的元素。

代码示例:

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


test("App Test with document.body container", () => {
  const div = document.createElement('div')
  document.body.appendChild(div);
  const { queryAllByText, getByText } = render(<App />, { container: div });
    
    expect(queryAllByText("Lucy").length).toBe(1);
    expect(queryAllByText("Jack").length).toBe(0);
    fireEvent.click(getByText("Lucy"));
  // 直接通过 screen 查询 body 下的元素
    expect(screen.queryAllByText("Jack").length).toBe(1);
    document.body.removeChild(div); // Cleanup
  });

步骤:

  1. document.body 中创建 div 元素。
  2. 将 div 传递给 render 作为 container 参数。
  3. 使用 screen.queryAllByTextscreen.getByText 查询 portal 元素,而不是通过 container 实例查询。
  4. 组件测试完成之后将 div 从 document.body 移除。

此方案利用了 ReactDOM 的渲染特性,确保 portal 元素与测试代码处于同一作用域内。

3. 针对 Select 组件特定的测试方案

一些情况下 waitFor 依然无法满足需要,可能是因为 antd 的下拉元素有比较复杂的动态生成逻辑,并且会在某个状态发生之后才存在于页面,这种时候可以通过直接通过模拟 onChange 事件进行断言来避免操作 UI。
代码示例

import "@testing-library/jest-dom/extend-expect";
import React from "react";
import { render,  getByText } from "@testing-library/react";
import App from "./App";

test("App Test - programmatically select value", () => {
  const { container } = render(<App />);
  
  const selectElement = container.querySelector('.ant-select-selector');
   // 检查默认选中的是 lucy
  expect(getByText(container, 'Lucy')).toBeInTheDocument()
  const selectValue= 'jack';

  // 使用 ant design 自定义的 select event 来模拟选项选择
  fireEvent.mouseDown(selectElement, { target: { className: 'ant-select-selector' } });
  const optionElement = getByText(container, 'Jack');

  fireEvent.click(optionElement);
   // 检查组件状态
    expect(getByText(container, 'Jack')).toBeInTheDocument();
});

步骤:

  1. 使用 querySelector 选取 Antd Select 组件。
  2. 使用 fireEvent.mouseDown + fireEvent.click 触发 Select 选项选中逻辑。
  3. 使用 getByText 直接验证选项的值已经生效。

此方案规避了复杂的 UI 交互,更加关注组件内部状态和逻辑,是一种更加轻便高效的测试方案。

安全建议

  1. 避免使用硬编码的 timeoutwaitForwaitForNextUpdate 会更适合这种异步操作的断言。
  2. 对于复杂的组件交互,先进行小的测试单元,缩小错误范围,然后再构建更完整的测试用例。
  3. 测试应当覆盖组件的关键功能,关注用户视角。
  4. 合理使用debug 工具排查渲染和事件问题。

通过以上分析,在升级Ant Design v4时遇到React Testing Library 测试问题的开发者可以根据自己的实际情况选择合适的解决方案,确保组件的功能正常以及测试顺利运行。