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

Jest测试AppRouter组件报ReferenceError: Worker is not defined

问题:Jest + React Testing Library 测试时抛出 Reference Error: Worker is not defined

问题描述

使用Jest与React Testing-Library测试React/Typescript组件,执行简单渲染测试时抛出Reference Error: Worker is not defined报错,不清楚测试场景下Worker被调用的原因,也不知道如何处理该报错。

测试用例代码

import {render, screen} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import React from 'react'
import {Router} from 'react-router-dom'
import AppRouter from '../router'

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

AppRouter组件实现

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

调用栈截图

代码调用栈截图

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)

初步排查

初步怀疑问题和MobX状态管理有关,测试时没有将组件包裹在对应Provider中,但不确定具体成因和修复方式。检索到的相关解决方案多为发布时间较早的StackOverflow内容,已知jest-worker包存在,但不确定运行这个基础渲染测试为何需要用到该包,也不清楚该如何配置使用。


解决方案

报错根因

这个报错和jest-worker包没有任何关系。Jest测试默认运行在jsdom环境中,jsdom本身没有实现原生Web Worker API,当依赖链中存在调用new Worker()的逻辑时就会抛出该错误。从代码依赖判断,触发Worker调用的大概率是项目引入的Unirep相关SDK——这类链上/加密计算类库通常会用Web Worker处理耗时计算避免阻塞主线程。
另外MobX Provider缺失的问题确实存在,但这类问题只会触发context取值为undefined的报错,和Worker异常无关,初步排查方向有误。

修复方法

1. 为jsdom环境补充Worker mock

首先在Jest配置中指定setup文件,打开jest.config.js/jest.config.ts,添加如下配置:

module.exports = {
  // 保留原有其他配置
  setupFilesAfterEnv: ['<rootDir>/src/setupTests.ts'],
}

在项目src目录下新建/修改setupTests.ts,添加全局Worker的最小mock实现:

class MockWorker {
  onmessage: (payload: any) => void = () => {}
  constructor(public url: string) {}
  postMessage(message: any) {
    // 如果依赖需要Worker返回特定数据,可以在这里手动触发onmessage传值,基础渲染场景留空即可
    this.onmessage({ data: null })
  }
  terminate() {}
}

Object.defineProperty(window, 'Worker', {
  writable: true,
  value: MockWorker
})

2. 补全测试中缺失的Context包裹

Header组件依赖UnirepContext、UserContext两个上下文,直接渲染AppRouter会因为上下文取值失败报错,修改测试用例,用对应Provider包裹组件并传入mock值:

import {render, screen} from '@testing-library/react'
import React from 'react'
import AppRouter from '../router'
import { UnirepContext } from './context/Unirep'
import UserContext from './context/User'

test('AppRouter renders all routes and I can navigate to those pages', () => {
  // 按照业务逻辑填入测试需要的mock值即可
  const mockUnirepConfig = { postReputation: 1 }
  const mockUserValue = { userState: false, netReputation: 0 }

  render(
    <UnirepContext.Provider value={mockUnirepConfig}>
      <UserContext.Provider value={mockUserValue}>
        <AppRouter />
      </UserContext.Provider>
    </UnirepContext.Provider>
  )
  screen.debug()
})

3. 可选优化:mock无关第三方组件

如果react-favicon这类和页面核心逻辑无关的第三方组件仍触发环境兼容问题,可以直接在setup文件中mock掉,避免加载其内部实现:

// 添加到setupTests.ts中
jest.mock('react-favicon', () => () => null)

内容的提问来源于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 14:36:16