diff --git a/README.md b/README.md index c78250e41..56689aad1 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,8 @@ func TestSomething(t *testing.T) { // assert that the expectations were met testObj.AssertExpectations(t) + // assert that the expectations were met in order + testObj.AssertExpectationsInOrder(t) } @@ -188,6 +190,9 @@ func TestSomethingWithPlaceholder(t *testing.T) { // assert that the expectations were met testObj.AssertExpectations(t) + // assert that the expectations were met in order + testObj.AssertExpectationsInOrder(t) + } ``` diff --git a/mock/mock.go b/mock/mock.go index 5d445c6d3..3d0d8c234 100644 --- a/mock/mock.go +++ b/mock/mock.go @@ -533,6 +533,137 @@ func (m *Mock) AssertExpectations(t TestingT) bool { return !somethingMissing } +// AssertExpectationsInOrder asserts that everything specified with On and Return +// was in fact called as expected in the order expected. Expectations set up for +// a specific number of times must be called that number of times before the next +// call to the mock is made. If optional, they do not need to be called the full +// number of times. Expectation with no specific limit must be called at least +// once unless optional. +func (m *Mock) AssertExpectationsInOrder(t TestingT) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + m.mutex.Lock() + defer m.mutex.Unlock() + var somethingMissing bool + var failedExpectations int + + expectedCalls := m.expectedCalls() + actualCalls := m.calls() + + actualCallPtr := 0 + + // iterate through each expectation + for _, expectedCall := range expectedCalls { + if expectedCall.Repeatability == 0 { + // if the number of calls is unbounded, ensure the call was made at least once as expected + wasCalled := false + + for { + if actualCallPtr >= len(actualCalls) { + // end of actual call list, this is a problem only if call is required an not yet called + if expectedCall.totalCalls == 0 && !expectedCall.optional { + somethingMissing = true + failedExpectations++ + t.Logf( + "FAIL:\texpected %s(%s) to be called\n\t\tat: %s", + expectedCall.Method, + expectedCall.Arguments.String(), + expectedCall.callerInfo, + ) + } + break + } + + if !m.callsMatch(expectedCall, &actualCalls[actualCallPtr]) { + break + } + + wasCalled = true + actualCallPtr++ + } + + if !wasCalled && !expectedCall.optional { + somethingMissing = true + failedExpectations++ + t.Logf( + "FAIL:\texpected %s(%s)\n\t\tat: %s", + expectedCall.Method, + expectedCall.Arguments.String(), + expectedCall.callerInfo, + ) + } + } else { + // if there's a specific number of calls expected, check each one + + if expectedCall.Repeatability != -1 { + if !expectedCall.optional { + somethingMissing = true + failedExpectations++ + expectedCallsStr := "1 time" + if expectedCall.Repeatability+expectedCall.totalCalls != 1 { + expectedCallsStr = fmt.Sprintf("%d times", expectedCall.Repeatability+expectedCall.totalCalls) + } + actualCallsStr := "1 time" + if expectedCall.totalCalls != 1 { + actualCallsStr = fmt.Sprintf("%d times", expectedCall.totalCalls) + } + t.Logf( + "FAIL:\texpected %s(%s) to be called %s, actually called %s\n\t\tat: %s", + expectedCall.Method, + expectedCall.Arguments.String(), + expectedCallsStr, + actualCallsStr, + expectedCall.callerInfo, + ) + } + break + } + + for i := 0; i < expectedCall.totalCalls; i++ { + if actualCallPtr >= len(actualCalls) { + // this should never happen because `.Times` prevents additional calls + // before this point can be hit + somethingMissing = true + failedExpectations++ + t.Logf( + "FAIL:\texpected %s(%s) to be called\n\t\tat: %s", + expectedCall.Method, + expectedCall.Arguments.String(), + expectedCall.callerInfo, + ) + break + } + + actualCall := actualCalls[actualCallPtr] + + wasCalled := m.callsMatch(expectedCall, &actualCall) + + if !wasCalled && !expectedCall.optional { + somethingMissing = true + failedExpectations++ + t.Logf( + "FAIL:\texpected %s(%s)\nactual %s(%s)\n\t\tat: %s", + expectedCall.Method, + expectedCall.Arguments.String(), + actualCall.Method, + actualCall.Arguments.String(), + expectedCall.callerInfo, + ) + } + + actualCallPtr++ + } + } + } + + if somethingMissing { + 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()) + } + + return !somethingMissing +} + // AssertNumberOfCalls asserts that the method was called expectedCalls times. func (m *Mock) AssertNumberOfCalls(t TestingT, methodName string, expectedCalls int) bool { if h, ok := t.(tHelper); ok { @@ -643,6 +774,16 @@ func (m *Mock) methodWasCalled(methodName string, expected []interface{}) bool { return false } +func (m *Mock) callsMatch(expected, actual *Call) bool { + if actual.Method == expected.Method { + _, differences := Arguments(expected.Arguments).Diff(actual.Arguments) + if differences == 0 { + return true + } + } + return false +} + func (m *Mock) expectedCalls() []*Call { return append([]*Call{}, m.ExpectedCalls...) } diff --git a/mock/mock_test.go b/mock/mock_test.go index 097addc8a..36525dd13 100644 --- a/mock/mock_test.go +++ b/mock/mock_test.go @@ -1065,6 +1065,317 @@ func Test_Mock_AssertExpectations_With_Repeatability(t *testing.T) { } +func Test_Mock_AssertExpectationsInOrder_InOrder(t *testing.T) { + + var mockedService = new(TestExampleImplementation) + + mockedService.On("Test_Mock_AssertExpectationsInOrder_InOrder", 1, 2, 3).Return() + mockedService.On("Test_Mock_AssertExpectationsInOrder_InOrder", 2, 3, 4).Return() + mockedService.On("Test_Mock_AssertExpectationsInOrder_InOrder", 3, 4, 5).Return() + + tt := new(testing.T) + assert.False(t, mockedService.AssertExpectationsInOrder(tt)) + + // make most of the calls + mockedService.Called(1, 2, 3) + mockedService.Called(2, 3, 4) + + assert.False(t, mockedService.AssertExpectationsInOrder(tt)) + + // make the last call + mockedService.Called(3, 4, 5) + + // now assert expectations + assert.True(t, mockedService.AssertExpectationsInOrder(tt)) + +} + +func Test_Mock_AssertExpectationsInOrder_MissingFirstCall(t *testing.T) { + + var mockedService = new(TestExampleImplementation) + + mockedService.On("Test_Mock_AssertExpectationsInOrder_MissingFirstCall", 1, 2, 3).Return() + mockedService.On("Test_Mock_AssertExpectationsInOrder_MissingFirstCall", 2, 3, 4).Return() + mockedService.On("Test_Mock_AssertExpectationsInOrder_MissingFirstCall", 3, 4, 5).Return() + + tt := new(testing.T) + assert.False(t, mockedService.AssertExpectationsInOrder(tt)) + + // make the calls + mockedService.Called(2, 3, 4) + mockedService.Called(3, 4, 5) + + // now assert expectations + assert.False(t, mockedService.AssertExpectationsInOrder(tt)) + +} + +func Test_Mock_AssertExpectationsInOrder_MissingMidCall(t *testing.T) { + + var mockedService = new(TestExampleImplementation) + + mockedService.On("Test_Mock_AssertExpectationsInOrder_MissingMidCall", 1, 2, 3).Return() + mockedService.On("Test_Mock_AssertExpectationsInOrder_MissingMidCall", 2, 3, 4).Return() + mockedService.On("Test_Mock_AssertExpectationsInOrder_MissingMidCall", 3, 4, 5).Return() + + tt := new(testing.T) + assert.False(t, mockedService.AssertExpectationsInOrder(tt)) + + // make the calls + mockedService.Called(1, 2, 3) + mockedService.Called(3, 4, 5) + + // now assert expectations + assert.False(t, mockedService.AssertExpectationsInOrder(tt)) + +} + +func Test_Mock_AssertExpectationsInOrder_MissingLastCall(t *testing.T) { + + var mockedService = new(TestExampleImplementation) + + mockedService.On("Test_Mock_AssertExpectationsInOrder_MissingLastCall", 1, 2, 3).Return() + mockedService.On("Test_Mock_AssertExpectationsInOrder_MissingLastCall", 2, 3, 4).Return() + mockedService.On("Test_Mock_AssertExpectationsInOrder_MissingLastCall", 3, 4, 5).Return() + + tt := new(testing.T) + assert.False(t, mockedService.AssertExpectationsInOrder(tt)) + + // make the calls + mockedService.Called(1, 2, 3) + mockedService.Called(2, 3, 4) + + // now assert expectations + assert.False(t, mockedService.AssertExpectationsInOrder(tt)) + +} + +func Test_Mock_AssertExpectationsInOrder_OutOfOrder(t *testing.T) { + + var mockedService = new(TestExampleImplementation) + + mockedService.On("Test_Mock_AssertExpectationsInOrder_OutOfOrder", 1, 2, 3).Return() + mockedService.On("Test_Mock_AssertExpectationsInOrder_OutOfOrder", 2, 3, 4).Return() + mockedService.On("Test_Mock_AssertExpectationsInOrder_OutOfOrder", 3, 4, 5).Return() + + tt := new(testing.T) + assert.False(t, mockedService.AssertExpectationsInOrder(tt)) + + // make the calls + mockedService.Called(1, 2, 3) + mockedService.Called(3, 4, 5) + mockedService.Called(2, 3, 4) + + // now assert expectations + assert.False(t, mockedService.AssertExpectationsInOrder(tt)) + +} + +func Test_Mock_AssertExpectationsInOrder_OptionalLast(t *testing.T) { + + var mockedService = new(TestExampleImplementation) + + mockedService.On("Test_Mock_AssertExpectationsInOrder_OptionalLast", 1, 2, 3).Return() + mockedService.On("Test_Mock_AssertExpectationsInOrder_OptionalLast", 2, 3, 4).Return().Maybe() + + tt := new(testing.T) + assert.False(t, mockedService.AssertExpectationsInOrder(tt)) + + // make the calls + mockedService.Called(1, 2, 3) + mockedService.Called(1, 2, 3) + + // now assert expectations + assert.True(t, mockedService.AssertExpectationsInOrder(tt)) + +} + +func Test_Mock_AssertExpectationsInOrder_OptionalFirst(t *testing.T) { + + var mockedService = new(TestExampleImplementation) + + mockedService.On("Test_Mock_AssertExpectationsInOrder_OptionalFirst", 1, 2, 3).Return().Maybe() + mockedService.On("Test_Mock_AssertExpectationsInOrder_OptionalFirst", 2, 3, 4).Return() + + tt := new(testing.T) + assert.False(t, mockedService.AssertExpectationsInOrder(tt)) + + // make the calls + mockedService.Called(2, 3, 4) + mockedService.Called(2, 3, 4) + + // now assert expectations + assert.True(t, mockedService.AssertExpectationsInOrder(tt)) + +} + +func Test_Mock_AssertExpectationsInOrder_Repeatability(t *testing.T) { + + var mockedService = new(TestExampleImplementation) + + mockedService.On("Test_Mock_AssertExpectationsInOrder_Repeatability", 1, 2, 3).Return().Once() + mockedService.On("Test_Mock_AssertExpectationsInOrder_Repeatability", 2, 3, 4).Return().Twice() + mockedService.On("Test_Mock_AssertExpectationsInOrder_Repeatability", 3, 4, 5).Return().Once() + + tt := new(testing.T) + assert.False(t, mockedService.AssertExpectationsInOrder(tt)) + + // make the calls + mockedService.Called(1, 2, 3) + mockedService.Called(2, 3, 4) + assert.False(t, mockedService.AssertExpectationsInOrder(tt)) + mockedService.Called(2, 3, 4) + mockedService.Called(3, 4, 5) + + // now assert expectations + assert.True(t, mockedService.AssertExpectationsInOrder(tt)) + +} + +func Test_Mock_AssertExpectationsInOrder_Repeatability_TooFewCalls(t *testing.T) { + + var mockedService = new(TestExampleImplementation) + + mockedService.On("Test_Mock_AssertExpectationsInOrder_Repeatability_TooFewCalls", 1, 2, 3).Return().Once() + mockedService.On("Test_Mock_AssertExpectationsInOrder_Repeatability_TooFewCalls", 2, 3, 4).Return().Twice() + mockedService.On("Test_Mock_AssertExpectationsInOrder_Repeatability_TooFewCalls", 3, 4, 5).Return().Once() + + tt := new(testing.T) + assert.False(t, mockedService.AssertExpectationsInOrder(tt)) + + // make the calls + mockedService.Called(1, 2, 3) + mockedService.Called(2, 3, 4) + mockedService.Called(3, 4, 5) + + // now assert expectations + assert.False(t, mockedService.AssertExpectationsInOrder(tt)) + +} + +func Test_Mock_AssertExpectationsInOrder_Repeatability_NotCalled(t *testing.T) { + + var mockedService = new(TestExampleImplementation) + + mockedService.On("Test_Mock_AssertExpectationsInOrder_Repeatability_NotCalled", 1, 2, 3).Return().Once() + mockedService.On("Test_Mock_AssertExpectationsInOrder_Repeatability_NotCalled", 2, 3, 4).Return().Twice() + mockedService.On("Test_Mock_AssertExpectationsInOrder_Repeatability_NotCalled", 3, 4, 5).Return().Once() + + tt := new(testing.T) + assert.False(t, mockedService.AssertExpectationsInOrder(tt)) + + // make the calls + mockedService.Called(1, 2, 3) + mockedService.Called(3, 4, 5) + + // now assert expectations + assert.False(t, mockedService.AssertExpectationsInOrder(tt)) + +} + +func Test_Mock_AssertExpectationsInOrder_Repeatability_LastNotCalled(t *testing.T) { + + var mockedService = new(TestExampleImplementation) + + mockedService.On("Test_Mock_AssertExpectationsInOrder_Repeatability_LastNotCalled", 1, 2, 3).Return().Once() + mockedService.On("Test_Mock_AssertExpectationsInOrder_Repeatability_LastNotCalled", 2, 3, 4).Return().Twice() + mockedService.On("Test_Mock_AssertExpectationsInOrder_Repeatability_LastNotCalled", 3, 4, 5).Return().Once() + + tt := new(testing.T) + assert.False(t, mockedService.AssertExpectationsInOrder(tt)) + + // make the calls + mockedService.Called(1, 2, 3) + mockedService.Called(2, 3, 4) + mockedService.Called(2, 3, 4) + + // now assert expectations + assert.False(t, mockedService.AssertExpectationsInOrder(tt)) + +} + +func Test_Mock_AssertExpectationsInOrder_Repeatability_CalledOutOfOrder(t *testing.T) { + + var mockedService = new(TestExampleImplementation) + + mockedService.On("Test_Mock_AssertExpectationsInOrder_Repeatability_CalledOutOfOrder", 1, 2, 3).Return().Twice() + mockedService.On("Test_Mock_AssertExpectationsInOrder_Repeatability_CalledOutOfOrder", 2, 3, 4).Return().Twice() + + tt := new(testing.T) + assert.False(t, mockedService.AssertExpectationsInOrder(tt)) + + // make the calls + mockedService.Called(1, 2, 3) + mockedService.Called(2, 3, 4) + mockedService.Called(1, 2, 3) + mockedService.Called(2, 3, 4) + + // now assert expectations + assert.False(t, mockedService.AssertExpectationsInOrder(tt)) + +} + +func Test_Mock_AssertExpectationsInOrder_Repeatability_Optional(t *testing.T) { + + var mockedService = new(TestExampleImplementation) + + mockedService.On("Test_Mock_AssertExpectationsInOrder_Repeatability_Optional", 1, 2, 3).Return().Twice() + mockedService.On("Test_Mock_AssertExpectationsInOrder_Repeatability_Optional", 2, 3, 4).Return().Twice().Maybe() + + tt := new(testing.T) + assert.False(t, mockedService.AssertExpectationsInOrder(tt)) + + // make the calls + mockedService.Called(1, 2, 3) + mockedService.Called(1, 2, 3) + + // now assert expectations + assert.True(t, mockedService.AssertExpectationsInOrder(tt)) + +} + +func Test_Mock_AssertExpectationsInOrder_Repeatability_PartialCalledOptional(t *testing.T) { + + var mockedService = new(TestExampleImplementation) + + mockedService.On("Test_Mock_AssertExpectationsInOrder_Repeatability_PartialCalledOptional", 1, 2, 3).Return().Twice() + mockedService.On("Test_Mock_AssertExpectationsInOrder_Repeatability_PartialCalledOptional", 2, 3, 4).Return().Twice().Maybe() + + tt := new(testing.T) + assert.False(t, mockedService.AssertExpectationsInOrder(tt)) + + // make the calls + mockedService.Called(1, 2, 3) + mockedService.Called(1, 2, 3) + mockedService.Called(2, 3, 4) + + // now assert expectations + assert.True(t, mockedService.AssertExpectationsInOrder(tt)) + +} + +// this test provides code coverage of an edge case that shouldn't be hit +// in real-world-usage +func Test_Mock_AssertExpectationsInOrder_Repeatability_HackAdditionalExpectedCalls(t *testing.T) { + + var mockedService = new(TestExampleImplementation) + + mockedService.On("Test_Mock_AssertExpectationsInOrder_Repeatability_HackAdditionalExpectedCalls", 2, 3, 4).Return().Once() + + tt := new(testing.T) + assert.False(t, mockedService.AssertExpectationsInOrder(tt)) + + // make the calls + mockedService.Called(2, 3, 4) + + // HACK + mockedService.expectedCalls()[0].totalCalls++ + + // now assert expectations + assert.False(t, mockedService.AssertExpectationsInOrder(tt)) + +} + func Test_Mock_TwoCallsWithDifferentArguments(t *testing.T) { var mockedService = new(TestExampleImplementation) @@ -1209,14 +1520,14 @@ func Test_Mock_AssertOptional(t *testing.T) { ms1.TheExampleMethod(1, 2, 3) tt1 := new(testing.T) - assert.Equal(t, true, ms1.AssertExpectations(tt1)) + assert.Equal(t, true, ms1.AssertExpectationsInOrder(tt1)) // Optional not called var ms2 = new(TestExampleImplementation) ms2.On("TheExampleMethod", 1, 2, 3).Maybe().Return(4, nil) tt2 := new(testing.T) - assert.Equal(t, true, ms2.AssertExpectations(tt2)) + assert.Equal(t, true, ms2.AssertExpectationsInOrder(tt2)) // Non-optional called var ms3 = new(TestExampleImplementation) @@ -1224,7 +1535,7 @@ func Test_Mock_AssertOptional(t *testing.T) { ms3.TheExampleMethod(1, 2, 3) tt3 := new(testing.T) - assert.Equal(t, true, ms3.AssertExpectations(tt3)) + assert.Equal(t, true, ms3.AssertExpectationsInOrder(tt3)) } /* @@ -1441,7 +1752,7 @@ func Test_MockMethodCalled(t *testing.T) { retArgs := m.MethodCalled("foo", "hello") require.True(t, len(retArgs) == 1) require.Equal(t, "world", retArgs[0]) - m.AssertExpectations(t) + m.AssertExpectationsInOrder(t) } func Test_MockMethodCalled_Panic(t *testing.T) { @@ -1449,7 +1760,7 @@ func Test_MockMethodCalled_Panic(t *testing.T) { m.On("foo", "hello").Panic("world panics") require.PanicsWithValue(t, "world panics", func() { m.MethodCalled("foo", "hello") }) - m.AssertExpectations(t) + m.AssertExpectationsInOrder(t) } // Test to validate fix for racy concurrent call access in MethodCalled() @@ -1551,7 +1862,7 @@ func TestArgumentMatcherToPrintMismatch(t *testing.T) { res := m.GetTime(1) require.Equal(t, "SomeTime", res) - m.AssertExpectations(t) + m.AssertExpectationsInOrder(t) } func TestClosestCallMismatchedArgumentInformationShowsTheClosest(t *testing.T) {