Skip to content

Commit cf1284f

Browse files
Allow mock expectations to be ordered (#1106)
* Allow mock expectations to be ordered * Only say another call if it has been called before
1 parent 66eef0e commit cf1284f

File tree

2 files changed

+247
-14
lines changed

2 files changed

+247
-14
lines changed

mock/mock.go

+58-14
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ type Call struct {
7070
// if the PanicMsg is set to a non nil string the function call will panic
7171
// irrespective of other settings
7272
PanicMsg *string
73+
74+
// Calls which must be satisfied before this call can be
75+
requires []*Call
7376
}
7477

7578
func newCall(parent *Mock, methodName string, callerInfo []string, methodArguments ...interface{}) *Call {
@@ -236,6 +239,27 @@ func (c *Call) Unset() *Call {
236239
return c
237240
}
238241

242+
// NotBefore indicates that the mock should only be called after the referenced
243+
// calls have been called as expected. The referenced calls may be from the
244+
// same mock instance and/or other mock instances.
245+
//
246+
// Mock.On("Do").Return(nil).Notbefore(
247+
// Mock.On("Init").Return(nil)
248+
// )
249+
func (c *Call) NotBefore(calls ...*Call) *Call {
250+
c.lock()
251+
defer c.unlock()
252+
253+
for _, call := range calls {
254+
if call.Parent == nil {
255+
panic("not before calls must be created with Mock.On()")
256+
}
257+
}
258+
259+
c.requires = append(c.requires, calls...)
260+
return c
261+
}
262+
239263
// Mock is the workhorse used to track activity on another object.
240264
// For an example of its usage, refer to the "Example Usage" section at the top
241265
// of this document.
@@ -462,6 +486,25 @@ func (m *Mock) MethodCalled(methodName string, arguments ...interface{}) Argumen
462486
}
463487
}
464488

