如何使用Jest、Enzyme Mock request-promise-native进行React组件单元测试?
我之前也遇到过类似的麻烦——在测试依赖异步请求的React组件时,要么mock不生效,要么测试没等异步操作完成就急着断言状态。结合Jest和Enzyme,我们可以通过以下步骤轻松解决这个问题:
核心思路
我们要完全mock掉request-promise-native模块,手动控制它的返回结果(成功/失败),同时确保测试等待异步Promise执行完成后,再去断言组件状态的变化。用shallow渲染完全足够,因为我们只需要测试容器组件的逻辑,不需要关心子组件的渲染细节。
步骤1:正确Mock request-promise-native
首先在测试文件顶部用Jest的jest.mock来mock整个模块,这样组件中导入的request就会是我们的mock版本,而不是真实的HTTP请求工具:
import React from 'react'; import { shallow } from 'enzyme'; import SomeSearchContainer from './SomeSearchContainer'; const request = require('request-promise-native'); // Mock 整个 request-promise-native 模块 jest.mock('request-promise-native', () => jest.fn());
步骤2:编写完整测试用例(含异步处理)
我们需要用async/await来处理异步逻辑,确保测试等待Promise完成后再执行断言。下面是完整的测试示例:
describe('SomeSearchContainer', () => { let wrapper; // Mock 错误回调函数 const mockOnResponseError = jest.fn(); beforeEach(() => { // 每次测试前重置mock,避免测试间互相干扰 request.mockReset(); // 初始化shallow渲染的组件 wrapper = shallow( <SomeSearchContainer someId="1234" // 符合4位长度的ID,触发请求分支 onResponseError={mockOnResponseError} /> ); }); it('请求成功时更新组件状态', async () => { // 设置request的mock返回值(成功响应) const mockSuccessResponse = { var1: 'validData', var2: 'extraData' }; request.mockResolvedValue(mockSuccessResponse); // 模拟点击搜索按钮 wrapper.find('#btn-search').simulate('click'); // 等待异步Promise完成(让微任务队列执行完毕) await Promise.resolve(); // 也可以用这个工具函数:const flushPromises = () => new Promise(setImmediate); 然后 await flushPromises(); // 断言状态是否正确更新 expect(wrapper.state()).toEqual({ // 这里填你预期的状态对象,比如包含var1对应的值 // 示例:var1: 'validData', ...其他初始状态字段 }); // 额外断言request是否被正确调用 expect(request).toHaveBeenCalledWith({ uri: `${location.href}api/1234`, json: true, }); }); it('请求失败时调用错误回调', async () => { // 设置request的mock返回值(失败) const mockError = new Error('API请求失败'); request.mockRejectedValue(mockError); wrapper.find('#btn-search').simulate('click'); await Promise.resolve(); // 断言错误回调被正确调用 expect(mockOnResponseError).toHaveBeenCalledWith(mockError); }); it('当someId长度不为4时直接更新状态', () => { // 更新组件props,传入长度不符合要求的ID wrapper.setProps({ someId: '123' }); wrapper.find('#btn-search').simulate('click'); // 这里不需要等待异步,因为走的是else分支 expect(wrapper.state()).toEqual({ // 填else分支预期的状态对象 }); // 断言request没有被调用 expect(request).not.toHaveBeenCalled(); }); });
关键注意事项
Mock生效的核心:
jest.mock会在测试运行前替换掉模块的导出,不管组件里是用import还是require导入request-promise-native,都会拿到mock版本。如果之前mock无效,大概率是mock的时机不对——一定要把jest.mock放在测试文件顶部,在组件导入之前执行。等待异步的技巧:用
await Promise.resolve()或者flushPromises的目的是让Jest等待微任务队列执行完毕,这样request的Promise回调(onResponse)已经执行,组件状态也完成了更新。如果不等待,测试会在状态更新前就执行断言,导致失败。为什么不用Nock:你提到之前尝试Nock无效,这很常见——因为组件里用
location.href拼接URL,测试环境中的location.href可能和你预期的不一致,导致Nock无法匹配请求。直接mockrequest模块是更可靠的单元测试方式,我们只需要断言请求参数是否正确,不需要关心真实的HTTP交互。
内容的提问来源于stack exchange,提问作者Snake_A

