在 Jest 中灵活切换手动 Mock 与实际模块实现


在 Jest 中灵活切换手动 Mock 与实际模块实现

本文深入探讨了在 jest 测试框架中,如何有效管理手动模拟(manual mock)与实际模块的切换使用。通过详细介绍 `jest.dontmock()` 和 `jest.resetmodules()` 这两个关键 api,文章提供了在特定测试场景下绕过全局手动模拟、调用真实模块实现的实用方法,并辅以代码示例和注意事项,旨在帮助开发者实现更灵活、精准的测试控制。

理解 Jest 模块模拟与缓存机制

在 Jest 中,当我们为某个模块(例如 axios)创建了手动模拟文件(如 __mocks__/axios.js),Jest 会在测试运行时自动使用这个模拟版本,而不是实际的 axios 包。这种机制在大多数情况下非常方便,它允许我们隔离外部依赖,专注于测试当前模块的逻辑。

然而,有时我们可能需要在同一个测试套件中,针对某些特定测试用例调用模块的真实实现。例如,在集成测试或端到端测试中,我们可能希望 axios 真正地发出 HTTP 请求。直接在测试内部使用 jest.mock("axios", () => jest.requireActual("axios")) 尝试覆盖模拟可能不会立即生效,这是因为 Jest 有一个模块注册表缓存机制。一旦模块被加载并模拟,其模拟版本就会被缓存起来,后续的 require 或 import 语句会直接从缓存中获取,而不会重新解析或应用新的模拟指令。

解决方案:jest.dontMock() 与 jest.resetModules()

为了在特定测试中绕过手动模拟并加载真实模块,我们需要结合使用 jest.dontMock(moduleName) 和 jest.resetModules()。

  1. jest.dontMock(moduleName): 这个 API 告诉 Jest 不要模拟指定的模块。它会阻止 Jest 使用 __mocks__ 目录下的手动模拟文件,而是加载模块的实际实现。

  2. jest.resetModules(): 这个 API 用于重置 Jest 的模块注册表缓存。它会清除所有已加载模块的缓存,确保下一次 require 或 import 操作会重新加载模块,从而使得 jest.dontMock() 的效果得以应用。

重要提示: jest.resetModules() 必须在 jest.dontMock() 之前调用,或者至少在模块被 import 之前调用,以确保模块注册表被清除,并且 jest.dontMock() 能够影响到后续的模块加载行为。通常,我们会在 beforeEach 钩子中调用 jest.resetModules(),以确保每个测试用例都在一个“干净”的模块环境中运行。

实践示例

下面通过一个具体的例子来演示如何实现手动模拟与实际模块的切换。

假设我们有一个 axios 的手动模拟,它总是返回一个固定的假数据:

__mocks__/axios.js:

let mockAxios = jest.genMockFromModule('axios');

// 为 get 方法提供一个默认的模拟实现
mockAxios.get = jest.fn().mockResolvedValue({ data: 'fake' });

export default mockAxios;

接着,我们有一个业务逻辑模块 main.ts,它使用了 axios 发送 HTTP 请求:

Jaaz Jaaz

开源的AI设计智能体

Jaaz 216 查看详情 Jaaz

main.ts:

import axios from 'axios';

export function main() {
  return axios.get('https://jsonplaceholder.typicode.com/todos/1');
}

现在,我们编写测试文件 main.test.ts,其中包含一个使用模拟 axios 的测试和一个使用真实 axios 的测试:

main.test.ts:

