How to Use mockery for Generating Mocks in Go

Generate Go interface mocks instantly using the mockery CLI command with the --name flag to specify the target interface.

When the real dependency is too heavy

You are writing a service that processes payments. The production code calls a payment gateway over HTTPS, handles retries, and logs transactions. You want to test the logic that decides whether a payment should be authorized. You do not want to charge real credit cards every time you run go test. You also do not want to spin up a fake HTTP server in your test suite just to return a JSON response. You need a fake implementation that behaves exactly like the real gateway but lets you control the responses and verify the calls.

That is where mocking comes in. Go does not include a built-in mock generator. The language relies on interfaces for polymorphism, and the community uses tools to generate mock implementations automatically. mockery is the most popular tool for this job. It reads your interface definitions and generates a struct that implements the interface, recording every method call so your tests can assert expectations.

Interfaces are the contract

Mocking only works if your code depends on interfaces, not concrete types. If your service takes a *Database struct, you cannot swap it out for a mock. The compiler will reject the substitution. Go's design encourages a specific pattern to keep code testable.

Accept interfaces, return structs. Functions and methods should accept interface parameters so callers can provide any implementation, including mocks. Functions should return concrete structs so callers get a stable type without needing to know about internal dependencies. This convention makes mocking possible without changing the public API.

If you find yourself unable to mock a dependency, check the function signature. The parameter is likely a concrete type. Change it to an interface. Define the interface in the same package as the code that uses it, not in the package that implements it. This keeps the interface small and focused on the behavior the consumer needs.

The minimal mock

Start with a simple interface. This example defines a Greeter that returns a message. The interface lives in the production package.

package service

// Greeter defines the contract for generating greetings.
type Greeter interface {
	// Greet returns a personalized message for the given name.
	Greet(name string) (string, error)
}

Run mockery to generate the mock. The command specifies the interface name, the output directory, and the package name for the generated file.

mockery --name=Greeter --output=./mocks --outpkg=mocks --case=snake

The tool creates mocks/mock_greeter.go. The generated file contains a struct that embeds testify/mock.Mock and implements the Greet method. The method delegates to the embedded mock to record the call and return configured values.

package mocks

import mock "github.com/stretchr/testify/mock"

// MockGreeter is an autogenerated mock type for the Greeter interface.
type MockGreeter struct {
	mock.Mock
}

// Greet provides a mock function with given fields: name.
func (_m *MockGreeter) Greet(name string) (string, error) {
	// Called records the invocation and checks expectations.
	ret := _m.Called(name)

	// Extract return values from the mock's expectation store.
	var r0 string
	var r1 error

	if rf, ok := ret.Get(0).(func(string) string); ok {
		r0 = rf(name)
	} else {
		r0 = ret.Get(0).(string)
	}

	if rf, ok := ret.Get(1).(func(string) error); ok {
		r1 = rf(name)
	} else {
		r1 = ret.Error(1)
	}

	return r0, r1
}

The generated code is verbose by design. It handles type assertions, function return values, and error extraction. You do not need to write this boilerplate. mockery keeps the mock in sync with the interface. If you add a method to Greeter, the next run of mockery adds the corresponding method to MockGreeter.

How mockery generates the code

mockery parses the Go AST to find the interface definition. It generates a struct with a name prefixed by Mock. The struct embeds mock.Mock from the testify package. This embedded field provides the expectation tracking machinery. Every method on the mock calls m.Called(args...). This call checks if the test set up an expectation for the method and arguments. If an expectation exists, the mock returns the configured values. If no expectation exists, the mock panics by default. This behavior catches missing setup early.

The receiver name in the generated code is _m. This follows the convention of using short, lowercase receiver names. The underscore indicates the receiver is not used directly in the method body; the embedded mock.Mock handles the logic.

Tests use the mock by calling On to set expectations and Return to define results. The On method matches the method name and arguments. Return specifies the values the mock should yield. After the test runs, call AssertExpectations to verify that all expected calls happened.

package service_test

import (
	"testing"

	"example.com/service"
	"example.com/service/mocks"
)

func TestGreeterUsage(t *testing.T) {
	// Create a new mock instance.
	mockGreeter := new(mocks.MockGreeter)

	// Set up expectation: Greet called with "Alice" returns a specific message.
	mockGreeter.On("Greet", "Alice").Return("Hello, Alice", nil)

	// Use the mock where the interface is expected.
	msg, err := mockGreeter.Greet("Alice")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	// Verify the result matches the expectation.
	if msg != "Hello, Alice" {
		t.Errorf("got %q, want %q", msg, "Hello, Alice")
	}

	// Assert that Greet was called exactly once with the expected arguments.
	mockGreeter.AssertExpectations(t)
}

The On call chains with Return. The mock stores this expectation. When Greet("Alice") runs, the mock finds the matching expectation and returns the configured values. AssertExpectations checks that the call count matches. If the test calls Greet("Bob") instead, AssertExpectations fails because the expectation for "Alice" was never satisfied.

A realistic service layer

Real code often involves context.Context and error handling. This example shows a UserService that depends on a repository. The repository interface includes a method that takes a context.

package service

import "context"

// User represents a user entity.
type User struct {
	ID   int
	Name string
}

