I've written http client wrapper in go and I need to test it thoroughly. I'm reading the response body with ioutil.ReadAll within the wrapper. I'm having a bit of trouble figuring out how I can force a read from the response body to fail with the help of 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
}
I'm assuming I can set up a fake server as such:
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")
}
}
I'm assuming I need to modify the ResposeWriter. Now, is there any way for me to modify the responseWriter and thereby force the ioutil.ReadAll in the wrapper to fail?
I realize that you seem to think it's a duplicate of this post and while you may believe so or it might be, just marking it as a duplicate doesn't really help me. The code provided in the answers in the "duplicate" post makes very little sense to me in this context.
Check the documentation of Response.Body
to see when reading from it might return an error:
// 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
The easiest way is to generate an invalid HTTP response from the test handler.
How to do that? There are many ways, a simple one is to "lie" about the content length:
handler := func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Length", "1")
}
This handler tells it has 1 byte body, but actually it sends none. So at the other end (the client) when attempting to read 1 byte from it, obviously that won't succeed, and will result in the following error:
Unable to read from body unexpected EOF
See related question if you would need to simulate error reading from a request body (not from a response body): How do I test an error on reading from a request body?
To expand upon icza's awesome answer, you can also do this with an httptest.Server
object:
bodyErrorServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Length", "1")
}))
defer bodyErrorServer.Close()
You can then pass bodyErrorServer.URL
in your tests like normal, and you'll always get an EOF error:
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!")
}
}
Playground example here: https://play.golang.org/p/JzPmatibgZn
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With