You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用React Testing Library+Jest测试React下拉组件?报错求助

解决React Testing Library测试下拉组件的问题

错误原因分析

你遇到的报错是因为**toHaveBeenCalledTimes只能用于断言mock函数的调用次数**,但你现在把DOM按钮元素(dropdwonBtn)传入了这个断言,所以Jest报错提示你传入的不是函数。

另外你的测试代码还存在两个问题:

  • 没有用Router包裹Dropdown组件,组件内的Link会因为缺少路由上下文而报错
  • 测试逻辑偏离了RTL的核心原则:应该测试用户能看到的UI变化,而非内部函数调用

修正后的测试方案

1. 修复路由上下文问题

测试时需要用MemoryRouter(更适合测试场景)包裹组件,提供路由上下文:

2. 测试下拉菜单的显示/隐藏行为

RTL推荐测试用户可见的结果,所以点击按钮后,应该断言下拉项容器是否出现:

import { render, screen, fireEvent } from "@testing-library/react";
import { MemoryRouter } from "react-router-dom";
import Dropdown from "./Dropdown";

const testOptions = [
    { value: "item1", label: "item 1", to: "/about" },
    { value: "item2", label: "item 2", to: "/contact" }, // 修正重复value,避免React key警告
];

it("displays dropdown items when button is clicked", () => {
    render(
        <MemoryRouter>
            <Dropdown placeholder="placeholder name" options={testOptions} />
        </MemoryRouter>
    );

    const dropdownBtn = screen.getByTestId("dropdownBtn");
    // 初始状态下拉容器不存在
    expect(screen.queryByTestId("dropdownItems")).not.toBeInTheDocument();

    fireEvent.click(dropdownBtn);

    // 断言下拉容器及选项可见
    expect(screen.getByTestId("dropdownItems")).toBeInTheDocument();
    expect(screen.getByText("item 1")).toBeInTheDocument();
});

it("hides dropdown items when button is clicked again", () => {
    render(
        <MemoryRouter>
            <Dropdown placeholder="placeholder name" options={testOptions} />
        </MemoryRouter>
    );

    const dropdownBtn = screen.getByTestId("dropdownBtn");
    fireEvent.click(dropdownBtn);
    expect(screen.getByTestId("dropdownItems")).toBeInTheDocument();

    // 再次点击关闭下拉
    fireEvent.click(dropdownBtn);
    expect(screen.queryByTestId("dropdownItems")).not.toBeInTheDocument();
});

3. 若需测试点击事件函数的调用次数(可选)

如果你确实需要验证内部点击函数的调用次数,可将函数作为props传入组件,再用mock函数测试:

修改Dropdown组件,支持外部传入点击回调:

import { useState } from "react";
import { Link } from "react-router-dom";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faChevronDown } from "@fortawesome/free-solid-svg-icons";

export default function Dropdown({ placeholder, options, onDropdownClick }) {
    const [showDropdown, setShowDropdown] = useState(false);

    const handleDropdownClick = () => {
        setShowDropdown(prev => !prev);
        onDropdownClick?.();
    };

    return (
        <div>
            <button data-testid="dropdownBtn" className="dropdownBtn" onClick={handleDropdownClick}>
                {placeholder}
                <span>
                    <FontAwesomeIcon icon={faChevronDown} />
                </span>
            </button>
            {showDropdown && (
                <div data-testid="dropdownItems" className="dropdownItems">
                    {options.map((option) => (
                        <Link to={option.to} key={option.value}>
                            {option.label}
                        </Link>
                    ))}
                </div>
            )}
        </div>
    );
}

测试mock函数的调用次数:

it("triggers onDropdownClick when button is clicked", () => {
    const mockClickHandler = jest.fn();
    render(
        <MemoryRouter>
            <Dropdown 
                placeholder="placeholder name" 
                options={testOptions} 
                onDropdownClick={mockClickHandler} 
            />
        </MemoryRouter>
    );

    const dropdownBtn = screen.getByTestId("dropdownBtn");
    fireEvent.click(dropdownBtn);

    expect(mockClickHandler).toHaveBeenCalledTimes(1);
});

额外注意事项

  • 优先使用screen.getByText而非data-testid查询元素,更贴近用户实际操作逻辑
  • 使用queryBy*方法查询可能不存在的元素,getBy*会在元素不存在时直接抛出错误
  • 确保组件内的列表项key唯一,避免React警告

内容的提问来源于stack exchange,提问作者Tapesh Patel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 05:55:15