如何测试useEffect内部的if语句?(依赖react-redux useSelector返回值)
测试问题修复方案
你当前的测试无法模拟状态变化有两个核心原因:
- 组件完成初始化渲染后,单纯修改
useAppSelector的mock不会触发组件重渲染,已经执行过的hook不会自动重新调用 - 你写的
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
相关产品推荐
相关产品推荐

