Переопределить метод Go в тестах - PullRequest
0 голосов
/ 26 мая 2018

Итак, у меня есть структура Client, которая имеет метод UserByID, который отправляет HTTP-запрос к конечной точке для User.Я хочу выполнить модульное тестирование этой функции, но не могу сделать фактический HTTP-запрос в функции c.Request.Я хочу заглушить эту функцию с помощью ответа и ошибки, которыми я могу управлять.

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 голосов
/ 26 мая 2018

Тогда я предполагаю, что мой подход не верен.

Ваше описание охватывает именно это!Даже если вы встраиваете Client в mockClient, когда вы вызываете client.UserByID(c.id), go смотрит на mockClient и видит, что метод поднялся с Client.это заканчивается так, что Client !!!является получателем UserByID вызова НЕ mockClient.Вы можете увидеть это здесь:

func (c Client) UserByID(id string) (u User, err error)

Как только Client получатель resp, err := c.Request(opts) вызывается с Client получателем выше, а НЕ с вашим mockClient, как вы 'повторное наблюдение.


Один из способов ввести шов для c.Request, который вы можете предоставить в качестве пользовательской реализации для использования в модульном тестировании, - это сделать Request методом вызова наваш Client struct.

type Client struct {
    Request func(opts Request) (resp []byte, err error) 
}

Вышеуказанное должно помочь отделить клиента от реализации 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,
}

Это идет со своими собственными компромиссами, хотя вы потенциальнопотерять клиента в качестве приемника указателя в вашей функции Request callout.

Еще одна интересная часть Callout - это то, что он дает вам еще один вариант инкапсуляции.Предположим, что в будущем вы захотите обеспечить некоторый экспоненциальный откат или повторить попытку.Это позволит вам предоставить более интеллектуальный метод Request для Client без необходимости изменения Client.

0 голосов
/ 26 мая 2018

Чтобы выполнить то, что вам нужно, вы можете немного перестроить свой код.

Вы можете найти полный рабочий пример здесь: 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...