使用Jest、RTL测试React+Redux Toolkit时mock useAppSelector报错求助
问题描述
已安装React Redux Toolkit,使用Jest进行测试,需求是:当isOpen标识为true时,验证App组件是否渲染模态窗口,且仅mock所需的store部分而非整个store。
App组件代码
function App() { const { isOpen: isPostModalWindowOpen } = useAppSelector(selectPostModal) return ( <div className="app-container" id="app-container"> {isPostModalWindowOpen && <PostModalWindow />} <Posts /> <PageLoader /> <Alert /> </div> ) } export default App
测试文件代码
import App from "../App" import { PostModalWindow } from "../components/PostModalWindow/PostModalWindow" import { selectPostModal } from "../redux/postModal" import { Alert } from "../components/Alert/Alert" import { PageLoader } from "../components/PageLoader/PageLoader" import { Posts } from "../components/Posts/Posts" import { useAppSelector } from "../redux/hooks" import { render } from "@testing-library/react" import { RootState } from "../redux/store" import { clone } from "ramda" jest.mock("../App", () => ({ ...jest.requireActual("../App"), App: jest.fn(), })) jest.mock("../components/PostModalWindow/PostModalWindow", () => ({ ...jest.requireActual("../components/PostModalWindow/PostModalWindow"), PostModalWindow: jest.fn(), })) jest.mock("../redux/hooks", () => ({ ...jest.requireActual("../redux/hooks"), useAppSelector: jest.fn(), })) jest.mock("../redux/postModal", () => ({ ...jest.requireActual("../redux/postModal"), selectPostModal: jest.fn(), })) jest.mock("../components/Alert/Alert", () => ({ ...jest.requireActual("../components/Alert/Alert"), Alert: jest.fn(), })) jest.mock("../components/PageLoader/PageLoader", () => ({ ...jest.requireActual("../components/PageLoader/PageLoader"), PageLoader: jest.fn(), })) jest.mock("../components/Posts/Posts", () => ({ ...jest.requireActual("../components/Posts/Posts"), Posts: jest.fn(), })) describe("Test <App />", () => { beforeEach(() => { jest.clearAllMocks() const initialState = { postModal: { isOpen: true, }, } as RootState let currentState: RootState currentState = clone(initialState) jest.mocked(useAppSelector).mockImplementation((fn) => fn(currentState)) }) it("show modal window if isOpen is true", () => { const { container } = render(<App />) expect(container).toMatchSnapshot() }) })
报错信息
TypeError: Cannot destructure property 'isOpen' of '(0 , hooks_1.useAppSelector)(...)' as it is undefined
已确认currentState返回{postModal: {isOpen: true}},但仍出现上述错误。
问题原因与解决方法
错误原因
- 错误mock了被测试的App组件:你通过
jest.mock("../App")把App组件替换成了mock函数,导致渲染时根本不会执行真实的App组件逻辑,完全偏离测试目标。 - mock的选择器无返回值:你mock了
selectPostModal但未设置返回值,当useAppSelector调用这个mock函数时,它默认返回undefined,解构isOpen自然会报错。
修正后的测试文件
import App from "../App" import { PostModalWindow } from "../components/PostModalWindow/PostModalWindow" import { Alert } from "../components/Alert/Alert" import { PageLoader } from "../components/PageLoader/PageLoader" import { Posts } from "../components/Posts/Posts" import { useAppSelector } from "../redux/hooks" import { render, screen } from "@testing-library/react" import { RootState } from "../redux/store" // 仅mock子组件为占位元素,避免子组件逻辑干扰测试 jest.mock("../components/PostModalWindow/PostModalWindow", () => ({ PostModalWindow: jest.fn(() => <div data-testid="post-modal" />), })) jest.mock("../components/Alert/Alert", () => ({ Alert: jest.fn(() => <div data-testid="alert" />), })) jest.mock("../components/PageLoader/PageLoader", () => ({ PageLoader: jest.fn(() => <div data-testid="page-loader" />), })) jest.mock("../components/Posts/Posts", () => ({ Posts: jest.fn(() => <div data-testid="posts" />), })) // mock useAppSelector,模拟store逻辑 jest.mock("../redux/hooks", () => ({ useAppSelector: jest.fn(), })) describe("Test <App />", () => { beforeEach(() => { jest.clearAllMocks() // 让useAppSelector执行传入的选择器函数,返回模拟的state结果 jest.mocked(useAppSelector).mockImplementation((selector) => { const mockState = { postModal: { isOpen: true, }, } as RootState return selector(mockState) }) }) it("当isOpen为true时显示模态窗口", () => { render(<App />) // 验证模态窗口组件被渲染 expect(PostModalWindow).toHaveBeenCalledTimes(1) // 或者通过testid直接查找元素(更直观) expect(screen.getByTestId("post-modal")).toBeInTheDocument() }) })
关键修正点
- 删除对App组件的mock:必须渲染真实的App组件才能测试其逻辑。
- 保留子组件的mock:将子组件mock成简单的占位元素,既可以验证是否被渲染,又能避免子组件的内部逻辑影响测试。
- 正确mock useAppSelector:让mock的
useAppSelector接收选择器函数后,传入我们模拟的state并执行,确保返回正确的{isOpen: true}对象,解决解构报错问题。 - 改用更直观的断言方式:直接验证模态窗口组件是否被调用,或通过
testid查找元素,比快照测试更清晰。
内容的提问来源于stack exchange,提问作者George Tsintsadze
相关产品推荐
相关产品推荐

