Search code examples
unit-testinghttpgoerror-handling

How do I test an error on reading from a request body?


I'm writing unit tests for http Handlers in golang. When looking at code coverage reports of this I am running into the following issue: When reading the request body from a request, ioutil.ReadAll might return an error that I need to handle. Yet, when I write unit tests for my handler I do not know how to send a request to my handler in a way that it will trigger such an error (premature end of content seems not to generate such an error but will generate an error on unmarshaling the body). This is what I am trying to do:

package demo

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

func HandlePostRequest(w http.ResponseWriter, r *http.Request) {
    body, bytesErr := ioutil.ReadAll(r.Body)
    if bytesErr != nil {
        // intricate logic goes here, how can i test it?
        http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
        return
    }
    defer r.Body.Close()
    // continue...
}

func TestHandlePostRequest(t *testing.T) {
    ts := httptest.NewServer(http.HandlerFunc(HandlePostRequest))
    data, _ := ioutil.ReadFile("testdata/fixture.json")
    res, err := http.Post(ts.URL, "application/json", bytes.NewReader(data))
    // continue...
}

How can I write a test case for HandlePostRequest that also covers the case of bytesErr not being nil?


Solution

  • You may create and use an http.Request forged by you, which deliberately returns an error when reading its body. You don't necessarily need a whole new request, a faulty body is enough (which is an io.ReadCloser).

    Simplest achieved by using the httptest.NewRequest() function where you can pass an io.Reader value which will be used (wrapped to be an io.ReadCloser) as the request body.

    Here's an example io.Reader which deliberately returns an error when attempting to read from it:

    type errReader int
    
    func (errReader) Read(p []byte) (n int, err error) {
        return 0, errors.New("test error")
    }
    

    Example that will cover your error case:

    func HandlePostRequest(w http.ResponseWriter, r *http.Request) {
        defer r.Body.Close()
        body, err := ioutil.ReadAll(r.Body)
        if err != nil {
            fmt.Printf("Error reading the body: %v\n", err)
            return
        }
        fmt.Printf("No error, body: %s\n", body)
    }
    
    func main() {
        testRequest := httptest.NewRequest(http.MethodPost, "/something", errReader(0))
        HandlePostRequest(nil, testRequest)
    }
    

    Output (try it on the Go Playground):

    Error reading the body: test error
    

    See related question if you would need to simulate error reading from a response body (not from a request body): How to force error on reading response body