Skip to content

Commit

Permalink
Support type-safe InOrder and After
Browse files Browse the repository at this point in the history
Currently when type-safe return values are used, one can't use
`InOrder` neither `After` with them as they expect
`*gomock.Call` as parameters. This changes the type these functions
expect to be an interface which implements the `GetCall() *Call` method,
which returns the embedded `*gomock.Call` type for generated mock types.

Using this approach helps to have compile-time error/warnings instead of
runtime errors with a reflection based solution.

Fixes (#70)
  • Loading branch information
EstebanOlmedo committed Sep 7, 2023
1 parent 837f20a commit 9b18c60
Show file tree
Hide file tree
Showing 6 changed files with 222 additions and 3 deletions.
13 changes: 10 additions & 3 deletions gomock/call.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ func newCall(t TestHelper, receiver any, method string, methodType reflect.Type,
args: mArgs, origin: origin, minCalls: 1, maxCalls: 1, actions: actions}
}

// GetCall returns the current `*Call` instance, this is needed to fulfill the
// interface that `InOrder` and `After` receive as parameter.
func (c *Call) GetCall() *Call {
return c
}

// AnyTimes allows the expectation to be called 0 or more times
func (c *Call) AnyTimes() *Call {
c.minCalls, c.maxCalls = 0, 1e8 // close enough to infinity
Expand Down Expand Up @@ -288,7 +294,8 @@ func (c *Call) isPreReq(other *Call) bool {
}

// After declares that the call may only match after preReq has been exhausted.
func (c *Call) After(preReq *Call) *Call {
func (c *Call) After(prq interface{GetCall() *Call}) *Call {
preReq := prq.GetCall()
c.t.Helper()

if c == preReq {
Expand Down Expand Up @@ -435,9 +442,9 @@ func (c *Call) call() []func([]any) []any {
}

// InOrder declares that the given calls should occur in order.
func InOrder(calls ...*Call) {
func InOrder(calls ...interface{GetCall() *Call}) {
for i := 1; i < len(calls); i++ {
calls[i].After(calls[i-1])
calls[i].GetCall().After(calls[i-1])
}
}

Expand Down
4 changes: 4 additions & 0 deletions mockgen/internal/tests/typed_after_in_order/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# `gomock.InOrder` and `*gomock.Call.After()` with typed generated code

From #70, this tests that `InOrder` and `*gomock.Call.After` work with code
generated usign the `typed` flag.
15 changes: 15 additions & 0 deletions mockgen/internal/tests/typed_after_in_order/inorder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package inorder


//go:generate mockgen -package inorder -source=inorder.go -destination=mock.go -typed
type Animal interface {
GetNoise() string
Feed(string) error
}

func Interact(a Animal, food string) (string, error) {
if err := a.Feed(food); err != nil {
return "", err
}
return a.GetNoise(), nil
}
45 changes: 45 additions & 0 deletions mockgen/internal/tests/typed_after_in_order/inorder_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package inorder

import (
"fmt"
"testing"

"go.uber.org/mock/gomock"
)

func TestInteract_InOrder(t *testing.T) {
ctrl := gomock.NewController(t)

mockAnimal := NewMockAnimal(ctrl)
gomock.InOrder(
mockAnimal.EXPECT().Feed("burguir").DoAndReturn(func(s string) error {
if s != "chocolate" {
return nil
}
return fmt.Errorf("Dogs can't eat chocolate!")
}),
mockAnimal.EXPECT().GetNoise().Return("Woof!"),
)
_, err := Interact(mockAnimal, "burguir")
if err != nil {
t.Fatalf("sad")
}
}

func TestInteract_After(t *testing.T) {
ctrl := gomock.NewController(t)

mockAnimal := NewMockAnimal(ctrl)
mockAnimal.EXPECT().GetNoise().Return("Woof!").After(
mockAnimal.EXPECT().Feed("burguir").DoAndReturn(func(s string) error {
if s != "chocolate" {
return nil
}
return fmt.Errorf("Dogs can't eat chocolate!")
}),
)
_, err := Interact(mockAnimal, "burguir")
if err != nil {
t.Fatalf("sad")
}
}
134 changes: 134 additions & 0 deletions mockgen/internal/tests/typed_after_in_order/mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions mockgen/mockgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,20 @@ func (g *generator) GenerateMockReturnCallMethod(intf *model.Interface, m *model
g.p("return %s", idRecv)
g.out()
g.p("}")

g.p("// Call rewrite *gomock.Call.GetCall")
g.p("func (%s *%sCall%s) GetCall() *gomock.Call {", idRecv, recvStructName, shortTp)
g.in()
g.p("return %s.Call", idRecv)
g.out()
g.p("}")

g.p("// After rewrite *gomock.Call.After")
g.p("func (%s *%sCall%s) After(prq interface{ GetCall() *gomock.Call }) *gomock.Call {", idRecv, recvStructName, shortTp)
g.in()
g.p("return %s.Call.After(prq)", idRecv)
g.out()
g.p("}")
return nil
}

Expand Down

0 comments on commit 9b18c60

Please sign in to comment.