如何模拟返回JSON响应的http.Client

8

我正在尝试测试使用net/http进行请求的方法。具体来说,我要实现的是注入一个模拟的http.Client,它会响应特定的JSON主体。

type clientMock struct{}

func (c *clientMock) Do(req *http.Request) (*http.Response, error) {
  json := struct {
    AccessToken string `json:"access_token`
    Scope       string `json:"scope"`
  }{
    AccessToken: "123457678",
    Scope:       "read, write",
  }
  body := json.Marshal(json)
  res := &http.Response {
    StatusCode: http.StatusOK,
    Body:       // I haven't got a clue what to put here
  }
  return res
}

func TestRequest(t *testing.T) { //tests here }

我知道Body是一个类型为io.ReadCloser接口的对象。问题在于我无法找到一种方法来在模拟响应体中实现它。
目前在这里找到的示例仅演示了返回空白的&http.Response{}

这可能更容易(也更全面)通过模拟服务而不是客户端进行测试。请查看httptest.Server - Adrian
向模拟服务器发出真实请求。看看stdlib如何做,并使用net/http/httptest。 - Volker
你可以使用ioutil.NopCloser(bytes.NewReader(body))来设置响应的Body字段。 - mkopriva
4个回答

14

虽然使用httptest.Server模拟完整的请求周期可能更有用,但是你可以使用ioutil.NopCloser来创建任意读取器的关闭器:

Body: ioutil.NopCloser(bytes.NewReader(body))

如果你想要一个空的主体,只需要提供一个没有内容的读取器。

Body: ioutil.NopCloser(bytes.NewReader(nil))

我进行了一些深入研究,特别是httptest.Server中的ts:=httptest.NewServer(http.HandlerFunc(handler))。它给了我一个ts.URL属性,我可以用它来指向我的测试请求。问题是我的URL是在我正在测试的方法上生成的。我可以将URL的主机名部分传递给该方法,但问题是我的方法在URL前面添加了https://,但httptest.NewServer()似乎无法使用https://。我还尝试过httptest.NewTLSServer(),但仍然没有成功。你有什么想法吗?@JimB - George Ananda Eman
如果您需要https,则需要使用httptest.NewTLSServer()。什么没有起作用? - JimB
http: TLS handshake error from 127.0.0.1:49876: remote error: tls: bad certificate - George Ananda Eman
看一下TLSServer示例,你需要使用已经在传输中加载了证书的测试客户端。 - JimB

6
在你的测试文件中(my_test.go):
type MyJSON struct {
        Id          string
        Age         int
}

// Interface which is the same as httpClient because it implements "Do" method.
type ClientMock struct {}

func (c *ClientMock) Do(req *http.Request) (*http.Response, error) {
    mockedRes := MyJSON {"1", 3}

    // Marshal a JSON-encoded version of mockedRes
    b, err := json.Marshal(mockedRes)
    if err != nil {
        log.Panic("Error reading a mockedRes from mocked client", err)
    }

    return &http.Response{Body: ioutil.NopCloser(bytes.NewBuffer(b))}, nil
}

// your test which will use the mocked response
func TestMyFunction(t *testing.T) {

    mock := &ClientMock{}
    actualResult := myFunction(mock)
    assert.NotEmpty(t, actualResult, "myFunction should have at least 1 result")

}

在您的实现中 (main.go):
package main

import (
    "net/http"
)

func main() {
    myFunction(&http.Client{})
}

0

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

像JimB一样,我建议在本地启动一个真正的HTTP服务器,因为在Go中使用https://golang.org/pkg/net/http/httptest/很容易实现。

然而,由于我做了很多HTTP模拟,我想要更多的功能,就像一个好的模拟库一样:返回特定的数据,轻松设置期望,验证所有请求是否都被执行等。我通常使用https://godoc.org/github.com/stretchr/testify/mock进行模拟,并希望有类似的功能。

所以我写了https://github.com/dankinder/httpmock,它基本上将两者结合起来。如果你只想要一个接受JSON并输出JSON的模拟,这可能是一个更简单的方法。


0
你可以做以下操作:
在你的 client.go 文件中:
var cl HTTPClient

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

func init() {
    cl = &http.Client{}
}

func Start() error {
    // jsonData: Some JSON marshalled data
    // Url: Some HTTP URL

    req, err := http.NewRequest(http.MethodPost, Url, bytes.NewBuffer(jsonData))
    if err != nil {
        log.Printf("Error in creating new HTTP request: %v", err)
        return err
    }
    req.Header.Set("Content-Type", "application/json")

    resp, err := cl.Do(req)
    if err != nil {
        log.Printf("Error in sending HTTP request: %v", err)
        return err
    }
    defer resp.Body.Close()
    log.Printf("Successfully sent HTTP request")
    return nil
}

在你的client_test.go文件中
const errHttpFake = "fake HTTP Error"

type mockDoType func(req *http.Request) (*http.Response, error)

type mockClient struct {
    mockDo mockDoType
}

func (m *mockClient) Do(req *http.Request) (*http.Response, error) {
    return m.mockDo(req)
}

func getHttpFailureClient() {
    req := io.NopCloser(bytes.NewReader([]byte(mockResult)))
    cl = &mockClient{
        mockDo: func(*http.Request) (*http.Response, error) {
            return &http.Response{
                StatusCode: 404,
                Body:       req,
            }, errors.New(errHttpFake)
        },
    }
}

func getHttpSuccessClient() {
    req := io.NopCloser(bytes.NewReader([]byte(mockResult)))
    cl = &mockClient{
        mockDo: func(*http.Request) (*http.Response, error) {
            return &http.Response{
                StatusCode: 200,
                Body:       req,
            }, nil
        },
    }
}

func TestHttpSuccess(t *testing.T) {
    getHttpSuccessClient()
    errHttp := Start() //Replace with function name containing client.Do()
    assert.Nil(t, errHttp)
}

func TestHttpClientFailure(t *testing.T) {
    getHttpFailureClient()
    errHttp := Start() //Replace with function name containing client.Do()
    assert.NotNil(t, errHttp)
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接