liveBook · Manning

Minimock

As explained in Learn Go with Pocket-Sized Projects

Minimock is a Go mocking library that generates mock implementations and supports configurable expectations and behavior.

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

How Learn Go with Pocket-Sized Projects covers it

How deep it goes
Works through installation, generation, controller setup, expectations, and input-dependent responses; does not examine generated-code internals.
Examples
Uses a `habitCreator` mock and gRPC client tests for successful listing, list-fetch errors, and status-fetch errors.
What it assumes
Assumes familiarity with Go interfaces and tests while spelling out the minimock installation, generation commands, and versioned import path.
What you can do after
Apply it via minimock to generate interface mocks and configure single-call expectations or input-dependent responses in Go tests.
How it connects
Connects Go interfaces, `go:generate`, dependency injection, gRPC clients, and error-path testing.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Learn Go with Pocket-Sized Projects. The section links point to the parts of the book it draws on.