mockx

package module
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Mar 26, 2025 License: MIT Imports: 3 Imported by: 0

README

Mockx

Mockx is a lightweight and intuitive mocking library for Go interfaces. It simplifies testing by allowing you to create mock implementations, define method behaviors, and capture method arguments with minimal boilerplate.

Motivation

This section reflects only the author's opinion. Whether you agree or disagree, you are encouraged to continue using the library as you see fit.

  • Dissatisfaction with the excessive boilerplate required to create mocks with other libraries, which practically forces the use of code generation.
  • Code generation feels like a hack rather than a proper solution in any sense.
  • Lack of clarity and directness when defining mock behaviors, especially due to the use of expects.
  • Using expects in mocks is conceptually wrong. If you find expects useful in your mocks, you are probably not writing unit tests. Additionally, integration tests should not use mocks.
  • Using a bunch of "anything" as expects feels even worse.

Installation

go get github.com/AndreyArthur/mockx

Features

  • Automatic Method Initialization: Generate zero-value implementations for all interface methods.
  • Method Behavior Mocking: Override methods with custom implementations or predefined return values.
  • Argument Capture: Easily retrieve arguments passed to mocked methods during tests.
  • Lightweight: No external dependencies and minimal setup required.
  • Example-Driven: Comprehensive examples included in tests for quick learning.

Usage

1. Define Your Interface
type Calculator interface {
    Add(a int, b int) int
}
2. Create a Mock Struct

Embed mockx.Mockx and implement the interface methods using Call:

type CalculatorMock struct {
    mockx.Mockx
}

func (m *CalculatorMock) Add(a int, b int) int {
    values := m.Call("Add", a, b)
    return mockx.Value[int](values[0])
}
3. Initialize the Mock

Use Init to auto-generate zero-value implementations for all methods:

calculator := &CalculatorMock{}
calculator.Init((*Calculator)(nil)) // Pass a nil interface pointer
4. Mock Method Behaviors
Set a Custom Implementation
calculator.Impl("Add", func(a int, b int) int {
    return a + b
})
Define Return Values
calculator.Return("Add", 42) // Always returns 42
Capture Arguments
calculator.Add(1, 2)
args := calculator.Args("Add") // Returns [1, 2]

Examples

Basic Mock Setup
// Initialize mock and use default zero values
calculator := &CalculatorMock{}
calculator.Init((*Calculator)(nil))

result := calculator.Add(3, 4) // Returns 0 (default)
Override Method Implementation
calculator.Impl("Add", func(a, b int) int {
    return a * b // Change behavior to multiply
})

result := calculator.Add(3, 4) // Returns 12
Force Specific Return Value
calculator.Return("Add", 100)

result := calculator.Add(5, 5) // Returns 100, ignores inputs
Retrieve Method Arguments
calculator.Add(10, 20)
args := calculator.Args("Add") // [10, 20]

License

MIT License. See LICENSE for details.

Documentation

Overview

Package mockx provides a lightweight mocking utility for Go interfaces. It allows you to create mock implementations, register method behaviors and retrieve method arguments.

Example (Mockx)
package main

import (
	"fmt"

	"github.com/AndreyArthur/mockx"
)

type Greeter interface {
	Greet(name string) string
}

type GreeterMock struct {
	mockx.Mockx
}

func (greeter *GreeterMock) Greet(name string) string {
	values := greeter.Call("Greet", name)
	return mockx.Value[string](values[0])
}

func main() {
	greeter := &GreeterMock{}
	greeter.Init((*Greeter)(nil))

	greeter.Impl("Greet", func(name string) string {
		return "Hello, " + name + "!"
	})

	result := greeter.Greet("Mockx")

	fmt.Printf("result = %q\n", result)
}
Output:

result = "Hello, Mockx!"
Example (Usage)

Use the mockx library in tests.

package main

import (
	"errors"
	"fmt"

	"github.com/AndreyArthur/mockx"
)

// Define a sorting interface.
type Sorting interface {
	IsSorted(slice []int) (bool, error)
}

// Implement a Searcher struct that depends on the Sorting interface.
type Searcher struct {
	sorting Sorting
}

func NewSearcher(sorting Sorting) *Searcher {
	return &Searcher{
		sorting: sorting,
	}
}

func (searcher *Searcher) linear(slice []int, target int) int {
	for i, value := range slice {
		if value == target {
			return i
		}
	}
	return -1
}
func (searcher *Searcher) binary(slice []int, target int) int {
	left, right := 0, len(slice)-1

	for left <= right {
		mid := left + (right-left)/2

		if slice[mid] == target {
			return mid
		} else if slice[mid] < target {
			left = mid + 1
		} else {
			right = mid - 1
		}
	}
	return -1
}

func (searcher *Searcher) Search(slice []int, target int) int {
	sorted, err := searcher.sorting.IsSorted(slice)
	if err != nil {
		panic(err)
	}

	if sorted {
		return searcher.binary(slice, target)
	}
	return searcher.linear(slice, target)
}

// Define a Sorting mock struct.
type SortingMock struct {
	mockx.Mockx
}

