简体   繁体   English

如何模拟 function 将结果写入 Go 中的参数

[英]How do I mock a function that write result to it's argument in Go

I am writing unit test in golang by https://github.com/stretchr/testify Suppose I have a method below,我正在通过https://github.com/stretchr/testify在 golang 中编写单元测试假设我有以下方法,

func DoSomething(result interface{}) error {
    // write some data to result
    return nil
}

so the caller can call DoSomething as following所以调用者可以调用DoSomething如下

result := &SomeStruct{}
err := DoSomething(result)

if err != nil {
  fmt.Println(err)
} else {
  fmt.Println("The result is", result)
}

Now I know how to use testify or some other mocking tools to mock the returns value (it's err here) by something like现在我知道如何使用testify或其他一些 mocking 工具来模拟返回值(这里是err的),例如

mockObj.On("DoSomething", mock.Anything).Return(errors.New("mock error"))

My question is "how do i mock the result argument" in this kind of scenario?我的问题是在这种情况下“我如何模拟result参数”?

Since result is not a return value but a argument, the caller calls it by passing a pointer of a struct, and the function modify it.由于result不是返回值而是参数,调用者通过传递一个结构的指针来调用它,function 对其进行修改。

You can use the (*Call).Run method:您可以使用(*Call).Run方法:

Run sets a handler to be called before returning. Run 设置在返回之前调用的处理程序。 It can be used when mocking a method (such as an unmarshaler) that takes a pointer to a struct and sets properties in such struct它可以用于当 mocking 一个方法(例如解组器),该方法接受一个指向结构的指针并在该结构中设置属性

Example:例子:

Mock.On("Unmarshal", AnythingOfType("*map[string]interface{}").Return().Run(func(args Arguments) {
    arg := args.Get(0).(*map[string]interface{})
    arg["foo"] = "bar"
})

As @bikbah said, here is an example:正如@bikbah 所说,这是一个例子:

services/message.go : services/message.go

type messageService struct {
    HttpClient http.Client
    BaseURL    string
}
func (m *messageService) MarkAllMessages(accesstoken string) []*model.MarkedMessage {
    endpoint := m.BaseURL + "/message/mark_all"
    var res model.MarkAllMessagesResponse
    if err := m.HttpClient.Post(endpoint, &MarkAllMessagesRequestPayload{Accesstoken: accesstoken}, &res); err != nil {
        fmt.Println(err)
        return res.MarkedMsgs
    }
    return res.MarkedMsgs
}

We passes res to the m.HttpClient.Post method.我们将res传递给m.HttpClient.Post方法。 In this method, the res will be populated with json.unmarshal method.在此方法中,将使用json.unmarshal方法填充res

mocks/http.go : mocks/http.go

package mocks

import (
    "io"

    "github.com/stretchr/testify/mock"
)

type MockedHttp struct {
    mock.Mock
}

func (m *MockedHttp) Get(url string, data interface{}) error {
    args := m.Called(url, data)
    return args.Error(0)
}

func (m *MockedHttp) Post(url string, body interface{}, data interface{}) error {
    args := m.Called(url, body, data)
    return args.Error(0)
}

services/message_test.go : services/message_test.go

package services_test

import (
    "errors"
    "reflect"
    "strconv"
    "testing"

    "github.com/stretchr/testify/mock"
    "github.com/mrdulin/gqlgen-cnode/graph/model"
    "github.com/mrdulin/gqlgen-cnode/services"
    "github.com/mrdulin/gqlgen-cnode/mocks"
)

const (
    baseURL     string = "http://localhost/api/v1"
    accesstoken string = "123"
)

func TestMessageService_MarkAllMessages(t *testing.T) {
    t.Run("should mark all messaages", func(t *testing.T) {
        testHttp := new(mocks.MockedHttp)
        var res model.MarkAllMessagesResponse
        var markedMsgs []*model.MarkedMessage
        for i := 1; i <= 3; i++ {
            markedMsgs = append(markedMsgs, &model.MarkedMessage{ID: strconv.Itoa(i)})
        }
        postBody := services.MarkAllMessagesRequestPayload{Accesstoken: accesstoken}
        testHttp.On("Post", baseURL+"/message/mark_all", &postBody, &res).Return(nil).Run(func(args mock.Arguments) {
            arg := args.Get(2).(*model.MarkAllMessagesResponse)
            arg.MarkedMsgs = markedMsgs
        })
        service := services.NewMessageService(testHttp, baseURL)
        got := service.MarkAllMessages(accesstoken)
        want := markedMsgs
        testHttp.AssertExpectations(t)
        if !reflect.DeepEqual(got, want) {
            t.Errorf("got wrong return value. got: %#v, want: %#v", got, want)
        }
    })

    t.Run("should print error and return empty slice", func(t *testing.T) {
        var res model.MarkAllMessagesResponse
        testHttp := new(mocks.MockedHttp)
        postBody := services.MarkAllMessagesRequestPayload{Accesstoken: accesstoken}
        testHttp.On("Post", baseURL+"/message/mark_all", &postBody, &res).Return(errors.New("network"))
        service := services.NewMessageService(testHttp, baseURL)
        got := service.MarkAllMessages(accesstoken)
        var want []*model.MarkedMessage
        testHttp.AssertExpectations(t)
        if !reflect.DeepEqual(got, want) {
            t.Errorf("got wrong return value. got: %#v, want: %#v", got, want)
        }
    })
}

In the unit test case, we populated the res in #Call.Run method and assigned the return value( res.MarkedMsgs ) of service.MarkAllMessages(accesstoken) to got variable.在单元测试用例中,我们在#Call.Run方法中填充了res并将service.MarkAllMessages(accesstoken)的返回值 ( res.MarkedMsgs ) 分配给got变量。

unit test result and coverage:单元测试结果和覆盖率:

=== RUN   TestMessageService_MarkAllMessages
--- PASS: TestMessageService_MarkAllMessages (0.00s)
=== RUN   TestMessageService_MarkAllMessages/should_mark_all_messaages
    TestMessageService_MarkAllMessages/should_mark_all_messaages: message_test.go:39: PASS: Post(string,*services.MarkAllMessagesRequestPayload,*model.MarkAllMessagesResponse)
    --- PASS: TestMessageService_MarkAllMessages/should_mark_all_messaages (0.00s)
=== RUN   TestMessageService_MarkAllMessages/should_print_error_and_return_empty_slice
network
    TestMessageService_MarkAllMessages/should_print_error_and_return_empty_slice: message_test.go:53: PASS: Post(string,*services.MarkAllMessagesRequestPayload,*model.MarkAllMessagesResponse)
    --- PASS: TestMessageService_MarkAllMessages/should_print_error_and_return_empty_slice (0.00s)
PASS
coverage: 5.6% of statements in ../../gqlgen-cnode/...

Process finished with exit code 0

在此处输入图像描述

I highly recommend to get familiar with the gomock framework and develop towards interfaces.我强烈建议您熟悉gomock框架并朝着接口发展。 What you need would look something like this.你需要的东西看起来像这样。

// SetArg does the job
myObj.EXPECT().DoSomething(gomock.Any()).SetArg(0, <value you want to r eturn>).Return(nil)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM