When writing tests, gophers will often bump into the scenario where they have SUT (software under test) that use a go routine. Concurrency is always hard and there are countless articles on how to best write and test concurrent systems.

One area where this can become in particularly hard is when using mocks. gomock is no exception. When a test exits before the underlying code has time to schedule and execute the go routines, Controller.Finish() will fail the test. Below is a proposed pattern to help with this.

Consider the following SUT ( foo.go ):

package async import "time" type Foo interface {

Bar()

} func DoAsync(f Foo) {

go func() {

time.Sleep(100 * time.Millisecond)

f.Bar()

}()

}

Notice that the function DoAsync() uses the Foo object within a go routine.

NOTE: You can generate the mocks with the following:

mockgen \

--source=foo.go \

--destination=mock_test.go \

--package=async_test

Now lets consider some test code:

func TestDoAsyncWrong(t *testing.T) {

ctrl := gomock.NewController(t)

defer ctrl.Finish() mockFoo := NewMockFoo(ctrl)

// Assert that Foo() is invoked

mockFoo.

EXPECT().

Bar() async.DoAsync(mockFoo)

}

This fails…

go test

--- FAIL: TestDoAsyncWrong (0.00s)

foo_test.go:44: missing call(s) to *async_test.MockFoo.Bar() foo_test.go:41

foo_test.go:44: aborting test due to missing call(s)

FAIL

exit status 1

FAIL test/async 0.140s

But why? Our SUT clearly invokes Foo() . The problem is, the test finishes and the defer ctrl.Finish() fires before the go routine is scheduled. So how then do we deal with this?

We need a mechanism to ensure that the test doesn’t finish until we’re ready for it to. This is where sync.WaitGroup can be quite helpful:

func TestDoAsync(t *testing.T) {

ctrl := gomock.NewController(t)

defer ctrl.Finish() var wg sync.WaitGroup

wg.Add(1)

defer wg.Wait() mockFoo := NewMockFoo(ctrl) // Assert that Foo() is invoked

mockFoo.

EXPECT().

Bar().

Do(func() {

wg.Done()

}) async.DoAsync(mockFoo)

}

We use the sync.WaitGroup to block the test until Bar() has been invoked. (NOTE: If we so desired, we could change the wg.Add(1) to wg.Add(5) if we instead wanted Bar() to be invoked 5 times) This will also work if the go routine is removed and the code becomes synchronous. This tends to be nice as it treats the SUT as more of a black box.

Conclusion

This pattern works because it forces the test to stay within scope until the mock has been used as expected. This has downsides as the test is further dictating what the SUT has to look like. However, there are cases where its nice to be able to do this and the before mentioned pattern will suit those.