Last active
February 20, 2024 14:34
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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