如何测试Redux dispatch控制的React模态框显隐逻辑
React+Redux模态框单元测试修复方案
现有代码问题排查
测试跑不通是业务代码和测试逻辑同时存在问题:
- 组件
mapDispatchToProps写法错误:dispatch(hideModal)传入的是action创建函数本身,没有执行hideModal()返回合法action对象,点击按钮时无法触发正确的状态更新 - 组件JSX语法错误:按钮标签写成了自闭合形式
<button onClick={closeModal}/>Close</button>,语法不合法;且showModal为false时没有返回值,会导致React渲染报错 - 测试用的静态mockStore不会响应dispatch更新状态,点击按钮后store内的
showModal始终为初始值true,断言自然失败 - 测试文本匹配大小写错误:组件内文本是
My Modal,断言写的是My modal,大小写不匹配无法定位元素 - Reducer未设置初始状态,首次渲染时
showModal为undefined,容易出现边界问题
第一步:修复业务代码
Action(原有逻辑正确,无需修改)
const HIDE_MODAL = 'HIDE_MODAL'; export const hideModal = () => ({ type: HIDE_MODAL });
Reducer优化
// import HIDE_MODAL const initialState = { showModal: false } export default (state = initialState, action) => { switch (action.type) { case HIDE_MODAL: return {...state, showModal: false}; // 普通对象用浅拷贝即可,无需deepClone default: return state; } }
组件修复
import { connect } from 'react-redux'; import { hideModal } from './你的action路径'; const mapStateToProps = (state) => ({ showModal: state.showModal }); const mapDispatchToProps = dispatch => ({ closeModal: () => dispatch(hideModal()) // 执行action创建函数,传入合法action对象 }); // 导出纯组件方便单元测试 export const MyComponent = ({closeModal, showModal}) => { if (showModal) { return <div> <div>My Modal</div> <button onClick={closeModal}>Close</button> </div> } return null; // 隐藏状态返回null,避免返回undefined报错 }; export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);
第二步:编写有效测试用例
两种测试思路二选一即可,推荐项目中两种搭配使用。
方案1:全链路集成测试(覆盖Redux交互完整流程)
使用真实Redux store替代不会更新的静态mockStore,模拟用户真实操作路径:
import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { Provider } from 'react-redux'; import { createStore } from 'redux'; import modalReducer from './你的reducer路径'; import MyComponent from './你的组件路径'; it('点击关闭按钮后模态框应该隐藏', () => { // 创建真实store,初始化模态框为显示状态 const store = createStore(modalReducer, { showModal: true }); render( <Provider store={store}> <MyComponent /> </Provider> ); // 初始状态模态框可见,文本严格匹配大小写 expect(screen.getByText('My Modal')).toBeInTheDocument(); // 模拟点击关闭按钮 userEvent.click(screen.getByText('Close')); // 状态更新后模态框消失 expect(screen.queryByText('My Modal')).not.toBeInTheDocument(); });
方案2:分层单元测试(执行速度更快,定位问题更精准)
将UI组件、Reducer、action逻辑拆分测试,不需要每次测试都挂载Redux环境:
import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import modalReducer, { hideModal } from './你的reducer/action路径'; // 导入未connect的纯组件 import { MyComponent } from './你的组件路径'; // 测试纯组件渲染逻辑 it('点击关闭按钮应该触发closeModal回调', () => { const mockClose = jest.fn(); render(<MyComponent showModal={true} closeModal={mockClose} />); expect(screen.getByText('My Modal')).toBeInTheDocument(); userEvent.click(screen.getByText('Close')); expect(mockClose).toHaveBeenCalledTimes(1); }); // 测试Reducer逻辑 it('触发HIDE_MODAL action后应该将showModal设为false', () => { const testState = { showModal: true }; const newState = modalReducer(testState, hideModal()); expect(newState.showModal).toBe(false); }); // 测试Action创建函数 it('hideModal应该返回正确的action对象', () => { expect(hideModal()).toEqual({ type: 'HIDE_MODAL' }); });
测试注意事项
- 断言元素存在时用
getBy*系列查询方法,断言元素不存在时用queryBy*系列方法,语义更清晰,报错信息更准确 - 文本匹配需要严格对应内容、大小写、空格,避免出现找不到元素的问题
- 不要用静态mockStore测试依赖状态更新的交互逻辑,mockStore仅适合断言dispatch的action类型是否符合预期
- 普通状态更新优先用对象浅拷贝,没必要使用深拷贝,避免不必要的性能损耗
内容的提问来源于stack exchange,提问作者physicsboy
相关产品推荐
相关产品推荐

