Mockery — Go Interface Mock Generation
Overview
Mockery is a mock generator for Go that automatically generates mock implementations of interfaces using testify/mock. Instead of writing verbose mock structs by hand, you annotate interfaces with a //go:generate mockery comment and run go generate. The result is a fully type-safe mock that integrates with testify assertions.
When to Use
- Unit testing Go code that depends on interfaces (repositories, HTTP clients, services)
- Replacing real implementations with controlled doubles in tests
- Verifying that methods were called with specific arguments
- Setting up expected return values and errors in service-layer tests
- Avoiding network/database calls in fast unit tests
Installation
# Install mockery CLI
go install github.com/vektra/mockery/v2@latest
# Or with Homebrew
brew install mockery
# Add to go.mod (for go generate)
# mockery is a dev tool — invoke via go generate, not imported as a package
# Verify
mockery --version
Key Patterns
Configure mockery (.mockery.yaml)
# .mockery.yaml (project root)
with-expecter: true # generate type-safe Expecter helpers
mockname: "Mock{{.InterfaceName}}"
filename: "mock_{{.InterfaceName | snakecase}}.go"
outpkg: mocks
dir: "{{.InterfaceDir}}/mocks"
packages:
github.com/myorg/myapp/internal/repo:
interfaces:
UserRepository:
github.com/myorg/myapp/internal/services:
interfaces:
EmailService:
PaymentService:
Annotate interfaces for generation
// internal/repo/user_repository.go
package repo
//go:generate mockery --name=UserRepository
type UserRepository interface {
GetByID(ctx context.Context, id int64) (*User, error)
Save(ctx context.Context, user *User) error
Delete(ctx context.Context, id int64) error
ListActive(ctx context.Context) ([]*User, error)
}
# Generate mocks (run from project root)
go generate ./...
# Or run mockery directly
mockery --config .mockery.yaml
Use mocks in tests (with Expecter — recommended)
// internal/services/user_service_test.go
package services_test
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/myorg/myapp/internal/repo/mocks"
"github.com/myorg/myapp/internal/services"
)
func TestUserService_GetUser_Success(t *testing.T) {
ctx := context.Background()
// Create mock
mockRepo := mocks.NewMockUserRepository(t)
// Set expectation using type-safe Expecter
mockRepo.EXPECT().
GetByID(ctx, int64(42)).
Return(&repo.User{ID: 42, Name: "Alice", Email: "alice@example.com"}, nil).
Once()
// Inject mock into service
svc := services.NewUserService(mockRepo)
// Execute
user, err := svc.GetUser(ctx, 42)
// Assert
assert.NoError(t, err)
assert.Equal(t, "Alice", user.Name)
// mockRepo.AssertExpectations is called automatically when t is passed to NewMock
}
func TestUserService_GetUser_NotFound(t *testing.T) {
ctx := context.Background()
mockRepo := mocks.NewMockUserRepository(t)
mockRepo.EXPECT().
GetByID(ctx, int64(99)).
Return(nil, repo.ErrNotFound).
Once()
svc := services.NewUserService(mockRepo)
_, err := svc.GetUser(ctx, 99)
assert.ErrorIs(t, err, services.ErrUserNotFound)
}
Match any argument with matchers
import "github.com/stretchr/testify/mock"
func TestUserService_Save_AnyUser(t *testing.T) {
ctx := context.Background()
mockRepo := mocks.NewMockUserRepository(t)
// Match any *repo.User argument
mockRepo.EXPECT().
Save(ctx, mock.AnythingOfType("*repo.User")).
Return(nil).
Once()
svc := services.NewUserService(mockRepo)
err := svc.CreateUser(ctx, "Bob", "bob@example.com")
assert.NoError(t, err)
}
Multiple calls and call ordering
func TestEmailService_SendWelcomeAndFollowUp(t *testing.T) {
mockEmail := mocks.NewMockEmailService(t)
// Expect 2 calls in order
call1 := mockEmail.EXPECT().
SendEmail(mock.Anything, "welcome", mock.Anything).
Return(nil).Once()
mockEmail.EXPECT().
SendEmail(mock.Anything, "follow-up", mock.Anything).
Return(nil).Once().
NotBefore(call1) // must be called after call1
svc := services.NewOnboardingService(mockEmail)
err := svc.OnboardUser(context.Background(), "alice@example.com")
assert.NoError(t, err)
}
Mock returning a function (dynamic responses)
func TestRetryOnFailure(t *testing.T) {
mockRepo := mocks.NewMockUserRepository(t)
callCount := 0
// Fail first call, succeed second
mockRepo.EXPECT().
GetByID(mock.Anything, int64(1)).
RunAndReturn(func(ctx context.Context, id int64) (*repo.User, error) {
callCount++
if callCount == 1 {
return nil, errors.New("temporary error")
}
return &repo.User{ID: 1, Name: "Alice"}, nil
}).
Times(2)
svc := services.NewUserService(mockRepo)
user, err := svc.GetUserWithRetry(context.Background(), 1)
assert.NoError(t, err)
assert.Equal(t, "Alice", user.Name)
}
Makefile integration
# Makefile
.PHONY: mocks test
mocks:
go generate ./...
test: mocks
go test ./... -race -cover
lint:
golangci-lint run
Common Pitfalls
- Pass
ttoNewMock: Usingmocks.NewMockUserRepository(t)(notnew(mocks.MockUserRepository)) automatically callsAssertExpectations(t)at end of test. EXPECT()vsOn(): Prefer the type-safeEXPECT()Expecter API — it catches wrong argument types at compile time.On()is stringly-typed and error-prone.- Regenerate after interface changes: Mocks go stale when interfaces change. Add
go generate ./...to your pre-commit hook or CI. - Commit generated mocks: Generated mock files should be committed so CI doesn't require mockery installed. Regenerate locally after changes.
- Don't mock types you don't own: Only mock interfaces defined in your own codebase. For third-party types, wrap them in your own interface first.
Related Skills
- vitest-testing — JavaScript/TypeScript mocking (Vitest's
vi.fn()) - testcontainers-integration — when mocks aren't enough and you need a real database
- go-microservices — Go service patterns that pair with mockery testing
GitNexus Index
domain: testing
maturity: stable
complexity: low-medium
language: go
integrates-with: testify/mock, go generate
config-file: .mockery.yaml
output-dir: <package>/mocks/