describe('Axios 模块模拟与真实实现切换', () => {
  // 在每个测试用例运行前重置模块注册表
  // 这一步至关重要,它清除了之前测试可能留下的模块缓存,
  // 确保每个测试用例都能根据其自身的模拟配置加载模块。
  beforeEach(() => {
    jest.resetModules();
  });

  test('应该使用模拟的 axios', async () => {
    // 默认情况下,如果存在手动模拟,Jest 会使用它。
    // 在此测试中,我们不需要额外的 dontMock,因为我们希望使用模拟。
    const { main } = await import('./main'); // 导入 main 模块,此时 axios 会被模拟
    const actual = await main();
    console.log('使用模拟 axios 的结果:', actual?.data);
    expect(actual?.data).toEqual('fake'); // 预期返回模拟数据
  });

  test('应该使用真实的 axios', async () => {
    // 在这个测试用例中,我们明确告诉 Jest 不要模拟 axios
    jest.dontMock('axios');

    // 再次导入 main 模块。由于 resetModules 和 dontMock 的作用,
    // 此时 main 内部的 axios 将是真实实现。
    const { main } = await import('./main'); 
    const actual = await main();
    console.log('使用真实 axios 的结果:', actual?.data);
    // 预期返回真实 API 的数据结构
    expect(actual?.data).toH*eProperty('userId');
    expect(actual?.data).toH*eProperty('id');
    expect(actual?.data).toH*eProperty('title');
    expect(actual?.data).toH*eProperty('completed');
  });
});

运行上述测试,你将观察到以下输出:

  console.log
    使用模拟 axios 的结果: fake

  console.log
    使用真实 axios 的结果: { userId: 1, id: 1, title: 'delectus aut autem', completed: false }

这清楚地表明,第一个测试用例成功使用了手动模拟的 axios,而第二个测试用例通过 jest.dontMock('axios') 和 jest.resetModules() 成功加载并使用了真实的 axios 模块,并从外部 API 获取了数据。

注意事项与最佳实践

  • jest.resetModules() 的位置: 最好将其放在 beforeEach 钩子中,以确保每个测试用例都从一个干净的模块状态开始。这可以避免测试之间的状态泄露,提高测试的独立性和可靠性。
  • 性能考量: jest.resetModules() 会清除模块缓存并重新加载所有模块,这可能会对测试运行时间产生轻微影响,尤其是在大型项目中。然而,对于大多数单元和集成测试场景,这种开销通常是可接受的,并且其带来的测试隔离性收益远大于性能损失。
  • 局部模拟: 如果你只是想在某个测试文件中对某个模块进行临时模拟,而不是全局手动模拟,可以使用 jest.doMock() 结合 jest.unmock()。但对于本文讨论的“绕过已存在的手动模拟”场景,jest.dontMock() 是更直接的方案。
  • jest.requireActual() 的替代用途: jest.requireActual(moduleName) 通常用于在模拟文件中获取模块的真实实现,以便在模拟中部分地使用真实逻辑。例如:const actualAxios = jest.requireActual('axios'); mockAxios.post = actualAxios.post;。这与本文讨论的在测试中完全禁用模拟是不同的场景。

总结

通过熟练运用 jest.dontMock() 和 jest.resetModules(),开发者可以在 Jest 测试中获得对模块模拟行为的精细控制。这使得在同一个测试套件中,既能利用手动模拟进行隔离测试,又能按需调用真实模块进行更接近生产环境的集成测试,从而极大地提升了测试的灵活性和覆盖范围。理解并正确使用这些 Jest API 是编写健壮、可维护测试的关键。

以上就是在 Jest 中灵活切换手动 Mock 与实际模块实现的详细内容,更多请关注其它相关文章!


# 套件  # 抖音搜索排名关键词排名  # 河北优化型网站建设  # 广州市网站建设免费  # 摄影网站建设批发  # 营销推广烧烤  # seo电脑流量  # 衡阳网络推广seo  # 灰帽seo外包  # 邯郸广告网站推广电话  # 全网营销推广哪家最好  # 这可  # 它会  # react  # 自定义  # 用了  # 会在  # 有一个  # 测试中  # 加载  # 注册表  # ios  # ai  # axios  # json  # js 


相关栏目: 【 Google疑问12 】 【 Facebook疑问10 】 【 优化推广96088 】 【 技术知识133117 】 【 IDC资讯59369 】 【 网络运营7196 】 【 IT资讯61894


