32

I'm running a test with multiple parameters in a for loop using go lang testing.

I ran into a situation where same return value (and first set) is returned every time the mock is called. What I want to be able to do is change the return value for each test when the input is same i.e., same On but different Return in a loop.

I am using stretchr/testify for mocks. It looks like it will not overwrite already created mock when On is same.

func TestUpdateContactWithNewActions(t *testing.T) {
    tests := []struct {
        testName  string
        getParams func() *activities.UpdateContactWithNewActionsActivity
        mockError error
    }{

        {"UpdateContactWithNewActions with error from contact service",
            func() *activities.UpdateContactWithNewActionsActivity {
                return fixtures.GetUpdateContactWithNewActionsActivity()
            }, fixtures.Err},
        {"UpdateContactWithNewActions valid",
            func() *activities.UpdateContactWithNewActionsActivity {
                return fixtures.GetUpdateContactWithNewActionsActivity()
            }, nil},
    }

    lib.LoadWithMockClients()

    for _, test := range tests {
        test := test
        t.Run(test.testName, func(t *testing.T) {
            lib.MockCSClient.On(
                "UpdateContactWithNewActions",
                mock.AnythingOfType("tchannel.headerCtx"),
                fixtures.UpdateContactWithNewActions).Return(test.mockError)

            returnedResult, err := test.getParams().Execute(fixtures.Ctx)
            if test.mockError == nil {
                // some assertion
            }
            assert.Error(t, err)
        })
    }
}
TechCrunch
  • 2,924
  • 5
  • 45
  • 79
  • What package are you using which provides this `On` method? It seems that is the problem here, not Go itself. – lmars Sep 22 '17 at 23:37
  • 1
    stretchr/testify – TechCrunch Sep 22 '17 at 23:46
  • 2
    It looks like the library just appends to an internal list, so when the method gets called it always matches the first return result which was registered. Have you considered using a new mock for each test run? – lmars Sep 22 '17 at 23:55
  • I considered that but din't try seriously because of the way `MockCSClient` is created in my test. But thats a good idea, will try that. – TechCrunch Sep 23 '17 at 07:06
  • 2
    Hey Did you find any solution for this ? – user2823667 Apr 05 '19 at 05:31
  • I ran into the same problem. If the function that is being tested can be broken down, then we can reset the mocks when testing different parts.. – Rajan Ponnappan Mar 25 '20 at 17:51
  • Looks like on recent version, you can call On() multiple times and stretchr seems to be using the setup – Rajan Ponnappan Mar 25 '20 at 17:54
  • Would it be possible to make an extract of this in https://play.golang.org. It is very hard to judge without being able to reproduce. I do see some strange things like the line in the for loop where you are doing `test := test`. In for loops you have to keep in mind pointers, which is a common pitfall. This article might highlight the problem you are facing https://medium.com/@pedram.esmaeeli/golang-pitfalls-f2ebae9c8208 – Marco Apr 08 '20 at 14:17

2 Answers2

67

I had a similar problem.

The solution was the method Once()

In your mock add an .Once() and repeat the mock with each result you need.

Something like this:

lib.Mock.On("method", arg).Return(test.mockError).Once()
lib.Mock.On("method", arg).Return(nil).Once()

Each mock result will be returned only once.

https://godoc.org/github.com/stretchr/testify/mock#Call.Once

Marcos
  • 1,240
  • 10
  • 20
6

The answer @Marcos provided works well when the result needs to be returned exactly once.
But in the scenario where each return value needs to be returned multiple (unknown) times, it won't work.

The way I solved it is by manipulating the mock.ExpectedCalls directly. In my case the mock was holding only a single method, so it was simple to just cleanup the whole ExpectedCalls slice, but in case there are multiple methods, the ExpectedCalls slice can be iterated, and update only the required call.

here is a working example for the simple case:

lib.Mock.On("method", arg).Return("1")

assert.Equal(t, lib.Mock.method(arg), "1")
assert.Equal(t, lib.Mock.method(arg), "1")
....
assert.Equal(t, lib.Mock.method(arg), "1")

lib.Mock.ExpectedCalls = nil // cleanup the previous return value
lib.Mock.On("method", arg).Return("2")
assert.Equal(t, lib.Mock.method(arg), "2")
assert.Equal(t, lib.Mock.method(arg), "2")
....
assert.Equal(t, lib.Mock.method(arg), "2")
lev
  • 3,986
  • 4
  • 33
  • 46