Search code examples
unit-testinggomockingmockery

How to override mocked calls expectations in table driven tests


In working on a table-driven test where I'm using some mocks generated by mockery and set some method-call expectations that depend on the data provided in the data set for each test case. I'm facing the issue that the mocked call is returning always the result set for the expectation in the first test case instead of the one defined for the test case bein executed.

func (s *MyTestSuite) TestMySampleTest() {
    testCases := []struct {
        Name     string
        Requests []*service.CreateCredentialRequest
    }{
        {
            Name:     "first case",
            mockedResult: 1,
            expected: 1,
        },
        {
            Name:     "second case",
            mockedResult: 2,
            expected: 2,
        },
    }

    for _, tc := range testCases {
        s.Run(tc.Name, func() {
            s.someMock.On("SomeMethodCall", mock.Anything).Return(tc.mockedResult)

            result := s.SUT.SomeMethodThatCallsTheMockedObe()

            s.Equal(expected, result)
        })
    }
}

When I run this test, it fails for the second case because the result is 1 instead of 2 as expected, and I can see that the problem is the mocked method is returning 1 (the value set for the first test case) instead of 2 (the value set for the current test case).

Any idea on how to fix it?


Solution

  • It isn't probably the most elegant solution and I would like to know if there is any other way of doing it, but for the time being, I've found this solution that makes the trick. It consists of generating a new mock for every subtest run by the table-driven test, so in every subtest we use a fresh new mock instance that doesn't have any expectations set from a previous subtest. Taking into account that I'm using testify.Suite to organize and handle my tests, doing this is as easy as manually calling the s.SetupTest() method in every subtest:

    // SetupTest is executed before every test is run, I instantiate the SUT and 
    // its dependencies here.
    func (s *MyTestSuite) SetupTest() {
        // Instantiate the mock
        s.someMock = mocks.NewSomeMock(s.T())
        // Instantiate the SUT, injecting the mock through the constructor function
        s.SUT = NewSUT(s.someMock)
    }
    
    func (s *MyTestSuite) TestMySampleTest() {
        testCases := []struct {
            Name     string
            Requests []*service.CreateCredentialRequest
        }{
            // test cases here 
        }
    
        for _, tc := range testCases {
            s.Run(tc.Name, func() {
                // Manually calling s.SetupTest() to generate new instances of the mocks in every subtest.
                // If we don't do this, the mock will always return the first expectation set (the one set for the first test case).
                s.SetupTest()
    
                // Here comes the logic of the text as we had it before
                s.someMock.On("SomeMethodCall", mock.Anything).Return(tc.mockedResult)
    
                result := s.SUT.SomeMethodThatCallsTheMockedObe()
    
                s.Equal(expected, result)
            })
        }
    }