testify
testify copied to clipboard
Calling Called() in a goroutine can result in a call to t.FailNow()
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 calls. 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():
https://github.com/stretchr/testify/blob/7c367bb7bc7dad2d8b17921a604157dc27ebe06f/mock/mock.go#L509-L524
and fail() calls T.FailNow(): https://github.com/stretchr/testify/blob/7c367bb7bc7dad2d8b17921a604157dc27ebe06f/mock/mock.go#L352
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.