zzbozheng

2018-07-06 11:23

使用 Nock 来模拟 http 请求响应

本文作者:IMWeb zzbozheng 原文出处:IMWeb社区 未经同意,禁止转载

nock 是前端常用来模拟http请求响应的工具,它基于nodejs的原生http模块,并且他可以让我们写一些轻逻辑的代码,我们先看一个简单的例子:

nock('http://www.example.com')
  .post('/login', 'username=pgte&password=123456')
  .reply(200, { id: '123ABC' });

fetchUser('pgte', '123456');

上面的例子中fetchUser会发出一个post请求到example.com/login. Nock将会拦截这个请求并立即返回你预先定义好的响应。

当我第一次开始使用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}`
  };
}

/api/users/<用户ID>发送请求,当处理完响应结果返回一个 firstName 和 lastName 的对象。 此功能的测试代码可能如下所示:

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');
});

上面的测试代码主要分为两部分:

  • 发送和处理HTTP请求
  • 业务逻辑
// api.js
export 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.js
import { 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}`
  };
}

使用正则匹配hostname

var scope = nock(/\.qq\.com$/)
    .get('/resource')
    .reply(200, 'domain regex matched');

path 可以通过正则或过滤函数

var scope = nock('http://www.example.com')
    .get(/source$/)
    .reply(200, 'path using regex matched');
var scope = nock('http://www.example.com')
    .get(function(uri) {
      return uri.indexOf('cats') >= 0;
    })
    .reply(200, 'path using function matched');

请求响应可以使用回调函数

var scope = nock('http://www.google.com')
   .filteringRequestBody(/.*/, '*')
   .post('/echo', '*')
   .reply(201, function(uri, requestBody) {
     return requestBody;
   });
var scope = nock('http://www.google.com')
   .filteringRequestBody(/.*/, '*')
   .post('/echo', '*')
   .reply(201, function(uri, requestBody, cb) {
     fs.readFile('cat-poems.txt' , cb); // Error-first callback
   });

最后

你也可以使用您选择的模拟库来模拟我们自己的API包装器,而不是使用Nock来模拟HTTP请求。 我更喜欢Jest,但这种模式并不依赖于任何特定的模拟库。

import { getUserFromApi } from './api';
jest.mock('./api');  

it('should properly decorate the fullName', async () => {
  getUserFromApi.mockResolvedValueOnce(
    { 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 () => {
  getUserFromApi.mockResolvedValueOnce(null);

  const user = await getUser(1337);
  expect(user).toBe(null);
});

这样的测试看起来更简洁。 所有HTTP开销现在都包含在API模块中。 我们已经最小化地完成了HTTP传输,最大限度地减少了使用 Nock 来测度 。

0条评论

    您需要 注册 一个IMWeb账号或者 才能进行评论。