如何解决React Testing Library测试useEffect触发组件更新失败问题?
问题根因
- 错误Mock了
React.useState:你在beforeEach里把React.useState替换成了返回固定productsList和无效setter的假实现,组件内部调用setproductsList根本无法更新状态,也不会触发重渲染。 - 错误覆盖了按钮点击事件:你自定义了全局
handleSizeClickmock,还手动给XL按钮的onclick赋值为这个空mock,组件原本的handleSizeClick逻辑完全没有执行,后续的navigate、参数更新、过滤逻辑都不会触发。 useLocation的Mock是静态的:就算navigate调用了,useLocation返回的search值永远是空,监听queryObj.size的useEffect检测不到参数变化,不会执行过滤逻辑。- 断言时机错误:过滤逻辑在useEffect中异步执行,你同步断言的时候状态还没更新。
修复方案
- 先删除所有无效的错误Mock:移除自定义的
handleSizeClick变量、移除React.useState的Mock、不要手动覆盖按钮的onclick属性。 - 正确处理路由Mock:推荐使用react-router-dom自带的
MemoryRouter包裹测试组件,无需手动mockuseLocation和useNavigate,更贴近真实运行场景。 - 等待异步状态更新后再断言,使用
waitFor或者findBy*查询方法等待结果更新。
修改后的测试代码
import React from "react"; import { render, screen, fireEvent, waitFor } from "@testing-library/react"; import "@testing-library/jest-dom"; import Content from "./Content"; import httpService from "../../services/httpService"; import { productsList } from "../../mockData/productsList"; import { MemoryRouter } from "react-router-dom"; const mockData = [...productsList]; jest.mock("../../services/httpService"); const mockSelector = jest.fn(); const mockDispatch = jest.fn(); jest.mock("react-redux", () => ({ ...jest.requireActual("react-redux"), useSelector: () => mockSelector, useDispatch: () => mockDispatch })); describe("product should be render in Content component correctly", () => { beforeAll(() => { httpService.get.mockImplementation(() => Promise.resolve(mockData)); }); beforeEach(() => { jest.spyOn(window, "alert").mockImplementation(() => {}); }); afterEach(() => { jest.clearAllMocks(); }); it("should render products at the content", async () => { const url = "/products"; render( <MemoryRouter initialEntries={[url]}> <Content /> </MemoryRouter> ); expect(httpService.get).toHaveBeenCalledTimes(1); expect(httpService.get).toHaveBeenCalledWith(url); expect(screen.getByText("Sort by")).toBeInTheDocument(); const countText = await screen.findByText(/found/g); expect(countText.textContent).toBe("10 products found"); }); it("the handleSizeClick is call when XL button is clicked", async () => { render( <MemoryRouter initialEntries={["/products"]}> <Content /> </MemoryRouter> ); const XLButton = await screen.findByTestId("XL"); fireEvent.click(XLButton); await waitFor(() => { expect(window.location.search).toContain("size=XL"); }) }); it("the productsList is filtered when XL button is clicked", async () => { render( <MemoryRouter initialEntries={["/products"]}> <Content /> </MemoryRouter> ); const XLButton = await screen.findByTestId("XL"); fireEvent.click(XLButton); await waitFor(() => { const countText = screen.getByText(/found/g); expect(countText.textContent).toBe("5 products found"); }) }); });
内容的提问来源于stack exchange,提问作者BenHuang923
相关产品推荐
相关产品推荐

