如何测试Redux连接的App组件?useEffect内dispatch调用验证咨询
Redux连接组件测试方案
测试必要性
这类根级Redux连接组件必须测试,原因如下:
- App是应用的根路由容器,负责token有效性校验触发、全局布局切换、权限路由分配三类核心逻辑,任意逻辑出错都会导致全站功能异常
- 你要验证的useEffect dispatch逻辑直接关系到用户登录态的有效性判断,是权限体系的核心入口,必须覆盖所有触发场景
- 测试可以提前规避依赖更新、逻辑调整导致的回归问题,比如后续修改useEffect依赖项遗漏触发dispatch的问题
测试实现方案
测试原则优先测组件本身的行为逻辑,不需要耦合Redux的connect层逻辑,推荐先导出未被connect包裹的原始App组件,通过传入mock props的方式完成测试,执行效率更高,用例更易维护。
前置准备
- 修改App组件代码,导出原始组件方便测试:
// 将原来的App组件定义改为带export的形式 export const App = props => { // 原有逻辑保持不变 }
- 测试依赖准备:已经使用Jest+Enzyme的前提下不需要额外安装依赖,只需要对localStorage、action函数做mock即可。
核心测试用例代码
完整测试文件示例如下:
import React from 'react'; import { mount } from 'enzyme'; import { MemoryRouter } from 'react-router-dom'; // 导入未连接的原始App组件 import { App } from './App'; // 导入目标action import { validateToken } from './store/auth/login/actions'; // 导入路由配置、布局组件用于校验渲染逻辑 import { authRoutes, userRoutes } from './routes/allRoutes'; import HorizontalLayout from './components/HorizontalLayout/'; import VerticalLayout from './components/VerticalLayout/'; // 1. mock validateToken action jest.mock('./store/auth/login/actions', () => ({ validateToken: jest.fn() })); // 2. mock localStorage const mockLocalStorage = (() => { let store = {}; return { getItem: jest.fn(key => store[key] || null), setItem: jest.fn((key, value) => { store[key] = value.toString(); }), clear: jest.fn(() => { store = {}; }) }; })(); Object.defineProperty(window, 'localStorage', { value: mockLocalStorage }); // 每次测试前重置所有mock状态 beforeEach(() => { jest.clearAllMocks(); mockLocalStorage.clear(); }); // 测试用例1:有token时挂载触发validateToken test('localStorage存在auth-token时,组件挂载调用validateToken', () => { mockLocalStorage.setItem('auth-token', 'test_valid_token'); const mockProps = { layout: { layoutType: 'vertical' }, loginData: { isAuthenticated: false } }; mount( <MemoryRouter> <App {...mockProps} /> </MemoryRouter> ); expect(validateToken).toHaveBeenCalledTimes(1); expect(validateToken).toHaveBeenCalledWith('test_valid_token'); }); // 测试用例2:无token时挂载不触发validateToken test('localStorage无auth-token时,组件挂载不调用validateToken', () => { const mockProps = { layout: { layoutType: 'vertical' }, loginData: { isAuthenticated: false } }; mount( <MemoryRouter> <App {...mockProps} /> </MemoryRouter> ); expect(validateToken).not.toHaveBeenCalled(); }); // 测试用例3:useEffect依赖项变化时重新触发校验 test('loginData.isAuthenticated更新时,重新执行token校验逻辑', () => { mockLocalStorage.setItem('auth-token', 'test_valid_token'); const initialProps = { layout: { layoutType: 'vertical' }, loginData: { isAuthenticated: false } }; const wrapper = mount( <MemoryRouter> <App {...initialProps} /> </MemoryRouter> ); // 初始挂载触发一次 expect(validateToken).toHaveBeenCalledTimes(1); // 更新isAuthenticated触发useEffect重执行 wrapper.setProps({ children: React.cloneElement(wrapper.props().children, { loginData: { isAuthenticated: true } }) }); wrapper.update(); // 验证第二次触发 expect(validateToken).toHaveBeenCalledTimes(2); }); // 测试用例4:布局切换逻辑正常 test('layoutType为horizontal时使用水平布局,否则使用垂直布局', () => { // 测试水平布局 let mockProps = { layout: { layoutType: 'horizontal' }, loginData: { isAuthenticated: true } }; let wrapper = mount( <MemoryRouter> <App {...mockProps} /> </MemoryRouter> ); // 取第一个用户路由的layout属性验证 const firstUserRoute = wrapper.find('Authmiddleware').at(authRoutes.length); expect(firstUserRoute.prop('layout')).toBe(HorizontalLayout); // 测试垂直布局 mockProps = { layout: { layoutType: 'vertical' }, loginData: { isAuthenticated: true } }; wrapper = mount( <MemoryRouter> <App {...mockProps} /> </MemoryRouter> ); expect(firstUserRoute.prop('layout')).toBe(VerticalLayout); });
连接后组件的补充测试
如果需要验证Redux connect的映射逻辑是否正确,可以使用redux-mock-store创建模拟store,渲染被connect包裹的默认导出组件,验证store的dispatch是否被正确触发即可,不需要重复测试组件本身的逻辑。
内容的提问来源于stack exchange,提问作者Charles Katada
相关产品推荐
相关产品推荐

