英文:
How do I test an error on reading from a request body?
问题
我正在为golang中的http处理程序编写单元测试。在查看代码覆盖率报告时,我遇到了以下问题:当从请求中读取请求体时,ioutil.ReadAll
可能会返回一个需要处理的错误。然而,当我为处理程序编写单元测试时,我不知道如何以触发此类错误的方式发送请求到处理程序(似乎提前结束内容不会生成此类错误,但会在解组请求体时生成错误)。这是我想要做的:
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...
}
我如何为HandlePostRequest
编写一个测试用例,以覆盖bytesErr
不为nil
的情况?
英文:
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
?
答案1
得分: 51
你可以创建并使用一个由你伪造的http.Request
,当读取其请求体时故意返回一个错误。你不一定需要一个全新的请求,一个有问题的请求体就足够了(它是一个io.ReadCloser
)。
最简单的方法是使用httptest.NewRequest()
函数,你可以传递一个io.Reader
值作为请求体(被包装成io.ReadCloser
)。
下面是一个示例的io.Reader
,当尝试从中读取时故意返回一个错误:
type errReader int
func (errReader) Read(p []byte) (n int, err error) {
return 0, errors.New("test error")
}
以下是一个覆盖你错误情况的示例:
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)
}
输出结果(在Go Playground上尝试):
Error reading the body: test error
如果你需要模拟从响应体(而不是请求体)读取时出现错误,可以参考相关问题:如何强制在读取响应体时出现错误?
英文:
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): https://stackoverflow.com/questions/53171123/how-to-force-error-on-reading-response-body/53173459#53173459
答案2
得分: 0
我提取了读取主体和处理响应的代码,并使用以下方法创建了一个在读取主体时失败的响应:
package main
import (
"fmt"
"io"
"net/http"
)
type BrokenReader struct{}
func (br *BrokenReader) Read(p []byte) (n int, err error) {
return 0, fmt.Errorf("读取失败")
}
func (br *BrokenReader) Close() error {
return fmt.Errorf("关闭失败")
}
func main() {
headers := http.Header{
"Content-Length": {"1"},
}
reader := BrokenReader{}
resp := http.Response{
Body: &reader,
Header: headers,
}
_, err := io.ReadAll(resp.Body)
fmt.Println(err)
}
你可以在以下链接中查看代码:https://go.dev/play/p/_R9hI2AWm9G
英文:
I did extract the code that reads the body and processes the response and then used the following approach to create a response that would fail when reading the body:
package main
import (
"fmt"
"io"
"net/http"
)
type BrokenReader struct{}
func (br *BrokenReader) Read(p []byte) (n int, err error) {
return 0, fmt.Errorf("failed reading")
}
func (br *BrokenReader) Close() error {
return fmt.Errorf("failed closing")
}
func main() {
headers := http.Header{
"Content-Length": {"1"},
}
reader := BrokenReader{}
resp := http.Response{
Body: &reader,
Header: headers,
}
_, err := io.ReadAll(resp.Body)
fmt.Println(err)
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论