How to Use mockgen for Generating Mock Implementations

Use `mockgen` to generate mock implementations by defining your target interface in Go code and running the tool with the interface name and destination package as arguments.

Testing without the database

You are writing a function that saves a user profile. It needs to write to a database. You write the test. The test fails because there is no database running. You spin up Docker. The test passes. You change a column name. The test breaks because the schema is out of sync. You realize testing real infrastructure is slow, fragile, and distracting. You want to test the logic, not the database driver.

Go solves this with interfaces. You define a contract for the database, and your function depends on that contract. In production, you inject the real database. In tests, you inject a fake. mockgen is the tool that writes the fake for you. It reads your interface and generates a Go struct that can pretend to be anything you ask it to.

The interface contract and the stunt double

An interface in Go is a contract. It lists the methods a type must implement. A mock is a fake implementation of that contract. Think of it like a stunt double. The real actor goes on vacation. The stunt double wears the same costume and performs the exact moves the director calls out, but only when the script says so. If the director calls a move that isn't in the script, the stunt double stops and points at the camera.

mockgen is the tool that hires the stunt double. It parses your Go source file, finds the interface, and generates a new file with a struct that implements that interface. Every method on the interface becomes a method on the mock struct. Inside each method, the mock checks a list of expectations. If the call matches an expectation, it returns the scripted value. If not, it panics. This strict behavior is a feature. It forces you to declare exactly what your code is supposed to do.

Generating the mock

Start with a simple interface. This interface defines how your application talks to storage.

// storage.go
package storage

// DataStore defines the contract for persisting key-value pairs.
type DataStore interface {
    // Get retrieves a value by key.
    Get(key string) (string, error)
    // Save writes a value to a key.
    Save(key, value string) error
}

Add a go:generate directive to automate mock creation. This comment tells the Go toolchain to run mockgen whenever you run go generate.

// storage.go
//go:generate mockgen -source=storage.go -destination=storage_mock.go -package=storage

Run go generate ./... in your terminal. The tool creates storage_mock.go. The file contains a MockDataStore struct and a NewMockDataStore constructor. The generated code is idiomatic Go. It uses reflection and a controller to track calls. You can read the generated file to understand the mechanics, but you never edit it by hand.

The golang/mock repository is archived. The community migrated to go.uber.org/mock. The API is identical, but the import path changed. New projects should use the Uber fork. Install the tool with go install go.uber.org/mock/mockgen@latest.

Using the mock in a test

The mock requires a controller to manage expectations. The controller tracks which calls are scripted and verifies that all scripted calls happen.

// storage_test.go
package storage_test

import (
    "testing"

    "go.uber.org/mock/gomock"
    "example.com/myapp/storage"
)

func TestGet(t *testing.T) {
    // Create a controller to manage mock expectations.
    ctrl := gomock.NewController(t)
    defer ctrl.Finish()

    // Instantiate the mock generated by mockgen.
    mockStore := storage.NewMockDataStore(ctrl)

    // Script the behavior: when Get is called with "user:1", return "Alice".
    mockStore.EXPECT().Get("user:1").Return("Alice", nil)

    val, err := mockStore.Get("user:1")
    if err != nil {
        t.Fatalf("unexpected error: %v", err)
    }
    if val != "Alice" {
        t.Fatalf("expected Alice, got %s", val)
    }
}

The test creates a controller, passes the test object to it, and defers Finish. The EXPECT call adds a rule to the script. The mock returns "Alice" and nil. If the code under test calls Get with a different key, the mock panics. If the code never calls Get, ctrl.Finish() fails the test. This ensures your test covers the exact interaction you expect.

A realistic service layer

In a real application, your service depends on the interface. The service struct holds the dependency. The test injects the mock.

// service.go
package service

import "example.com/myapp/storage"

// UserFetcher retrieves and formats user data.
type UserFetcher struct {
    // Store holds the dependency for data access.
    Store storage.DataStore
}

// FetchUser returns the user string or an error.
func (f *UserFetcher) FetchUser(id string) (string, error) {
    val, err := f.Store.Get("user:" + id)
    if err != nil {
        return "", err
    }
    return "User: " + val, nil
}

The test constructs the service with the mock. It scripts the storage call and verifies the service logic.

// service_test.go
package service_test

import (
    "testing"

    "go.uber.org/mock/gomock"
    "example.com/myapp/service"
    "example.com/myapp/storage"
)

func TestFetchUser(t *testing.T) {
    ctrl := gomock.NewController(t)
    defer ctrl.Finish()

    mockStore := storage.NewMockDataStore(ctrl)

    // Expect Get to be called with "user:42" and return "Bob".
    mockStore.EXPECT().Get("user:42").Return("Bob", nil)

    fetcher := &service.UserFetcher{Store: mockStore}
    result, err := fetcher.FetchUser("42")

    if err != nil {
        t.Fatalf("unexpected error: %v", err)
    }
    if result != "User: Bob" {
        t.Fatalf("expected 'User: Bob', got %s", result)
    }
}

This pattern follows the Go convention: accept interfaces, return structs. The service accepts the DataStore interface. The test provides a mock implementation. The production code provides the real implementation. The service logic stays pure and testable.

Matching arguments flexibly

Sometimes you don't care about the exact argument. gomock provides matchers for flexible expectations. Use gomock.Any() to match any value of the correct type.

// service_test.go
func TestFetchUserAnyID(t *testing.T) {
    ctrl := gomock.NewController(t)
    defer ctrl.Finish()

    mockStore := storage.NewMockDataStore(ctrl)

    // Match any string argument for the key.
    mockStore.EXPECT().Get(gomock.Any()).Return("Unknown", nil)

    fetcher := &service.UserFetcher{Store: mockStore}
    result, _ := fetcher.FetchUser("999")

    if result != "User: Unknown" {
        t.Fatalf("expected 'User: Unknown', got %s", result)
    }
}

Use gomock.Any() when the argument value doesn't affect the outcome. Use exact values when the argument matters. Overusing Any() weakens your tests. Specific expectations catch regressions where the code changes the argument but the result stays the same.

Pitfalls and compiler errors

Mocks are strict. If you call a method without setting an expectation, the mock panics at runtime. The output looks like Unexpected call to *storage.MockDataStore.Get([user:1]). This error stops the test immediately. It tells you that your code made a call the test didn't anticipate. Fix the test by adding the expectation, or fix the code if the call is wrong.

If you change the interface and forget to regenerate the mock, the compiler rejects the test file with undefined: storage.NewMockDataStore. The generated code is out of sync. Run go generate ./... to update the mock. Many editors run go generate automatically when you save the source file.

If you forget to call ctrl.Finish(), the test might pass even if expectations were never met. The controller checks unmet expectations in Finish. Always defer ctrl.Finish() right after creating the controller. This ensures the test fails if you scripted a call that the code under test never made.

A missing expectation is a test that lies.

When to use mockgen

Use mockgen when you have a Go interface and need a type-safe mock that compiles with your code. Use a hand-rolled mock struct when the interface is tiny and you only need one or two test cases. Use testify/mock when you prefer a fluent API for expectations and don't mind a reflection-based runtime cost. Use a real test database when you need to verify SQL queries, transactions, or schema migrations. Use a fake implementation when the mock is too complex and you need deterministic logic without scripting every call.

Generate mocks for interfaces. Write fakes for logic. Use real databases for integration.

Where to go next