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

React Testing Library解码JWT时出现Cannot read properties of undefined错误

问题根源分析

你的测试报错是因为jest.mock("utils/keycloak")将keycloak对象置为空mock,keycloak.token默认是undefined,导致parseJwt函数执行token.split('.')时抛出Cannot read properties of undefined错误。另外你的useEffect依赖数组存在问题:依赖userHasCommunities会导致每次调用setUserHasCommunities都触发useEffect,形成无限循环。

解决方案

步骤1:修复组件内的useEffect依赖

将useEffect的依赖数组改为空数组,确保仅在组件挂载时执行一次解码逻辑:

// Menu组件内的useEffect修改
useEffect(() => {
    function parseJwt (token) {
        if (!token) return {}; // 增加空值判断,避免token为undefined时报错
        var base64Url = token.split('.')[1];
        var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
        var jsonPayload = decodeURIComponent(window.atob(base64).split('').map(function(c) {
            return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
        }).join(''));

        if (JSON.parse(jsonPayload).communities?.length > 0){
            setUserHasCommunities(true)
        }

        return JSON.parse(jsonPayload);
    };

    parseJwt(keycloak.token);
}, []) // 改为空数组,仅挂载时执行

步骤2:修改测试文件,Mock keycloak.token并覆盖场景

给mock的keycloak对象赋值有效JWT(可通过在线JWT生成器生成对应payload),覆盖两种用户场景:

import React from "react";
import {shallow} from "enzyme";
import {Menu} from "features/layouts/logged-in/menu/index";
import {historyObject} from "historyObject";
import { keycloak } from "utils/keycloak";
jest.mock("utils/keycloak");
jest.mock("historyObject")

describe("Menu test", () => {
    const props = {
        t: jest.fn((key) => key), // 让翻译函数直接返回key,方便测试断言
    };
    
    beforeEach(() => {
        jest.resetAllMocks();
        historyObject.location.pathname = ""; // 重置路由路径
    });

    // 测试无社区权限的用户:渲染3个菜单
    it('renders menu without community entry for users without communities', () => {
        // Mock无communities字段的JWT
        keycloak.token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb21tdW5pdGllcyI6W10sImlhdCI6MTUxNjIzOTAyMn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
        
        const wrapper = shallow(<Menu {...props} />);
        expect(wrapper.find('li')).toHaveLength(3);
    });

    // 测试有社区权限的用户:渲染4个菜单
    it('renders menu with community entry for users with communities', () => {
        // Mock含communities字段的JWT
        keycloak.token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb21tdW5pdGllcyI6WyJjb21tdW5pdHkxIl0sImlhdCI6MTUxNjIzOTAyMn0.0W99VdZ7aU5Q4Ae6zr6XaK5Q4Ae6zr6XaK5Q4Ae6zr6X";
        
        const wrapper = shallow(<Menu {...props} />);
        expect(wrapper.find('li')).toHaveLength(4);
        const communityItem = wrapper.find('li').filterWhere(item => item.prop('onClick').toString().includes('/community'));
        expect(communityItem).toHaveLength(1);
    });

    it('marks current path as selected', () => {
        historyObject.location.pathname = "/portfolio";
        keycloak.token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb21tdW5pdGllcyI6W10sImlhdCI6MTUxNjIzOTAyMn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
        
        const wrapper = shallow(<Menu {...props} />);
        expect(wrapper.find('.selected')).toHaveLength(1);
    });

    it('triggers history push when menu item is clicked', () => {
        keycloak.token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb21tdW5pdGllcyI6W10sImlhdCI6MTUxNjIzOTAyMn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
        
        const wrapper = shallow(<Menu {...props} />);
        wrapper.find('li').at(0).prop('onClick')();
        expect(historyObject.push).toHaveBeenCalledWith("/portfolio");
    });
});

进阶优化:抽离parseJwt到工具函数

将parseJwt抽离到单独的工具文件,方便单独测试和Mock:

  1. 创建utils/jwt.js:
export function parseJwt(token) {
    if (!token) return {};
    const base64Url = token.split('.')[1];
    const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
    const jsonPayload = decodeURIComponent(window.atob(base64).split('').map(c => {
        return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
    }).join(''));
    return JSON.parse(jsonPayload);
}
  1. 组件内导入使用:
import { parseJwt } from "utils/jwt";

// useEffect内直接调用
useEffect(() => {
    const payload = parseJwt(keycloak.token);
    if (payload.communities?.length > 0) {
        setUserHasCommunities(true);
    }
}, [])
  1. 测试时Mock工具函数:
import { parseJwt } from "utils/jwt";
jest.mock("utils/jwt", () => ({
    parseJwt: jest.fn()
}));

// 在测试用例中控制返回值
it('renders community menu when user has communities', () => {
    parseJwt.mockReturnValue({ communities: ['test-community'] });
    const wrapper = shallow(<Menu {...props} />);
    expect(wrapper.find('li')).toHaveLength(4);
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 16:24:31