在 Jest 中模拟 API 调用只需 3 个步骤

2025-05-28

在 Jest 中模拟 API 调用只需 3 个步骤

最近,我在一个 JavaScript 代码库中工作,需要实现新的 Jest 测试。当时我对编写测试知之甚少,所以我查阅了 Jest 文档和代码库中现有的模式,以找出最佳实践以及如何实现它。这相当简单,我甚至发现自己很享受测试。但我无论如何都无法可靠地模拟 API 调用。

带标题的狗

文档看起来很清晰,现有代码的模式也很好,但模拟方法实在太多了。现有的测试使用了各种各样的模拟方法,例如jest.genMockFromModule()jest.spyOn()jest.mock()。有时模拟是内联的,有时是变量,有时是以某种神奇的方式从神秘的__mocks__文件夹中导入和导出的。每当我突然对理解充满信心时,我就会交替使用这些技巧,结果却发现自己在不同的方法及其效果之间磕磕绊绊。我完全不知道自己在做什么。

问题

问题是,我甚至还没学会走路就想学跑了。Jest 有很多强大的方法来模拟函数并优化这些模拟,但如果你一开始就不知道如何编写一个简单的模拟,这些方法就毫无用处。虽然 Jest 文档提供了很多很棒的见解和技巧,但我不知道从哪里开始

在本文中,我希望为您提供模拟 API 调用的绝对基础知识,以便您能从我 2020 年的回顾中受益(呵呵)。如果您像我一样因为无法弄清楚如何进行简单的模拟而抓狂,请从这里开始……

(注意:下面的代码是用 Node.js 编写的,但模拟概念也适用于前端 Javascript 和 ES6 模块)

未模拟的代码

我们将测试此getFirstAlbumTitle()功能,它从 API 中获取专辑数组并返回第一张专辑的标题:



// index.js
const axios = require('axios');

async function getFirstAlbumTitle() {
  const response = await axios.get('https://jsonplaceholder.typicode.com/albums');
  return response.data[0].title;
}

module.exports = getFirstAlbumTitle;



Enter fullscreen mode Exit fullscreen mode

...这是我们对该函数进行的初始无模拟测试,它验证该函数是否确实返回列表中第一张专辑的标题:



// index.test.js
const getFirstAlbumTitle = require('./index');

it('returns the title of the first album', async () => {
  const title = await getFirstAlbumTitle();  // Run the function
  expect(title).toEqual('quidem molestiae enim');  // Make an assertion on the result
});


Enter fullscreen mode Exit fullscreen mode

上面的测试虽然完成了它的任务,但实际上它在运行时会向 API 发出网络请求。如果 API 未能完全按照预期运行(例如,列表顺序发生变化、API 宕机、开发机器网络连接中断等),这会导致测试出现各种误报。更何况,在大量测试中发出这些请求会使测试运行速度变得非常缓慢。

但是我们该如何改变这种情况呢?API 请求是通过 axios 作为 的一部分发出的getFirstAlbumTitle()。我们究竟该如何进入函数内部并改变其行为呢?

只需 3 步即可完成模拟

好了,就是这样。这是一个大秘诀,在我努力学习模拟代码的时候,它可以帮我节省大量时间。要在函数中模拟 API 调用,只需执行以下 3 个步骤:

1. 将您想要模拟的模块导入到您的测试文件中。2
.jest.mock()模块
。3. 用于.mockResolvedValue(<mocked response>)模拟响应。

就是这样!

完成此操作后,我们的测试结果如下:



// index.test.js
const getFirstAlbumTitle = require('./index');
const axios = require('axios');

jest.mock('axios');

it('returns the title of the first album', async () => {
  axios.get.mockResolvedValue({
    data: [
      {
        userId: 1,
        id: 1,
        title: 'My First Album'
      },
      {
        userId: 1,
        id: 2,
        title: 'Album: The Sequel'
      }
    ]
  });

  const title = await getFirstAlbumTitle();
  expect(title).toEqual('My First Album');
});



Enter fullscreen mode Exit fullscreen mode

这里发生了什么?

让我们分解一下。这里需要理解的最重要的部分是 import 和jest.mock()



const axios = require('axios');

jest.mock('axios');


Enter fullscreen mode Exit fullscreen mode

当你将一个模块导入到测试文件中,然后在 中调用它时jest.mock(<module-name>),你就可以完全控制该模块中的所有函数,即使它们在另一个导入的函数 中被调用。调用 之后jest.mock('axios'),Jest 立即将 axios 模块中的每个函数替换为空的“模拟”函数,这些函数实际上什么也不做,并返回undefined



