模拟ioUtils.ReadAll在读取http响应的主体时失败,而不使用httptest。

huangapple go评论78阅读模式
英文:

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!

huangapple
  • 本文由 发表于 2022年11月23日 18:50:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/74545601.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定