我可以为我的测试案例编写自己的go的http client.Do()函数版本吗?

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

Can I write my own version of go's http client.Do() function for my test cases?

问题

我有一个名为user-service.go的文件,以及相应的测试文件user-service_test.go。当我尝试获得完整的代码覆盖率时,我很难让一些错误条件真正发生。

这是函数:GetOrCreateByAccessToken()

// GetOrCreateByAccessToken 从数据库中获取具有给定访问令牌的用户
func (s *service) GetOrCreateByAccessToken(aT string, client *Client) (*user.User, fcerr.FCErr) {

var currentUser user.OauthUser

req, err := http.NewRequest("GET", "https://openidconnect.googleapis.com/v1/userinfo?access_token="+aT, nil)
if err != nil {
	return nil, fcerr.NewInternalServerError("设置网络请求时出错")
}

response, err := client.httpClient.Do(req)
if err != nil {
	fmt.Println("使用访问令牌获取用户信息时出错")
	return nil, fcerr.NewInternalServerError("尝试验证用户身份时出错")
}

defer response.Body.Close()

contents, err := io.ReadAll(response.Body)
if err != nil {
	return nil, fcerr.NewInternalServerError("尝试读取来自Google的有关用户身份的响应时出错")
}

我测试的主要控制是我可以传入一个*Client。

这是测试用例的一部分,我希望io.ReadAll引发错误:

h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 手动返回Google在实际请求中返回的消息
w.Write([]byte(googleAPIOKResponse))
})
// 调用测试文件中定义的testHTTPClient()函数来替换自己的HandlerFunc
httpClient, teardown := testHTTPClient(h)
defer teardown()

// 调用我user-service.go中的真实NewClient()
client := NewClient()

// 将默认的httpClient替换为我刚刚设置的httpClient。
client.httpClient = httpClient

resultingUser, err := userService.GetOrCreateByAccessToken(nU.AccessToken, client)

assert.Nil(t, resultingUser)
assert.NotNil(t, err)
assert.Equal(t, http.StatusInternalServerError, err.Status())

有没有地方可以编写自己的.Do()方法的版本,以便在响应中放入某些内容,从而导致io.ReadAll返回错误?或者有没有更好的方法来实现只使用我已经使用的预先准备好的响应文本的错误?

英文:

I have a file, called user-service.go and the corresponding test file, called user-service_test.go. As I try to get complete code coverage, I am struggling to get some of the error conditions to actually happen.

Here is the function: GetOrCreateByAccessToken()

//GetOrCreateByAccessToken gets a user from the database with the given access token
func (s *service) GetOrCreateByAccessToken(aT string, client *Client) (*user.User, fcerr.FCErr) {

var currentUser user.OauthUser

req, err := http.NewRequest("GET", "https://openidconnect.googleapis.com/v1/userinfo?access_token="+aT, nil)
if err != nil {
	return nil, fcerr.NewInternalServerError("Error when setting up the network request")
}

response, err := client.httpClient.Do(req)
if err != nil {
	fmt.Println("error when getting the userinfo with the access token")
	return nil, fcerr.NewInternalServerError("Error when trying to verify user identity")
}

defer response.Body.Close()

contents, err := io.ReadAll(response.Body)
if err != nil {
	return nil, fcerr.NewInternalServerError("Error when trying to read response from Google about user identity")
}

The main control I have for my tests is that I can pass in a *Client.

Here is the part of the test case where I'd like to have io.ReadAll throw an error:

h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    //manually return the message google would return on an actual request
	w.Write([]byte(googleAPIOKResponse))
})
//Call the testHTTPClient() function defined in the test file to substitute my own HandlerFunc
httpClient, teardown := testHTTPClient(h)
defer teardown()

//Call the real NewClient() from my user-service.go
client := NewClient()

//Substitute the default httpClient for the one I've just set up.
client.httpClient = httpClient

resultingUser, err := userService.GetOrCreateByAccessToken(nU.AccessToken, client)

assert.Nil(t, resultingUser)
assert.NotNil(t, err)
assert.Equal(t, http.StatusInternalServerError, err.Status())

Is there somewhere I can write my own version of the .Do() method which will put something in the response which will cause io.ReadAll to return an error? Or is there a better way to achieve the error with just the pre-baked response text I'm already using?

答案1

得分: 0

没有一种方法可以替换Do方法,但有一种方法可以实现你的目标。

创建一个round tripper类型,返回任意的响应体:

type respondWithReader struct{ body io.Reader }

func (rr respondWithReader) RoundTrip(req *http.Request) (*http.Response, error) {
    return &http.Response{
        Proto:      "HTTP/1.0",
        ProtoMajor: 1,
        Header:     make(http.Header),
        Close:      true,
        Body:       ioutil.NopCloser(rr.body),
    }, nil
}

创建一个会失败的io.Reader:

var errReadFail = errors.New("blah!")

type failReader int

func (failReader) Read([]byte) (int, error) {
    return 0, errReadFail
}

使用上述的传输和读取器来使用默认的客户端:

c := http.Client{Transport: respondWithReader{body: failReader(0)}}
resp, err := c.Get("http://whatever.com")
if err != nil {
    t.Error(err)
}
defer resp.Body.Close()

// ReadAll返回errReadFail
_, err = ioutil.ReadAll(resp.Body)
if err != errReadFail {
    t.Errorf("got err %v, expect %v", err, errReadFail)
}

在Go playground上运行测试:Run the test on the Go playground

英文:

There is not a way to replace the Do method, but there is a way to accomplish your goal.

Create a round tripper type that returns an arbitrary response body:

type respondWithReader struct{ body io.Reader }

func (rr respondWithReader) RoundTrip(req *http.Request) (*http.Response, error) {
	return &http.Response{
		Proto:      "HTTP/1.0",
		ProtoMajor: 1,
		Header:     make(http.Header),
		Close:      true,
		Body:       ioutil.NopCloser(rr.body),
	}, nil

}

Create an io.Reader that fails:

var errReadFail = errors.New("blah!")

type failReader int

func (failReader) Read([]byte) (int, error) {
	return 0, errReadFail
}

Use the stock client with the transport and reader above:

c := http.Client{Transport: respondWithReader{body: failReader(0)}}
resp, err := c.Get("http://whatever.com")
if err != nil {
	t.Error(err)
}
defer resp.Body.Close()

// ReadAll returns errReadFail
_, err = ioutil.ReadAll(resp.Body)
if err != errReadFail {
	t.Errorf("got err %v, expect %v", err, errReadFail)
}

Run the test on the Go playground.

huangapple
  • 本文由 发表于 2022年3月27日 08:30:13
  • 转载请务必保留本文链接:https://go.coder-hub.com/71632844.html
匿名

发表评论

匿名网友

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

确定