// UserRepo defines data access operations.
type UserRepo interface {
	// FindByID retrieves a user by ID.
	FindByID(ctx context.Context, id int) (*User, error)
}

// Service handles business logic.
type Service struct {
	repo UserRepo
}

// GetUser fetches a user and validates the result.
func (s *Service) GetUser(ctx context.Context, id int) (*User, error) {
	// Delegate to the repository.
	user, err := s.repo.FindByID(ctx, id)
	if err != nil {
		return nil, err
	}

	// Business rule: users must have a name.
	if user.Name == "" {
		return nil, fmt.Errorf("user %d has no name", id)
	}

	return user, nil
}

The test mocks the repository. It sets up an expectation for FindByID and verifies the service logic. The mock handles the context argument using mock.Anything.

package service_test

import (
	"context"
	"testing"

	"example.com/service"
	"example.com/service/mocks"
)

func TestService_GetUser(t *testing.T) {
	// Create mock repository.
	mockRepo := new(mocks.MockUserRepo)

	// Expect FindByID to be called with any context and ID 1.
	mockRepo.On("FindByID", mock.Anything, 1).Return(&service.User{ID: 1, Name: "Alice"}, nil)

	// Inject mock into service.
	svc := &service.Service{repo: mockRepo}

	// Call the method under test.
	user, err := svc.GetUser(context.Background(), 1)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	// Verify result.
	if user.ID != 1 || user.Name != "Alice" {
		t.Errorf("got %+v, want ID=1, Name=Alice", user)
	}

	// Assert expectations were met.
	mockRepo.AssertExpectations(t)
}

The mock.Anything matcher accepts any value for the context argument. This is useful because context values can vary between tests. Be careful with mock.Anything. It matches too broadly. If you use it for arguments that should be specific, the mock might satisfy expectations when it should not. Prefer exact values or mock.MatchedBy for complex matching.

Convention aside: context.Context always goes as the first parameter. The generated mock preserves this order. The test passes context.Background() to satisfy the signature. The mock ignores the context value unless you add a custom matcher.

Configuring mockery at scale

Running mockery with flags works for small projects. Larger projects use a configuration file. mockery.yaml defines defaults for all interfaces. This keeps the command line clean and ensures consistent output.

# mockery.yaml
with-expecter: true
dir: mocks
filename: mock_{{.InterfaceName}}.go
outpkg: mocks
resolve-type-alias: true

The config file sets with-expecter to true. This generates an EXPECT helper on each mock. The expecter provides type-safe method chaining for expectations. Instead of mock.On("Method", args), you write mock.EXPECT().Method(args). The compiler checks the method name and argument types. This catches typos at compile time.

// Expecter usage example.
mockRepo.EXPECT().FindByID(mock.Anything, 1).Return(&User{ID: 1}, nil)

The dir and filename fields control output location. The template {{.InterfaceName}} inserts the interface name into the filename. resolve-type-alias handles type aliases correctly. Run mockery --config=mockery.yaml to generate all mocks defined in the config.

You can also use --all to generate mocks for every interface in a directory. This is useful in CI pipelines. Run mockery --all before tests to ensure mocks are up to date. If an interface changes, the build fails if the mock is not regenerated. This prevents silent mismatches between interfaces and mocks.

Pitfalls and silent failures

Mocks introduce new failure modes. The most common pitfall is forgetting to call AssertExpectations. If the test does not call the mock method, the test still passes. The mock silently ignores missing calls unless you assert them. Always call AssertExpectations(t) at the end of the test. This ensures the mock was used as expected.

Another pitfall is mocking concrete types. If you try to mock a struct, mockery fails. The compiler rejects the mock with cannot use mockStruct (type *MockStruct) as Struct value in argument. This error means your code depends on a concrete type. Refactor to use an interface.

Over-mocking is a design smell. If you need to mock many dependencies, the unit might be doing too much work. Split the unit into smaller components. Each component should have one responsibility and few dependencies. Mocks are cheap, but they add complexity. Use them only when the dependency is external or expensive.

Runtime panics occur when the mock receives unexpected arguments. If the test calls Greet("Bob") but the expectation is set for Greet("Alice"), the mock panics. This is intentional. It highlights a mismatch between the test setup and the code behavior. Fix the expectation or the code.

Convention aside: Error handling in tests should be explicit. Use t.Fatalf for fatal errors. Use t.Errorf for non-fatal errors. Do not ignore errors in tests. If a mock returns an error, handle it. Discarding errors with _ hides failures. Write result, err := mock.Method(); if err != nil { t.Fatal(err) }.

When to generate and when to write by hand

Choose the right tool for the situation. Generated mocks save time but add a dependency on the generator. Hand-written mocks give full control but require maintenance. Fakes provide realistic behavior without the overhead of expectation tracking.

Use mockery when you have a stable interface and need a quick mock for testing. Use a hand-written mock when the interface is tiny and the generated code adds more complexity than value. Use a fake implementation when you need to verify side effects or state changes rather than just call counts. Use the real dependency in integration tests when you want to verify the full stack, including the database or network call.

Generate mocks for interfaces, not structs. If you are mocking a struct, your design needs work. Keep interfaces small and focused. Large interfaces lead to large mocks and brittle tests. Split big interfaces into smaller ones. Each interface should represent a single capability.

Where to go next