如何用Jest Mock React Native中的DeviceEventEmitter?
Jest Mock React Native DeviceEventEmitter 解决方案
你之前的两种mock方式都存在问题:
- 第一种只mock了
addListener和remove,但实际DeviceEventEmitter的addListener会返回一个带remove方法的订阅实例,组件卸载时通常调用的是这个实例的remove,而非直接调用DeviceEventEmitter.remove;另外官方接口里移除监听器的方法是removeListener,不是remove。 - 第二种直接返回实际模块,等于没有做mock,自然无法追踪监听器的调用情况。
下面是两种可行的实现方案:
方案1:完全Mock DeviceEventEmitter
直接模拟符合官方接口的结构,同时记录调用细节,方便测试断言。
// 定义mock函数,用于后续断言 const mockAddListener = jest.fn((eventName, callback) => ({ remove: jest.fn() // 模拟订阅对象的remove方法 })); const mockRemoveListener = jest.fn(); const mockEmit = jest.fn(); // 注意:如果你的代码是从'react-native'导入DeviceEventEmitter,这里要改成mock 'react-native' jest.mock('react-native/Libraries/EventEmitter/RCTDeviceEventEmitter', () => ({ addListener: mockAddListener, removeListener: mockRemoveListener, emit: mockEmit }));
测试示例
import { render, cleanup } from '@testing-library/react-native'; import YourComponent from './YourComponent'; import DeviceEventEmitter from 'react-native/Libraries/EventEmitter/RCTDeviceEventEmitter'; describe('YourComponent', () => { afterEach(() => { cleanup(); jest.clearAllMocks(); // 清空mock调用记录 }); // 测试挂载时添加监听器 it('registers event listener on mount', () => { render(<YourComponent />); expect(DeviceEventEmitter.addListener).toHaveBeenCalledWith('target-event-name', expect.any(Function)); }); // 测试卸载时移除监听器 it('removes event listener on unmount', () => { const { unmount } = render(<YourComponent />); // 获取addListener返回的订阅实例 const mockSubscription = mockAddListener.mock.results[0].value; unmount(); // 如果组件调用的是订阅实例的remove方法 expect(mockSubscription.remove).toHaveBeenCalled(); // 如果组件调用的是DeviceEventEmitter.removeListener // expect(DeviceEventEmitter.removeListener).toHaveBeenCalledWith('target-event-name', expect.any(Function)); }); });
方案2:Mock实际模块的指定方法
如果需要保留DeviceEventEmitter的部分原生功能,可以用jest.spyOn来mock指定方法:
import DeviceEventEmitter from 'react-native/Libraries/EventEmitter/RCTDeviceEventEmitter'; // 在测试文件顶部mock jest.spyOn(DeviceEventEmitter, 'addListener').mockImplementation((eventName, callback) => { // 保留实际的订阅逻辑,但替换remove方法为mock const subscription = jest.requireActual('react-native/Libraries/EventEmitter/RCTDeviceEventEmitter').addListener(eventName, callback); subscription.remove = jest.fn(subscription.remove); return subscription; }); jest.spyOn(DeviceEventEmitter, 'removeListener').mockImplementation(jest.requireActual('react-native/Libraries/EventEmitter/RCTDeviceEventEmitter').removeListener);
注意事项
如果你的业务代码是通过import { DeviceEventEmitter } from 'react-native';导入的,那么mock路径要改为react-native,示例如下:
jest.mock('react-native', () => ({ ...jest.requireActual('react-native'), // 保留其他原生模块 DeviceEventEmitter: { addListener: mockAddListener, removeListener: mockRemoveListener, emit: mockEmit } }));
内容的提问来源于stack exchange,提问作者Joshua
相关产品推荐
相关产品推荐

