英文:
Mock ioUtils.ReadAll to fail when reading body of httpresponse, without using httptest
问题
我目前正在尝试避免使用httpserver。原因是我正在尝试一种不同的方法,通过模拟httpClient(作为接口传递给我的逻辑)来编写所有的单元测试。
我目前遇到的问题是,当无法读取响应时,我希望我的逻辑(如下所示)失败:
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, fmt.Errorf("Error reading response body: %w", err)
}
其中res
来自于:
res, err := myClient.Do(req)
if err != nil {
return nil, fmt.Errorf("Could not submit file: %w", err)
}
myClient
是一个实现了Do
方法的接口类型,因此我可以模拟它。
在阅读了一些相关问题后,我尝试模拟我的客户端的Do
方法返回:
response := http.Response{
Body: ioutil.NopCloser(bytes.NewBufferString("")),
ContentLength: 1
}
我参考了这个问题。不幸的是,这并不起作用,我的代码仍然能够读取响应体而不生成错误。
英文:
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.
答案1
得分: 1
我已经构建了一个示例程序,帮助你理解如何处理这种情况。让我们看一下涉及的两个文件:
main.go
package mockhttpbody
import "net/http"
func DummyServer(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Length", "1")
}
这个文件与你从其他问题中获取的文件相同,所以我不会在这里涵盖任何内容。
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))
}
让我们逐步解决这个问题。
io
包
我建议你使用 io
包而不是 ioutil
包,因为后者已经被弃用。
Reader
接口实现
你真正需要的模拟是这个接口:
type Reader interface {
Read(p []byte) (n int, err error)
}
当你调用 io.ReadAll
函数时,它会起作用。在这里,你需要传入自己的自定义实现。你可以使用以下代码定义你的实现:
type mockReader struct{}
func (m *mockReader) Read(p []byte) (n int, err error) {
return 0, io.ErrUnexpectedEOF
}
然后,在你的测试代码中,你必须使用 data, err := io.ReadAll(&mockReader{})
(所以从模拟的 HTTP 服务器返回的结果是无用的)。
ErrUnexpectedEOF
最后,io.ReadAll
函数不将 EOF
视为错误,所以你应该使用其他不同的错误。我选择的错误是 ErrUnexpectedEOF
,但你可以根据自己的决定使用其他错误。
如果有任何疑问,请告诉我!
英文:
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
package
I suggest you use the package io
instead of ioutil
as the latter has been deprecated.
Reader
implementation
The 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!
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论