stretchr / testify

A toolkit with common assertions and mocks that plays nicely with the standard library
MIT License
23.53k stars 1.6k forks source link

Is there a way to override the return in .On().Return()? #1633

Closed KerbsOD closed 2 months ago

KerbsOD commented 3 months ago

Context

I'm mocking a service and i generalize the mocking of a method in the SetupTest() method like this:

type CashierTestSuite struct {
    suite.Suite
    factory               testsObjectFactory.TestsObjectFactory
    mockMerchantProcessor *merchantProcessor.MockMerchantProcessor
}

func (s *CashierTestSuite) SetupTest() {
    s.mockMerchantProcessor = s.factory.NewMockMerchantProcessor()
        s.mockMerchantProcessor.On("DebitOn", mock.Anything, mock.Anything).Return(nil)
}

For a specific test i want the mocked service to return an actual error like this:

func (s *CashierTestSuite) Test06CanNotCheckOutIfCreditCardHasInsufficientFunds() {
    errorCardWithNoFunds := errors.New(merchantProcessor.InvalidCreditCard)
    s.mockMerchantProcessor.On("DebitOn", mock.Anything, mock.Anything).Return(errorCardWithNoFunds)
     // more  test specific setup code and the definition of 'cashier' (skipping because does not add up to the question)...

    _, err := cashier.Checkout()
    assert.EqualError(s.T(), err, merchantProcessor.InvalidCreditCard)
    // some other assertions
}

Problem

The test fails because the return is nil instead of errorCardWithNoFunds. I've debugged it and it seems like mocked returns for a call are stacked in a return array.

Question

Is there a way, for a single test, to override the return of a method?

P.S. I'm new to testify and Go. I'm learning by doing some projects. I'm open to suggestions about a more idiomatic way of solving the problem.

My solution: Define s.mockMerchantProcessor.On("DebitOn", mock.Anything, mock.Anything).Return(nil) on each test where the DebitOn method is called

tscales commented 2 months ago

When you call On, The mock appends a Call to a list. When your mock calls the interface method, it will iterate through that list looking for a match on the Method name and arguments, but not the return arguments. You have two calls with arguments that match exactly, so its always going to pick the first one you defined in SetupTest, since it was inserted first in the list.

https://github.com/stretchr/testify/blob/v1.9.0/mock/mock.go#L366-L376

some solutions are discussed in https://github.com/stretchr/testify/issues/558. Creating a new mock per test is pretty common.