Skip to content

Commit

Permalink
Merge pull request #1 from r-insider/assert-mock-ordered
Browse files Browse the repository at this point in the history
Add `AssertExpectationsInOrder`
  • Loading branch information
r-insider committed Sep 23, 2021
2 parents 6241f9a + 3631f9c commit c00b2e3
Show file tree
Hide file tree
Showing 3 changed files with 463 additions and 6 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

}

Expand All @@ -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)


}
```
Expand Down
141 changes: 141 additions & 0 deletions mock/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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...)
}
Expand Down

0 comments on commit c00b2e3

Please sign in to comment.