如何用Jest测试Redux组件挂载时dispatch属性是否被调用?
问题:验证React Redux组件渲染时是否调用API请求的dispatch方法
需要编写Jest测试,验证React Redux组件在渲染时是否调用了用于API请求的dispatch方法fetchData。组件代码如下:
import * as React from 'react'; import { connect } from 'react-redux'; import { bindActions } from 'reports/utils/redux'; const connectedComponent = ({ foo, fetchData }) => { const loadData = React.useCallback(async () => { if (foo) { await fetchData(foo.id); } }, []); React.useEffect(() => { loadData(); }, []); return <></>; } const mapStateToProps = (state) => { const foo = <从Redux store中获取foo> return { foo }; }; const mapDispatchToProps = bindActions({ fetchData: (fooId) => api.get({ foo_id: fooId }), }); export default connect(mapStateToProps, mapDispatchToProps)(connectedComponent);
测试时尝试过mock react-redux的connect函数,也尝试过将fetchData作为state传入mockStore,但都无效,测试代码如下:
... const middlewares = [ReduxThunk]; const mockStore = configureStore(middlewares); describe('Connected Component', () => { it('should load team usage and limits on mount', async () => { let fetchDataMock = jest.fn(); const store = mockStore({ user: { team_id: 1 }, fetchData: fetchDataMock }); await act(async () => { render( <Provider store={store}> <connectedComponent /> </Provider>, ); }); console.log(store); const actions = store.getActions(); console.log(actions); // [] 此处为空 expect(fetchDataMock).toHaveBeenCalledTimes(1); expect(fetchDataMock).toHaveBeenCalledWith({ team_id: 1 }); }); });
出现错误:Actions must be plain objects. Use custom middleware for async actions.(翻译:动作必须是普通对象。请为异步动作使用自定义中间件。)
错误原因分析
- dispatch函数放错位置:
fetchData是通过mapDispatchToProps注入的组件props,并非store的state属性,直接加到mockStore的state里不会被组件识别。 - 异步动作未正确处理:
fetchData返回异步函数(因api.get是异步操作),若bindActions未用thunk包装,或mockStore未正确配置thunk中间件,就会触发"动作必须是普通对象"的错误。
解决方案
方法1:直接测试未连接的原始组件(推荐)
跳过Redux连接逻辑,直接测试纯组件connectedComponent,手动传入所需props,聚焦组件本身的业务逻辑:
import connectedComponent from './your-component-path'; describe('Connected Component', () => { it('调用fetchData当foo存在时', async () => { const fetchDataMock = jest.fn().mockResolvedValue({}); const foo = { id: 1 }; await act(async () => { render(<connectedComponent foo={foo} fetchData={fetchDataMock} />); }); expect(fetchDataMock).toHaveBeenCalledTimes(1); expect(fetchDataMock).toHaveBeenCalledWith(1); }); it('不调用fetchData当foo不存在时', async () => { const fetchDataMock = jest.fn(); await act(async () => { render(<connectedComponent foo={null} fetchData={fetchDataMock} />); }); expect(fetchDataMock).not.toHaveBeenCalled(); }); });
方法2:Mock bindActions工具函数
若要测试连接后的组件,可mockreports/utils/redux中的bindActions,让它返回我们的mock函数:
import { bindActions } from 'reports/utils/redux'; import ConnectedComponent from './your-component-path'; import { Provider } from 'react-redux'; import { configureStore } from '@reduxjs/toolkit'; // Mock bindActions jest.mock('reports/utils/redux', () => ({ bindActions: jest.fn(() => ({ fetchData: jest.fn().mockResolvedValue({}) })) })); describe('Connected Component', () => { it('挂载时触发fetchData', async () => { const mockFetchData = jest.fn().mockResolvedValue({}); (bindActions as jest.Mock).mockReturnValue({ fetchData: mockFetchData }); // 创建包含foo的store const store = configureStore({ reducer: () => ({ foo: { id: 1 } }) }); await act(async () => { render( <Provider store={store}> <ConnectedComponent /> </Provider> ); }); expect(mockFetchData).toHaveBeenCalledTimes(1); expect(mockFetchData).toHaveBeenCalledWith(1); }); });
方法3:修复mockStore配置
确保store返回正确的foo数据,同时mockapi.get接口,保证thunk中间件能处理异步动作:
import ConnectedComponent from './your-component-path'; import { Provider } from 'react-redux'; import { configureStore } from '@reduxjs/toolkit'; import thunk from 'redux-thunk'; import * as api from './your-api-path'; // Mock api.get jest.mock('./your-api-path', () => ({ get: jest.fn().mockResolvedValue({}) })); describe('Connected Component', () => { it('挂载时调用api.get并传入foo.id', async () => { const store = configureStore({ reducer: () => ({ foo: { id: 1 } }), middleware: [thunk] }); await act(async () => { render( <Provider store={store}> <ConnectedComponent /> </Provider> ); }); expect(api.get).toHaveBeenCalledTimes(1); expect(api.get).toHaveBeenCalledWith({ foo_id: 1 }); }); });
内容的提问来源于stack exchange,提问作者Jonathan Lee
相关产品推荐
相关产品推荐

