The mocker package provides a simple and flexible way to generate mock
implementations for Go interfaces in tests. It automates the creation of mock
structs that integrate with testing frameworks like testing.T and use
github.com/ctx42/testing/pkg/mock as the main driver for the mocks. With
mocker, you can generate mocks for interfaces in the same package or across
different packages, writing the output to files, buffers, or other destinations.
Key features:
- Automatic mock generation for any Go interface.
- Configurable output (files, buffers, or custom writers).
- Support for cross-package mocking with source and target package specifications.
- Integration with testing utilities for robust test assertions.
To generate a mock for an interface in the same package, call Generate with
the interface name. The mock is written to a default file
(e.g., _mock.go) in the current package.
package main
import (
"log"
"github.com/ctx42/testing/pkg/mocker"
)
func main() {
err := mocker.Generate("MyInterface")
if err != nil {
log.Fatalf("failed to generate mock: %v", err)
}
}This creates a mock file with a struct named MyInterfaceMock, including
methods to record calls and integrate with testing.T.
For more control, use configuration options to specify the source package, target package, and output destination. This example generates a mock for an interface in another package and writes it to a buffer:
out := &bytes.Buffer{}
err := mocker.Generate(
"Case00",
mocker.WithSrc("github.com/ctx42/testing/pkg/mocker/testdata/cases"),
mocker.WithTgt("github.com/ctx42/testing/pkg/goldy"),
mocker.WithTgtOutput(out),
)
if err != nil {
panic(err)
}
fmt.Println(out.String())
// Output:
// package goldy
//
// // Code generated by mocker. DO NOT EDIT.
//
// import (
// "github.com/ctx42/testing/pkg/mock"
// "github.com/ctx42/testing/pkg/tester"
// )
//
// type Case00Mock struct {
// *mock.Mock
// t tester.T
// }
//
// func NewCase00Mock(t tester.T) *Case00Mock {
// t.Helper()
// return &Case00Mock{Mock: mock.NewMock(t), t: t}
// }
//
// func (_mck *Case00Mock) Method00() {
// _mck.t.Helper()
// var _args []any
// _mck.Called(_args...)
// }See examples_test.go for additional examples.
The Generate function accepts optional configuration via option functions:
WithSrc(src string): the source package or directory. Defaults to the current package.WithTgt(tgt string): the target package or directory for the generated mock. Defaults to the current package.WithTgtOutput(w io.Writer): directs the mock output to the given writer. Defaults to a file named<interface>_mock.go.WithTgtName(name string): customize mock type name. Defaults toTypeMock.WithTgtFilename(filename string): customize mock filename.WithTgtOnHelpers(): generate additional mock helper methods.WithTesterAlias(alias string): sets alias for "github.com/ctx42/testing/pkg/tester" import.
Generating mocks involves resolving Go packages and parsing source files, which can be time-consuming due-to-disk I/O and AST parsing. These steps are particularly slow when generating mocks for many interfaces or interfaces in packages with many files.
To optimize performance, use a Mocker instance instead of the standalone
Generate function. The Mocker struct maintains an in-memory cache of parsed
packages and type information, reusing this data across multiple mock
generations. This can significantly reduce execution time, especially in large
projects or when generating mocks for multiple interfaces in the same package.
mck := mocker.New()
err := mck.Generate("ItfName0", mocker.WithSrc("src0"), mocker.WithTgt("tgt0"))
// Handle error.
err := mck.Generate("ItfName1", mocker.WithSrc("src1"), mocker.WithTgt("tgt1"))
// Handle error.
err := mck.Generate("ItfName2", mocker.WithSrc("src2"), mocker.WithTgt("tgt2"))
// Handle error.
err := mck.Generate("ItfName3", mocker.WithSrc("src3"), mocker.WithTgt("tgt3"))
// Handle error.The mocker was designed to be used with Go’s go generate tool. By using
go generate, you can embed mock generation directly into your build workflow
without relying on external scripts or manual commands. This approach leverages
Go’s standard tooling, keeping your project self-contained and idiomatic.
To use mocker with go generate, set up two files in the package where you
want to generate mocks: one to trigger the generation and another to define the
mock generation logic. This structure keeps the generation process organized
and reusable.
Create the following files in your package directory (e.g., pkg/):
pkg/
├── 00_generate.go
├── 00_generate_main.go
The 00_ prefix ensures these files appear at the top of file listings for
visibility, though any valid filename works. The files serve distinct purposes.
The 00_generate.go contains //go:generate directives to invoke the mock
generation program.
package pkg
//go:generate go run 00_generate_main.goThe 00_generate_main.go defines a standalone program that calls mocker to
create the mocks.
//go:build ignore
package main
import (
"github.com/ctx42/testing/pkg/mocker"
)
func main() {
err := mocker.Generate(
"Case00",
mocker.WithSrc("github.com/ctx42/testing/pkg/mocker/testdata/cases"),
mocker.WithTgt("github.com/ctx42/testing/pkg/goldy"),
)
if err != nil {
panic(err)
}
}The //go:build ignore ensures the file is not included in the package build,
avoiding conflicts with the pkg package.
From the Root of your Go module, execute:
go generate ./...This command scans all packages in the module for //go:generate directives
and runs them. In this case, it executes go run 00_generate_main.go,
generating the mock in the specified target package.