相关推荐: J*aScript类型数组_TypedArray使用  食品生产用水只要符合国家规定的生活饮用水卫生标准就可以吗  不吃碳水化合物是健康减肥的好办法吗  《王者荣耀世界》英雄获取攻略  C++ static关键字作用_C++静态成员变量与静态函数  传统曲艺莲花落的表演形式是  cad加载的线型看不见怎么办_cad线型不可见问题解决方法  有道AI翻译入口 智能写作官方网站入口  多闪电脑版下载_多闪PC端模拟器使用  《桃源记2》资源采集攻略  J*a中逻辑运算符如何使用_逻辑与或非的基础用法讲解  《火花chat》搜索好友方法  《KARDS》冬季扩展包“国土阵线”上线!全新“协力”机制改变战场格局  Golang如何实现HTTP请求重试机制_Golang HTTP请求错误处理策略  邮政快递寄件查询入口 邮政快递收件查询入口  《密马》发布账号方法  如何外贸网站设计-能留住客户提升用户体验!  12306夜间购票失败? | 查看官方公布的暂停服务公告与应对方案  Sublime怎么配置YAML文件格式化_Sublime YAML Formatter插件教程  狙击外星人小游戏在线链接_狙击外星人小游戏网页链接  解决CSS布局中意外顶部空白问题的教程  Win11怎么设置分辨率 Win11显示设置调整分辨率及刷新率修改  奥克斯空调不制热啥毛病_奥克斯空调不制热原因分析及解决技巧  vivo手机视频通话美颜怎么设置_vivo视频通话美颜开启方法  windows server2019显卡驱动怎么安装_winserver2019显卡驱动安装与远程桌面优化  《美篇》取消会员自动续费方法  银信通自动开通原因揭秘  Django模型动态关联检查:高效管理复杂关系  盲鳗善于分泌黏液猜猜主要用来做什么  猫眼电影app怎么查询电影院的营业时间_猫眼电影影院营业时间查询教程  秋风萧瑟洪波涌起中的萧瑟指的是什么  《鹿路通》退余额方法  C++中的explicit关键字有什么作用_C++类型转换控制与explicit使用  《爱笔思画x》魔棒工具抠图教程  汽水音乐官网网页版入口 汽水音乐官网网页版在线入口  Go语言反射机制下访问嵌入结构体中的被遮蔽方法  顺丰快递怎么查物流_顺丰快递物流信息实时查询操作指南  我的世界官方网址入口 我的世界游戏主页直达入口  J*aScript深度克隆:实现高效、健壮与安全的复杂对象复制  创建快捷方式启动系统保护  汽水音乐官方网站登录入口_汽水音乐网页版进入链接  如何取消数字签名  三角洲行动2025年9月10日摩斯密码分享  composer licenses 命令:如何检查项目依赖的许可证?  《伊瑟》凶影追缉库卢鲁boss攻略  word文档行距怎么调?word文档调行距的操作步骤  b站网页版入口 哔哩哔哩官方网站直接进入  《海贝音乐》均衡器设置方法  掌握CSS :has() 选择器:父选择器、嵌套限制与常见陷阱解析  悟空浏览器网页版在线工具 悟空浏览器网页版在线平台入口 

 2025-10-29

了解您产品搜索量及市场趋势,制定营销计划

同行竞争及网站分析保障您的广告效果

点击免费数据支持

提交您的需求,1小时内享受我们的专业解答。

运城市盐湖区信雨科技有限公司


运城市盐湖区信雨科技有限公司

运城市盐湖区信雨科技有限公司是一家深耕海外推广领域十年的专业服务商,作为谷歌推广与Facebook广告全球合作伙伴,聚焦外贸企业出海痛点,以数字化营销为核心,提供一站式海外营销解决方案。公司凭借十年行业沉淀与平台官方资源加持,打破传统外贸获客壁垒,助力企业高效开拓全球市场,成为中小企业出海的可靠合作伙伴。

 8156699

 13765294890

 8156699@qq.com

Notice

We and selected third parties use cookies or similar technologies for technical purposes and, with your consent, for other purposes as specified in the cookie policy.
You can consent to the use of such technologies by closing this notice, by interacting with any link or button outside of this notice or by continuing to browse otherwise.