Skip to content

Commit

Permalink
Merge pull request #35 from ovechkin-dm/34-verifynomoreinteractions-i…
Browse files Browse the repository at this point in the history
…s-not-working-as-expected

correct behavior for VerifyNoMoreInteractions
  • Loading branch information
ovechkin-dm authored Jan 5, 2024
2 parents 2380f83 + d3b6e34 commit 5f8c844
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 51 deletions.
18 changes: 0 additions & 18 deletions matchers/verification.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ type InvocationData struct {
Args []reflect.Value
}

type InstanceVerifier interface {
RecordInteraction(data *InvocationData) error
}

type MethodVerifier interface {
Verify(data *MethodVerificationData) error
}
Expand Down Expand Up @@ -47,24 +43,10 @@ func MethodVerifierFromFunc(f func(data *MethodVerificationData) error) MethodVe
}
}

func InstanceVerifierFromFunc(f func(data *InvocationData) error) InstanceVerifier {
return &instanceVerifierImpl{
f: f,
}
}

type methodVerifierImpl struct {
f func(data *MethodVerificationData) error
}

func (m *methodVerifierImpl) Verify(data *MethodVerificationData) error {
return m.f(data)
}

type instanceVerifierImpl struct {
f func(data *InvocationData) error
}

func (i *instanceVerifierImpl) RecordInteraction(data *InvocationData) error {
return i.f(data)
}
13 changes: 9 additions & 4 deletions mock/api.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package mock

import (
"context"
"fmt"
"github.com/ovechkin-dm/mockio/matchers"
"github.com/ovechkin-dm/mockio/registry"
Expand Down Expand Up @@ -97,6 +98,12 @@ func AnyInterface() any {
return Any[any]()
}

// AnyContext is an alias for Any[context.Context]
// See Any for more description
func AnyContext() context.Context {
return Any[context.Context]()
}

// AnyOfType is an alias for Any[T] for specific type
// Used for automatic type inference
func AnyOfType[T any](t T) T {
Expand Down Expand Up @@ -354,7 +361,7 @@ func Never() matchers.MethodVerifier {
}

// VerifyNoMoreInteractions verifies that there are no more unverified interactions with the mock object.
//
// For example if
// Example usage:
//
// // Create a mock object for testing
Expand All @@ -369,7 +376,5 @@ func Never() matchers.MethodVerifier {
// // Verify that there are no more unverified interactions
// VerifyNoMoreInteractions(mockObj)
func VerifyNoMoreInteractions(value any) {
registry.VerifyInstance(value, matchers.InstanceVerifierFromFunc(func(data *matchers.InvocationData) error {
return fmt.Errorf("no more interactions should be recorded for mock")
}))
registry.VerifyNoMoreInteractions(value)
}
40 changes: 14 additions & 26 deletions registry/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
type invocationHandler struct {
ctx *mockContext
calls []*methodRecorder
instanceVerifiers []matchers.InstanceVerifier
lock sync.Mutex
instanceType reflect.Type
}
Expand All @@ -35,10 +34,6 @@ func (h *invocationHandler) Handle(method *dyno.Method, values []reflect.Value)

func (h *invocationHandler) DoAnswer(c *MethodCall) []reflect.Value {
rec := h.calls[c.Method.Num]
ok := h.VerifyInstance(c)
if !ok {
return createDefaultReturnValues(c.Method.Type)
}
h.ctx.getState().whenHandler = h
h.ctx.getState().whenCall = c
var matched bool
Expand Down Expand Up @@ -130,26 +125,6 @@ func (h *invocationHandler) When() matchers.ReturnerAll {
return NewReturnerAll(h.ctx, m)
}

func (h *invocationHandler) VerifyInstance(m *MethodCall) bool {
data := &matchers.InvocationData{
MethodType: m.Method.Type,
MethodName: m.Method.Name,
Args: m.Values,
}
for _, v := range h.instanceVerifiers {
err := v.RecordInteraction(data)
if err != nil {
h.ctx.reporter.FailNow(err)
return false
}
}
return true
}

func (h *invocationHandler) AddInstanceVerifier(v matchers.InstanceVerifier) {
h.instanceVerifiers = append(h.instanceVerifiers, v)
}

func (h *invocationHandler) VerifyMethod(verifier matchers.MethodVerifier) {
h.lock.Lock()
defer h.lock.Unlock()
Expand Down Expand Up @@ -198,6 +173,7 @@ func (h *invocationHandler) DoVerifyMethod(call *MethodCall) []reflect.Value {
}

if matches {
c.Verified = true
numMethodCalls += 1
}
}
Expand Down Expand Up @@ -225,7 +201,6 @@ func newHandler[T any](holder *mockContext) *invocationHandler {
return &invocationHandler{
ctx: holder,
calls: recorders,
instanceVerifiers: make([]matchers.InstanceVerifier, 0),
instanceType: tp,
}
}
Expand Down Expand Up @@ -308,3 +283,16 @@ func (h *invocationHandler) CheckUnusedStubs() {
}
}
}

func (h *invocationHandler) VerifyNoMoreInteractions() {
for _, rec := range h.calls {
for _, call := range rec.calls {
if call.WhenCall {
continue
}
if !call.Verified {
h.ctx.reporter.ReportNoMoreInteractionsExpected(h.instanceType, call)
}
}
}
}
4 changes: 2 additions & 2 deletions registry/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,14 +107,14 @@ func VerifyMethod(t any, v matchers.MethodVerifier) {
})
}

