Golang: how to mock ...interface{} arguents using gomock
Asked Answered
D

3

7

I have a Printer interface that uses the standard go Printf function signature:

type Printer interface {
    Printf(format string, tokens ...interface{})
}

I would like to be able to mock this interface using gomock, but I'm not sure how setup the tokens ...interface{} argument properly.

I expected that Printf(gomock.Any(), gomock.Any()) would cover all potential cases (since tokens compiles to []interface{}), but it appears you need to setup an explicit call for N number of tokens:

// no tokens
mockPrinter.EXPECT().
    Printf(gomock.Any()).
    AnyTimes()

// 1 token
mockPrinter.EXPECT().
    Printf(gomock.Any(), gomock.Any()).
    AnyTimes()

// 2 tokens
mockPrinter.EXPECT().
    Printf(gomock.Any(), gomock.Any(), gomock.Any()).
    AnyTimes()

// ... up to N tokens

Does anyone know of a better way to do this?

Denning answered 24/2, 2016 at 20:32 Comment(0)
T
2

Not possible with the current version of gomock. Maybe you can extend it, and send a pull request in. To understand why it's not possible, you have to look at the mock generated for variadic functions.

To do that, let's look at the examples in gomock's repository, specifically ./sample/mock_user/user.go and ./sample/mock_user/mock_user.go.

Generated Mock

You'll see a function in the Index inteface called Ellip, which is like your Printf function:

type Index interface {
    // ...
    Ellip(fmt string, args ...interface{})
    // ...
}

Now, here's what the mocked function looks like for Ellip:

func (_m *MockIndex) Ellip(_param0 string, _param1 ...interface{}) {
    _s := []interface{}{_param0}
    for _, _x := range _param1 {
        _s = append(_s, _x)
    }
    _m.ctrl.Call(_m, "Ellip", _s...)
}

Notice anything odd? Well, gomock is creating a slice of interfaces, _s, initialized with the first parameter. Then it appends the variadic parameters to that slice of interfaces, _s.

So, to be clear, it doesn't just append the variadic parameter, _param1, to the slice. Each individual variadic from _param1 is appended to the new slice, by iterating through it.

This means that the slice of variadic parameters is not preserved. It's broken out.

Trevortrevorr answered 25/2, 2016 at 0:54 Comment(0)
M
1

As of October 1, 2017, gomock.Any() works correctly for variadic args: https://github.com/golang/mock/pull/101

Morpho answered 1/4, 2022 at 0:14 Comment(0)
R
0

One approach is to define a custom matcher for the variadic argument that checks if the provided arguments match the expected ones. Here's an example of how you can do this:

// Custom matcher for variadic argument
type variadicMatcher struct {
    expected []interface{}
}

func (v variadicMatcher) Matches(x interface{}) bool {
    if len(v.expected) != len(x.([]interface{})) {
        return false
    }
    for i, val := range v.expected {
        if val != x.([]interface{})[i] {
            return false
        }
    }
    return true
    }



func (v variadicMatcher) String() string {
    return fmt.Sprintf("%v", v.expected)
}

// MatchesVariadic creates a variadicMatcher
func MatchesVariadic(expected ...interface{}) gomock.Matcher {
    return variadicMatcher{expected}
}


func TestPrinter(t *testing.T) {
    ctrl := gomock.NewController(t)
    defer ctrl.Finish()

    mockPrinter := NewMockPrinter(ctrl)

    // Setting up expectations with custom variadic matcher
    mockPrinter.EXPECT().
        Printf(gomock.Any(), MatchesVariadic("foo", 42)).
        AnyTimes()

    // Test with two variadic arguments
    mockPrinter.Printf("Test: %s, %d\n", "foo", 42)
}

I think we use limitedly gomock.Any() while doing unit test. I hope this will help.

Rancidity answered 15/4, 2024 at 7:59 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.