Testing with testify & mockery
Go's testing package is enough for correctness, but testify improves failure messages and mockery generates mock implementations from interfaces.
Together they speed up table-driven tests, HTTP handler checks, and service-layer doubles without abandoning idiomatic Go interfaces.
Recipe
Quick-reference recipe card - copy-paste ready.
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestSum(t *testing.T) {
require.Equal(t, 5, sum(2, 3))
}# mockery v2+ (install binary separately)
mockery --name=UserRepository --dir=./internal/user --output=./internal/user/mocksWhen to reach for this:
- Tests with many equality checks where
t.Fatalfnoise hides the first diff - Integration-style suites sharing
httptestservers and databases - Services defined against interfaces that need generated mocks in CI
- Teams standardizing assertion style across dozens of packages
Working Example
package user_test
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"example.com/libdemo/internal/user"
)
type mockRepo struct{ mock.Mock }
func (m *mockRepo) FindByID(ctx context.Context, id string) (user.User, error) {
args := m.Called(ctx, id)
return args.Get(0).(user.User), args.Error(1)
}
func TestService_GetUser(t *testing.T) {
repo := new(mockRepo)
repo.On("FindByID", mock.Anything, "u1").Return(user.User{ID: "u1"}, nil)
svc := user.NewService(repo)
got, err := svc.GetUser(context.Background(), "u1")
require.NoError(t, err)
assert.Equal(t, "u1", got.ID)
repo.AssertExpectations(t)
}
func TestService_GetUser_NotFound(t *testing.T) {
repo := new(mockRepo)
repo.On("FindByID", mock.Anything, "missing").Return(user.User{}, errors.New("not found"))
svc := user.NewService(repo)
_, err := svc.GetUser(context.Background(), "missing")
require.Error(t, err)
}What this demonstrates:
requirefor fatal preconditions,assertfor additional checks in the same test- Hand-written mock using testify's
mock.Mock(mockery generates this shape) AssertExpectationsverifies all expected calls happened- Table-driven expansion is natural: add cases to a slice and loop
Deep Dive
How It Works
- testify/assert records failures and continues; require stops the test immediately.
- testify/suite embeds
suite.Suitefor setup/teardown hooks on methods namedTestX. - mockery reads interface definitions and emits typed mock structs with
On/Returnhelpers. - Go prefers small interfaces at the consumer; mockery targets those interfaces, not concrete structs.
require vs assert
| Helper | On failure | Use for |
|---|---|---|
require.* | Stops test | Setup, errors that invalidate rest of test |
assert.* | Marks failure, continues | Multiple independent field checks |
cmp.Diff (stdlib) | Manual | Complex structs when testify diff is noisy |
mockery Workflow
- Define
type UserRepository interface { ... }in production package. - Run mockery in CI or
go generatewith a//go:generatedirective. - Commit generated mocks or regenerate in CI before test (team policy).
- Prefer fakes (in-memory maps) when behavior is simple; mocks when verifying call contracts.
Go Notes
//go:generate mockery --name=UserRepository --output=./mocks --outpkg=mocks- Keep mocks in
mockssubpackages to avoid import cycles. - Pass
context.Contextthrough mocks the same way production code does. - Do not mock stdlib types; wrap external clients behind your interface instead.
Gotchas
- Over-mocking concrete types - mockery needs interfaces; mocking structs couples tests to implementation. Fix: extract narrow interfaces at call site.
- Assert on error strings - brittle when messages change. Fix: use
errors.Is/errors.Aswith testifyErrorIs. - Global suite state - shared fields mutate between parallel tests. Fix: run suites without
t.Parallel()or isolate state per test. - Stale generated mocks - interface changes break compile in CI. Fix:
go generate ./...in CI or pre-commit. - mock.Anything abuse - hides incorrect arguments. Fix: use
mock.MatchedByfor partial matchers when needed. - testify in libraries - publishing packages should not force testify on consumers. Fix: keep assertions in
_test.gofiles only.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
stdlib testing only | Zero-deps policy | Large teams want consistent diffs |
google/go-cmp | Deep struct compare | You want one assertion library |
gomock (go.uber.org/mock) | Recorder-style mocks preferred | Team already standardized on mockery |
| Hand-written fakes | Simple in-memory behavior | Call order verification is critical |
FAQs
Is testify worth the dependency?
Yes for most application repos - the dependency is small and test-only; stdlib purists can stay on cmp and manual t.Helper.
mockery v2 or v3?
Follow your org's pinned binary; both generate similar mocks - document the install path in CONTRIBUTING.
Should I commit generated mocks?
Either commit for reproducible CI or regenerate every test run - pick one policy and enforce in review.
How does this relate to testing-benchmarking section?
That section covers httptest, benchmarks, and fuzzing; this page focuses on testify and mockery libraries specifically.
Can I use suites for everything?
No - prefer table-driven functions for pure logic; suites help shared HTTP server setup.
How do I mock HTTP clients?
Implement RoundTripper fakes or use httptest.Server instead of mocking http.Client internals.
What about testify and race detector?
Always run go test -race in CI; mocks do not remove shared-state races in production code.
Should interfaces be huge for mockery?
No - follow Go's small interface rule; one or two methods per mock keeps tests readable.
How do I test slog or zap output?
Use buffer handlers or observer cores; testify asserts on parsed fields after the log call.
Does mockery work with generics?
Support evolves with versions - generate from concrete interface instantiations your code actually uses.
Related
- testify, httptest & Test Doubles - httptest and fakes
- Essential Libraries Basics - testify quick assertion
- Go Testing Basics - table-driven patterns
- Defining & Implementing Interfaces - interface design for mocks
- Handler, Service, Repository Layering - where doubles attach
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).