【问题标题】:How do I unit test HTTP requests?如何对 HTTP 请求进行单元测试?
【发布时间】:2018-03-13 20:58:36
【问题描述】:

正如标题所问,如何使用 Mocha 和 Chai 测试 HTTP 请求?

我最近开始学习单元测试,但我仍然对测试的某些方面感到困惑。我可以通过返回值的精细测试方法获得,但我对如何测试发出 HTTP/IO 请求的方法感到困惑。

例如我有以下代码:

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

我要测试2个案例:

  1. 向 Google 的请求成功
  2. 对 Google 的请求失败

我是否必须模拟这些请求?如果需要,模拟旨在发出 HTTP 请求的方法的目的是什么?

【问题讨论】:

  • 模拟 http.get 怎么样?
  • 目的是不依赖网络一致性来验证单元测试。与此相反的(?)是集成测试。
  • 谢谢@aurelienshz。这是让我有点困惑的部分。如果服务器无法访问怎么办?模拟如何帮助那里?这是一个集成测试吗?
  • @zero298 确切地说,这就是为什么我建议使用存根来模拟 http.get,这样他就不会依赖 Google 响应是成功还是错误来检查模块的行为测试。
  • 还有@BugHunterUK,请注意resp.headers['content-type'] 不是您回复的状态。您可能正在那里寻找 resp.status。

标签: node.js unit-testing mocha.js chai


【解决方案1】:

用类似的东西模拟 http.get 怎么样?

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

那么您的测试可能如下所示:

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

这将满足良好单元测试的基本约定:无论外部模块和依赖项如何,它都能确保您当前正在测试的模块在每种可能的情况下都具有正确的行为。

【讨论】:

    【解决方案2】:

    我以前用过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);
        });
      });
    });
    

    【讨论】:

      【解决方案3】:

      试试Mockttp(免责声明:我是 Mockttp 的维护者)。

      Mockttp 为您提供了一个真正的本地 HTTP 服务器和代理,您可以轻松配置和使用它来进行测试,正是这些情况。

      您可以使用 localhost URL 直接向 Mockttp 发出请求,或者继续向 google.com 发出请求但使用 Mockttp 作为代理,然后配置您想要模拟的任何响应(或失败的连接/超时) .

      自述文件中有一些示例可以准确显示您正在寻找的内容:https://www.npmjs.com/package/mockttp#get-testing。在您的具体情况下,我会尝试类似:

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

      配置代理将取决于您使用的库,但对于 http.get https://www.npmjs.com/package/global-tunnel 应该这样做,或者您可以将 { proxy: mockServer.url } 选项直接传递给 http.get 调用。或者,当然你可以使http://google.com URL 可配置,将其设置为mockServer.url,然后模拟/ 而不是http://google.com

      关键区别在于这是一个真正的集成测试。您不是在 JS 级别进行模拟,而是发送真正的 HTTP 请求,测试真正发送和接收的内容,以及您的完整 Node + 应用程序代码在实践中将如何响应。

      您可以完全在 JS 级别进行模拟,并且您的测试会稍微快一些(在每个测试 2 毫秒和 10 毫秒的范围内),但是很容易得到不准确的结果,并且通过了测试但破坏了实际功能.

      【讨论】:

        【解决方案4】:

        这是一个使用mochachai 进行测试的示例。我们还需要sinon 来存根http 库。

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

        希望对您有所帮助。

        【讨论】:

          猜你喜欢
          • 2023-03-20
          • 2020-08-13
          • 1970-01-01
          • 2017-06-05
          • 2014-10-11
          • 1970-01-01
          • 2014-08-20
          • 2015-06-01
          • 2013-06-10
          相关资源
          最近更新 更多