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

测试接入MobX的React/TS组件报Worker未定义错误如何解决

问题描述

我正在使用Jest和React Testing-Library测试以MobX作为状态管理器的React/Typescript组件。
我了解RTL官方文档提供了创建custom render用于测试的方案,但不清楚在MobX场景下该如何实现,测试时需要为组件包裹对应的上下文包装层。
目前我仅尝试完成组件的基础渲染,运行测试时出现Reference Error: Worker is not defined报错,测试代码如下:

test('AppRouter renders all routes and I can navigate to those pages', () => {
    render(
        <AppRouter />
    )
})

App Router 组件

import { BrowserRouter, Switch, Route, Redirect } from 'react-router-dom'
import { useState } from 'react'

import useLocalStorage from './hooks/useLocalStorage'
import * as Constants from './constants'

import Header from './layout/header/header'
import MainPage from './pages/mainPage/mainPage'
import PostPage from './pages/postPage/postPage'
import UserPage from './pages/userPage/userPage'
import LoginPage from './pages/loginPage/loginPage'
import SignupPage from './pages/signupPage/signupPage'
import NewPage from './pages/newPage/newPage'
import FeedbackPage from './pages/feedbackPage/feedbackPage'
import AdminPage from './pages/adminPage/adminPage'
import SettingPage from './pages/settingPage/settingPage'

import { WebContext } from './context/WebContext'
import Favicon from 'react-favicon'

const AppRouter = () => {
    const [adminCode, setAdminCode] = useLocalStorage('admin', '')
    const [isMenuOpen, setIsMenuOpen] = useState(false)
    const [page, setPage] = useState(Constants.Page.Home)

    return (
        <BrowserRouter>
            <div>
                <Favicon url={require('../public/favicon.ico')} />
                <WebContext.Provider
                    value={{
                        isMenuOpen,
                        setIsMenuOpen,
                        page,
                        setPage,
                        adminCode,
                        setAdminCode,
                    }}
                >
                    <Header />

                    <Switch>
                        <Route component={MainPage} path="/" exact={true} />
                        <Route component={PostPage} path="/post/:id" />
                        <Route component={UserPage} path="/user" />
                        <Route component={LoginPage} path="/login" />
                        <Route component={SignupPage} path="/signup" />
                        <Route component={NewPage} path="/new" />
                        <Route component={FeedbackPage} path="/feedback" />
                        <Route component={AdminPage} path="/admin" />
                        <Route component={SettingPage} path="/setting" />
                        <Route component={() => <Redirect to="/" />} />
                    </Switch>
                </WebContext.Provider>
            </div>
        </BrowserRouter>
    )
}

export default AppRouter

我怀疑AppRouter渲染的Header组件是报错来源,因为Header组件依赖MobX store。

Header 组件

import { useContext, useState } from 'react'
import { NavLink, useHistory, useLocation } from 'react-router-dom'
import { observer } from 'mobx-react-lite'

import { WebContext } from '../../context/WebContext'
import UnirepContext from '../../context/Unirep'
import UserContext from '../../context/User'

const Header = () => {
    const history = useHistory()
    const location = useLocation()
    const { isMenuOpen, setIsMenuOpen } = useContext(WebContext)
    const [searchInput, setSearchInput] = useState<string>('')
    const unirepConfig = useContext(UnirepContext)
    const userContext = useContext(UserContext)

    const gotoNewPage = () => {
        if (
            userContext.userState &&
            userContext.netReputation >= unirepConfig.postReputation
        ) {
            history.push(`/new`, { isConfirmed: true })
        }
    }

    const gotoUserPage = () => {
        history.push(`/user`, { isConfirmed: true })
    }

    const openMenu = () => {
        if (!isMenuOpen) {
            console.log('open menu!')
            setIsMenuOpen(true)
        }
    }

    const handleSearchInput = (event: any) => {
        console.log('search input : ' + event.target.value)
    }

    return (
        <header>
            <div className="navLinks">
                <NavLink to="/" className="link" activeClassName="active" exact>
                    <img
                        src={require('../../../public/images/unirep-title.svg')}
                    />
                </NavLink>
            </div>
            {userContext.userState ? (
                <div className="navButtons">
                    <div id="rep" onClick={gotoUserPage}>
                        <img
                            src={require('../../../public/images/lighting.svg')}
                        />
                        {userContext.netReputation}
                    </div>
                    <div
                        id="new"
                        className={
                            location.pathname === '/new'
                                ? 'navBtn chosen'
                                : 'navBtn'
                        }
                    >
                        <img
                            src={require('../../../public/images/newpost.svg')}
                            onClick={gotoNewPage}
                        />
                    </div>
                    <div
                        id="user"
                        className={
                            location.pathname === '/user'
                                ? 'navBtn chosen'
                                : 'navBtn'
                        }
                    >
                        <img
                            src={require('../../../public/images/user.svg')}
                            onClick={gotoUserPage}
                        />
                    </div>
                    <div className="navBtn">
                        <img
                            src={require('../../../public/images/menu.svg')}
                            onClick={openMenu}
                        />
                    </div>
                </div>
            ) : (
                <div className="navButtons">
                    <div
                        id="login"
                        className="whiteButton"
                        onClick={() => history.push('/login')}
                    >
                        Sign in
                    </div>
                    <div
                        id="join"
                        className="blackButton"
                        onClick={() => history.push('/signup')}
                    >
                        Join
                    </div>
                    <div id="menu" className="navBtn">
                        <img
                            src={require('../../../public/images/menu.svg')}
                            onClick={openMenu}
                        />
                    </div>
                </div>
            )}
        </header>
    )
}

