React Testing Library中点击NavLink时fireEvent.click()未触发history.push()
核心问题分析
- React Router v6+ API变更:
MemoryRouter不再支持传入自定义history实例,直接传入会被忽略,导致你mock的history.push永远不会被调用。 - 错误的handleClick mock方式:直接给DOM元素赋值
card.handleClick = jest.fn()无效,React组件的点击处理函数是内部绑定的,并非DOM元素的属性。 - 事件触发的合理性:仅添加
bubbles: true不足以模拟真实用户交互,且路由跳转可能存在异步逻辑,需要等待断言。
修复后的测试代码
import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import { Router } from "react-router-dom"; // 替换为Router组件 import { createMemoryHistory } from 'history'; import Card from "./Card"; // 推荐用userEvent模拟真实用户行为 import userEvent from '@testing-library/user-event'; test('点击卡片跳转到newgame/gameName路由', async () => { const history = createMemoryHistory(); // 对history.push进行mock const mockPush = jest.spyOn(history, 'push').mockImplementation(() => {}); // 使用Router组件传入自定义history实例 render( <Router location={history.location} navigator={history}> <Card element={{name: "3ts", title: "3Ts", description: "Tic Tac Toe"}}/> </Router> ); const cardLink = await screen.findByTestId('card-link'); expect(cardLink).toHaveAttribute('href', '/newGame/3ts'); // 模拟真实用户点击 await userEvent.click(cardLink); // 等待异步路由跳转完成后断言 await waitFor(() => { expect(mockPush).toHaveBeenCalledWith('/newGame/3ts'); }); });
额外说明
- 如果
Card组件有自定义handleClick逻辑:- 若
handleClick是组件props,渲染时直接传入mock函数:const mockHandleClick = jest.fn(); render(<Card handleClick={mockHandleClick} ... />) - 若为组件私有函数,应通过断言其产生的副作用(如状态变化、API调用)来测试,而非直接mock私有函数。
- 若
userEvent比fireEvent更贴近真实用户交互,会触发mouseDown、mouseUp、click等一系列完整事件链,测试稳定性更高。
内容的提问来源于stack exchange,提问作者Mike Czak
相关产品推荐
相关产品推荐

