React测试问题:Jest Mock函数已执行但调用次数验证失败
解决React Jest+Enzyme测试中Redux注入函数mock不生效的问题
我之前踩过完全一样的坑!你遇到的问题核心在于类属性箭头函数的绑定时机,再加上Redux Provider的包装,导致你的mock函数没有被正确关联到onDeleteClick里的调用。虽然console.log执行了,但deleteAccount调用的是组件初始化时绑定的原始函数,不是你后来mock的那个。下面给你几个针对性的解决办法:
1. 直接通过组件props覆盖Redux注入的函数
如果你的父组件是通过connect把deleteAccount注入到props中的,最直接的方式就是在挂载组件时,手动传入mock函数覆盖Redux的注入值。这样能确保onDeleteClick里调用的是你的mock:
import React from 'react'; import { mount } from 'enzyme'; import { Provider } from 'react-redux'; import configureStore from 'redux-mock-store'; import ParentComponent from './ParentComponent'; const mockStore = configureStore([]); const mockDeleteAccount = jest.fn(); // 提前创建干净的mock函数 describe('ParentComponent', () => { let store; beforeEach(() => { // 每次测试重新初始化store,避免跨测试污染 store = mockStore({ // 填入组件需要的基础state,保证组件能正常渲染 auth: { user: {} } }); }); it('triggers deleteAccount when child button is clicked', () => { const wrapper = mount( <Provider store={store}> {/* 直接传入mock函数覆盖Redux注入的props */} <ParentComponent deleteAccount={mockDeleteAccount} /> </Provider> ); // 模拟子组件按钮的点击事件(根据你的实际选择器调整) wrapper.find('ChildComponent button').simulate('click'); // 验证mock函数被调用 expect(mockDeleteAccount).toHaveBeenCalledTimes(1); }); });
2. Mock整个action creator模块
如果deleteAccount是一个单独的action creator(比如从../actions/auth导入),你可以直接mock这个模块,确保组件内调用的是mock版本:
import React from 'react'; import { mount } from 'enzyme'; import { Provider } from 'react-redux'; import configureStore from 'redux-mock-store'; import ParentComponent from './ParentComponent'; // 导入action模块并mock import * as authActions from '../actions/auth'; jest.mock('../actions/auth'); const mockStore = configureStore([]); describe('ParentComponent', () => { let store; beforeEach(() => { store = mockStore({ auth: { user: {} } }); // 重置mock状态,避免跨测试污染 authActions.deleteAccount.mockReset(); }); it('triggers deleteAccount when child button is clicked', () => { const wrapper = mount( <Provider store={store}> <ParentComponent /> </Provider> ); wrapper.find('ChildComponent button').simulate('click'); // 验证action creator被调用 expect(authActions.deleteAccount).toHaveBeenCalledTimes(1); }); });
3. 替换组件实例上的类属性箭头函数
如果deleteAccount是父组件内部定义的类属性箭头函数(不是从props注入的),你可以直接替换组件实例上的这个函数——因为类属性箭头函数是绑定到实例的,直接替换就能生效:
import React from 'react'; import { mount } from 'enzyme'; import { Provider } from 'react-redux'; import configureStore from 'redux-mock-store'; import ParentComponent from './ParentComponent'; const mockStore = configureStore([]); describe('ParentComponent', () => { let store; beforeEach(() => { store = mockStore({ auth: { user: {} } }); }); it('triggers deleteAccount when child button is clicked', () => { const wrapper = mount( <Provider store={store}> <ParentComponent /> </Provider> ); // 创建mock函数并替换组件实例上的deleteAccount const mockDeleteAccount = jest.fn(); wrapper.instance().deleteAccount = mockDeleteAccount; wrapper.find('ChildComponent button').simulate('click'); expect(mockDeleteAccount).toHaveBeenCalledTimes(1); }); });
关键注意事项
- 类属性箭头函数在组件实例创建时就绑定了上下文和函数引用,所以一定要确保mock函数在组件挂载前就准备好,不要在挂载后再修改然后调用
forceUpdate——这不会改变箭头函数的引用。 - 每次测试前要重置mock函数(用
mockReset()或mockClear()),避免之前的测试调用影响当前结果。
内容的提问来源于stack exchange,提问作者Oisín Foley
相关产品推荐
相关产品推荐

