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

React Testing Library中forEach点击不触发onClick,for循环却可行?

为何forEach遍历点击React组件元素时,Jest Mock回调调用次数为0?

我写了一个React函数组件Shop,它接收onClickMenu回调作为props,渲染成列表后每个列表项的onClick都绑定这个回调。用Jest Mock回调、userEvent做测试时发现:用forEach遍历点击所有元素,测试会失败,显示回调调用次数为0;但换成for循环或者手动逐个点击(加await),测试就能正常通过。

组件代码

function Shop(props) {
    const { onClickMenu } = props;

    const tabs = ['All', 'Boards', 'Boots', 'Bindings'];

    const renderedList = tabs.map((tab, i) => (
        <li key={i} onClick={onClickMenu}>
            {tab}
        </li>
    ));

    return <ul>{renderedList}</ul>;
}

失败的测试代码

test('calls onClickMenu correct number of times', async () => {
        const mockHandler = jest.fn();
        const user = userEvent.setup();

        render(<Shop onClickMenu={mockHandler} />);

        const menu = ['All', 'Boards', 'Boots', 'Bindings'];
        menu.forEach((tab) => {
            user.click(screen.getByText(tab));
        });

        expect(mockHandler).toHaveBeenCalledTimes(4);
});

测试失败结果

expect(jest.fn()).toHaveBeenCalledTimes(expected)

    Expected number of calls: 4
    Received number of calls: 0

可行的测试代码

// for循环方式
for (let i = 0; i < menu.length; i++) {
    await user.click(screen.getByText(menu[i]))
}

// 手动逐个点击方式
await user.click(screen.getByText('All'));
await user.click(screen.getByText('Boards'));
await user.click(screen.getByText('Boots'));
await user.click(screen.getByText('Bindings'));

原因解析

核心问题出在异步操作的处理:

  • userEvent.click()是异步函数,返回Promise对象,它模拟真实用户点击的完整流程(包括事件冒泡、状态更新等),需要等待其完成才能确保回调被触发。
  • forEach循环中,直接调用user.click()但没有加await,会导致所有点击操作都处于pending状态时,就直接执行了后面的expect断言。此时Jest的mock回调还没被任何点击触发,所以调用次数为0。
  • 而for循环或手动逐个点击时,用await等待每个user.click()完成,确保每一次点击的异步流程结束、回调被触发后,再进行下一次操作或断言,因此mock的调用次数能被正确统计。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 21:22:39