如何模拟 http.Client 的 Do 方法?

huangapple go评论88阅读模式
英文:

How to mock http.Client Do method

问题

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

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

我使用基本身份验证进行HTTP GET请求:

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,但只有对Get和Post的模拟。

也许我应该用另一种方式来做。我将非常感谢您的建议。

英文:

I'm trying to find a solution to write test and mock HTTP response.
In my function where I accept interface:

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

I makes http get request with base auth

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)

How can I mock this HttpClient?
I'm looking for mock library, for instance: https://github.com/h2non/gock
but there is only mock for Get and Post

Maybe I should do it in a different way.
I'll be grateful for advice

答案1

得分: 65

任何具有与接口中定义的方法签名匹配的结构体都可以实现该接口。例如,你可以创建一个名为ClientMock的结构体:

type ClientMock struct {
}

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

然后,你可以将这个ClientMock结构体注入到你的GetOverview函数中。这里是在Go Playground中的一个示例。

英文:

Any struct with a method matching the signature you have in your interface will implement the interface. For example, you could create a struct ClientMock

type ClientMock struct {
}

with the method

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

You could then inject this ClientMock struct into your GetOverview func. Here's an example in the Go Playground.

答案2

得分: 57

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

// 生成一个测试服务器,以便我们可以捕获和检查请求
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, "状态码应与预期响应匹配")

英文:

The net/http/httptest package is your best friend:

// 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")

答案3

得分: 9

你需要创建一个具有与接口匹配的方法的结构体。模拟通常用于测试目的,因此人们希望能够准备模拟方法的返回值。为了实现这一点,我们创建一个具有与方法对应的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
}

接下来,你需要编写一些测试。可以在这里找到一个示例。

英文:

You have to create a struct with methods that match interface. Mocks are commonly used for testing purposes, therefore people want the ability to prepare return values of mock methods. To achieve this, we create struct with func attributes corresponding to methods.

As your interface is:

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

Equivalent mock:

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
}

Then, next step is to write some tests. Example here.

答案4

得分: 3

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

通常,为了模拟HTTP请求,我建议在本地启动一个真实的HTTP服务器,因为在Go语言中这很容易实现。https://golang.org/pkg/net/http/httptest/ 是一个相当标准的方法(请参考Server类型下给出的示例代码)。

然而,由于我经常进行HTTP模拟,我希望有一些更多的功能,就像一个好的模拟库一样:方便设置期望值,验证所有请求是否都被执行等等。我通常使用https://godoc.org/github.com/stretchr/testify/mock 进行模拟,并希望有类似的功能。

所以我写了https://github.com/dankinder/httpmock,它基本上结合了这两个功能。

英文:

I know it's been a little while but I just wrote something to help with this recently.

Generally to mock HTTP requests I recommend starting up a real HTTP server locally, since in Go this is easy to do. https://golang.org/pkg/net/http/httptest/ is a pretty standard way to do that (see the example code given under the Server type).

However having done a lot of HTTP mocking I wanted something that does a little more, like a good mock library would: easy setting of expectations, validation that all requests were made, etc. I have generally used https://godoc.org/github.com/stretchr/testify/mock for mocking and wanted features like that.

So I wrote https://github.com/dankinder/httpmock, which basically combines the two.

答案5

得分: 0

如果你正在使用http库中的Client,我建议使用RoundTripper来完成这个任务。

首先,你需要定义一个RoundTripper:

type mockRoundTripper struct {
	response *http.Response
}

func (rt *mockRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
	return rt.response, nil
}

你的单元测试代码应该如下所示:

func TestGetOverview(t *testing.T) {
    // 设置响应
	json := `{"code": 0, "message": "success"}`
	recorder := httptest.NewRecorder()
	recorder.Header().Add("Content-Type", "application/json")
	recorder.WriteString(json)
	expectedResponse := recorder.Result()

    // 创建一个HTTP客户端
	client := http.Client{Transport: &mockRoundTripper{expectedResponse}}

    // 调用你的函数
	overview, err := yourlib.GetOverview(client, &yourlib.Overview{})
    ....
}

这里有一个简单的示例:
https://go.dev/play/p/wtamTRahsZX

英文:

If you are using the Client from http library, I would prefer using a RoundTripper to do this.

You should define a RoundTripper first

type mockRoundTripper struct {
	response *http.Response
}

func (rt *mockRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
	return rt.response, nil
}

You unittest test would look like this:

func TestGetOverview(t *testing.T) {
    // Set up your response
	json := `{"code": 0, "message": "success"}`
	recorder := httptest.NewRecorder()
	recorder.Header().Add("Content-Type", "application/json")
	recorder.WriteString(json)
	expectedResponse := recorder.Result()

    // Create an HTTP client
	client := http.Client{Transport: &mockRoundTripper{expectedResponse}}

    // Call your function
	overview, err := yourlib.GetOverview(client, &yourlib.Overview{})
    ....
}

Here is a simple example:
https://go.dev/play/p/wtamTRahsZX

huangapple
  • 本文由 发表于 2017年4月6日 04:24:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/43240970.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定