如何测试从请求体中读取错误?

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

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)
}

https://go.dev/play/p/_R9hI2AWm9G

huangapple
  • 本文由 发表于 2017年7月16日 15:53:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/45126312.html
匿名

发表评论

匿名网友

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

确定