diff --git a/mock/mock.go b/mock/mock.go index e2e6a2d..5d445c6 100644 --- a/mock/mock.go +++ b/mock/mock.go @@ -221,6 +221,14 @@ type Mock struct { mutex sync.Mutex } +// String provides a %v format string for Mock. +// Note: this is used implicitly by Arguments.Diff if a Mock is passed. +// It exists because go's default %v formatting traverses the struct +// without acquiring the mutex, which is detected by go test -race. +func (m *Mock) String() string { + return fmt.Sprintf("%[1]T<%[1]p>", m) +} + // TestData holds any data that might be useful for testing. Testify ignores // this data completely allowing you to do whatever you like with it. func (m *Mock) TestData() objx.Map { diff --git a/mock/mock_test.go b/mock/mock_test.go index 2b6e704..097addc 100644 --- a/mock/mock_test.go +++ b/mock/mock_test.go @@ -1653,3 +1653,40 @@ func unexpectedCallRegex(method, calledArg, expectedArg, diff string) string { func ConcurrencyTestMethod(m *Mock) { m.Called() } + +func TestConcurrentArgumentRead(t *testing.T) { + methodUnderTest := func(c caller, u user) { + go u.Use(c) + c.Call() + } + + c := &mockCaller{} + defer c.AssertExpectations(t) + + u := &mockUser{} + defer u.AssertExpectations(t) + + done := make(chan struct{}) + + c.On("Call").Return().Once() + u.On("Use", c).Return().Once().Run(func(args Arguments) { close(done) }) + + methodUnderTest(c, u) + <-done // wait until Use is called or assertions will fail +} + +type caller interface { + Call() +} + +type mockCaller struct{ Mock } + +func (m *mockCaller) Call() { m.Called() } + +type user interface { + Use(caller) +} + +type mockUser struct{ Mock } + +func (m *mockUser) Use(c caller) { m.Called(c) }