-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Description
Description
I'm using this library indirectly, through vektra/mockery. I have a mock that I use in a goroutine, and I don't expect any calls on it. However, because of a bug in my code, there sometimes is an unexpected call. This results in a call to t.FailNow(), which causes necessary cleanup to be skipped and the entire test suite to hang.
Looking at MethodCalled(), which is called by Called(), we can see several calls to m.fail():
Lines 509 to 524 in 7c367bb
| m.fail("\n\nmock: Unexpected Method Call\n-----------------------------\n\n%s\n\nThe closest call I have is: \n\n%s\n\n%s\nDiff: %s\nat: %s\n", | |
| callString(methodName, arguments, true), | |
| callString(methodName, closestCall.Arguments, true), | |
| diffArguments(closestCall.Arguments, arguments), | |
| strings.TrimSpace(mismatch), | |
| assert.CallerInfo(), | |
| ) | |
| } else { | |
| m.fail("\nassert: mock: I don't know what to return because the method call was unexpected.\n\tEither do Mock.On(\"%s\").Return(...) first, or remove the %s() call.\n\tThis method was unexpected:\n\t\t%s\n\tat: %s", methodName, methodName, callString(methodName, arguments, true), assert.CallerInfo()) | |
| } | |
| } | |
| for _, requirement := range call.requires { | |
| if satisfied, _ := requirement.Parent.checkExpectation(requirement); !satisfied { | |
| m.mutex.Unlock() | |
| m.fail("mock: Unexpected Method Call\n-----------------------------\n\n%s\n\nMust not be called before%s:\n\n%s", |
and fail() calls T.FailNow():
Line 352 in 7c367bb
| m.test.FailNow() |
Looking at the documentation of FailNow(), we can read the following:
FailNowmust be called from the goroutine running the test or benchmark function, not from other goroutines created during the test.
I think both m.fail() and m.MethodCalled() should come with the same warning.
Step To Reproduce
create a goroutine that calls a mock, and performs an unexpected call on the mock, then closes a channel that should cause the main thread to hang until it is closed
// reproducer.go
package main
import "fmt"
type logger interface {
Log(string)
}
func reproducer(log logger) {
ch := make(chan int)
go func() {
ch <- 1
log.Log("Hello")
close(ch)
}()
for i := range ch {
fmt.Println(i)
}
}// reproducer_test.go
package main
import (
"testing"
"github.com/stretchr/testify/mock"
)
type myMockedLogger struct {
mock.Mock
}
func (m *myMockedLogger) Log(msg string) {
m.Called(msg)
}
func TestReproducer(t *testing.T) {
t.Run("Works fine", func(t *testing.T) {
mock := &myMockedLogger{}
mock.On("Log", "Hello").Return()
mock.Test(t)
reproducer(mock)
})
t.Run("Hangs forever", func(t *testing.T) {
mock := &myMockedLogger{}
mock.Test(t)
reproducer(mock)
})
}Expected behavior
The channel should still get closed.
Actual behavior
The test hangs forever.