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

如何测试useEffect内部的if语句?(依赖react-redux useSelector返回值)

测试问题修复方案

你当前的测试无法模拟状态变化有两个核心原因:

  1. 组件完成初始化渲染后,单纯修改useAppSelector的mock不会触发组件重渲染,已经执行过的hook不会自动重新调用
  2. 你写的reduxHooks.useAppSelector(() => '1')只是单次调用mock函数,并没有修改mock的实现逻辑

方案1:调整mock实现支持动态返回值(保持现有mock hook的测试逻辑)

// App.test.tsx 调整后
jest.mock('../utils/redux/hooks');
import * as reduxHooks from '../utils/redux/hooks';
import { act } from 'react-dom/test-utils';

describe('App', () => {
  let mockRiskSelection: string | undefined = undefined;

  beforeEach(() => {
    mockRiskSelection = undefined;
    jest.spyOn(reduxHooks, 'useAppSelector').mockImplementation((selector) => 
      selector({
        riskSelector: { riskSelection: mockRiskSelection }
      })
    );
  });

  afterEach(() => {
    jest.clearAllMocks();
  });

  it('初始化riskSelection为undefined时不展示PieSVG', () => {
    const wrapper = shallow(<App />);
    expect(wrapper.find(PieSVG)).toHaveLength(0);
  });

  it('riskSelection有值时渲染PieSVG', () => {
    // 渲染前先设置mock值,覆盖useEffect的分支逻辑
    mockRiskSelection = '1';
    const wrapper = shallow(<App />);
    expect(wrapper.find(PieSVG)).toHaveLength(1);
  });

  it('点击li触发状态更新后展示PieSVG', () => {
    // 模拟useActions的save方法
    const mockSave = jest.fn((payload) => {
      mockRiskSelection = payload.riskSelection;
    });
    jest.mock('../路径/useActions', () => ({
      useActions: () => ({ save: mockSave })
    }));

    const wrapper = shallow(<App />);
    // 触发点击事件
    act(() => {
      wrapper.find('li').at(0).simulate('click');
    });
    // 手动触发组件重渲染,拉取最新的mock状态
    wrapper.setProps({});

    expect(mockSave).toHaveBeenCalledWith({riskSelection: '1'});
    expect(wrapper.find(PieSVG)).toHaveLength(1);
  });
});

方案2:使用真实Redux Store测试(更推荐)

不用mock hook,直接用redux-mock-store模拟真实的Redux运行逻辑,测试结果更贴近实际运行场景:

// App.test.tsx
import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';
import { render, screen, fireEvent } from '@testing-library/react';
import App from './App';

const mockStore = configureStore([]);

describe('App', () => {
  it('点击li后正常渲染PieSVG', () => {
    // 初始化store
    let store = mockStore({
      riskSelector: { riskSelection: undefined }
    });

    const { rerender } = render(
      <Provider store={store}>
        <App />
      </Provider>
    );

    // 初始状态无PieSVG(提前给PieSVG加data-testid="pie-svg"方便查询)
    expect(screen.queryByTestId('pie-svg')).not.toBeInTheDocument();

    // 触发点击
    fireEvent.click(screen.getByText('3'));

    // 模拟store状态更新后重渲染
    store = mockStore({
      riskSelector: { riskSelection: '3' }
    });
    rerender(
      <Provider store={store}>
        <App />
      </Provider>
    );

    expect(screen.getByTestId('pie-svg')).toBeInTheDocument();
  });
});

关于useEffect使用的疑问

你当前场景的useEffect属于完全不必要的使用,你存储的data是完全可以通过riskSelection直接计算得到的派生状态,不需要单独维护state+useEffect更新,优化后代码更简洁,同时你之前Jest覆盖率未覆盖的问题也会直接解决:

// 优化后的App.tsx核心逻辑
function App(): JSX.Element {
  const riskSelection = useAppSelector(selectRiskSelection);
  const {save} = useActions({...riskSelectorActions});
  const risks: Risk = risks_levels;
  // 直接计算data,移除useState和useEffect
  const data = typeof riskSelection !== 'undefined' ? Object.values(risks[riskSelection]) : undefined;

  const width = 450,
    height = 450,
    margin = 40;
  const radius = Math.min(width, height) / 2 - margin;

  // 其余返回逻辑保持不变
}

useEffect只应该用于处理副作用场景:比如接口请求、DOM操作、事件订阅、跨状态源同步等,单纯的派生状态直接在渲染时计算即可,不需要用useEffect绕一圈增加逻辑复杂度。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.22 02:36:04