Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
greg0ire opened this issue Mar 5, 2025 · 0 comments
Open
Labels

Comments

@greg0ire
Copy link

greg0ire commented Mar 5, 2025

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():

testify/mock/mock.go

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():

m.test.FailNow()

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 greg0ire added the bug label Mar 5, 2025
@greg0ire greg0ire changed the title Calling MethodCalled() in a goroutine can result in a call to t.FailNow() Calling Called() in a goroutine can result in a call to t.FailNow() Mar 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant