testify icon indicating copy to clipboard operation
testify copied to clipboard

Calling Called() in a goroutine can result in a call to t.FailNow()

Open greg0ire opened this issue 7 months ago • 0 comments

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:

FailNow must 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.

greg0ire avatar Mar 05 '25 13:03 greg0ire