enufstub

package module
v0.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 6, 2026 License: MIT Imports: 5 Imported by: 0

README

enufstub

Inline interface stubs for Go tests — no code generation required.

The name comes from "enough" + "stub": this stub is enough.

Why?

Tools like gomock require go generate to produce mock files from interface definitions. In large codebases this adds up: every interface change triggers regeneration, CI pipelines run generators before tests, and reviewing diffs full of generated code is tedious. enufstub skips all of that — define stubs inline, right next to the test that uses them.

Installation

go get github.com/mickamy/enufstub

Requires Go 1.24+ and amd64 or arm64 architecture.

Quick Start

type UserRepository interface {
    GetUser(id int) (User, error)
    CreateUser(u User) error
    DeleteUser(id int) error
}

func TestSomething(t *testing.T) {
    stub := enufstub.Of[UserRepository]().
        With("GetUser", func(id int) (User, error) {
            return User{ID: id, Name: "Alice"}, nil
        }).
        With("CreateUser", func(u User) error {
            return nil
        }).
        DefaultZero().
        Build()

    repo := stub.Impl()
    user, _ := repo.GetUser(1) // returns User{ID: 1, Name: "Alice"}, nil
    _ = repo.DeleteUser(1)     // returns zero value (nil) via DefaultZero
}

API

Creating a Stub
enufstub.Of[T]() *Builder[T]

Creates a new stub builder for interface T. Panics if T is not an interface type.

Registering Methods
(*Builder[T]).With(method string, fn any) *Builder[T]

Registers an implementation for a method. fn must match the method's exact signature. Panics if the method doesn't exist or the signature doesn't match.

stub := enufstub.Of[Greeter]().
    With("Greet", func(name string) string {
        return "Hello, " + name
    }).
    Build()
Default Behavior for Unstubbed Methods
(*Builder[T]).DefaultPanic() *Builder[T]   // panics (default)
(*Builder[T]).DefaultZero() *Builder[T]    // returns zero values
(*Builder[T]).Default(fn) *Builder[T]      // custom handler

DefaultPanic is the default — calling an unstubbed method panics with a descriptive message.

DefaultZero makes unstubbed methods return zero values for all return types.

Default accepts a custom handler:

stub := enufstub.Of[UserRepository]().
    Default(func(method string, args []any) []any {
        return nil // return zero values
    }).
    Build()
Building
(*Builder[T]).Build() *Stub[T]

Constructs the stub. Panics if proxy creation fails.

Using the Stub
(*Stub[T]).Impl() T

Returns the interface implementation to pass into your code under test.

Call Verification
Calls
(*Stub[T]).Calls(method string) []Call

Returns the call history for a method. Each Call has Args []any and Returns []any. Returns nil if the method was never called.

calls := stub.Calls("GetUser")
fmt.Println(calls[0].Args[0])    // first call's first argument
fmt.Println(calls[0].Returns[0]) // first call's first return value
Times
(*Stub[T]).Times(method string) int

Returns how many times a method was called.

if stub.Times("GetUser") != 2 {
    t.Fatal("expected GetUser to be called twice")
}
CalledWith
(*Stub[T]).CalledWith(method string, args ...any) bool

Reports whether the method was called with matching arguments. Supports exact values (compared with reflect.DeepEqual) and matchers.

stub.CalledWith("GetUser", 42)              // exact match
stub.CalledWith("GetUser", enufstub.Any())  // matches any value
stub.CalledWith("Do", 1, enufstub.Any())    // mix concrete and matcher
InOrder
enufstub.InOrder(groups ...[]Call) bool

Verifies that call groups occurred in sequential order. Each group's last call must precede the next group's first call.

_ = repo.CreateUser(User{Name: "Alice"})
_, _ = repo.GetUser(1)
_ = repo.DeleteUser(1)

enufstub.InOrder(
    stub.Calls("CreateUser"),
    stub.Calls("GetUser"),
    stub.Calls("DeleteUser"),
) // true

Concurrency Safety

All call recording and verification methods are safe for concurrent use. You can call the stub from multiple goroutines without data races.

Constraints

  • String() method: Interfaces with a String() string method cannot be stubbed due to a limitation in the underlying proxy library.
  • Method limit: Up to 250 methods per interface.
  • Architecture: amd64 and arm64 only.

License

MIT

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func InOrder

func InOrder(groups ...[]Call) bool

InOrder reports whether the call groups occurred in the given order. Each group's last call must precede the next group's first call. Empty groups are skipped.

Types

type Builder

type Builder[T any] struct {
	// contains filtered or unexported fields
}

Builder constructs a stub for interface T.

func Of

func Of[T any]() *Builder[T]

Of creates a new stub builder for interface T. Panics if T is not an interface type.

func (*Builder[T]) Build

func (b *Builder[T]) Build() *Stub[T]

Build constructs and returns the stub. Panics if the underlying proxy creation fails.

func (*Builder[T]) Default

func (b *Builder[T]) Default(fn func(method string, args []any) []any) *Builder[T]

Default sets a custom default behavior for unstubbed methods.

func (*Builder[T]) DefaultPanic

func (b *Builder[T]) DefaultPanic() *Builder[T]

DefaultPanic makes unstubbed methods panic (this is the default).

func (*Builder[T]) DefaultZero

func (b *Builder[T]) DefaultZero() *Builder[T]

DefaultZero makes unstubbed methods return zero values.

func (*Builder[T]) With

func (b *Builder[T]) With(method string, fn any) *Builder[T]

With registers a method implementation. fn must match the method's exact signature. Panics if the method doesn't exist or the signature doesn't match.

type Call

type Call struct {
	Args    []any
	Returns []any
	// contains filtered or unexported fields
}

Call records a single method invocation.

type Matcher

type Matcher interface {
	Match(v any) bool
}

Matcher matches a single argument value.

func Any

func Any() Matcher

Any returns a Matcher that matches any value.

type Stub

type Stub[T any] struct {
	// contains filtered or unexported fields
}

Stub wraps a dynamically generated interface implementation.

func (*Stub[T]) CalledWith

func (s *Stub[T]) CalledWith(method string, args ...any) bool

CalledWith reports whether the method was called with arguments matching the pattern. Pattern arguments can be concrete values (compared with reflect.DeepEqual) or Matchers.

func (*Stub[T]) Calls

func (s *Stub[T]) Calls(method string) []Call

Calls returns the call history for the named method. Returns nil if the method was never called.

func (*Stub[T]) Impl

func (s *Stub[T]) Impl() T

Impl returns the interface implementation.

func (*Stub[T]) Times

func (s *Stub[T]) Times(method string) int

Times returns the number of times the named method was called.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL