node 单元测试_如何在Node中模拟对单元测试的请求

node 单元测试

by Edo Rivai

由Edo Rivai

如何在Node中模拟对单元测试的请求 (How to mock requests for unit testing in Node)

Let’s say you’ve decided to test your codebase, and you’ve read that unit and integration tests shouldn’t perform I/O. You’ve figured you need to mock out the outbound HTTP requests that your app is making, but you’re not sure where to start.

假设您已经决定测试您的代码库 ,并且已经阅读了单元测试和集成测试不应执行I / O的操作 。 您已经确定需要模拟应用程序发出的出站HTTP请求,但是不确定从哪里开始。

I decided to ask Kent C. Dodds on twitter how he approaches HTTP mocking:

我决定在Twitter上询问Kent C. Dodds,他如何处理HTTP模拟:

Fair enough, Kent! I guess this topic is worth a more elaborate write-up.

公平,肯特! 我想这个主题值得更详尽的撰写。

TL; DR (TL;DR)

When you need to test code that sends out HTTP requests, try the following.

当您需要测试发送HTTP请求的代码时,请尝试以下操作。

  1. Split up the HTTP requests from your business logic of processing the response. Very often the code that handles the HTTP level protocol stuff is not very interesting, and arguably doesn’t require testing. Use your mocking tool of choice to mock out your API wrapper.

    从处理响应的业务逻辑中分离HTTP请求。 通常,处理HTTP级别协议内容的代码不是很有趣,并且可以说不需要测试。 使用您选择的模拟工具来模拟您的API包装器。
  2. If you really need to test HTTP-specific code, and the response from the external API is relatively simple, use Nock, and manually mock out requests.

    如果您确实需要测试特定于HTTP的代码,并且来自外部API的响应相对简单,请使用Nock并手动模拟请求。
  3. If the response you need to test against is fairly complex, use nock-record to record a response once, and use that recording for subsequent tests.

    如果您需要测试的响应相当复杂,请使用nock-record一次记录一次响应,然后将该记录用于后续测试。

Since the testing community is obsessed with pyramids, here ya go:

由于测试社区着迷于金字塔,因此请继续:

输入Nock (Enter Nock)

I would say that the general consensus in NodeJS land is to use nock , which works by patching Node’s native http module. This works really well, because even if you’re not using the http module directly, most user-land libraries like axios, superagent , and node-fetch still use http under the hood.

