I am currently trying to avoid httpserver. Reason behind is that I am trying a different approach and write all my unit tests by mocking the httpClient, passed as interface to my logic.
The problem I am having at the moment is that I want my logic, see right below, to fail when the response cannot be read:
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, fmt.Errorf("Error reading response body: %w", err)
}
where res
comes from:
res, err := myClient.Do(req)
if err != nil {
return nil, fmt.Errorf("Could not submit file: %w", err)
}
myClient
is of an interface type that implements the Do
method, hence I am able to mock it.
After reading some questions on the related matter I tried to mock my client's Do
method to return:
response := http.Response{
Body: ioutil.NopCloser(bytes.NewBufferString("")),
ContentLength: 1
}
I based myself on top of this question. Unfortunately this doesn't work and my code is still able to read the body without generating an error.
I've built a sample program to help you understand how to deal with this scenario. Let's see the two files involved:
main.go
package mockhttpbody
import "net/http"
func DummyServer(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Length", "1")
}
This file is the same one you take from the other question, so I won't cover anything here.
main_test.go
package mockhttpbody
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"testing"
)
type mockReader struct{}
func (m *mockReader) Read(p []byte) (n int, err error) {
return 0, io.ErrUnexpectedEOF
}
func Test(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/example", nil)
w := httptest.NewRecorder()
DummyServer(w, req)
data, err := io.ReadAll(&mockReader{})
if err != nil {
panic(err)
}
fmt.Println(string(data))
}
Let's tackle this step-by-step.
io
packageI suggest you use the package io
instead of ioutil
as the latter has been deprecated.
Reader
implementationThe mock that you really need for what you want to achieve is of this interface:
type Reader interface {
Read(p []byte) (n int, err error)
}
This comes into play when you issue the io.ReadAll
function. Here you've to pass your own custom implementation. You can define your implementation with the following code:
type mockReader struct{}
func (m *mockReader) Read(p []byte) (n int, err error) {
return 0, io.ErrUnexpectedEOF
}
Then, in your test code, you have to use this data, err := io.ReadAll(&mockReader{})
(so the result returned from the mock HTTP server is useless).
ErrUnexpectedEOF
Last, the io.ReadAll
function doesn't treat the EOF
as an error so you should use something different. The error I choose is the ErrUnexpectedEOF
one but it's up to you this decision.
Let me know if this clarifies a little bit!