在测试中覆盖Go方法

时间:2018-05-26 16:41:06

标签: unit-testing oop testing go

所以我有这个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?我的方法的设计应该不同吗?

2 个答案:

答案 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)

  

然后我认为我的方法不正确。

您的描述完全涵盖了它!即使您在致电mockClientclient.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}}不必更改。