【问题标题】:How to mock http.Client Do method如何模拟 http.Client Do 方法
【发布时间】:2017-04-05 20:24:40
【问题描述】:

我正在尝试寻找一种解决方案来编写测试和模拟 HTTP 响应。 在我接受接口的函数中:

type HttpClient interface {
    Do(req *http.Request) (*http.Response, error)
}

我使用基本身份验证发出 http 获取请求

func GetOverview(client HttpClient, overview *Overview) (*Overview, error) {

    request, err := http.NewRequest("GET", fmt.Sprintf("%s:%s/api/overview", overview.Config.Url, overview.Config.Port), nil)
    if (err != nil) {
        log.Println(err)
    }
    request.SetBasicAuth(overview.Config.User, overview.Config.Password)
    resp, err := client.Do(request)

如何模拟这个 HttpClient? 我正在寻找模拟库,例如:https://github.com/h2non/gock 但只有获取和发布的模拟

也许我应该换一种方式。 我会很感激你的建议

【问题讨论】:

    标签: unit-testing testing go mocking


    【解决方案1】:

    任何具有与您在接口中的签名匹配的方法的结构都将实现该接口。例如,您可以创建一个结构 ClientMock

    type ClientMock struct {
    }
    

    用方法

    func (c *ClientMock) Do(req *http.Request) (*http.Response, error) {
        return &http.Response{}, nil
    }
    

    然后您可以将这个 ClientMock 结构注入到您的 GetOverview 函数中。 Here 是 Go Playground 中的一个示例。

    【讨论】:

      【解决方案2】:

      net/http/httptest 包是你最好的朋友:

      // generate a test server so we can capture and inspect the request
      testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
          res.WriteHeader(scenario.expectedRespStatus)
          res.Write([]byte("body"))
      }))
      defer func() { testServer.Close() }()
      
      req, err := http.NewRequest(http.MethodGet, testServer.URL, nil)
      assert.NoError(t, err)
      
      res, err := http.DefaultClient.Do(req)
      assert.NoError(t, err)
      assert.Equal(t, scenario.expectedRespStatus, res.StatusCode, "status code should match the expected response")
      

      【讨论】:

      • 我不明白如何使用它来确认函数调用 http.DefaultClient.Do 具有特定的外部 URL。这可能吗?
      • 不,你不能。在这种情况下,您需要创建一个包装 INTERFACE 并在顶部进行模拟。我经常用这个github.com/vektra/mockery
      • @rocketspacer:我遇到httpmock 的问题,当用于通过并行化测试的 ginkgo 运行测试以及代码包含多个 goroutine 时会导致“数据竞争”情况。使用http.DefaultClient 线程安全吗?谢谢!
      • 根据 Go 文档 http.Client 是并发安全的 golang.org/pkg/net/http/#Client
      【解决方案3】:

      您必须使用与接口匹配的方法创建一个结构。模拟通常用于测试目的,因此人们希望能够准备模拟方法的返回值。为了实现这一点,我们创建了具有 func 属性的结构体 对应于方法。

      你的界面是这样的:

      type HttpClient interface {
          Do(req *http.Request) (*http.Response, error)
      }
      

      等效模拟:

      type MockClient struct {
          DoFunc func(req *http.Request) (*http.Response, error)
      }
      
      func (m *MockClient) Do(req *http.Request) (*http.Response, error) {
          if m.DoFunc != nil {
              return m.DoFunc(req)
          }
          return &http.Response{}, nil
      }
      

      然后,下一步是编写一些测试。示例here

      【讨论】:

      • 如何使用这种方法验证多个场景?假设我想测试多个返回码和错误。
      • @rajk 您可以覆盖DoFunc 方法以表现不同。请注意,您不应该使用这种方法并行运行测试。
      【解决方案4】:

      我知道这已经有一段时间了,但我最近写了一些东西来帮助解决这个问题。

      一般来说,为了模拟 HTTP 请求,我建议在本地启动一个真正的 HTTP 服务器,因为在 Go 中这很容易做到。 https://golang.org/pkg/net/http/httptest/ 是一种非常标准的方法(参见服务器类型下给出的示例代码)。

      然而,在完成了很多 HTTP 模拟之后,我想要做更多的事情,比如一个好的模拟库:轻松设置期望、验证所有请求都已发出等。我通常使用 https://godoc.org/github.com/stretchr/testify/mock 进行模拟并且想要这样的功能。

      于是我写了https://github.com/dankinder/httpmock,基本上把两者结合了。

      【讨论】:

      • 虽然添加指向参考的链接是个好主意,但重要的是答案中有足够的上下文,以便在将来链接失效时它可以独立存在。添加一两个代码示例肯定会对此有所帮助,并防止您的答案在审核期间被删除。
      猜你喜欢
      • 2010-10-19
      • 2021-09-04
      • 2012-07-29
      • 2022-09-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多