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

如何使用JEST对使用Redux及Hooks的React函数组件进行单元测试

测试代码问题排查与修改方案

核心问题梳理

  • 错误mock了useEffect:你直接将useEffect mock为空实现,导致组件内触发dispatch(getDetails)的逻辑完全不会执行
  • useSelector mock逻辑错误:多次调用mockReturnValue会覆盖前一次的返回值,且没有按照selector入参正确返回state对应字段,还存在拼写错误mocdata
  • 错误使用renderHook测试组件:renderHook是用于测试自定义Hook的API,测试React组件应该使用@testing-library/react的render方法
  • useDispatch mock逻辑错误:你直接返回了getDetails的调用结果,而useDispatch应该返回一个可调用的dispatch函数
  • 无效的sleep调用:Jest测试中普通sleep无法等待异步逻辑执行,当前场景也不需要延迟等待
  • redux-mock-store只记录action不会更新state:如果要测试不同accounts值下的渲染效果,直接修改mock的state值即可,不需要模拟dispatch更新流程

修改后完整测试代码

import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import * as reactRedux from 'react-redux';
import * as SelectionActions from './你的action文件路径';
import MyFunctionalComponent from './你的组件路径';

// 统一mock react-redux的hook
jest.mock('react-redux', () => ({
  ...jest.requireActual('react-redux'),
  useSelector: jest.fn(),
  useDispatch: jest.fn()
}));

const mockUser = {
  id: '32sdfsdfr-fjdfdk33-4434sdfs',
  otherid: '73587hdfjg-dfghd94-jgdj'
};

describe('<MyFunctionalComponent/>', () => {
  const useSelectorMock = reactRedux.useSelector;
  const useDispatchMock = reactRedux.useDispatch;
  const mockDispatch = jest.fn();

  beforeEach(() => {
    useDispatchMock.mockReturnValue(mockDispatch);
    // 每次测试前重置所有mock
    jest.clearAllMocks();
  });

  // 测试用例1:初始加载accounts为null,触发getDetails调用
  it('should dispatch getDetails when accounts is null', () => {
    // 按组件内useSelector的调用逻辑返回对应state
    useSelectorMock.mockImplementation(selector => 
      selector({
        user: mockUser,
        Selection: { accounts: null }
      })
    );
    const getDetailsSpy = jest.spyOn(SelectionActions, 'getDetails').mockReturnValue({ type: 'GET_DETAILS' });

    render(<MyFunctionalComponent />);

    expect(getDetailsSpy).toHaveBeenCalledWith(mockUser.id, mockUser.otherid);
    expect(mockDispatch).toHaveBeenCalledWith(getDetailsSpy.mock.results[0].value);
    expect(screen.getByTestId('circular-loader')).toBeInTheDocument(); // 给CircularLoader加data-testid属性方便验证
  });

  // 测试用例2:accounts长度大于1,渲染MyList
  it('should render MyList when there are more than 1 accounts', () => {
    const mockAccounts = [
      { id: '637ghgh' },
      { id: '10a190abd' }
    ];
    useSelectorMock.mockImplementation(selector => 
      selector({
        user: mockUser,
        Selection: { accounts: mockAccounts }
      })
    );

    const { asFragment } = render(<MyFunctionalComponent />);
    expect(screen.getByTestId('my-list')).toBeInTheDocument(); // 给MyList加data-testid属性
    expect(asFragment()).toMatchSnapshot();
  });

  // 测试用例3:accounts为空数组,渲染AccessUnavailablePanel
  it('should render AccessUnavailablePanel when accounts is empty array', () => {
    useSelectorMock.mockImplementation(selector => 
      selector({
        user: mockUser,
        Selection: { accounts: [] }
      })
    );

    const { asFragment } = render(<MyFunctionalComponent />);
    expect(screen.getByTestId('access-unavailable')).toBeInTheDocument(); // 给AccessUnavailablePanel加data-testid属性
    expect(asFragment()).toMatchSnapshot();
  });
});

补充说明

  • 如果需要测试异步action的执行逻辑,可以单独对getDetails这个action creator写单元测试,不需要和组件渲染逻辑耦合在一起
  • 给组件加对应的data-testid属性可以更稳定的定位元素,避免文本或者样式修改导致测试失败
  • 如果要测试点击事件的dispatch逻辑,可以模拟点击MyList的项,验证saveId对应的action被正确dispatch即可

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 00:45:01