unit-testinggomockingtestify

Mock go function that modifies and returns the value passed as argument


I have a function in Go that takes an input argument, modifies it, and finally returns it. To be concrete, this function persists some data in DB using the Bun ORM and returns the persisted entity. That said, I have a unit test where I mock this function, and would need the mocked function to return the variable it receives as an argument. Let's better explain it with a fairly simple example:

package doer

type Something struct {
    Message string
}

type Doer interface {
    DoSomething(s *Something) *Something
}
func TestDoer(t *testing.T) {
    var response *doer.Something

    request := &doer.Something{
        Message: "foo",
    }

    sut := mocks.Doer{}
    sut.On("DoSomething", mock.Anything).Run(func(args mock.Arguments) {
        response = args.Get(0).(*doer.Something)
    }).Return(response)

    r := sut.DoSomething(request)

    assert.Equal(t, request.Message, r.Message)
}

The issue I'm facing here is that the assertion triggers a panic because r is nil. After thinking about it, I think this happens because Return is executed before the function passed to Run and at this point response is nil, so it always returns nil. That said, I would like to know if there is any way of achieving what I want. Please take into account that I need to honor the interface, I already know that I could just rely on the pointer passed to the function as an input argument and get rid of the return value, but I need to stick to this contract.

I'm using testify as the mocking framework


Solution

  • The solution has already been posted as a comment by @mkopriva. Adding that as an answer along with explanation.

    The issue is that when you declare:

        sut.On("DoSomething", mock.Anything).Run(func(args mock.Arguments) {
            response = args.Get(0).(*doer.Something)
        }).Return(response)
    

    response is nil. It is actually being initialized inside the Run() block when the function DoSomething is being invoked. But at that point, it doesn't matter. The value of response that was nil at the time of declaration, is copied.

    Changing it to *response = *(args.Get(0).(*doer.Something)) ensures that you're actually modifying the memory location that is being pointed to by the response pointer.

    To explain in simpler words: say response is storing an address : 0x7FFF5FBFFD98. Now , when you do suite.On(...) you're instructing it to return response i.e. 0x7FFF5FBFFD98. And inside, Run() you're modifying the memory being pointed to by 0x7FFF5FBFFD98.