gomock Tutorial: Mocking in Go with gomock and mockery
Mocking in Go is interface-based. You define an interface, generate a mock implementation, and use it in tests to control behavior and verify interactions. The two main tools are gomock (from the Google team) and mockery (a third-party code generator). This tutorial covers both.
Why Mock?
Pure unit tests isolate the code under test from dependencies. When your service calls a database, sends an email, or hits an external API, you don't want those side effects in unit tests. Mocks replace those dependencies with controllable fakes:
// Real service: hits the database
type UserRepository interface {
FindByID(id int) (*User, error)
Save(user *User) error
}
// In tests: use a mock that returns whatever you needMocks let you:
- Test error handling without triggering real errors
- Verify that methods were called with correct arguments
- Test code that depends on infrastructure you can't control in tests
gomock Setup
Install:
go install go.uber.org/mock/mockgen@latest(Note: the canonical gomock is now at go.uber.org/mock after Google transferred the project.)
go get go.uber.org/mock/gomockGenerating Mocks
Given this interface:
// user/repository.go
package user
type Repository interface {
FindByID(id int) (*User, error)
Save(user *User) error
Delete(id int) error
}Generate a mock:
mockgen -source=user/repository.go -destination=user/mocks/mock_repository.go -package=mocksOr use the go:generate directive in your source file:
//go:generate mockgen -source=repository.go -destination=mocks/mock_repository.go -package=mocksRun: go generate ./...
This creates mocks/mock_repository.go with a MockRepository struct.
Writing Tests with gomock
package user_test
import (
"testing"
"go.uber.org/mock/gomock"
"github.com/myapp/user"
"github.com/myapp/user/mocks"
)
func TestUserService_GetUser(t *testing.T) {
ctrl := gomock.NewController(t)
// ctrl.Finish() is called automatically in Go 1.14+ via t.Cleanup
mockRepo := mocks.NewMockRepository(ctrl)
// Set expectation
expected := &user.User{ID: 1, Name: "Alice"}
mockRepo.EXPECT().
FindByID(1).
Return(expected, nil)
svc := user.NewService(mockRepo)
got, err := svc.GetUser(1)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got.Name != "Alice" {
t.Errorf("got name %q, want %q", got.Name, "Alice")
}
}ctrl.Finish() (or automatic cleanup) verifies all EXPECT() calls were satisfied.
Setting Expectations
Basic Expectation
mockRepo.EXPECT().FindByID(1).Return(&user.User{ID: 1}, nil)The mock will:
- Expect
FindByIDto be called with argument1 - Return the
&User{ID: 1}andnilerror - Fail the test if
FindByIDis NOT called
Any Argument
import "go.uber.org/mock/gomock"
mockRepo.EXPECT().FindByID(gomock.Any()).Return(nil, ErrNotFound)Argument Matchers
// Exact match
mockRepo.EXPECT().FindByID(42)
// Any value
mockRepo.EXPECT().FindByID(gomock.Any())
// Custom matcher
mockRepo.EXPECT().Save(gomock.AssignableToTypeOf(&user.User{}))
// Inline matcher
mockRepo.EXPECT().Save(gomock.Cond(func(u interface{}) bool {
return u.(*user.User).Name != ""
}))Call Counts
// Called exactly once (default)
mockRepo.EXPECT().FindByID(1).Return(user, nil).Times(1)
// Called exactly N times
mockRepo.EXPECT().Save(gomock.Any()).Return(nil).Times(3)
// Called at least once
mockRepo.EXPECT().Delete(gomock.Any()).Return(nil).AnyTimes()
// May or may not be called
mockRepo.EXPECT().FindByID(gomock.Any()).Return(nil, nil).MaxTimes(1)Ordered Calls
gomock.InOrder(
mockRepo.EXPECT().FindByID(1).Return(user, nil),
mockRepo.EXPECT().Save(user).Return(nil),
mockRepo.EXPECT().Delete(1).Return(nil),
)This enforces that the three calls happen in the specified order.
Return Functions
For dynamic return values:
mockRepo.EXPECT().
FindByID(gomock.Any()).
DoAndReturn(func(id int) (*user.User, error) {
if id == 0 {
return nil, ErrNotFound
}
return &user.User{ID: id}, nil
})mockery: An Alternative Generator
mockery generates testify-compatible mocks with a simpler syntax:
go install github.com/vektra/mockery/v2@latestGenerate:
mockery --name Repository --output ./mocksThe generated mock uses testify/mock:
type MockRepository struct {
mock.Mock
}
func (m *MockRepository) FindByID(id int) (*User, error) {
args := m.Called(id)
return args.Get(0).(*User), args.Error(1)
}Using in tests:
func TestGetUser_NotFound(t *testing.T) {
mockRepo := new(mocks.MockRepository)
mockRepo.On("FindByID", 99).Return((*user.User)(nil), ErrNotFound)
svc := user.NewService(mockRepo)
_, err := svc.GetUser(99)
assert.ErrorIs(t, err, ErrNotFound)
mockRepo.AssertExpectations(t)
}mockery + testify has a looser feel compared to gomock's strict compile-time verification, but the testify ecosystem integration is convenient if you're already using it.
gomock vs mockery
| Feature | gomock | mockery |
|---|---|---|
| Interface | EXPECT() fluent API |
On()/Return() testify-style |
| Argument matching | Rich, typed matchers | mock.Anything, custom matchers |
| Verification | Auto on ctrl.Finish() |
Manual AssertExpectations(t) |
| Generation | mockgen |
mockery |
| Integration | Stand-alone | Works best with testify |
Choose gomock for strict behavior verification with compile-time safety. Choose mockery if you're already using testify and want consistent style.
Mocking Best Practices
Mock Only External Dependencies
Mock interfaces that represent:
- Databases and repositories
- External HTTP APIs
- Message queues
- Email senders
- File systems
Don't mock your own business logic. If you're mocking a struct in the same package, the design likely needs refactoring.
Design for Testability
Interfaces should be at the consumer side, not the implementation:
// Good — the service defines what it needs
type UserService struct {
repo UserGetter // interface defined here
}
type UserGetter interface {
GetByID(id int) (*User, error)
}
// Bad — interface mirrors the full repository
type FullRepository interface {
GetByID(id int) (*User, error)
Save(*User) error
Delete(id int) error
List(filter Filter) ([]*User, error)
Count() int
// ... 20 more methods
}Smaller interfaces are easier to mock and indicate tighter design.
Keep Expectations Readable
Group setup, action, and assertion clearly:
func TestCheckoutOrder(t *testing.T) {
ctrl := gomock.NewController(t)
mockPayment := mocks.NewMockPaymentGateway(ctrl)
mockInventory := mocks.NewMockInventoryService(ctrl)
// Arrange: set expectations
mockInventory.EXPECT().Reserve("SKU-123", 1).Return(nil)
mockPayment.EXPECT().Charge(29.99).Return("txn_456", nil)
svc := NewCheckoutService(mockPayment, mockInventory)
// Act
order, err := svc.Checkout("user-1", "SKU-123", 1, 29.99)
// Assert
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if order.TransactionID != "txn_456" {
t.Errorf("wrong txn ID")
}
}Auto-Regenerating Mocks
Add a generate.go file at the package root:
// generate.go
package myapp
//go:generate mockgen -source=user/repository.go -destination=user/mocks/mock_repository.go -package=mocks
//go:generate mockgen -source=payment/gateway.go -destination=payment/mocks/mock_gateway.go -package=mocksRegenerate all mocks after interface changes:
go generate ./...Run this in CI to verify mocks are in sync with interfaces.