gofasta g mock
Walks the project’s interface declarations and emits a testify/mock implementation under testutil/mocks/<snake>_mock.go. The generated mock:
- Embeds
testify/mock.Mockso every method getsOn(...).Return(...)ergonomics. - Includes a compile-time assertion (
var _ iface.OrderService = (*OrderServiceMock)(nil)) so a future interface change breaks the build rather than silently producing a non-conforming mock. - Picks the right testify accessor per return type (
Error,String,Int,Bool, or a nil-safe typedGetassertion for pointer / slice / map / qualified-type returns). - Is gofmt-clean out of the box.
Two operating modes: generate one mock for a named interface, or --all to refresh every mock under the standard interface dirs.
Usage
gofasta g mock [InterfaceName] [flags]The positional arg is the exact interface name (e.g. OrderService, UserRepositoryInterface). Omit it with --all to refresh every mock.
Flags
| Flag | Default | Description |
|---|---|---|
--all | false | Regenerate every mock under app/services/interfaces and app/repositories/interfaces. Ignores the positional arg. |
--check | false | Don’t write — exit non-zero with MOCK_DRIFT if the on-disk mock differs from what would be generated. CI gate. |
Examples
Generate one mock:
gofasta g mock OrderService
# → testutil/mocks/order_service_mock.goRefresh every mock after changing several interfaces at once:
gofasta g mock --allCI: detect mocks that haven’t been regenerated after an interface change:
gofasta g mock --all --check
# exit 0 → all mocks in sync
# exit !0 → MOCK_DRIFT — re-run without --check to updateCI: check one mock specifically:
gofasta g mock OrderService --checkOutput shape
Given:
// app/services/interfaces/order_service.go
package interfaces
import "context"
type OrderService interface {
Get(ctx context.Context, id string) (*Order, error)
List(ctx context.Context) ([]*Order, error)
Archive(ctx context.Context, id string) error
}gofasta g mock OrderService writes:
// testutil/mocks/order_service_mock.go
// Code generated by `gofasta g mock`. DO NOT EDIT.
// Regenerate by running `gofasta g mock OrderService` (or `--all`).
package mocks
import (
"context"
"myapp/app/services/interfaces"
"myapp/app/models"
"github.com/stretchr/testify/mock"
)
// OrderServiceMock is a testify/mock implementation of interfaces.OrderService.
type OrderServiceMock struct {
mock.Mock
}
var _ interfaces.OrderService = (*OrderServiceMock)(nil)
func (m *OrderServiceMock) Get(ctx context.Context, id string) (*models.Order, error) {
args := m.Called(ctx, id)
return func() *models.Order {
if v := args.Get(0); v != nil {
return v.(*models.Order)
}
return nil
}(), args.Error(1)
}
func (m *OrderServiceMock) List(ctx context.Context) ([]*models.Order, error) {
args := m.Called(ctx)
return func() []*models.Order {
if v := args.Get(0); v != nil {
return v.([]*models.Order)
}
return nil
}(), args.Error(1)
}
func (m *OrderServiceMock) Archive(ctx context.Context, id string) error {
args := m.Called(ctx, id)
return args.Error(0)
}The pointer / slice / map / qualified-type returns are guarded with the if v := args.Get(...); v != nil { ... } shim so a nil return value doesn’t panic at runtime.
Where it looks
--all walks two directories:
app/services/interfaces/app/repositories/interfaces/
Every interface declared in any *.go (not _test.go) file there is regenerated. Other directories aren’t scanned — interfaces defined elsewhere need the explicit form (gofasta g mock <Name>).
For the single-interface form, the same two directories are searched. An interface declared in both (rare) returns AMBIGUOUS_SYMBOL with the list of files.
Embedded interfaces
The initial release supports flat interfaces only. An interface that embeds another:
type AdminService interface {
UserService // embedded — skipped
PromoteToAdmin(ctx context.Context, userID string) error
}Will generate a mock with PromoteToAdmin only; the embedded UserService methods are silently dropped. If you need to mock an embedded interface, define the flat version directly or compose mocks at the test site.
Drift detection (--check)
--check compares the generated bytes to the on-disk file. Differences (whitespace-normalized) trigger MOCK_DRIFT. Use this in CI to enforce that interface changes are accompanied by a mock regeneration:
# .github/workflows/ci.yml
- run: gofasta g mock --all --checkIf it fails, the developer’s fix is gofasta g mock --all locally + commit the updated mock.
Error codes
| Code | When |
|---|---|
INTERFACE_NOT_FOUND | The named interface isn’t declared in either standard dir (or, with --all, no interfaces exist in either dir). |
AMBIGUOUS_SYMBOL | The same interface name is declared in two files. Rename one or use the explicit form. |
MOCK_DRIFT | --check mode: the on-disk mock differs from what would be generated. |
NOT_GOFASTA_PROJECT | No go.mod in the cwd. |
AST_PARSE_FAILED | One of the interface files has a syntax error. |
FILE_IO | Couldn’t create testutil/mocks/ or write the output file. |