const axios = require('axios');
jest.mock('axios')

// Does nothing, then returns undefined:
axios.get('https://www.google.com')

// Does nothing, then returns undefined:
axios.post('https://jsonplaceholder.typicode.com/albums', {
    id: 3,
    title: 'Album with a Vengeance'
})


Enter fullscreen mode Exit fullscreen mode

因此,既然您已经消除了默认行为,您可以用自己的行为替换它……



  axios.get.mockResolvedValue({
    data: [
      {
        userId: 1,
        id: 1,
        title: 'My First Album'
      },
      {
        userId: 1,
        id: 2,
        title: 'Album: The Sequel'
      }
    ]
  });



Enter fullscreen mode Exit fullscreen mode

Jest 插入的模拟替换函数axios恰好带有一大堆很酷的超强方法来控制它们的行为!这里最重要的一个,对于一个简单的初学者模拟来说,是.mockResolvedValue()。当你在模拟方法上调用它时,你传入的任何内容都将成为模拟函数在测试的剩余部分被调用时的默认返回值。简而言之:你可以让axios.get()return 任何你想要的!而且,无论是在测试文件中直接调用,还是作为导入测试的函数的一部分调用,Jest 都会模拟该函数,无论它在哪里被调用!

利用这项新发现的功能,让您的函数准确获得 API 调用所需的功能。无需再担心网络请求的返回结果,只需专注于您的代码在收到响应后执行的操作!

如果您想试用这些示例,请随意使用这个演示存储库:

GitHub 徽标 ZakLaughton / DEMO-simple-api-mocking-with-jest

使用 Jest 的简单 API 模拟示例。

总结

就是这样!这就是模拟另一个模块函数所需的最基本步骤:导入模块,jest.mock()导入模块,然后插入你自己的返回值.mockResolvedValue()

我建议从这里开始,在构建第一个网络调用模拟时仅使用这些技术。一旦你对这里发生的事情有了基本的了解,就可以慢慢开始添加 Jest 中包含的其他强大的模拟功能。

另请参阅:Mocking 模块(Jest 文档)。

补充:另外,请务必在每次测试后运行清除模拟代码jest.resetAllMocks()。这将有助于确保模拟代码不会干扰后续测试。(感谢@mjeffe指出这一点!)


下一步该怎么做

好了,您已经学习了模拟的基础知识,并在几个测试中成功实现了上述策略。您可以像老手一样,为所有 API 调用导入并模拟已解析的值。下一步是什么?

虽然上面描述的方法涵盖了大多数简单的用例,但 Jest 还有很多模拟功能和方法可以实现一些非常强大的功能。你可以逐步添加以下一些概念来增强你的模拟功能:

  1. 查看 Jest 文档中列出的其他模拟函数方法:模拟函数。您可以使用诸如mockReturnedValue()模拟同步返回以及mockResolvedValueOnce()仅在首次调用时返回值之类的方法。
  2. 想要查看模拟函数被调用了多少次、调用方式是什么以及返回了什么?查看mock.callsmock.results属性(同样位于模拟函数文档中)
  3. 您是否有自定义的函数来发出网络请求?您也可以在将自己的模块导入测试文件后对其进行模拟:jest.mock('./path/to/js/module/file')!不过,请注意,您只模拟必要的部分。您的测试应该确保您的函数能够根据给定的模拟输入执行预期的操作,否则很容易编写出仅仅确认您传入了模拟数据的测试。
  4. 想要让函数按照最初编写的方式运行,但仍想查看它被调用了多少次?请查看jest.spyOn()
  5. 发现自己在多个测试中一遍又一遍地模拟同一个函数?__mocks__使用Manual Mocks在文件夹中为其提供默认的模拟响应!

我希望这能帮助其他人避免像我一样浪费时间和感到沮丧!如果这里有什么不明白的地方,请留言,我很乐意解答您的疑问。另外,如果您在学习模仿时还有其他什么方法让您顿悟,也请告诉我!


你觉得这篇文章有用吗?欢迎订阅我下面的文章,或者在 Twitter 上关注我,获取更多开发者技巧和文章公告!

文章来源:https://dev.to/zaklaughton/the-only-3-steps-you-need-to-mock-an-api-call-in-jest-39mb
PREV
10 个实用的 JavaScript 技巧
NEXT
2021 年 JavaScript Web 开发人员应该拥有的每个 VS Code 扩展(带有 gif!)必备扩展(按字母顺序排列)我不再使用的奖励扩展还有其他吗?