英文:
How to properly mock a struct with member functions in Golang?
问题
我有两个结构体:FunctionalityClient
和 TestClient
,它们都实现了 Interface
接口。我有一个全局变量 Client
,类型为 Interface
。根据是测试还是正常运行,我将 Client
分配给实际的客户端或模拟客户端。
Interface
接口有一个名为 Request
的方法,我想在测试中模拟这个方法。也就是说,我想要:
- 记录传递给函数的参数
- 从函数返回一些任意定义的返回值
所以结构体看起来像这样:
type TestClient struct {
recordedArgs []interface{}
returnValues []interface{}
}
func (c *TestClient) Request(body io.Reader, method string, endpoint string, headers []Header) ([]byte, error) {
c.recordedArgs = append(c.recordedArgs, []interface{}{body, method, endpoint, headers}) // 如果我想要代码可重用,这里不能指定类型
if len(c.returnValues) != 0 {
last := c.returnValues[0]
c.returnValues = c.returnValues[1:]
return last.([]byte), nil
}
return nil, nil
}
我这样使用它:
testClient := TestClient{
returnValues: []interface{}{
[]byte("任意定义的返回值"),
[]byte("调用 Request 第二次后将返回这个值"),
}
}
Client = &testClient
// 运行测试
// 现在我们来检查结果
r1 := testClient.recordedArgs[1].([]interface{}) // 因为我将未指定类型的列表附加到 recordedArgs 中
assert.Equal(t, "POST", r1[1].(string))
assert.Equal(t, "/file", r1[2].(string))
// 等等
现在是问题。
我有几个结构体想要像这样进行模拟。目前我只是将上面的代码复制粘贴到每个结构体中。但这真的很糟糕,我希望模拟逻辑能以某种方式抽象出来。我也可以接受类似 Mockito 的 when
:当使用特定参数调用模拟函数时,返回特定值并记录调用。
如何正确地在 Golang 中模拟带有成员函数的结构体?
英文:
I have two structs: FunctionalityClient
and TestClient
, both implementing Interface
. I have a global variable Client
of type Interface
. I assign to Client
either the actual client, or the mock client, depending on whether it's a test or a normal run.
Interface
has a method Request
that I want to mock in tests. That is, I want to:
- record what were the argument passed to the function
- return some arbitrarily defined return value from the function
So the struct looks like this:
type TestClient struct {
recordedArgs []interface{}
returnValues []interface{}
}
func (c *TestClient) Request(body io.Reader, method string, endpoint string, headers []Header) ([]byte, error) {
c.recordedArgs = append(c.recordedArgs, []interface{}{body, method, endpoint, headers}) // this can't be typed if I want the code to be reusable
if len(c.returnValues) != 0 {
last := c.returnValues[0]
c.returnValues = c.returnValues[1:]
return last.([]byte), nil
}
return nil, nil
}
And I use it like so:
testClient := TestClient{
returnValues: []interface{}{
[]byte("arbitrarily defined return value"),
[]byte("this will be returned after calling Request a second time"),
}
}
Client = &testClient
// run the test
// now let's check the results
r1 := testClient.recordedArgs[1].([]interface{}) // because I append untyped lists to recordedArgs
assert.Equal(t, "POST", r1[1].(string))
assert.Equal(t, "/file", r1[2].(string))
// and so on
Now the question.
I have a few structs that I want to mock like this. Currently I just copy and paste the code above for each struct. But that really sucks, I would like the mock logic to be abstracted away somehow. I would also accept something like Mockito's when
: when the mocked function is called with specific arguments, return a specific value and record the call.
How can I properly mock a struct with member functions in Golang?
答案1
得分: 0
如果你要模拟HTTP API的客户端,你可以直接使用httptest.Server
,这样会大大简化代码。不需要模拟客户端,而是模拟客户端连接的服务器。这个方法非常简单易用,你仍然可以记录请求的方法、路径、主体等信息,并且可以像模拟客户端一样返回任意的响应值。
如果这不是一个选项,你可以将模拟方法抽象出来,以便重复使用:
type TestClient struct {
recordedArgs [][]interface{}
returnValues []interface{}
}
func (c *TestClient) mock(args ...interface{}) interface{} {
c.recordedArgs = append(c.recordedArgs, args)
if len(c.returnValues) != 0 {
last := c.returnValues[0]
c.returnValues = c.returnValues[1:]
return last
}
return nil
}
func (c *TestClient) Request(body io.Reader, method string, endpoint string, headers []Header) ([]byte, error) {
return c.mock(body, method, endpoint, headers).([]byte), nil
}
这样可以将你的特定用法方法简化为一行代码。
英文:
If you're mocking out clients for HTTP APIs, you might want to just use httptest.Server
, which would simplify this tremendously. Rather than mocking out the client, mock out the server the client connects to. It's really easy to use, and you can still record the request method, path, body, etc., as well as returning arbitrary response values the same way you're doing with the mock client.
If that's not an option, you can abstract out your mock method to make it reusable:
type TestClient struct {
recordedArgs [][]interface{}
returnValues []interface{}
}
func (c *TestClient) mock(args ...interface{}) interface{} {
c.recordedArgs = append(c.recordedArgs, args)
if len(c.returnValues) != 0 {
last := c.returnValues[0]
c.returnValues = c.returnValues[1:]
return last
}
return nil
}
func (c *TestClient) Request(body io.Reader, method string, endpoint string, headers []Header) ([]byte, error) {
return c.mock(body,method,endpoint,headers).([]byte), nil
}
This cuts your usage-specific method down to one line.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论