嵌套在Provider与Router中的组件如何Mock this.props.dispatch?
我完全明白你的困扰——当组件被Redux Provider和React Router层层包裹时,手动传入的mock dispatch props被忽略了,这确实是Redux的connect高阶组件在背后起了作用。让我一步步帮你解决这个问题:
问题根源
如果你的Login组件是通过connect连接到Redux的,那么当它被包裹在Provider中时,connect会自动从Provider的context里获取store的dispatch方法,并将其作为props注入到组件中,这会直接覆盖你手动传递的dispatch props。这就是为什么直接挂载<Login {...props} />时mock有效,但嵌套在Provider里就失效的原因。
解决方案
方案1:Mock Redux Store的dispatch方法(推荐用于测试Redux交互)
既然组件从store获取dispatch,那我们直接mock store的dispatch即可。你可以自己手动mock,或者用redux-mock-store库来简化操作:
用redux-mock-store的示例:
首先安装依赖(如果还没装):
npm install redux-mock-store --save-dev
然后修改你的测试代码:
import configureMockStore from 'redux-mock-store'; import { userActions } from './path-to-your-actions'; // 导入你的action describe("Pokedex", () => { let mockDispatch; let store; beforeEach(() => { // 创建mock store实例 const mockStoreCreator = configureMockStore(); // 可以传入你的初始state,比如 { user: { error: null } } store = mockStoreCreator({}); // 替换store的dispatch为jest mock函数 mockDispatch = jest.fn(); store.dispatch = mockDispatch; wrapper = mount( <Provider store={store}> <Router> <Login /> {/* 不需要手动传dispatch了 */} </Router> </Provider> ); wrapper.setState({ loading: false }); store.clearActions(); }); it('should dispatch resetError action when component mounts', () => { // 断言mockDispatch被正确调用 expect(mockDispatch).toHaveBeenCalledWith(userActions.resetError()); }); });
手动mock store的示例:
如果你不想额外安装库,也可以自己创建测试用的store并替换dispatch:
import { createStore } from 'redux'; import rootReducer from './path-to-your-reducer'; // 导入你的根reducer describe("Pokedex", () => { let mockDispatch; let store; beforeEach(() => { mockDispatch = jest.fn(); // 创建真实的store,然后替换dispatch为mock函数 store = createStore(rootReducer); store.dispatch = mockDispatch; wrapper = mount( <Provider store={store}> <Router> <Login /> </Router> </Provider> ); wrapper.setState({ loading: false }); }); it('triggers resetError on mount', () => { expect(mockDispatch).toHaveBeenCalledWith(userActions.resetError()); }); });
方案2:Mock react-redux的connect(适合独立测试组件UI逻辑)
如果你只是想测试组件的UI行为,不想依赖Redux的连接逻辑,可以mockconnect函数,让它直接返回原组件,这样组件就不会从store注入props,你手动传递的mock dispatch就会生效:
// 在测试文件顶部mock react-redux jest.mock('react-redux', () => ({ connect: () => (Component) => Component, // 让connect变成恒等函数 })); describe("Pokedex", () => { let mockDispatch; let props; beforeEach(() => { mockDispatch = jest.fn(); props = { dispatch: mockDispatch }; // 现在不需要Provider了,直接用Router包裹组件 wrapper = mount( <Router> <Login {...props} /> </Router> ); wrapper.setState({ loading: false }); }); it('calls dispatch with resetError on mount', () => { expect(mockDispatch).toHaveBeenCalledWith(userActions.resetError()); }); });
这个方案的好处是让组件测试完全脱离Redux依赖,专注于组件本身的逻辑。
方案3:从组件实例中获取真实的dispatch(应急调试可用)
如果你只是想临时调试,也可以从Enzyme的wrapper中获取组件实例,然后通过store的getActions()方法验证已触发的action:
it('dispatches resetError', () => { // 验证store中是否存在对应的action expect(store.getActions()).toContainEqual(userActions.resetError()); });
注意:这个方法用的是真实的store dispatch,不是mock函数,仅适合简单验证,不推荐作为正式测试方案。
总结
- 若要测试组件与Redux的交互逻辑,优先选择方案1,mock store的dispatch;
- 若只想测试组件本身的UI和业务逻辑,不关心Redux连接,选择方案2,mock connect;
- 方案3仅适合临时调试场景。
内容的提问来源于stack exchange,提问作者MatTaNg

