所以我有这个Client
结构,它有一个方法UserByID
,它向User
的端点发出HTTP请求。我想对此函数进行单元测试,但也不在函数c.Request
中发出实际的HTTP请求。我希望用我能控制的响应和错误来存储该函数。
func (c Client) UserByID(id string) (u User, err error) {
v := url.Values{}
v.Set("id", id)
opts := Request{
HTTP: http.Request{
Method: http.MethodGet,
Form: v,
},
URL: 'some/endpoint/users',
}
resp, err := c.Request(opts)
err = json.Unmarshal(resp, &u)
return
}
以下是存根的样子:
type mockClient struct {
Client
fakeUser User
fakeError error
}
func (mc mockClient) Request(opts Request) (resp []byte, err error) {
resp, err = json.Marshal(mc.fakeUser)
err = mc.fakeError
return
}
在一次测试中,我有类似的东西:
client := mockClient{
fakeUser: User{},
fakeError: nil,
}
user, err := client.UserByID(c.id)
然后我可以断言client.UserByID
的返回值。在这个例子中,我试图覆盖client.Request
函数,但我理解Go不是继承类型的语言。在我的测试中,我的mockClient.Request
函数没有被调用。原来的client.Request
仍然被调用。
然后我假设我的方法不对。如何在不实际调用其中的真实client.UserByID
函数的情况下测试client.Request
?我的方法的设计应该不同吗?
答案 0 :(得分:2)
要完成您的需要,您可以稍微重新构建代码。
您可以在此处找到完整的工作示例:https://play.golang.org/p/VoO4M4U0YcA
以下是解释。
首先,在包中声明一个变量函数来封装实际的HTTP请求:
var MakeRequest = func(opts Request) (resp []byte, err error) {
// make the request, return response and error, etc
}
然后,在Client
中使用该功能发出请求:
func (c Client) Request(opts Request) (resp []byte, err error) {
return MakeRequest(opts)
}
这样,当您实际使用客户端时,它将按预期发出HTTP请求。
但是当你需要测试时,你可以为这个MakeRequest
函数分配一个模拟函数,以便你可以控制它的行为:
// define a mock requester for your test
type mockRequester struct {
fakeUser User
fakeError error
}
func (mc mockRequester) Request(opts Request) (resp []byte, err error) {
resp, err = json.Marshal(mc.fakeUser)
err = mc.fakeError
return
}
// to use it, you can just point `MakeRequest` to the mock object function
mockRequester := mockRequester{
fakeUser: User{ ID: "fake" },
fakeError: nil,
}
MakeRequest = mockRequester.Request
答案 1 :(得分:1)
然后我认为我的方法不正确。
您的描述完全涵盖了它!即使您在致电mockClient
时client.UserByID(c.id)
mockClient
Client
,请查看Client
并查看从{UserByID
拉出的方法1}}。它最终成为mockClient
!!! func (c Client) UserByID(id string) (u User, err error)
调用的接收者不是Client
。你可以在这里看到:
resp, err := c.Request(opts)
Client
接收方mockClient
后,c.Request
接收方就会调用接收方Request
,而不是您正在观察的Client
。
为type Client struct {
Request func(opts Request) (resp []byte, err error)
}
引入embedding的一种方法是,您可以提供用于单元测试的自定义实现,是在Request
结构上使Request
成为一种标注方法
Client
以上内容应有助于将客户端从Request
实施中解耦。它说的是type mockRequester struct {
fakeUser User
fakeError error
}
func (mc mockRequester) Request(opts Request) (resp []byte, err error) {
resp, err = json.Marshal(mc.fakeUser)
err = mc.fakeError
return
}
mr := mockRequester{...}
c := Client{
Request: mr.Request,
}
将是一个带有一些返回值的args的函数,允许你替换不同的函数,这取决于你是否正在进行生产或测试。现在,在Request
的公开初始化期间,您可以提供Request
的真实实现,而在单元测试中,您可以提供虚假实现。
Client
虽然您可能在Client
标注功能中丢失了客户端作为指针接收器,但它还有自己的权衡。
Callout的另一个很酷的部分是它为你提供了另一种封装选择。假设您将来想要提供某种指数退避或重试。它允许您向# Logging level
logging_level = INFO
提供更智能的{{1}}方法,而{{1}}不必更改。