简体   繁体   English

如何对 HTTP 请求进行单元测试?

[英]How do I unit test HTTP requests?

As the title asks how do I test HTTP requests with Mocha and Chai?正如标题所问我如何使用 Mocha 和 Chai 测试 HTTP 请求?

I've recently started learning unit testing and I'm still getting confused with certain aspects of testing.我最近开始学习单元测试,但我仍然对测试的某些方面感到困惑。 I can get by fine testing methods that return a value but I'm confused on how to test methods that make HTTP/IO requests.我可以通过返回值的精细测试方法获得,但我对如何测试发出 HTTP/IO 请求的方法感到困惑。

For example I have the following code:例如,我有以下代码:

module.exports = someRequest => new Promise((resolve, reject) => 
    http.get('http://google.com', resp => {
        if(resp.headers['content-type'] !== 200) {
            reject(new Error('Failed to connect to Google'));
        }
        resolve('Connected to Google');
    })
);

I want to test 2 cases:我想测试两种情况:

  1. The request to Google succeed向 Google 的请求成功
  2. The request to Google failed对 Google 的请求失败

Do I have to mock these requests and if so what's the purpose of mocking a method that is intended to make a HTTP request?我是否必须模拟这些请求,如果是这样,模拟旨在发出 HTTP 请求的方法的目的是什么?

How about mocking http.get with something like that ?用类似的东西嘲笑 http.get 怎么样?

const createHttpGetMock = (expectedStatus) => {
  return httpGetMock = (address) => {
    return new Promise((resolve, reject) => {
      resolve({
        status: expectedStatus,
        headers: {
          // ... headers
        },
        // mock response
      })
    })
  }
}

Then your test could look like this :那么您的测试可能如下所示:

describe("Google request", () => {
  it("Resolves when google responds", async () => {
    const httpSuccessMock = createHttpGetMock(200);
    // Inject your mock inside your request function here, using your favorite lib

    const message = await fetchGoogle();
    assert.equals(message, 'Connected to Google');
  })

  it("Rejects when google responds with error", async () => {
    const httpSuccessMock = createHttpGetMock(500);
    // Inject your mock inside your request function here, using your favorite lib

    const message = await fetchGoogle();
    assert.equals(message, 'Failed to connect to Google');
  })
});

This would fulfill the basic contract of a good unit test : regardless of external modules and dependencies, it ensures the module you're currently testing has the correct behaviour in each possible case.这将满足良好单元测试的基本契约:不管外部模块和依赖项如何,它确保您当前正在测试的模块在每种可能的情况下都具有正确的行为。

I've used supertest in the past and was very happy with it我过去使用过supertest并且对它非常满意

import server from '../src/server';
import Request from 'supertest';

describe('Server', () => {
  const request = Request(server());

  describe('/api', () => {
    it('Should return a 404 when invalid route', done => {
      request
        .post('/api/notfound')
        .expect(404)
        .end(done);
    });
  });
});

Try Mockttp (disclaimer: I'm the maintainer of Mockttp).尝试Mockttp (免责声明:我是 Mockttp 的维护者)。

Mockttp gives you a real local HTTP server & proxy that you can easily configure & use for testing, for exactly these kind of cases. Mockttp 为您提供了一个真正的本地 HTTP 服务器和代理,您可以轻松配置和用于测试,正是这些情况。

You can either make requests to Mockttp directly, using a localhost URL, or keep making requests to google.com but use Mockttp as a proxy, and then configure whatever responses (or failed connections/timeouts) you'd like to simulate.您可以使用本地主机 URL 直接向 Mockttp 发出请求,或者继续向 google.com 发出请求但使用 Mockttp 作为代理,然后配置您想要模拟的任何响应(或失败的连接/超时)。

There's some examples in the README that show exactly what you're looking for: https://www.npmjs.com/package/mockttp#get-testing . README 中有一些示例可以准确显示您要查找的内容: https : //www.npmjs.com/package/mockttp#get-testing In your specific case, I'd try something like:在您的具体情况下,我会尝试以下操作:

const makeRequest = require("./your-code");
const mockServer = require("mockttp").getLocal();

describe("makeRequest", () => {
    beforeEach(() => mockServer.start());
    afterEach(() => mockServer.stop());

    it("resolves happily for successful requests", () => {
        await mockServer.get("http://google.com").thenReply(200, "A mocked response");

        // Here: configure your code to use mockServer.url as a proxy

        let result = await makeRequest();

        expect(response).to.equal('Connected to google');
    });

    it("rejects failed requests with an error", () => {
        await mockServer.get("http://google.com").thenReply(500, "An error!");

        // Here: configure your code to use mockServer.url as a proxy

        let result = makeRequest();

        expect(response).to.be.rejectedWith('Failed to connect to Google');
    });
});

Configuring the proxy will depend on the libraries you're using, but for http.get https://www.npmjs.com/package/global-tunnel should do it, or you can pass a { proxy: mockServer.url } option to the http.get call directly.配置代理将取决于您使用的库,但对于 http.get https://www.npmjs.com/package/global-tunnel应该这样做,或者您可以传递{ proxy: mockServer.url }选项直接到http.get调用。 Or of course you could make the http://google.com URL configurable, set that to mockServer.url , and mock / instead of http://google.com .或者,当然您可以使http://google.com URL 可配置,将其设置为mockServer.url ,并模拟/而不是http://google.com

The key difference is that this is an real integration test .关键的区别在于这是一个真正的集成测试 You're not mocking at the JS level, instead you're sending real HTTP requests, testing what will really be sent and received, and exactly how your full Node + application code will respond to that in practice.您不是在 JS 级别进行嘲笑,而是发送真实的 HTTP 请求,测试将真正发送和接收的内容,以及您的完整 Node + 应用程序代码在实践中将如何响应。

You can mock entirely at the JS level, and your tests will be marginally quicker (in the region of 2ms vs 10ms per test), but it's very easy to get inaccurate results, and have passing tests but broken real-world functionality.您可以完全在 JS 级别进行模拟,并且您的测试会稍微快一点(在每个测试 2 毫秒和 10 毫秒的范围内),但是很容易得到不准确的结果,并且通过测试但破坏了现实世界的功能。

Here is an example of test with mocha and chai .这是一个使用mochachai进行测试的例子。 We also need sinon to stub the http library.我们还需要sinon来存根 http 库。

http://sinonjs.org/releases/v1.17.7/stubs/ http://sinonjs.org/releases/v1.17.7/stubs/

// test.js

const chai = require('chai');
const expect = chai.expect;
const sinon = require('sinon');
const http = require('http');

const someRequest = require('./index');

describe('some request test', function() {
  let httpGetStub;

  beforeEach(function() {
    httpGetStub = sinon.stub(http, 'get'); // stub http so we can change the response
  });

  afterEach(function() {
    httpGetStub.restore();
  });

  it('responses with success message', function() {
    httpGetStub.yields({ headers: { 'content-type': 200 }}); // use yields for callback based function like http.get
    return someRequest().then(res => { 
      expect(res).to.equal('Connected to Google');      
    });
  });

  it('rejects with error message', function() {
    httpGetStub.yields({ headers: { 'content-type': 400 }});
    return someRequest().catch(err => { 
      expect(err.message).to.equal('Failed to connect to Google');      
    });
  });
})

Hope it helps.希望它有帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM