如何使用Jest和React Testing Library模拟React组件的异步Redux Action调用
问题原因
你收到的 TypeError: Cannot read property 'then' of undefined 翻译为 类型错误:无法读取undefined的属性'then',触发原因是点击按钮调用的action()方法执行后没有返回Promise,导致后续调用.then()失败。核心问题出在两点:
- 你的mock逻辑不符合Redux异步Action的执行规则
- 测试代码存在两处笔误:组件名和props之间缺少空格、查询的按钮文本和组件实际的label不匹配
推荐解决方案
方案一:测试未连接的原始组件(单元测试首选,逻辑更简单)
单元测试只需要验证组件本身的逻辑即可,Redux的连接逻辑可以单独测试,这种写法不需要处理Redux相关的mock,实现成本最低:
- 先修改组件代码,增加未连接的原始组件的命名导出:
import { reduxAction } from 'store/actions'; // 增加命名导出 export function TestBtn(props) { const { href, action } = props; const onClick= () => action ({ method: 'post', path: href }) .then(() => { //do something }) .catch(() => { //do something }); return ( <Button variant={'outlined'} onClick={onClick} label='Click' /> ); } const mapDispatchToProps = dispatch => ({ action : payload => dispatch(reduxAction(payload)) }); export default connect(null, mapDispatchToProps)(TestBtn);
- 测试代码直接导入原始组件,传入mock的
actionprop:
import React from 'react'; import { render, screen, fireEvent } from 'test-utils'; import '@testing-library/jest-dom'; import { TestBtn } from 'components'; // 导入未连接的原始组件 // 直接mock action方法,调用后返回Promise const mockAction = jest.fn(() => Promise.resolve()); const href = '/test'; test('点击按钮后逻辑正常执行', async () => { render(<TestBtn href={href} action={mockAction} />); // 匹配组件实际的按钮label fireEvent.click(screen.getByText('Click')); // 验证action被正确传参调用 expect(mockAction).toHaveBeenCalledWith({ method: 'post', path: href }); // 如果要验证then/catch内的逻辑,等待Promise执行完成即可 await Promise.resolve(); // 后续可以加DOM、状态相关的断言 });
方案二:测试连接后的完整组件
如果你需要验证Redux连接逻辑,需要修正mock和测试配置:
- 确保你的
test-utils中的render方法已经配置了带redux-thunk的Redux Provider,因为只有配置了thunk中间件,dispatch异步Action的时候才会返回Promise:
// test-utils.js 参考配置 import { render } from '@testing-library/react'; import { Provider } from 'react-redux'; import { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import rootReducer from '你的根reducer路径'; const renderWithProviders = (ui, { preloadedState, store = createStore(rootReducer, preloadedState, applyMiddleware(thunk)) } = {}) => { return render(ui, { wrapper: ({ children }) => <Provider store={store}>{children}</Provider> }); } export * from '@testing-library/react'; export { renderWithProviders as render };
- 修正
reduxAction的mock,符合thunk的结构:
如果你用的是redux-thunk,异步Action创建函数返回的是一个接收dispatch、getState的函数,这个函数执行后才会返回Promise,不能直接返回Promise
import React from 'react'; import { render, screen, fireEvent } from 'test-utils'; import '@testing-library/jest-dom'; import TestBtn from 'components'; import * as actions from 'store/actions'; const mockReduxAction = jest .spyOn(actions , 'reduxAction') // 返回符合thunk格式的函数,执行后返回Promise .mockImplementation(() => (dispatch) => Promise.resolve()); const href = '/test'; test('点击按钮后逻辑正常执行', async () => { // 修正组件props的空格问题 render(<TestBtn href={href} />); // 匹配组件实际的按钮label fireEvent.click(screen.getByText('Click')); expect(mockReduxAction).toHaveBeenCalledWith({ method: 'post', path: href }); await Promise.resolve(); // 后续加其他断言 });
内容的提问来源于stack exchange,提问作者goscamp
相关产品推荐
相关产品推荐

