How do you mock dependencies in Go, given there's no built-in mocking framework?

5 minintermediategotestingmockinginterfaces

Quick Answer

Since Go interfaces are satisfied implicitly, the standard approach is to define a small interface for the dependency you want to mock (often from the consumer's side, not the dependency's), then write a hand-rolled struct implementing that interface with whatever canned behavior your test needs — no reflection or code generation required. For larger interfaces, generate mocks automatically with a tool like mockgen (from go.uber.org/mock) or moq, driven by a //go:generate comment, which saves writing repetitive mock boilerplate by hand. Either way, the key enabling factor is designing your production code to depend on a small interface (often defined right where it's used) instead of a concrete type directly, which is exactly what makes swapping in a test double possible without any special framework support.

Detailed Answer

Go's structural typing means mocking rarely needs a heavyweight framework — a small interface and a hand-written struct are often enough.

Hand-rolled mock, using a consumer-defined interface

// Production code depends on a narrow interface, not a concrete *sql.DB
type UserFinder interface {
    FindUser(id int) (*User, error)
}

func GreetUser(f UserFinder, id int) (string, error) {
    u, err := f.FindUser(id)
    if err != nil {
        return "", err
    }
    return "Hello, " + u.Name, nil
}
// Test code: a fake implementation with canned behavior
type fakeUserFinder struct {
    user *User
    err  error
}
func (f *fakeUserFinder) FindUser(id int) (*User, error) {
    return f.user, f.err
}

func TestGreetUser(t *testing.T) {
    fake := &fakeUserFinder{user: &User{Name: "Alice"}}
    got, err := GreetUser(fake, 1)
    if err != nil || got != "Hello, Alice" {
        t.Errorf("got %q, %v", got, err)
    }
}

No mocking library was needed at all — just a plain struct satisfying the interface.

Generated mocks for larger interfaces

//go:generate mockgen -source=finder.go -destination=mocks/finder_mock.go
func TestGreetUser_WithGeneratedMock(t *testing.T) {
    ctrl := gomock.NewController(t)
    mockFinder := mocks.NewMockUserFinder(ctrl)
    mockFinder.EXPECT().FindUser(1).Return(&User{Name: "Alice"}, nil)

    got, _ := GreetUser(mockFinder, 1)
    if got != "Hello, Alice" {
        t.Errorf("got %q", got)
    }
}

Generated mocks add call expectations, argument matching, and call-count verification, which is worth the extra tooling once an interface is large or a test needs to assert on exactly how a dependency was called, not just what it returns.

Why this design works so well in Go

Because interfaces are satisfied implicitly, defining a small interface purely for testability costs nothing. You don't need to modify the real dependency at all. The interface can live entirely in the consuming package, scoped exactly to what that specific code needs.

Related Resources