petergtz / pegomock

Pegomock is a powerful, yet simple mocking framework for the Go programming language
Apache License 2.0
252 stars 28 forks source link

Overriding / Resetting stubs on a mock #78

Open alexlingww opened 5 years ago

alexlingww commented 5 years ago

My team and I are currently using ginkgo/gomega in such a way that we end up stubbing out methods twice for the same input.

   mock := NewMock()
   Whenever(mock.Method(AnyString(), AnyInt())).ThenReturn(FirstReturnValue)
   // use mock.Method for expecting FirstReturnValue
   Whenever(mock.Method(AnyString(), AnyInt())).ThenReturn(SecondReturnValue)
   // use mock.Method expecting SecondReturnValue

Currently it seems the behaviour is that FirstReturnValue would be returned regardless based on some poking and prodding

The use case in is that in Ginkgo my team has begun structuring our tests such that the valid case is at the top level context, while subsequent contexts are error cases or modifications of the happy path.

Example:

var _ = Describe("endpoint.test", func() {

        Context("Calling `Endpoint`", func() {

            var (
                ctx context.Context
                endpointRequest endpoint.Request

                endpointResponse endpoint.Response
                err error

                db Database
            )

            BeforeEach(func() {
                ctx = ValidContext()
                db = NewMockDatabase()
            })

            JustBeforeEach(func() {
                Whenever(db.GetResource(AnyString()).ThenReturn(func() (params []Param) ReturnValues { // Stubbed out for happy path
                    return []ReturnValue{"resource", nil}
                })

                endpointResponse, err = endpoint(ctx, endpointRequest)
            })

            It("Should not error", func() {
                Expect(err).ToNot(HaveOccurred())
            })

            It("Returns a valid response", func() {
                Expect(endpointResponse).ToNot(BeNil())
            })

            Context("With an invalid context", func() { // Error case
                BeforeEach(func() {
                    Whenever(db.GetResource(AnyString()).ThenReturn(func() (params []Param) ReturnValues { // Redone for error cases
                        return []ReturnValue{"", errors.New("Invalid Context)}
                    })
                })
            })

            Context("With a nil endpointRequest", func() { // Error case
               BeforeEach(func() {
                    Whenever(db.GetResource(AnyString()).ThenReturn(func() (params []Param) ReturnValues {
                        return []ReturnValue{"", errors.New("Invalid Request)}
                    })
                })
            })
        })
    })
})

We've been getting around this problem by making the callback function a parameter in a helper function to construct the response, but that solution isn't ideal.

It seems either an override for the same parameters so that a second, or a reset to the mock as in Issue #15 would be ideal.

One of my teammates @jpopadak and myself are willing to work with you to implement either or both solutions.

petergtz commented 5 years ago

Hi @alexlingww, I think your suggestion for overriding makes sense. In fact, I thought the current behavior is that the last stubbing call overrides any previous ones, just like it is documented in Mockito (which I pretty much used a template for Pegomock). So I think it's even fair to consider this a bug.

I'm happy to merge both as PR. Not sure if you even need both though in your use case. Thanks, Peter

alexlingww commented 5 years ago

Hi @petergtz

Sorry for the really late reply. I'm personally not familiar with Mockito so I thought that was just intended behavior. Good to know we'd be fixing a bug.

Awesome, will get on that when I can need to cut a bit more red tape before I can get going.