mofu

package module
v1.3.0 Latest Latest
Warning

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

Go to latest
Published: Jul 30, 2025 License: BSD-3-Clause Imports: 8 Imported by: 0

README

mofu

Mofu provides utilities to create a mock function, like as jest.fn, to use in test code without any interfaces.

GoDev Actions Status

Usage

func SUT(f func() int) {
	// ...
}

To test above SUT, you can create a mock function with Mock.Make.

import (
	"testing"

	"github.com/lufia/mofu"
)

func TestFunc(t *testing.T) {
	m := mofu.MockFor[func() int]()
	m.ReturnOnce(1)
	fn, r := m.Make()
	SUT(fn)
	if n := r.Count(); n != 1 {
		t.Errorf("fn has been called %d times; but want 1", n)
	}
}

ReturnOnce can stock multiple return values int the mock. If the return values reached to empty, the mock function returns default return values (initially zero values).

There is also Return method. This method can update default return values of the mock.

Condition

If you'd like to switch return values by the function arguments, you can use When method.

m := MockFor[func(string) int]()
m.When("foo").ReturnOnce(1)
m.When("bar").ReturnOnce(2)
fn, _ := m.Make()
fn("foo") // 1
fn("bar") // 2
fn("baz") // 0

Interface

read := mofu.MockOf(io.Reader.Read).Return(0, io.EOF)
close := mofu.MockOf(io.Closer.Close).Return(nil)
iface, r := mofu.ImplementInterface[io.ReadCloser](read, close)
defer iface.Close()

io.ReadAll(iface)
rr := mofu.RecorderFor(r, read)
if n := rr.Count(); n != 1 {
	t.Errorf("read has been called %d times; but want 1", n)
}

Documentation

Overview

Package mofu provides utilities to create a mock function to use in test code.

There are some internal queues in the mock object. A queue is created for corresponding to an argument pattern. Additionally there is a queue for the default pattern.

The mock function consumes an item on the top of the queue when the mock function is called. If the queue is empty, the mock function returns default values or zero values.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lufia/mofu"
)

type Retriever func(key string) (string, error)

func (fn Retriever) Get(key string) (string, error) {
	return fn(key)
}

func SUT(r Retriever) {
	r.Get("key1")
	r.Get("key2")
}

func main() {
	mock := mofu.MockFor[Retriever]()
	mock.Return("OK", nil)

	fn, r := mock.Make()
	SUT(fn)

	fmt.Println(r.Count())
	scene := slices.Collect(r.Replay())
	scene[0](func(key string) (string, error) {
		fmt.Println(key)
		return "", nil // not used
	})
	scene[1](func(key string) (string, error) {
		fmt.Println(key)
		return "", nil // not used
	})
}
Output:

2
key1
key2

Index

Examples

Constants

View Source
const (
	Any = anyMatcher(0)
)

Variables

This section is empty.

Functions

func Implement added in v1.1.0

func Implement[I any](mocks ...MockFunc) I

Implement implements the interface I. It is constructed of mocks. Each mock must be created by MockOf with I.Method syntax.

Example
package main

import (
	"fmt"
	"io"

	"github.com/lufia/mofu"
)

func main() {
	read := mofu.MockOf(io.Reader.Read).Return(0, io.EOF)
	close := mofu.MockOf(io.Closer.Close).Return(nil)
	m := mofu.Implement[io.ReadCloser](read, close)
	fmt.Println(Consume(m))
}

func Consume(r io.ReadCloser) error {
	defer r.Close()
	buf := make([]byte, 1<<8)
	if _, err := r.Read(buf); err != nil {
		return err
	}
	return nil
}
Output:

EOF

Types

type Cond added in v0.4.0

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

Cond represents a condition for returning values identified by the arguments.

func (*Cond[T]) Panic added in v1.0.0

func (c *Cond[T]) Panic(v any) *Cond[T]

Panic overwrites default behavior of the mock function with panic(v). It panics if any of Cond.Return, Cond.ReturnFunc and this is called two or more times.

func (*Cond[T]) PanicOnce added in v1.0.0

func (c *Cond[T]) PanicOnce(v any) *Cond[T]

PanicOnce adds panic(v) to the eval queue of the mock function.

func (*Cond[T]) Return added in v0.4.0

func (c *Cond[T]) Return(results ...any) *Cond[T]

Return overwrites default behavior of the mock function with results. It panics if any of Cond.ReturnFunc, Cond.Panic and this is called two or more times.

func (*Cond[T]) ReturnFunc added in v1.2.0

func (c *Cond[T]) ReturnFunc(fn T) *Cond[T]

ReturnFunc overwrites default behavior of the mock function with fn. It panics if any of Cond.Return, Cond.Panic and this is called two or more times.

func (*Cond[T]) ReturnOnce added in v0.4.0

func (c *Cond[T]) ReturnOnce(results ...any) *Cond[T]

ReturnOnce adds the return values to the eval queue of the mock function.

Example
package main

import (
	"errors"
	"fmt"
	"os"

	"github.com/lufia/mofu"
)

func main() {
	m := mofu.MockOf(os.ReadFile)
	m.When("a.txt").ReturnOnce([]byte("OK"), nil)
	m.When("x.txt").ReturnOnce(nil, errors.ErrUnsupported)
	readFile, _ := m.Make()
	s, _ := readFile("a.txt")
	fmt.Printf("%s\n", s)
	_, err := readFile("x.txt")
	fmt.Println(err)
}
Output:

OK
unsupported operation

func (*Cond[T]) ReturnOnceFunc added in v1.2.0

