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

使用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}},但仍出现上述错误。


问题原因与解决方法

错误原因

  1. 错误mock了被测试的App组件:你通过jest.mock("../App")把App组件替换成了mock函数,导致渲染时根本不会执行真实的App组件逻辑,完全偏离测试目标。
  2. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 20:35:13