func NewSortingMock() *SortingMock {
	sorting := &SortingMock{}
	sorting.Init((*Sorting)(nil))

	return sorting
}

func (sorting *SortingMock) IsSorted(slice []int) (bool, error) {
	values := sorting.Call("IsSorted", slice)
	return mockx.Value[bool](values[0]), mockx.Reference[error](values[1])
}

// Use the mockx library in tests.
func main() {
	sorting := NewSortingMock()
	searcher := NewSearcher(sorting)

	slice := []int{3, 1, 2, 5, 4}

	sorting.Return("IsSorted", false, nil)
	index := searcher.Search(slice, 3)
	fmt.Printf("index = %d\n", index)

	sorting.Return("IsSorted", true, nil)
	index = searcher.Search(slice, 3)
	fmt.Printf("index = %d\n", index)

	sorting.Impl("IsSorted", func(slice []int) (bool, error) {
		if slice == nil {
			return false, errors.New("Cannot verify, a nil slice was given.")
		}

		for i := range len(slice) - 1 {
			if slice[i] > slice[i+1] {
				return false, nil
			}
		}
		return true, nil
	})
	index = searcher.Search(slice, 4)
	fmt.Printf("index = %d\n", index)

}
Output:

index = 0
index = -1
index = 4

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Reference added in v1.1.0

func Reference[T any](value any) T

Reference is a helper function that handles the conversion of an any to a nillable type.

In Go, nillable types are:

Pointers - *T Slices - []T Maps - map[T]U Channels - chan T Functions - func(...) ... Interfaces - interface{} (Including custom interfaces)

The Reference function should be used to convert all of these types in mock method declaration, if not, you must handle the nil case manually.

Example
package main

import (
	"fmt"

	"github.com/AndreyArthur/mockx"
)

func main() {
	var untyped any = nil
	recovered := mockx.Reference[error](untyped)

	fmt.Printf("recovered = %v\n", recovered)

}
Output:

recovered = <nil>

func Value added in v1.1.0

func Value[T any](value any) T

Value is a helper function that handles the conversion of an any to a value (non-nillable) type.

In Go, non-nillable types are:

Integers - int, uint, int64, uint64... Floats - float32, float64 Booleans - bool Strings - string Arrays - [N]T Structs - struct{} (struct values, not pointers)

Using the Value function is not a must, but it helps to normalize your mock method declarations.

Example
package main

import (
	"fmt"

	"github.com/AndreyArthur/mockx"
)

func main() {
	var untyped any = 42
	recovered := mockx.Value[int](untyped)

	fmt.Printf("recovered = %v\n", recovered)

}
Output:

recovered = 42

Types

type Mockx

type Mockx struct {
	// contains filtered or unexported fields
}

Mockx is the main struct that manages mock method implementations and args tracking.

func (*Mockx) Args

func (mockInstance *Mockx) Args(method string) []any

Args retrieves the arguments used in the most recent call to the specified method. Panics if the method was never called.

Example
greeter := &GreeterMock{}
greeter.Init((*Greeter)(nil))

greeter.Greet("Mockx")
args := greeter.Args("Greet")
arg := mockx.Value[string](args[0])

fmt.Printf("arg = %q\n", arg)
Output:

arg = "Mockx"

func (*Mockx) Call

func (mockxInstance *Mockx) Call(method string, args ...any) []any

Call invokes a registered mock method with the given arguments. Panics if the method is not registered.

Example
greeter := &GreeterMock{}
greeter.Init((*Greeter)(nil))

values := greeter.Call("Greet", "Mockx")
result := mockx.Value[string](values[0])

fmt.Printf("result = %q\n", result)
Output:

result = ""

func (*Mockx) Impl

func (mockxInstance *Mockx) Impl(method string, fn any)

Impl manually registers a function as an implementation for a method. The provided function must match the signature of the method.

Example
greeter := &GreeterMock{}
greeter.Init((*Greeter)(nil))

greeter.Impl("Greet", func(name string) string {
	return "Welcome, " + name + "."
})

result := greeter.Greet("Mockx")

fmt.Printf("result = %q\n", result)
Output:

result = "Welcome, Mockx."

func (*Mockx) Init

func (mockxInstance *Mockx) Init(nilInterface any)

Init populates the mock instance with zero-value implementations for all methods of the given interface.

It's not necessary to call Init into a Mockx instance, but if you don't, you need to manually call Impl or Return for all used methods, otherwise, your program will panic.

Example
greeter := &GreeterMock{}
greeter.Init((*Greeter)(nil))

result := greeter.Greet("Mockx")

fmt.Printf("result = %q\n", result)
Output:

result = ""

func (*Mockx) Return

func (mockxInstance *Mockx) Return(method string, values ...any)

Return registers a mock method to return the given values.

Example
greeter := &GreeterMock{}
greeter.Init((*Greeter)(nil))

greeter.Return("Greet", "Hello, Golang!")

result := greeter.Greet("Mockx")

fmt.Printf("result = %q\n", result)
Output:

result = "Hello, Golang!"

Jump to

Keyboard shortcuts

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