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

Jest测试React/TS路由组件报JSON.parse Unexpected token u错误解决方法

Jest测试React路由组件报JSON.parse错误排查方案

问题背景

使用Jest搭配React Testing Library对React/TypeScript组件做单元测试时,抛出报错:
SyntaxError: Unexpected token u in JSON at position 0 at JSON.parse

已知该错误通常由JSON.parse(undefined)触发,但测试场景下根因暂未定位:测试逻辑为跳转至/new路由渲染NewPage组件,组件执行到const state = JSON.parse(JSON.stringify(location.state))代码行时抛出上述错误,初步判断需要在Jest中mock相关对象,但不清楚具体实现方式。

现有测试用例代码

test('AppRouter should navigate to /new', () => {
    window.history.pushState({}, '', '/new')
    render(
        <BrowserRouter>
            <AppRouter />
        </BrowserRouter>
    )
})

报错的NewPage组件代码

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

import UserContext from '../../context/User'
import PostContext from '../../context/Post'
import WritingField from '../../components/writingField/writingField'
import BasicPage from '../basicPage/basicPage'
import { DataType } from '../../constants'

const NewPage = () => {
    const history = useHistory()
    const location = useLocation<Location>()
    const state = JSON.parse(JSON.stringify(location.state))
    const isConfirmed = state.isConfirmed
    const userContext = useContext(UserContext)
    const postContext = useContext(PostContext)

    useEffect(() => {
        console.log('Is this new page being confirmd? ' + isConfirmed)
    }, [])

    const preventPropagation = (event: any) => {
        event.stopPropagation()
    }

    const submit = async (
        title: string,
        content: string,
        epkNonce: number,
        reputation: number
    ) => {
        if (!userContext.userState) {
            throw new Error('Should not be able to create post without login')
        }
        postContext.publishPost(title, content, epkNonce, reputation)
        history.push('/')
    }

    return (
        <BasicPage>
            <h3>Create post</h3>
            <WritingField
                type={DataType.Post}
                submit={submit}
                submitBtnName="Post - 5 points"
                onClick={preventPropagation}
            />
        </BasicPage>
    )
}

export default observer(NewPage)

已尝试的无效方案

已尝试mock react-router-dom的useHistory、useLocation钩子,但仍抛出相同错误,mock代码如下:

const mockHistoryPush = jest.fn();
jest.mock('react-router-dom', () => ({
    ...jest.requireActual('react-router-dom'),
    useHistory: () => ({
        push: mockHistoryPush,
    }),
    useLocation: () => ({
        pathname: "/"
    })
}));

补充说明:NewPage组件通过路由导航加载,该点不是问题诱因。


根因定位

报错本质是JSON.parse接收到了非字符串类型的入参undefined,具体触发原因有两个:

  1. 测试中直接调用window.history.pushState({}, '', '/new')时,第一个参数传的是空对象,react-router读取路由state时拿到的就是undefined。JSON.stringify(undefined)的返回值本身是undefined类型而非字符串,传入JSON.parse后会被隐式转换为字符串"undefined",解析第一个字符u时就会抛出对应错误。
  2. 之前写的useLocation mock存在缺陷:返回值里没有定义state字段,且mock的pathname写死为/,无法匹配/new路由;如果jest.mock没有放在测试文件顶层,mock甚至不会生效,组件还是会拿到真实的、不带state的location对象。

解决方案

方案1:使用React Router官方测试工具模拟路由(推荐)

不要手动mock useLocation,改用react-router-dom提供的MemoryRouter做测试,初始化时直接指定路由路径和对应的state,完全贴近真实运行场景,示例代码:

import { MemoryRouter } from 'react-router-dom';
import { render } from '@testing-library/react';
import AppRouter from './path/to/AppRouter';

test('AppRouter should navigate to /new', () => {
  render(
    <MemoryRouter initialEntries={[
      { pathname: '/new', state: { isConfirmed: true } }
    ]}>
      <AppRouter />
    </MemoryRouter>
  )
  // 后续写断言逻辑即可
})

说明:initialEntries中定义的路由对象会完整作为location传给组件,state字段会被正确初始化,不会出现undefined的问题。

方案2:修正useLocation的mock逻辑

如果确实需要mock路由钩子,必须在mock返回值中补全组件用到的state字段,同时修正pathname匹配目标路由,示例代码:

const mockHistoryPush = jest.fn();
// jest.mock必须放在测试文件最顶层,import语句之后,不能写在test/describe块内部
jest.mock('react-router-dom', () => ({
  ...jest.requireActual('react-router-dom'),
  useHistory: () => ({
    push: mockHistoryPush,
  }),
  useLocation: () => ({
    pathname: "/new",
    state: {
      isConfirmed: true // 字段和组件实际使用的state保持一致即可
    }
  })
}));

组件侧鲁棒性优化

组件中直接对可能为undefined的location.state做JSON.parse本身存在线上风险,建议加默认值兜底,避免用户直接访问/new路由没带state时页面白屏:

// 原代码
// const state = JSON.parse(JSON.stringify(location.state))
// 优化后
const state = JSON.parse(JSON.stringify(location.state || { isConfirmed: false }))

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 14:24:20