如何强制读取响应体时出现错误

7
我已经在 Go 语言中编写了 HTTP 客户端包装器,并且需要对其进行全面测试。在包装器中,我使用 ioutil.ReadAll 读取响应体。但是,我遇到了一些麻烦,无法确定如何借助 httptest 强制使响应体读取失败。
package req

func GetContent(url string) ([]byte, error) {
    response, err := httpClient.Get(url)
    // some header validation goes here
    body, err := ioutil.ReadAll(response.Body)
    defer response.Body.Close()

    if err != nil {
        errStr := fmt.Sprintf("Unable to read from body %s", err)
        return nil, errors.New(errStr)
    }

    return body, nil
}

我假设我可以这样设置一个虚假服务器:

package req_test

func Test_GetContent_RequestBodyReadError(t *testing.T) {

    handler := func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
    }

    ts := httptest.NewServer(http.HandlerFunc(handler))
    defer ts.Close()

    _, err := GetContent(ts.URL)

    if err != nil {
        t.Log("Body read failed as expected.")
    } else {
        t.Fatalf("Method did not fail as expected")
    }

}

我假设需要修改ResposeWriter。现在,是否有任何办法可以修改responseWriter并强制包装器中的ioutil.ReadAll失败?

我意识到你似乎认为这是此帖子的重复,并且虽然您可能认为是这样或者它可能是,但仅将其标记为重复并不能真正帮助我。在“重复”帖子中提供的答案代码在这种情况下几乎没有意义。


我已经查看了那篇帖子并尝试使其工作,但没有成功。 - ndx
1
那么你应该发布你尝试过的内容和遇到的问题。 - icza
原始帖子已更新。如果我创建一个自定义类型并按照您的建议实现接口,那么我该如何将其传递到测试服务器? - ndx
2个回答

13

查看Response.Body的文档以了解从中读取数据可能会返回错误的情况:

// Body represents the response body.
//
// The response body is streamed on demand as the Body field
// is read. If the network connection fails or the server
// terminates the response, Body.Read calls return an error.
//
// The http Client and Transport guarantee that Body is always
// non-nil, even on responses without a body or responses with
// a zero-length body. It is the caller's responsibility to
// close Body. The default HTTP client's Transport may not
// reuse HTTP/1.x "keep-alive" TCP connections if the Body is
// not read to completion and closed.
//
// The Body is automatically dechunked if the server replied
// with a "chunked" Transfer-Encoding.
Body io.ReadCloser

最简单的方法是从测试处理程序生成一个无效的HTTP响应。

要怎么做呢?有很多方法,一个简单的方法是“欺骗”内容长度:

handler := func(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Length", "1")
}

这个处理程序表示它有1个字节的正文,但实际上它没有发送任何内容。因此,在另一端(客户端)尝试从中读取1个字节时,显然不会成功,并将导致以下错误:

Unable to read from body unexpected EOF

如需模拟从请求主体(而非响应主体)读取错误,请参阅相关问题:如何测试从请求主体读取错误?


如果这个回答解决了您的问题,请考虑接受并点赞此答案。 - icza
怎么做?有很多种方法。你能列举一些方法吗? - sabbir
@sabbir 其中一种方法在答案中已经明确列出,并且给出了具体的示例:“一个简单的方法是“欺骗”内容长度”。 - icza
1
@JimC 如果你想让 http.Client.Get() 失败,只需传入一个无效的 URL。 - icza
谢谢@icza,很容易就搞定了。我只是在由server := httptest.NewServer(mux)得到的server.URL中添加了一个额外的数字。 - Jim C
显示剩余2条评论

3

为了进一步解释icza的优秀回答,您也可以使用httptest.Server对象来执行此操作:

bodyErrorServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  w.Header().Set("Content-Length", "1")
}))

defer bodyErrorServer.Close()

您可以像平常一样在测试中传递 bodyErrorServer.URL,然后您将始终收到一个 EOF 错误:

package main

import (
    "bytes"
    "fmt"
    "io/ioutil"
    "net/http"
    "net/http/httptest"
    "testing"
    "time"
)

func getBodyFromURL(service string, clientTimeout int) (string, error) {

    var netClient = &http.Client{
        Timeout: time.Duration(clientTimeout) * time.Millisecond,
    }

    rsp, err := netClient.Get(service)
    if err != nil {
        return "", err
    }

    defer rsp.Body.Close()

    if rsp.StatusCode != 200 {
        return "", fmt.Errorf("HTTP request error. Response code: %d", rsp.StatusCode)
    }

    buf, err := ioutil.ReadAll(rsp.Body)
    if err != nil {
        return "", err
    }

    return string(bytes.TrimSpace(buf)), nil
}

func TestBodyError(t *testing.T) {

    bodyErrorServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Length", "1")
    }))

    _, err := getBodyFromURL(bodyErrorServer.URL, 1000)

    if err.Error() != "unexpected EOF" {
        t.Error("GOT AN ERROR")
    } else if err == nil {
            t.Error("GOT NO ERROR, THATS WRONG!")
    } else {
        t.Log("Got an unexpected EOF as expected, horray!")
    }
}

在这里查看示例代码:https://play.golang.org/p/JzPmatibgZn


1
请提供完整可执行的示例,并为其加上一个! - danfromisrael

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