Skip to content

Instantly share code, notes, and snippets.

@komuw
Last active February 20, 2024 14:34
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save komuw/e7c8fab2ac00e46aca4aa3468db10579 to your computer and use it in GitHub Desktop.
Save komuw/e7c8fab2ac00e46aca4aa3468db10579 to your computer and use it in GitHub Desktop.
It's hard to use gomock when there are goroutines involved. gomock sometimes expects them to be called and sometimes it doesn't
// file: main_test.go
package main
import (
"math/rand"
"sync"
"testing"
"time"
gomock "github.com/golang/mock/gomock"
)
/*
It's hard to use gomock when there are goroutines involved.
gomock sometimes expects them to be called and sometimes it doesn't(depending on when the goroutines are scheduled).
Run this tests as(make sure you run them a number of times.):
go test ./... -count=1
*/
func TestFoo(t *testing.T) {
rand.Seed(time.Now().UTC().UnixNano())
// WaitGroup is needed so as to await the mocked func that is in a goroutine to finish.
// see: https://jeffy-mathew.medium.com/unit-testing-and-mock-calls-inside-goroutines-7a19b853e084
var wg sync.WaitGroup
wg.Add(1)
// defer wg.Wait()
t.Run("sync", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
m := NewMockFoo(ctrl)
m.EXPECT().
Bar(gomock.Eq(99)).
Return(101)
helloSync(m)
})
// This test will fail sometime and succed at other times.
t.Run("async", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
m := NewMockFoo(ctrl)
m.EXPECT().
Bar(gomock.Eq(99)).
DoAndReturn(
// signature of anonymous function must have the same number of input and output arguments as the mocked method.
// ie, It must match the signature of `impl.Bar()`
func(x int) int {
defer wg.Done()
return 101
},
)
/*
Another alternative is to do;
m.EXPECT().
Bar(gomock.Eq(99)).
Return(101).
AnyTimes()
*/
helloAsync(m)
if rand.Intn(10) > 4 {
time.Sleep(1 * time.Second)
}
wg.Wait()
})
}
type Foo interface {
Bar(x int) int
}
type impl struct{}
func (i impl) Bar(x int) int {
return 101
}
func helloSync(f Foo) {
_ = f.Bar(99)
}
func helloAsync(f Foo) {
go f.Bar(99)
}
// file: main_gen.go
// Code generated by MockGen. DO NOT EDIT.
// Source: main.go
// Package mock_main is a generated GoMock package.
package main
import (
reflect "reflect"
gomock "github.com/golang/mock/gomock"
)
// MockFoo is a mock of Foo interface.
type MockFoo struct {
ctrl *gomock.Controller
recorder *MockFooMockRecorder
}
// MockFooMockRecorder is the mock recorder for MockFoo.
type MockFooMockRecorder struct {
mock *MockFoo
}
// NewMockFoo creates a new mock instance.
func NewMockFoo(ctrl *gomock.Controller) *MockFoo {
mock := &MockFoo{ctrl: ctrl}
mock.recorder = &MockFooMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockFoo) EXPECT() *MockFooMockRecorder {
return m.recorder
}
// Bar mocks base method.
func (m *MockFoo) Bar(x int) int {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Bar", x)
ret0, _ := ret[0].(int)
return ret0
}
// Bar indicates an expected call of Bar.
func (mr *MockFooMockRecorder) Bar(x interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Bar", reflect.TypeOf((*MockFoo)(nil).Bar), x)
}
// file: main_test.go
package main
import (
"math/rand"
"testing"
"time"
gomock "github.com/golang/mock/gomock"
)
/*
It's hard to use gomock when there are goroutines involved.
gomock sometimes expects them to be called and sometimes it doesn't(depending on when the goroutines are scheduled).
// See `fixed_main_test.go` for a way to fix this issue.
Run this tests as(make sure you run them a number of times.):
go test ./... -count=1
*/
func TestFoo(t *testing.T) {
rand.Seed(time.Now().UTC().UnixNano())
t.Run("sync", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
m := NewMockFoo(ctrl)
m.EXPECT().
Bar(gomock.Eq(99)).
Return(101)
helloSync(m)
})
// This test will fail sometime and succed at other times.
t.Run("async", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
m := NewMockFoo(ctrl)
m.EXPECT().
Bar(gomock.Eq(99)).
Return(101)
helloAsync(m)
if rand.Intn(10) > 4 {
time.Sleep(1 * time.Second)
}
})
}
type Foo interface {
Bar(x int) int
}
type impl struct{}
func (i impl) Bar(x int) int {
return 101
}
func helloSync(f Foo) {
_ = f.Bar(99)
}
func helloAsync(f Foo) {
go f.Bar(99)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment