minimock generates Go implementations of interfaces for tests. It is useful when the code under test accepts a dependency through an interface: a test can supply a mock, choose its response, and check the resulting behavior. Its generated methods use typed parameters. section 10.4.1 section 10.4.2
To generate a mock:
- Install the command with `go install github.com/gojuno/minimock/v3/cmd/minimock@latest`. section 10.4.1
- Place a `//go:generate minimock` directive beside the interface. Use `-i` to name the interface, `-s` to set the generated file suffix, and `-o` to choose an output folder; create that folder first. section 10.4.1
- Run `go generate .` in the package, or `go generate ./...` from the module root. Inspect the generated package with `go doc`. section 10.4.1
//go:generate minimock -i habitCreator -s "_mock.go" -o "mocks"
type habitCreator interface {
...
The directive generates a mock for `habitCreator` in the `mocks` folder, using `_mock.go` as the file suffix. section 10.4.1
A test imports both its generated `mocks` package and `github.com/gojuno/minimock/v3`. The `/v3` suffix belongs to the Go module path; it is not the package name used in code. For a `habitCreator` test, create a `minimock.Controller`, defer `Finish()`, build the mock, then pass it to `habit.Create`. Configure `AddMock.Expect(ctx, h).Return(nil)` when that call should succeed. section 10.4.2
Listing 10.22 create_test.go: Using the mock when testing
t.Run(name, func(t *testing.T) {
t.Parallel()
ctrl := minimock.NewController(t)
defer ctrl.Finish() #1
db := tt.db(ctrl)
got, err := habit.Create(ctx, db, h)
assert.ErrorIs(t, err, tt.expectedErr)
if tt.expectedErr == nil {
assert.Equal(t, h.Name, got.Name)
}
}
The test creates and finishes a controller, supplies the configured mock to `habit.Create`, and checks the error and returned name. section 10.4.2
Use `Expect` for one expected call and `Set` for behavior that must vary by input. In a gRPC client test, `ListHabitsMock.Expect(minimock.AnyContext, request).Return(response, nil)` fixes the list response, while `GetHabitStatusMock.Set` supplies a function that can respond differently for each habit ID. Repeating `Expect` for the same endpoint is not the approach used for those varying responses. section 11.1.3
Listing 11.18 list_test.go: Testing when get status returns an error
func TestListHabits_statuserror(t *testing.T) {
now := time.Now()
mockClient := mocks.NewHabitsClientMock(t)
habitsClient := client.New(mockClient)
mockResponse := &api.ListHabitsResponse{
Habits: []*api.Habit{
{Id: "ID1", Name: ...},
{Id: "ID2", Name: ...},
},
}
sentinelErr := status.Error(codes.Internal, "not after 10PM")
mockClient.ListHabitsMock.
Expect(minimock.AnyContext, &api.ListHabitsRequest{}).
Return(mockResponse, nil) #1
mockClient.GetHabitStatusMock.Set(
func(ctx context.Context,
in *api.GetHabitStatusRequest,
opts ...grpc.CallOption) (
*api.GetHabitStatusResponse, error) {
switch {
case in.HabitId == "ID1":
return &api.GetHabitStatusResponse{}, nil #2
case in.HabitId == "ID2":
return nil, sentinelErr #3
default:
return nil, fmt.Errorf("unexpected call")
}
})
habits, err := habitsClient.ListHabits(context.Background(), now)
require.ErrorIs(t, err, sentinelErr) #4
assert.Nil(t, habits)
}
The status-error test returns a list, then uses `Set` to make a later status lookup fail for a particular input. section 11.1.3
Mocking keeps these tests focused on the code that consumes the dependency. The demonstrated cases include successful habit creation, successful gRPC listing, a list-fetch error, and a status-fetch error; the mocks do not establish that a real backend connection is reliable. section 10.4.2 section 11.1.3