func VerifyInstance(t any, v matchers.InstanceVerifier) {
func VerifyNoMoreInteractions(t any) {
withCheck(func() any {
handler, ok := getInstance().mapping[t]
if !ok {
getInstance().mockContext.reporter.ReportUnregisteredMockVerify(t)
return nil
}
handler.AddInstanceVerifier(v)
handler.VerifyNoMoreInteractions()
return nil
})
}
Expand Down
5 changes: 5 additions & 0 deletions registry/reporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,3 +202,8 @@ func PrettyPrintMethodInvocation(interfaceType reflect.Type, method reflect.Meth
sb.WriteRune(')')
return sb.String()
}

func (e *EnrichedReporter) ReportNoMoreInteractionsExpected(instanceType reflect.Type, call *MethodCall) {
methodSig := prettyPrintMethodSignature(instanceType, call.Method.Type)
e.Errorf("no more interactions expected on %v", methodSig)
}
1 change: 1 addition & 0 deletions registry/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,5 @@ type MethodCall struct {
Method *dyno.Method
Values []reflect.Value
WhenCall bool
Verified bool
}
40 changes: 39 additions & 1 deletion tests/verify/verify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,50 @@ func TestVerifyNeverFails(t *testing.T) {
r.AssertError()
}

func TestNoMoreInteractions(t *testing.T) {
func TestNoMoreInteractionsFails(t *testing.T) {
r := common.NewMockReporter(t)
SetUp(r)
m := Mock[iface]()
WhenSingle(m.Foo(Any[int]())).ThenReturn(10)
m.Foo(10)
VerifyNoMoreInteractions(m)
r.AssertError()
}

func TestNoMoreInteractionsSuccess(t *testing.T) {
r := common.NewMockReporter(t)
SetUp(r)
m := Mock[iface]()
WhenSingle(m.Foo(Any[int]())).ThenReturn(10)
m.Foo(10)
Verify(m, Once()).Foo(10)
VerifyNoMoreInteractions(m)
r.AssertNoError()
}

func TestNoMoreInteractionsComplexFail(t *testing.T) {
r := common.NewMockReporter(t)
SetUp(r)
m := Mock[iface]()
WhenSingle(m.Foo(10)).ThenReturn(10)
WhenSingle(m.Foo(11)).ThenReturn(10)
m.Foo(10)
m.Foo(11)
Verify(m, Once()).Foo(10)
VerifyNoMoreInteractions(m)
r.AssertError()
}

func TestNoMoreInteractionsComplexSuccess(t *testing.T) {
r := common.NewMockReporter(t)
SetUp(r)
m := Mock[iface]()
WhenSingle(m.Foo(10)).ThenReturn(10)
WhenSingle(m.Foo(11)).ThenReturn(10)
m.Foo(10)
m.Foo(11)
Verify(m, AtLeastOnce()).Foo(AnyInt())
Verify(m, Once()).Foo(11)
VerifyNoMoreInteractions(m)
r.AssertNoError()
}

0 comments on commit 5f8c844

Please sign in to comment.