Skip to content

Latest commit

 

History

History
217 lines (170 loc) · 6.39 KB

File metadata and controls

217 lines (170 loc) · 6.39 KB

Introduction

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.

Usage

Basic Mock Generation

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.

Advanced Mock Generation

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.

Configuration Options

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 to TypeMock.
  • 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.

Performance

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.

Go Generate

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.

Setup

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.go

The 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.

Generate

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.