489+
for _, requirement := range call.requires {
490+
if satisfied, _ := requirement.Parent.checkExpectation(requirement); !satisfied {
491+
m.mutex.Unlock()
492+
m.fail("mock: Unexpected Method Call\n-----------------------------\n\n%s\n\nMust not be called before%s:\n\n%s",
493+
callString(call.Method, call.Arguments, true),
494+
func() (s string) {
495+
if requirement.totalCalls > 0 {
496+
s = " another call of"
497+
}
498+
if call.Parent != requirement.Parent {
499+
s += " method from another mock instance"
500+
}
501+
return
502+
}(),
503+
callString(requirement.Method, requirement.Arguments, true),
504+
)
505+
}
506+
}
507+
465508
if call.Repeatability == 1 {
466509
call.Repeatability = -1
467510
} else if call.Repeatability > 1 {
@@ -541,32 +584,33 @@ func (m *Mock) AssertExpectations(t TestingT) bool {
541584

542585
m.mutex.Lock()
543586
defer m.mutex.Unlock()
544-
var somethingMissing bool
545587
var failedExpectations int
546588

547589
// iterate through each expectation
548590
expectedCalls := m.expectedCalls()
549591
for _, expectedCall := range expectedCalls {
550-
if !expectedCall.optional && !m.methodWasCalled(expectedCall.Method, expectedCall.Arguments) && expectedCall.totalCalls == 0 {
551-
somethingMissing = true
592+
satisfied, reason := m.checkExpectation(expectedCall)
593+
if !satisfied {
552594
failedExpectations++
553-
t.Logf("FAIL:\t%s(%s)\n\t\tat: %s", expectedCall.Method, expectedCall.Arguments.String(), expectedCall.callerInfo)
554-
} else {
555-
if expectedCall.Repeatability > 0 {
556-
somethingMissing = true
557-
failedExpectations++
558-
t.Logf("FAIL:\t%s(%s)\n\t\tat: %s", expectedCall.Method, expectedCall.Arguments.String(), expectedCall.callerInfo)
559-
} else {
560-
t.Logf("PASS:\t%s(%s)", expectedCall.Method, expectedCall.Arguments.String())
561-
}
562595
}
596+
t.Logf(reason)
563597
}
564598

565-
if somethingMissing {
599+
if failedExpectations != 0 {
566600
t.Errorf("FAIL: %d out of %d expectation(s) were met.\n\tThe code you are testing needs to make %d more call(s).\n\tat: %s", len(expectedCalls)-failedExpectations, len(expectedCalls), failedExpectations, assert.CallerInfo())
567601
}
568602

569-
return !somethingMissing
603+
return failedExpectations == 0
604+
}
605+
606+
func (m *Mock) checkExpectation(call *Call) (bool, string) {
607+
if !call.optional && !m.methodWasCalled(call.Method, call.Arguments) && call.totalCalls == 0 {
608+
return false, fmt.Sprintf("FAIL:\t%s(%s)\n\t\tat: %s", call.Method, call.Arguments.String(), call.callerInfo)
609+
}
610+
if call.Repeatability > 0 {
611+
return false, fmt.Sprintf("FAIL:\t%s(%s)\n\t\tat: %s", call.Method, call.Arguments.String(), call.callerInfo)
612+
}
613+
return true, fmt.Sprintf("PASS:\t%s(%s)", call.Method, call.Arguments.String())
570614
}
571615

572616
// AssertNumberOfCalls asserts that the method was called expectedCalls times.

mock/mock_test.go

+189
Original file line numberDiff line numberDiff line change
@@ -820,6 +820,195 @@ func Test_Mock_Return_Nothing(t *testing.T) {
820820
assert.Equal(t, 0, len(call.ReturnArguments))
821821
}
822822

823+
func Test_Mock_Return_NotBefore_In_Order(t *testing.T) {
824+
var mockedService = new(TestExampleImplementation)
825+
826+
b := mockedService.
827+
On("TheExampleMethod", 1, 2, 3).
828+
Return(4, nil)
829+
c := mockedService.
830+
On("TheExampleMethod2", true).
831+
Return().
832+
NotBefore(b)
833+
834+
require.Equal(t, []*Call{b, c}, mockedService.ExpectedCalls)
835+
require.NotPanics(t, func() {
836+
mockedService.TheExampleMethod(1, 2, 3)
837+
})
838+
require.NotPanics(t, func() {
839+
mockedService.TheExampleMethod2(true)
840+
})
841+
}
842+
843+
func Test_Mock_Return_NotBefore_Out_Of_Order(t *testing.T) {
844+
var mockedService = new(TestExampleImplementation)
845+
846+
b := mockedService.
847+
On("TheExampleMethod", 1, 2, 3).
848+
Return(4, nil).Twice()
849+
c := mockedService.
850+
On("TheExampleMethod2", true).
851+
Return().
852+
NotBefore(b)
853+
854+
require.Equal(t, []*Call{b, c}, mockedService.ExpectedCalls)
855+
856+
expectedPanicString := `mock: Unexpected Method Call
857+
-----------------------------
858+
859+
TheExampleMethod2(bool)
860+
0: true
861+
862+
Must not be called before:
863+
864+
TheExampleMethod(int,int,int)
865+
0: 1
866+
1: 2
867+
2: 3`
868+
require.PanicsWithValue(t, expectedPanicString, func() {
869+
mockedService.TheExampleMethod2(true)
870+
})
871+
}
872+
873+
func Test_Mock_Return_NotBefore_Not_Enough_Times(t *testing.T) {
874+
var mockedService = new(TestExampleImplementation)
875+
876+
b := mockedService.
877+
On("TheExampleMethod", 1, 2, 3).
878+
Return(4, nil).Twice()
879+
c := mockedService.
880+
On("TheExampleMethod2", true).
881+
Return().
882+
NotBefore(b)
883+
884+
require.Equal(t, []*Call{b, c}, mockedService.ExpectedCalls)
885+
886+
require.NotPanics(t, func() {
887+
mockedService.TheExampleMethod(1, 2, 3)
888+
})
889+
expectedPanicString := `mock: Unexpected Method Call
890+
-----------------------------
891+
892+
TheExampleMethod2(bool)
893+
0: true
894+
895+
Must not be called before another call of:
896+
897+
TheExampleMethod(int,int,int)
898+
0: 1
899+
1: 2
900+
2: 3`
901+
require.PanicsWithValue(t, expectedPanicString, func() {
902+
mockedService.TheExampleMethod2(true)
903+
})
904+
}
905+
906+
func Test_Mock_Return_NotBefore_Different_Mock_In_Order(t *testing.T) {
907+
var (
908+
mockedService1 = new(TestExampleImplementation)
909+
mockedService2 = new(TestExampleImplementation)
910+
)
911+
912+
b := mockedService1.
913+
On("TheExampleMethod", 1, 2, 3).
914+
Return(4, nil)
915+
c := mockedService2.
916+
On("TheExampleMethod2", true).
917+
Return().
918+
NotBefore(b)
919+
920+
require.Equal(t, []*Call{c}, mockedService2.ExpectedCalls)
921+
require.NotPanics(t, func() {
922+
mockedService1.TheExampleMethod(1, 2, 3)
923+
})
924+
require.NotPanics(t, func() {
925+
mockedService2.TheExampleMethod2(true)
926+
})
927+
}
928+
func Test_Mock_Return_NotBefore_Different_Mock_Out_Of_Order(t *testing.T) {
929+
var (
930+
mockedService1 = new(TestExampleImplementation)
931+
mockedService2 = new(TestExampleImplementation)
932+
)
933+
934+
b := mockedService1.
935+
On("TheExampleMethod", 1, 2, 3).
936+
Return(4, nil)
937+
c := mockedService2.
938+
On("TheExampleMethod2", true).
939+
Return().
940+
NotBefore(b)
941+
942+
require.Equal(t, []*Call{c}, mockedService2.ExpectedCalls)
943+
944+
expectedPanicString := `mock: Unexpected Method Call
945+
-----------------------------
946+
947+
TheExampleMethod2(bool)
948+
0: true
949+
950+
Must not be called before method from another mock instance:
951+
952+
TheExampleMethod(int,int,int)
953+
0: 1
954+
1: 2
955+
2: 3`
956+
require.PanicsWithValue(t, expectedPanicString, func() {
957+
mockedService2.TheExampleMethod2(true)
958+
})
959+
}
960+
961+
func Test_Mock_Return_NotBefore_In_Order_With_Non_Dependant(t *testing.T) {
962+
var mockedService = new(TestExampleImplementation)
963+
964+
a := mockedService.
965+
On("TheExampleMethod", 1, 2, 3).
966+
Return(4, nil)
967+
b := mockedService.
968+
On("TheExampleMethod", 4, 5, 6).
969+
Return(4, nil)
970+
c := mockedService.
971+
On("TheExampleMethod2", true).
972+
Return().
973+
NotBefore(a, b)
974+
d := mockedService.
975+
On("TheExampleMethod7", []bool{}).Return(nil)
976+
977+
require.Equal(t, []*Call{a, b, c, d}, mockedService.ExpectedCalls)
978+
require.NotPanics(t, func() {
979+
mockedService.TheExampleMethod7([]bool{})
980+
})
981+
require.NotPanics(t, func() {
982+
mockedService.TheExampleMethod(1, 2, 3)
983+
})
984+
require.NotPanics(t, func() {
985+
mockedService.TheExampleMethod7([]bool{})
986+
})
987+
require.NotPanics(t, func() {
988+
mockedService.TheExampleMethod(4, 5, 6)
989+
})
990+
require.NotPanics(t, func() {
991+
mockedService.TheExampleMethod7([]bool{})
992+
})
993+
require.NotPanics(t, func() {
994+
mockedService.TheExampleMethod2(true)
995+
})
996+
require.NotPanics(t, func() {
997+
mockedService.TheExampleMethod7([]bool{})
998+
})
999+
}
1000+
1001+
func Test_Mock_Return_NotBefore_Orphan_Call(t *testing.T) {
1002+
var mockedService = new(TestExampleImplementation)
1003+
1004+
require.PanicsWithValue(t, "not before calls must be created with Mock.On()", func() {
1005+
mockedService.
1006+
On("TheExampleMethod2", true).
1007+
Return().
1008+
NotBefore(&Call{Method: "Not", Arguments: Arguments{"how", "it's"}, ReturnArguments: Arguments{"done"}})
1009+
})
1010+
}
1011+
8231012
func Test_Mock_findExpectedCall(t *testing.T) {
8241013

8251014
m := new(Mock)

0 commit comments

Comments
 (0)