From eee1ad80e7516dd5da9d3067989a2ce32d8cfa49 Mon Sep 17 00:00:00 2001 From: kozmod Date: Thu, 17 Jul 2025 09:20:08 +0300 Subject: [PATCH] [#178] go.uber.org/mock/gomock testing examples --- Readme.md | 3 +- test/go.mod | 2 + test/go.sum | 10 ++ .../internal/mock/gomock/gomock_test.go | 110 ++++++++++++++++++ .../integration/internal/mock/gomock/mocks.go | 93 +++++++++++++++ .../internal/mock/gomock/use_case.go | 38 ++++++ .../internal/mock/mockery/mockery_test.go | 4 + .../internal/mock/mockery/use_case.go | 4 - test/integration/migration/Makefile | 4 + 9 files changed, 263 insertions(+), 5 deletions(-) create mode 100644 test/integration/internal/mock/gomock/gomock_test.go create mode 100644 test/integration/internal/mock/gomock/mocks.go create mode 100644 test/integration/internal/mock/gomock/use_case.go diff --git a/Readme.md b/Readme.md index d4dc69d..1e58ef4 100644 --- a/Readme.md +++ b/Readme.md @@ -467,4 +467,5 @@ func main() { [test](https://github.com/kozmod/oniontx/tree/master/test) package contains useful examples for creating unit test: -- [vektra/mockery **+** stretchr/testify](https://github.com/kozmod/oniontx/tree/main/test/integration/internal/mock/mockery) \ No newline at end of file +- [vektra/mockery **+** stretchr/testify](https://github.com/kozmod/oniontx/tree/main/test/integration/internal/mock/mockery) +- [go.uber.org/mock/gomock **+** stretchr/testify](https://github.com/kozmod/oniontx/tree/main/test/integration/internal/mock/gomock) \ No newline at end of file diff --git a/test/go.mod b/test/go.mod index 9cd26f5..30fbd9b 100644 --- a/test/go.mod +++ b/test/go.mod @@ -7,9 +7,11 @@ require ( github.com/jmoiron/sqlx v1.4.0 github.com/kozmod/oniontx v0.3.10 github.com/lib/pq v1.10.9 + github.com/pkg/errors v0.9.1 github.com/redis/go-redis/v9 v9.7.3 github.com/stretchr/testify v1.10.0 go.mongodb.org/mongo-driver/v2 v2.2.0 + go.uber.org/mock v0.5.2 gorm.io/driver/postgres v1.5.11 gorm.io/gorm v1.25.12 ) diff --git a/test/go.sum b/test/go.sum index da0865d..ccc2ffa 100644 --- a/test/go.sum +++ b/test/go.sum @@ -14,6 +14,7 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-sql-driver/mysql v1.9.1 h1:FrjNGn/BsJQjVRuSa8CBrM5BWA9BWoXXat3KrtSb/iI= +github.com/go-sql-driver/mysql v1.9.1/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -21,8 +22,11 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgx/v5 v5.7.4 h1:9wKznZrhWa2QiHL+NjTSPP6yjl3451BX3imWDnokYlg= +github.com/jackc/pgx/v5 v5.7.4/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= @@ -30,6 +34,7 @@ github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/ github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kozmod/oniontx v0.3.10 h1:dVRKS/7gdPhn6JoD2WgyFTAiUT3Y0ZrhAjUSovBUvmk= github.com/kozmod/oniontx v0.3.10/go.mod h1:OOa9nmU2nYLk7ZtHW+4olMx1LnoNZErVgkIgwla9Y/U= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= @@ -40,6 +45,8 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM= @@ -52,6 +59,7 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= @@ -63,6 +71,8 @@ github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfS github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.mongodb.org/mongo-driver/v2 v2.2.0 h1:WwhNgGrijwU56ps9RtIsgKfGLEZeypxqbEYfThrBScM= go.mongodb.org/mongo-driver/v2 v2.2.0/go.mod h1:qQkDMhCGWl3FN509DfdPd4GRBLU/41zqF/k8eTRceps= +go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= +go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= diff --git a/test/integration/internal/mock/gomock/gomock_test.go b/test/integration/internal/mock/gomock/gomock_test.go new file mode 100644 index 0000000..7285f4c --- /dev/null +++ b/test/integration/internal/mock/gomock/gomock_test.go @@ -0,0 +1,110 @@ +//go:generate mockgen -source=use_case.go -destination=mocks.go -package=gomock + +package gomock + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/mock/gomock" +) + +const ( + textValue = "some_text" +) + +func Test_GoMock(t *testing.T) { + t.Run("assert_success", func(t *testing.T) { + var ( + ctx = context.Background() + ctrl = gomock.NewController(t) + ) + t.Cleanup(ctrl.Finish) + + var ( + transactorMock = NewMocktransactor(ctrl) + repositoryMockA = NewMockrepository(ctrl) + repositoryMockB = NewMockrepository(ctrl) + ) + gomock.InOrder( + transactorMock.EXPECT(). + WithinTx( + ctx, + gomock.Any(), + ). + DoAndReturn(func(ctx context.Context, cb func(context.Context) error) error { + err := cb(ctx) + assert.NoError(t, err) + return nil + }). + Times(1), + repositoryMockA. + EXPECT(). + Insert(ctx, textValue). + Return(nil). + Times(1), + repositoryMockB. + EXPECT(). + Insert(ctx, textValue). + Return(nil). + Times(1), + ) + + useCase := UseCase{ + transactor: transactorMock, + textRepoA: repositoryMockA, + textRepoB: repositoryMockB, + } + + err := useCase.CreateTextRecords(ctx, textValue) + assert.NoError(t, err) + }) + t.Run("assert_error", func(t *testing.T) { + var ( + ctx = context.Background() + ctrl = gomock.NewController(t) + expError = fmt.Errorf("some_error") + transactorErr = fmt.Errorf("transactor_error") + ) + t.Cleanup(ctrl.Finish) + + var ( + transactorMock = NewMocktransactor(ctrl) + repositoryMockA = NewMockrepository(ctrl) + repositoryMockB = NewMockrepository(ctrl) + ) + gomock.InOrder( + transactorMock.EXPECT(). + WithinTx( + ctx, + gomock.Any(), + ). + DoAndReturn(func(ctx context.Context, cb func(context.Context) error) error { + err := cb(ctx) + assert.Error(t, err) + return transactorErr + }), + repositoryMockA. + EXPECT(). + Insert(ctx, textValue). + Return(nil), + + repositoryMockB. + EXPECT(). + Insert(ctx, textValue). + Return(expError), + ) + + useCase := UseCase{ + transactor: transactorMock, + textRepoA: repositoryMockA, + textRepoB: repositoryMockB, + } + + err := useCase.CreateTextRecords(ctx, textValue) + assert.Error(t, err) + assert.ErrorIs(t, err, transactorErr) + }) +} diff --git a/test/integration/internal/mock/gomock/mocks.go b/test/integration/internal/mock/gomock/mocks.go new file mode 100644 index 0000000..955d58f --- /dev/null +++ b/test/integration/internal/mock/gomock/mocks.go @@ -0,0 +1,93 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: use_case.go +// +// Generated by this command: +// +// mockgen -source=use_case.go -destination=mocks.go -package=gomock +// + +// Package gomock is a generated GoMock package. +package gomock + +import ( + context "context" + reflect "reflect" + + gomock "go.uber.org/mock/gomock" +) + +// Mockrepository is a mock of repository interface. +type Mockrepository struct { + ctrl *gomock.Controller + recorder *MockrepositoryMockRecorder + isgomock struct{} +} + +// MockrepositoryMockRecorder is the mock recorder for Mockrepository. +type MockrepositoryMockRecorder struct { + mock *Mockrepository +} + +// NewMockrepository creates a new mock instance. +func NewMockrepository(ctrl *gomock.Controller) *Mockrepository { + mock := &Mockrepository{ctrl: ctrl} + mock.recorder = &MockrepositoryMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *Mockrepository) EXPECT() *MockrepositoryMockRecorder { + return m.recorder +} + +// Insert mocks base method. +func (m *Mockrepository) Insert(ctx context.Context, val string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Insert", ctx, val) + ret0, _ := ret[0].(error) + return ret0 +} + +// Insert indicates an expected call of Insert. +func (mr *MockrepositoryMockRecorder) Insert(ctx, val any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*Mockrepository)(nil).Insert), ctx, val) +} + +// Mocktransactor is a mock of transactor interface. +type Mocktransactor struct { + ctrl *gomock.Controller + recorder *MocktransactorMockRecorder + isgomock struct{} +} + +// MocktransactorMockRecorder is the mock recorder for Mocktransactor. +type MocktransactorMockRecorder struct { + mock *Mocktransactor +} + +// NewMocktransactor creates a new mock instance. +func NewMocktransactor(ctrl *gomock.Controller) *Mocktransactor { + mock := &Mocktransactor{ctrl: ctrl} + mock.recorder = &MocktransactorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *Mocktransactor) EXPECT() *MocktransactorMockRecorder { + return m.recorder +} + +// WithinTx mocks base method. +func (m *Mocktransactor) WithinTx(ctx context.Context, fn func(context.Context) error) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WithinTx", ctx, fn) + ret0, _ := ret[0].(error) + return ret0 +} + +// WithinTx indicates an expected call of WithinTx. +func (mr *MocktransactorMockRecorder) WithinTx(ctx, fn any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WithinTx", reflect.TypeOf((*Mocktransactor)(nil).WithinTx), ctx, fn) +} diff --git a/test/integration/internal/mock/gomock/use_case.go b/test/integration/internal/mock/gomock/use_case.go new file mode 100644 index 0000000..4a31dc1 --- /dev/null +++ b/test/integration/internal/mock/gomock/use_case.go @@ -0,0 +1,38 @@ +package gomock + +import ( + "context" + "fmt" +) + +type ( + repository interface { + Insert(ctx context.Context, val string) error + } + + transactor interface { + WithinTx(ctx context.Context, fn func(ctx context.Context) error) (err error) + } +) + +type UseCase struct { + textRepoA repository + textRepoB repository + + transactor transactor +} + +func (u *UseCase) CreateTextRecords(ctx context.Context, text string) error { + return u.transactor.WithinTx(ctx, func(ctx context.Context) error { + err := u.textRepoA.Insert(ctx, text) + if err != nil { + return fmt.Errorf("text repo A: %w", err) + } + + err = u.textRepoB.Insert(ctx, text) + if err != nil { + return fmt.Errorf("text repo B: %w", err) + } + return nil + }) +} diff --git a/test/integration/internal/mock/mockery/mockery_test.go b/test/integration/internal/mock/mockery/mockery_test.go index 7b80165..6d73f4a 100644 --- a/test/integration/internal/mock/mockery/mockery_test.go +++ b/test/integration/internal/mock/mockery/mockery_test.go @@ -1,3 +1,7 @@ +//go:generate mockery +//go:generate sh ./scripts.sh update_mocks . +//go:generate git add . + package mockery import ( diff --git a/test/integration/internal/mock/mockery/use_case.go b/test/integration/internal/mock/mockery/use_case.go index 93c3171..806ad22 100644 --- a/test/integration/internal/mock/mockery/use_case.go +++ b/test/integration/internal/mock/mockery/use_case.go @@ -1,9 +1,5 @@ package mockery -//go:generate mockery -//go:generate sh ./scripts.sh update_mocks . -//go:generate git add . - import ( "context" "fmt" diff --git a/test/integration/migration/Makefile b/test/integration/migration/Makefile index a500f2d..655fef1 100644 --- a/test/integration/migration/Makefile +++ b/test/integration/migration/Makefile @@ -6,6 +6,10 @@ tools: ## Run tools (vet, gofmt, goimports, tidy, etc.) go mod tidy go vet ./... +.PHONY: tools.install +tools.install: ## Install tool for tests + go install go.uber.org/mock/mockgen@latest + .PHONY: deps.update deps.update: ## Update dependencies versions go get -u all