func (c *Cond[T]) ReturnOnceFunc(fn T) *Cond[T]

ReturnOnceFunc adds fn to the eval queue of the mock function.

type Mock

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

Mock is a mock object for creating a mock function.

func MockFor added in v0.2.0

func MockFor[T any]() *Mock[T]

MockFor creates an empty mock object.

func MockOf added in v0.2.0

func MockOf[T any](fn T) *Mock[T]

MockOf creates an empty mock object.

Fn is only used to specify the type of a mock function.

func (*Mock[T]) Make

func (m *Mock[T]) Make() (T, *Recorder[T])

Make returns a mock function and its recorder.

func (*Mock[T]) Name added in v1.1.0

func (m *Mock[T]) Name() string

Name returns name of m or empty string if m is created by an anonymous function.

func (*Mock[T]) Panic added in v1.0.0

func (m *Mock[T]) Panic(v any) *Mock[T]

Panic is like Cond.Panic except this overwrites to the default condition. It panics if either Mock.Return or Mock.Panic is called two or more times.

func (*Mock[T]) PanicOnce added in v1.0.0

func (m *Mock[T]) PanicOnce(v any) *Mock[T]

PanicOnce is like Cond.PanicOnce except this adds panic(v) to the default condition.

func (*Mock[T]) Return

func (m *Mock[T]) Return(results ...any) *Mock[T]

Return is like Cond.Return except this overwrites to the default condition. It panics if either Mock.Return or Mock.Panic is called two or more times.

func (*Mock[T]) ReturnFunc added in v1.2.0

func (m *Mock[T]) ReturnFunc(fn T) *Mock[T]

ReturnFunc is like Cond.ReturnFunc except this overwrites to the default condition.

func (*Mock[T]) ReturnOnce added in v0.4.0

func (m *Mock[T]) ReturnOnce(results ...any) *Mock[T]

ReturnOnce is like Cond.ReturnOnce except this adds the return values to the default condition.

Example
package main

import (
	"fmt"
	"time"

	"github.com/lufia/mofu"
)

func main() {
	m := mofu.MockOf(time.Now)
	m.ReturnOnce(time.Date(2025, time.March, 20, 0, 0, 0, 0, time.UTC))
	now, _ := m.Make()
	fmt.Println(now().Format(time.DateTime))
}
Output:

2025-03-20 00:00:00

func (*Mock[T]) ReturnOnceFunc added in v1.2.0

func (m *Mock[T]) ReturnOnceFunc(fn T) *Mock[T]

ReturnOnceFunc is like Cond.ReturnOnceFunc expect this adds fn to the default condition.

func (*Mock[T]) When added in v0.4.0

func (m *Mock[T]) When(args ...any) *Cond[T]

When returns a Cond.

Example
package main

import (
	"bytes"
	"fmt"
	"io"

	"github.com/lufia/mofu"
)

func main() {
	m := mofu.MockOf(io.ReadAll)
	m.When(mofu.Any).ReturnOnce([]byte("OK"), nil)
	readAll, _ := m.Make()
	b, _ := readAll(&bytes.Buffer{})
	fmt.Println(string(b))
}
Output:

OK

type MockFunc added in v1.1.0

type MockFunc interface {
	Name() string
	// contains filtered or unexported methods
}

MockFunc is an interface for wrapping Mock.

type Recorder

type Recorder[T any] struct {
	sync.RWMutex
	// contains filtered or unexported fields
}

Recorder records the statistics of a mock function.

func RecorderFor added in v1.3.0

func RecorderFor[I, T any](r *Recorders[I], m *Mock[T]) *Recorder[T]

RecorderFor returns Recorder corresponds to the Mock. It will panic if the method does not exist in the interface.

func (*Recorder[T]) Count

func (r *Recorder[T]) Count() int64

Count returns the call count of the mock function.

Example
package main

import (
	"fmt"
	"time"

	"github.com/lufia/mofu"
)

func main() {
	m := mofu.MockOf(time.Sleep)
	sleep, r := m.Make()
	sleep(100)
	fmt.Println(r.Count())
}

func (*Recorder[T]) Replay

func (r *Recorder[T]) Replay() iter.Seq[func(T)]

Replay returns an iterator over all call logs of an mock function. Each call reproduces its situation with function arguments.

Example
package main

import (
	"fmt"
	"time"

	"github.com/lufia/mofu"
)

func main() {
	m := mofu.MockOf(time.Sleep)
	sleep, r := m.Make()
	sleep(100 * time.Millisecond)
	for do := range r.Replay() {
		do(func(d time.Duration) {
			fmt.Println(d)
		})
	}
}
Output:

100ms

type Recorders added in v1.3.0

type Recorders[I any] struct {
	// contains filtered or unexported fields
}

Recorders is a collection of Recorder for the interface.

func ImplementInterface added in v1.3.0

func ImplementInterface[I any](mocks ...MockFunc) (I, *Recorders[I])

Implement implements the interface I. It is constructed of mocks. Each mock must be created by MockOf with I.Method syntax.

Example
package main

import (
	"fmt"
	"io"

	"github.com/lufia/mofu"
)

func main() {
	read := mofu.MockOf(io.Reader.Read).Return(0, io.EOF)
	m, r := mofu.ImplementInterface[io.Reader](read)
	b, err := io.ReadAll(m)
	if err != nil {
		panic(err)
	}
	rec := mofu.RecorderFor(r, read)
	fmt.Println(len(b), rec.Count())
}
Output:

0 1

Jump to

Keyboard shortcuts

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