export default observer(Header)

解决方案

ReferenceError: Worker is not defined报错和MobX无直接关联:Jest运行在Node环境,不存在浏览器原生的Web Worker API,项目依赖(大概率是Unirep SDK或react-favicon)调用了Worker导致报错;同时当前测试未注入Header组件依赖的UnirepContext、UserContext,即使解决Worker问题也会出现上下文缺失错误,按以下步骤修复即可:

  • 第一步:添加Worker全局Mock
    在Jest测试初始化文件(CRA项目默认为src/setupTests.ts,自定义配置可在jest.config.js中指定setupFilesAfterStage字段对应的文件)添加如下Mock代码:
class MockWorker {
  constructor(url: string) {
    this.url = url
  }
  url: string
  onmessage: (payload: any) => void = () => {}
  postMessage(payload: any) {
    this.onmessage({ data: null })
  }
  terminate() {}
}
(global as any).Worker = MockWorker
  • 第二步:封装适配全量上下文的自定义render方法
    参考RTL自定义渲染方案,将项目用到的所有Context(包括MobX相关的Context Provider)统一封装到测试渲染工具中,避免每个测试重复编写包裹层,代码示例:
// src/test-utils.tsx
import { render as originalRender, RenderOptions } from '@testing-library/react'
import { BrowserRouter } from 'react-router-dom'
import { ReactElement, ReactNode } from 'react'
import { WebContext } from './context/WebContext'
import UnirepContext from './context/Unirep'
import UserContext from './context/User'

// 定义各Context的默认Mock值,可根据业务自行补充字段
const defaultWebContext = {
  isMenuOpen: false,
  setIsMenuOpen: jest.fn(),
  page: 'Home',
  setPage: jest.fn(),
  adminCode: '',
  setAdminCode: jest.fn()
}
const defaultUnirepConfig = {
  postReputation: 10
}
const defaultUserContext = {
  userState: false,
  netReputation: 0
}

interface CustomRenderOptions extends Omit<RenderOptions, 'wrapper'> {
  webContext?: typeof defaultWebContext
  unirepConfig?: typeof defaultUnirepConfig
  userContext?: typeof defaultUserContext
}

const AllProviders = ({
  children,
  webContext = defaultWebContext,
  unirepConfig = defaultUnirepConfig,
  userContext = defaultUserContext
}: {
  children: ReactNode
  webContext?: typeof defaultWebContext
  unirepConfig?: typeof defaultUnirepConfig
  userContext?: typeof defaultUserContext
}) => {
  return (
    <BrowserRouter>
      <UnirepContext.Provider value={unirepConfig}>
        <UserContext.Provider value={userContext}>
          <WebContext.Provider value={webContext}>
            {children}
          </WebContext.Provider>
        </UserContext.Provider>
      </UnirepContext.Provider>
    </BrowserRouter>
  )
}

export const render = (
  ui: ReactElement,
  options?: CustomRenderOptions
) => {
  const { webContext, unirepConfig, userContext, ...rest } = options || {}
  return originalRender(ui, {
    wrapper: ({ children }) => (
      <AllProviders
        webContext={webContext}
        unirepConfig={unirepConfig}
        userContext={userContext}
      >
        {children}
      </AllProviders>
    ),
    ...rest
  })
}

// 导出RTL其余所有工具方法
export * from '@testing-library/react'
  • 第三步:改造测试用例
    测试文件中不再直接从@testing-library/react导入render,而是从刚才封装的test-utils导入,测试不同业务场景时可直接传入自定义的Context值覆盖默认Mock:
import { render, screen } from './test-utils'
import AppRouter from './AppRouter'

test('AppRouter renders all routes and I can navigate to those pages', () => {
  // 测试未登录场景,使用默认Mock值即可
  render(<AppRouter />)
  expect(screen.getByText('Sign in')).toBeInTheDocument()
})

test('render logged in state', () => {
  // 测试登录后场景,传入覆盖的用户上下文
  render(<AppRouter />, {
    userContext: {
      userState: true,
      netReputation: 20
    }
  })
  expect(screen.getByText('20')).toBeInTheDocument()
})

注意:你提供的原始组件代码中import关键字开头缺失了字母i(写为mport),运行前请先修正该语法错误。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 16:39:38