我想说,NodeJS领域的普遍共识是使用nock ,它通过修补Node的本机http模块来工作。 这非常有效,因为即使您没有直接使用http模块,大多数用户级库(例如axiossuperagentnode-fetch仍然在axios使用http

Writing and using a Nock looks like this:

编写和使用Nock如下所示:

// Set up an interceptornock('http://www.example.com')  .post('/login', 'username=pgte&password=123456')  .reply(200, { id: '123ABC' });
// Run your code, which sends out a requestfetchUser('pgte', '123456');

In the above example, fetchUser will send a POST request to example.com/login . Nock will intercept the request, and immediately respond with your predefined response, without actually hitting the network. Awesome!

在上面的示例中, fetchUser会将POST请求发送到example.com/login 。 Nock会拦截该请求,并立即以您的预定义响应进行响应,而无需实际访问网络。 太棒了!

没那么简单 (It’s not that simple)

When I first got started with Nock, I eagerly started using it with my unit tests. However, I was quickly getting the feeling that I was spending more time writing Nocks than actually testing business logic. One solution to this is to split your requesting code from your business logic. Let’s look at some code.

当我第一次开始使用Nock时,我热切地开始在单元测试中使用它。 但是,我很快感觉到,与实际测试业务逻辑相比,我花更多的时间编写Nocks。 一种解决方案是将请求代码与业务逻辑分开 。 让我们看一些代码。

async function getUser(id) {  const response = await fetch(`/api/users/${id}`);    // User does not exist  if (response.status === 404) return null;
// Some other error occurred  if (response.status > 400) {    throw new Error(`Unable to fetch user #${id}`);  }    const { firstName, lastName } = await response.json();  return {    firstName,    lastName,    fullName: `${firstName} ${lastName}`  };}

The above code sends out a request to /api/users/<user id>, and when a user is found, it receives an object containing a firstName and lastName. Finally, it constructs an object, which has an additional field fullName, which is computed from the first and last name received from the request.

上面的代码向/api/users/<user ID>发送一个请求,当找到一个用户时,它接收到一个对象,该对象包含ing a fir第一名称e and la ing a fir第一名称。 最后,它构造一个对象,该对象具有一个附加field fu llName,该field fu根据从请求中收到的名字和姓氏计算得出。

A test suite for this function could look like this:

此功能的测试套件如下所示:

it('should properly decorate the fullName', async () => {  nock('http://localhost')    .get('/api/users/123')    .reply(200, { firstName: 'John', lastName: 'Doe });    const user = await getUser(123);  expect(user).toEqual({    firstName: 'John',    lastName: 'Doe,    fullName: 'John Doe'  });});
it('should return null if the user does not exist', async () => {  nock('http://localhost')    .get('/api/users/1337')    .reply(404);    const user = await getUser(1337);  expect(user).toBe(null);});
it('should return null when an error occurs', async () => {  nock('http://localhost')    .get('/api/users/42')    .reply(404);    const userPromise = getUser(42);  expect(userPromise).rejects.toThrow('Unable to fetch user #42');});

As you can see, there is quite a lot going on in these tests. Let’s split the function up into two parts:

如您所见,这些测试中正在进行很多工作。 让我们将函数分为两部分:

  • code that sends and handles the HTTP request

    发送和处理HTTP请求的代码
  • our business logic

    我们的业务逻辑

Our example is a bit contrived, as the only business logic that we have is to “compute” the fullName . But you can imagine how a real-world app would have more complex business logic.

我们的示例有些人为,因为我们唯一的业务逻辑是“计算” fullName 。 但是您可以想象现实中的应用程序将如何具有更复杂的业务逻辑。

// api.jsexport async function getUserFromApi(id) {  const response = await fetch(`/api/users/${id}`);    // User does not exist  if (response.status === 404) return null;
// Some other error occurred  if (response.status > 400) {    throw new Error(`Unable to fetch user #${id}`);  }
return response.json();}
// user.jsimport { getUserFromApi } from './api';
async function getUserWithFullName(id) {  const user = await getUserFromApi(id);  if (!user) return user;
const { firstName, lastName } = user;  return {    firstName,    lastName,    fullName: `${firstName} ${lastName}`  };}

For the sake of not boring you to death, I’m only going to show you the tests for our business logic. Instead of using Nock to mock out the HTTP request, you can now use your mocking library of choice to mock out our own API wrapper. I prefer Jest, but this pattern is not tied to any specific mocking library.

为了不让您感到无聊,我仅向您展示我们的业务逻辑测试。 现在,您可以使用选择的模拟库来模拟我们自己的API包装器,而不必使用Nock来模拟HTTP请求。 我更喜欢Jest ,但是这种模式不依赖于任何特定的模拟库。

// The function we're testingimport { getUserWithFullName } from './user';
// Only imported for mockingimport { getUserFromApi } from './api';
jest.mock('./api');
it('should properly decorate the fullName', async () => {  getUserFromApi.mockResolvedValueOnce(    { firstName: 'John', lastName: 'Doe }  );    const user = await getUserWithFullName(123);  expect(user).toEqual({    firstName: 'John',    lastName: 'Doe,    fullName: 'John Doe'  });});
it('should return null if the user does not exist', async () => {  getUserFromApi.mockResolvedValueOnce(null);    const user = await getUserWithFullName(1337);  expect(user).toBe(null);});

As you can see, our tests look a bit cleaner. All of the HTTP overhead is now contained within the API module. What we’ve effectively done is minimize the surface of our code that knows about the HTTP transport. And in doing so, we minimize the need for using Nock in our tests.

如您所见,我们的测试看起来更加干净。 现在,所有HTTP开销都包含在API模块内。 我们有效完成的工作是最大程度地减少了解HTTP传输的代码表面。 因此,我们将在测试中使用Nock的需求降到最低。

但是HTTP逻辑正是我要测试的! (But the HTTP logic is exactly what I want to test!)

I hear you. Sometimes the connection to an external API is exactly what you want to test.

我听到你了 有时,与外部API的连接正是您要测试的。

I’ve already shown how you can use Nock to mock out a very basic HTTP request. Writing explicit Nocks for such simple request/response pairs is very effective, and I would recommend sticking to it as much as possible.

我已经展示了如何使用Nock模拟一个非常基本的HTTP请求。 为这样简单的请求/响应对编写显式Nocks非常有效,我建议尽可能坚持。

However, sometimes the content of the request or the response can get fairly complex. Writing manual Nocks for such cases quickly becomes tedious, and also brittle!

但是,有时请求或响应的内容会变得相当复杂。 在这种情况下编写Nocks手册很快就会变得乏味,而且很脆弱!

A very clear example of such a case would be testing a scraper. The main responsibility of a scraper is to convert raw HTML into useful data. However, when testing your scraper, you don’t want to be manually constructing an HTML page to feed into Nock. Moreover, the site that you intend to scrape already has the HTML that you want to process, so let’s make use of that! Think Jest Snapshots, for HTTP mocking.

这种情况的一个非常明显的例子是测试刮板。 刮板的主要职责是将原始HTML转换为有用的数据。 但是,在测试刮板时,您不想手动构造HTML页面以馈入Nock。 此外,您要抓取的站点已经具有要处理HTML,因此让我们利用它吧! 考虑一下Jest快照,以进行HTTP模拟。

从中刮取主题 (Scraping topics from Medium)

Let’s say I want to know all of the topics that are available on Medium.

假设我想了解Medium上所有可用的主题。

We’ll use scrape-it to request the Medium homepage, and extract the texts from all elements that match .ds-nav-item :

我们将使用scrape-it来请求Medium主页,并从与.ds-nav-item匹配的所有元素中提取文本:

import scrapeIt from "scrape-it";
export function getTopics() {  return scrapeIt("https://medium.com", {    topics: {      listItem: ".ds-nav-item"    }  }).then(({ data }) => data.topics);}
// UsagegetTopics().then(console.log);// [ 'Home', 'Tech', 'Culture', 'Entrepreneurship', 'Self', 'Politics', 'Media', 'Design', 'Science', 'Work', 'Popular', 'More' ]

? Looking good!

? 看起来不错!

Now how would we go about mocking the actual request in our test? One way to achieve this would be to go to medium.com in our browser, view-source, and copy/paste that into a Nock manually. That is tedious and error-prone. If we really want the entire HTML document, we might just as well let the computer handle that for us.

现在,我们将如何模拟测试中的实际请求? 实现此目的的一种方法是在我们的浏览器中转到medium.com,查看源代码,然后将其手动复制/粘贴到Nock中。 那是乏味且容易出错的。 如果我们真的想要整个HTML文档,我们最好还是让计算机为我们处理。

It turns out Nock has a built-in mechanism called “Recording”. This let’s you use the Nock interceptors to intercept actual HTTP traffic, then store the request/response pair in a file, and use that recording for future requests.

事实证明, 诺克具有称为“记录” 的内置机制 。 这让您使用Nock拦截器拦截实际的HTTP流量,然后将请求/响应对存储在文件中,并使用该记录 对于将来的要求。

Personally, I found the functionality of Nock recordings very useful, but the ergonomics of it could be improved upon. So here’s my shameless plug for nock-record , a more ergonomic library to leverage recordings:

就个人而言,我发现Nock录音的功能非常有用,但是可以改进其人体工程学。 因此,这是我nock-record无耻插件 ,它是一个更符合人体工程学的库,可以利用录音:

Let’s see how we could test our scraper using nock-record :

让我们看看如何使用nock-record测试刮板:

import { setupRecorder } from 'nock-record';import { getTopics } from './index';
const record = setupRecorder();
describe('#getTopics', () => {  it('should get all topics', async () => {    // Start recording, specify fixture name    const { completeRecording } = await record('medium-topics');
// Our actual function under test    const result = await getTopics();        // Complete the recording, allow for Nock to write fixtures    completeRecording();    expect(result).toEqual([      'Home',      'Tech',      'Culture',      'Entrepreneurship',      'Self',      'Politics',      'Media',      'Design',      'Science',      'Work',      'Popular',      'More'    ]);  });});

The first time we run this test, it will send out the actual request to fetch the HTML of the Medium homepage:

我们第一次运行此测试时,它将发出实际请求以获取Medium主页HTML:

✓ should get all topics (1163ms)

After that first run, nock-record has saved the recording to a file at__nock-fixtures__/medium-topics.json . For the second run, nock-record will automatically load the recording, and setup a Nock for you.

第一次运行后, nock-record已将nock-record保存到__nock-fixtures__/medium-topics.json 。 对于第二次运行, nock-record将自动加载记录,并为您设置一个Nock。

✓ should get all topics (116ms)

If you’ve used Jest snapshots before, this workflow will feel very familiar to you.

如果您以前使用过Jest快照,那么您对这个工作流程会非常熟悉。

We have now gained 3 things by leveraging recordings:

现在,利用录音,我们获得了三点好处:

  1. Deterministic: your test will always run against the same HTML document

    确定性的:您的测试将始终针对同一HTML文档运行
  2. Fast: subsequent tests will not hit the network

    快速:后续测试不会影响网络
  3. Ergonomic: no need to manually juggle response fixtures

    人体工程学:无需手动处理响应装置

让我知道你的想法 (Let me know what you think)

The approach that I’ve outlined in this article has worked well for me. I’d love to hear about your experience in the comments or on twitter: @EdoRivai .

我在本文中概述的方法对我来说效果很好。 我很想听听您在评论中或在Twitter上的经历: @EdoRivai

Same goes for nock-record; issues and PR’s are welcome!

nock-record问题公关都欢迎!

翻译自: https://www.freecodecamp.org/news/how-to-mock-requests-for-unit-testing-in-node-bb5d7865814a/

node 单元测试

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值