mockey

package module
v1.2.16 Latest Latest
Warning

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

Go to latest
Published: Nov 3, 2025 License: Apache-2.0 Imports: 8 Imported by: 6

README

Mockey

English | 中文

Release License Go Report Card codecov OpenIssue ClosedIssue

Mockey is a simple and easy-to-use golang mock library, which can quickly and conveniently mock functions and variables. At present, it is widely used in the unit test writing of ByteDance services (7k+ repos) and is actively maintained. In essence it rewrites function instructions at runtime similarly to gomonkey.

Mockey makes it easy to replace functions, methods and variables with mocks reducing the need to specify all dependencies as interfaces.

  1. Mockey requires inlining and compilation optimization to be disabled during compilation, or it won't work. See the FAQs for details.
  2. It is strongly recommended to use it together with the Convey library (module dependency) in unit tests.

Install

go get github.com/bytedance/mockey@latest

Quick Guide

package main

import (
	"fmt"
	"math/rand"

	. "github.com/bytedance/mockey"
)

func main() {
	Mock(rand.Int).Return(1).Build() // mock `rand.Int` to return 1
	
	fmt.Printf("rand.Int() always return: %v\n", rand.Int())
}

Features

  • Mock functions and methods
    • Basic
      • Simple / generic / variadic function or method (value or pointer receiver)
      • Supporting hook function
      • Supporting PatchConvey (automatically release mocks after each test case)
      • Providing GetMethod to handle special cases (e.g., exported method of unexported types, unexported method, and methods in nested structs)
    • Advanced
      • Conditional mocking
      • Sequence returning
      • Decorator pattern (execute the original function after mocking)
      • Goroutine filtering (inclusion, exclusion, targeting)
      • Acquire Mocker for advanced usage (e.g., getting the execution times of target/mock function)
  • Mock variable
    • Common variable
    • Function variable

Compatibility

OS Support
  • Mac OS(Darwin)
  • Linux
  • Windows
Arch Support
  • AMD64
  • ARM64
Version Support
  • Go 1.13+

Basic Features

Simple function/method

Use Mock to mock function/method, and use Return to specify the return value:

package main

import (
	"fmt"

	. "github.com/bytedance/mockey"
)

func Foo(in string) string {
	return in
}

type A struct{}

func (a A) Foo(in string) string { return in }

type B struct{}

func (b *B) Foo(in string) string { return in }

func main() {
	// mock function
	Mock(Foo).Return("MOCKED!").Build()
	fmt.Println(Foo("anything")) // MOCKED!

	// mock method (value receiver)
	Mock(A.Foo).Return("MOCKED!").Build()
	fmt.Println(A{}.Foo("anything")) // MOCKED!

	// mock method (pointer receiver)
	Mock((*B).Foo).Return("MOCKED!").Build()
	fmt.Println(new(B).Foo("anything")) // MOCKED!
    
    // Tips: if the target has no return value, you still need to call the empty `Return()`.
}
Generic function/method

Use MockGeneric to mock generic function/method:

package main

import (
	"fmt"

	. "github.com/bytedance/mockey"
)

func FooGeneric[T any](t T) T {
	return t
}

type GenericClass[T any] struct {
}

func (g *GenericClass[T]) Foo(t T) T {
	return t
}

func main() {
	// mock generic function 
	MockGeneric(FooGeneric[string]).Return("MOCKED!").Build() // `Mock(FooGeneric[string], OptGeneric)` also works
	fmt.Println(FooGeneric("anything"))                       // MOCKED!
	fmt.Println(FooGeneric(1))                                // 1 | Not working because of type mismatch!

	// mock generic method 
	MockGeneric((*GenericClass[string]).Foo).Return("MOCKED!").Build()
	fmt.Println(new(GenericClass[string]).Foo("anything")) // MOCKED!
}
Variadic function/method
package main

import (
	"fmt"

	. "github.com/bytedance/mockey"
)

func FooVariadic(in ...string) string {
	return in[0]
}

type A struct{}

func (a A) FooVariadic(in ...string) string { return in[0] }

func main() {
	// mock variadic function
	Mock(FooVariadic).Return("MOCKED!").Build()
	fmt.Println(FooVariadic("anything")) // MOCKED!

	// mock variadic method
	Mock(A.FooVariadic).Return("MOCKED!").Build()
	fmt.Println(A{}.FooVariadic("anything")) // MOCKED!
}
Supporting hook function

Use To to specify the hook function:

package main

import (
	"fmt"

	. "github.com/bytedance/mockey"
)

func Foo(in string) string {
	return in
}

type A struct {
	prefix string
}

func (a A) Foo(in string) string { return a.prefix + ":" + in }

func main() {
	// NOTE: hook function must have the same function signature as the original function!
	Mock(Foo).To(func(in string) string { return "MOCKED!" }).Build()
	fmt.Println(Foo("anything")) // MOCKED!

	// NOTE: for method mocking, the receiver can be added to the signature of the hook function on your need.
	Mock(A.Foo).To(func(a A, in string) string { return a.prefix + ":inner:" + "MOCKED!" }).Build()
	fmt.Println(A{prefix: "prefix"}.Foo("anything")) // prefix:inner:MOCKED!
}
Supporting PatchConvey

Use PatchConvey to organize test cases, just like Convey in smartystreets/goconvey. PatchConvey will automatically release the mocks after each test case:

package main_test

import (
	"testing"

	. "github.com/bytedance/mockey"
	. "github.com/smartystreets/goconvey/convey"
)

func Foo(in string) string {
	return "ori:" + in
}

func TestXXX(t *testing.T) {
    // mock 
	PatchConvey("mock 1", t, func() {
		Mock(Foo).Return("MOCKED-1!").Build() // mock 
		res := Foo("anything")                // invoke
		So(res, ShouldEqual, "MOCKED-1!")     // assert
	})

	// mock released
	PatchConvey("mock released", t, func() {
		res := Foo("anything")               // invoke
		So(res, ShouldEqual, "ori:anything") // assert
	})

	// mock again
	PatchConvey("mock 2", t, func() {
		Mock(Foo).Return("MOCKED-2!").Build() // mock 
		res := Foo("anything")                // invoke
		So(res, ShouldEqual, "MOCKED-2!")     // assert
	})
}
Providing GetMethod to handle special cases

Use GetMethod to get the specific method in special cases.

Mock method through an instance:

package main

import (
	"fmt"

	. "github.com/bytedance/mockey"
)

type A struct{}

func (a A) Foo(in string) string { return in }

func main() {
	a := new(A)

	// Mock(a.Foo) won't work, because `a` is an instance of `A`, not the type `A`
	Mock(GetMethod(a, "Foo")).Return("MOCKED!").Build()
	fmt.Println(a.Foo("anything")) // MOCKED!
}

Mock exported method of unexported types:

package main

import (
	"crypto/sha256"
	"fmt"

	. "github.com/bytedance/mockey"
)

func main() {
	// `sha256.New()` returns an unexported `*digest`, whose `Sum` method is the one we want to mock
	Mock(GetMethod(sha256.New(), "Sum")).Return([]byte{0}).Build()

	fmt.Println(sha256.New().Sum([]byte("anything"))) // [0]
}

Mock unexported method:

package main

import (
	"bytes"
	"fmt"

	. "github.com/bytedance/mockey"
)

func main() {
	// `*bytes.Buffer` has an unexported `empty` method, which is the one we want to mock
	Mock(GetMethod(new(bytes.Buffer), "empty")).Return(true).Build()

	buf := bytes.NewBuffer([]byte{1, 2, 3, 4})
	b, err := buf.ReadByte()
	fmt.Println(b, err)      // 0 EOF | `ReadByte` calls `empty` method inside to check if the buffer is empty and return io.EOF
}

Mock methods in nested structs:

package main

import (
	"fmt"

	. "github.com/bytedance/mockey"
)

type Wrapper struct {
	inner // nested struct
}

type inner struct{}

func (i inner) Foo(in string) string {
	return in
}

func main() {
	// Mock(Wrapper.Foo) won't work, because the target should be `inner.Foo`
	Mock(GetMethod(Wrapper{}, "Foo")).Return("MOCKED!").Build() // or Mock(inner.Foo).Return("MOCKED!").Build()

	fmt.Println(Wrapper{}.Foo("anything")) // MOCKED! 
    
	// Tips: `redis.Client` can be mocked in the same way as above
}

Advanced features

Conditional mocking

Use When to define multiple conditions:

package main

import (
	"fmt"

	. "github.com/bytedance/mockey"
)

func Foo(in string) string {
	return "ori:" + in
}

func main() {
	// NOTE: condition function must have the same input signature as the original function!
	Mock(Foo).
		When(func(in string) bool { return len(in) == 0 }).Return("EMPTY").
		When(func(in string) bool { return len(in) <= 2 }).Return("SHORT").
		When(func(in string) bool { return len(in) <= 5 }).Return("MEDIUM").
		Build()

	fmt.Println(Foo(""))            // EMPTY
	fmt.Println(Foo("h"))           // SHORT
	fmt.Println(Foo("hello"))       // MEDIUM
	fmt.Println(Foo("hello world")) // ori:hello world
}
Sequence returning

Use Sequence to mock multiple return values:

package main

import (
	"fmt"

	. "github.com/bytedance/mockey"
)

func Foo(in string) string {
	return in
}

func main() {
	Mock(Foo).Return(Sequence("Alice").Then("Bob").Times(2).Then("Tom")).Build()
	fmt.Println(Foo("anything")) // Alice
	fmt.Println(Foo("anything")) // Bob
	fmt.Println(Foo("anything")) // Bob
	fmt.Println(Foo("anything")) // Tom
}
Decorator pattern

Use Origin to keep the original logic of the target after mock:

package main

import (
	"fmt"

	. "github.com/bytedance/mockey"
)

func Foo(in string) string {
	return "ori:" + in
}

func main() {
	// `origin` will carry the logic of the `Foo` function
	origin := Foo

	// `decorator` will do some AOP around `origin`
	decorator := func(in string) string {
		fmt.Println("arg is", in)
		out := origin(in)
		fmt.Println("res is", out)
		return out
	}

	Mock(Foo).Origin(&origin).To(decorator).Build()

	fmt.Println(Foo("anything"))
	/*
		arg is anything
		res is ori:anything
		ori:anything
	*/
}
Goroutine filtering

Use ExcludeCurrentGoRoutine / IncludeCurrentGoRoutine / FilterGoRoutine to determine in which goroutine the mock takes effect:

package main

import (
	"fmt"
	"time"

	. "github.com/bytedance/mockey"
)

func Foo(in string) string {
	return in
}

func main() {
	// use `ExcludeCurrentGoRoutine` to exclude current goroutine
	Mock(Foo).ExcludeCurrentGoRoutine().Return("MOCKED!").Build()
	fmt.Println(Foo("anything")) // anything | mock won't work in current goroutine

	go func() {
		fmt.Println(Foo("anything")) // MOCKED! | mock works in other goroutines
	}()

	time.Sleep(time.Second) // wait for goroutine to finish
}
Acquire Mocker

Acquire Mocker to use advanced features:

package main

import (
	"fmt"

	. "github.com/bytedance/mockey"
)

func Foo(in string) string {
	return in
}

func main() {
	mocker := Mock(Foo).When(func(in string) bool { return len(in) > 5 }).Return("MOCKED!").Build()
	fmt.Println(Foo("any"))      // any
	fmt.Println(Foo("anything")) // MOCKED!

	// use `MockTimes` and `Times` to track the number of times mock worked and `Foo` is called
	fmt.Println(mocker.MockTimes()) // 1
	fmt.Println(mocker.Times())     // 2

	// remock `Foo` to return "MOCKED2!"
	mocker.Return("MOCKED2!")
	fmt.Println(Foo("anything")) // MOCKED2!

	// release `Foo` mock
	mocker.Release()
	fmt.Println(Foo("anything")) // anything | mock won't work, because mock released
}

FAQ/troubleshooting

How to disable inline and compile optimization?
  1. Command line:go test -gcflags="all=-l -N" -v ./... in tests or go build -gcflags="all=-l -N" in main packages.
  2. Goland:use Debug or fill -gcflags="all=-l -N" in the Run/Debug Configurations > Go tool arguments dialog box.
  3. VSCode: use Debug or add "go.buildFlags": ["-gcflags=\'all=-N -l\'"] in settings.json.
Mock doesn't work?
  1. Inline or compilation optimizations are not disabled. Please check if this log has been printed and refer to relevant section of FAQ.
    Mockey check failed, please add -gcflags="all=-N -l".
    
  2. Check if Build() was not called, or that neither Return(xxx) nor To(xxx) was called, resulting in no actual effect. If the target function has no return value, you still need to call the empty Return().
  3. Mock targets do not match exactly, as below:
    package main_test
    
    import (
    	"fmt"
    	"testing"
    
    	. "github.com/bytedance/mockey"
    )
    
    type A struct{}
    
    func (a A) Foo(in string) string { return in }
    
    func TestXXX(t *testing.T) {
    	Mock((*A).Foo).Return("MOCKED!").Build()
    	fmt.Println(A{}.Foo("anything")) // won't work, because the mock target should be `A.Foo`
    
    	a := A{}
    	Mock(a.Foo).Return("MOCKED!").Build()
    	fmt.Println(a.Foo("anything")) // won't work, because the mock target should be `A.Foo`
    }
    
    Please refer to Generic function/method if the target is generic. Otherwise, try to check if it hits specific cases in GetMethod.
  4. The target function is executed in other goroutines when mock released:
    package main_test
    
    import (
    	"fmt"
    	"testing"
    	"time"
    
    	. "github.com/bytedance/mockey"
    )
    
    func Foo(in string) string {
    	return in
    }
    
    func TestXXX(t *testing.T) {
    	PatchConvey("TestXXX", t, func() {
    		Mock(Foo).Return("MOCKED!").Build()
    		go func() { fmt.Println(Foo("anything")) }() // the timing of executing 'Foo' is uncertain
    	})
    	// when the main goroutine comes here, the relevant mock has been released by 'PatchConvey'. If 'Foo' is executed before this, the mock succeeds, otherwise it fails
    	fmt.Println("over")
    	time.Sleep(time.Second)
    }
    
Error "function is too short to patch"?
  1. Inline or compilation optimizations are not disabled. Please check if this log has been printed and refer to relevant section of FAQ.
    Mockey check failed, please add -gcflags="all=-N -l".
    
  2. The function is really too short resulting in the compiled machine code is not long enough. Generally, two or more lines will not cause this problem. If there is such a need, you may use MockUnsafe to mock it causing unknown issues.
  3. The function has been mocked by other tools (such as monkey etc.)
Error "re-mock , previous mock at: xxx"

The function has been mocked repeatedly in the smallest unit, as below:

package main

import (
	"fmt"

	. "github.com/bytedance/mockey"
)

func Foo() string { return "" }

func main() {
	Mock(Foo).Return("MOCKED!").Build() // mock for the first time
	Mock(Foo).Return("MOCKED2!").Build() // mock for the second time, will panic!
	fmt.Println(Foo())
}

Please refer to PatchConvey to organize your test cases. If there is such a need, please refer to Acquire Mocker to remock.

Don't mock system functions

It is best not to directly mock system functions, as it may cause probabilistic crash issues. For example, mock time.Now will cause:

fatal error: semacquire not on the G stack

License

Mockey is distributed under the Apache License, version 2.0. The licenses of third party dependencies of Mockey are explained here.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func GetGoroutineId

func GetGoroutineId() int64

GetGoroutineId gets the current goroutine ID

func GetMethod

func GetMethod(instance interface{}, methodName string) interface{}

GetMethod resolve a certain public method from an instance.

func GetNestedMethod

func GetNestedMethod(instance interface{}, methodName string) interface{}

GetNestedMethod resolves a certain public method in anonymous structs, it will look for the specific method in every anonymous struct field recursively. Deprecated, this is an old API in mockito. Please use GetMethod instead.

func GetPrivateMethod

func GetPrivateMethod(instance interface{}, methodName string) interface{}

GetPrivateMethod resolve a certain public method from an instance. Deprecated, this is an old API in mockito. Please use GetMethod instead.

func OptGeneric added in v1.2.5

func OptGeneric(o *option)

func OptUnsafe added in v1.2.5

func OptUnsafe(o *option)

func PatchConvey

func PatchConvey(items ...interface{})

func Sequence added in v1.2.4

func Sequence(value ...interface{}) sequenceOpt

func UnPatchAll added in v1.2.7

func UnPatchAll()

UnPatchAll unpatch all mocks in current 'PatchConvey' context

If the caller is out of 'PatchConvey', it will unpatch all mocks

For example:

Test1(t) {
    Mock(a).Build()
	Mock(b).Build()

    // a and b will be unpatched
	UnpatchAll()
}

Test2(t) {
	Mock(a).Build()
	PatchConvey(t,func(){
	    Mock(b).Build()

		// only b will be unpatched
		UnpatchAll()
	}
}

Types

type FilterGoroutineType

type FilterGoroutineType int64
const (
	Disable FilterGoroutineType = 0
	Include FilterGoroutineType = 1
	Exclude FilterGoroutineType = 2
)

type GenericInfo added in v1.2.8

type GenericInfo uintptr

func (GenericInfo) Equal added in v1.2.8

func (g GenericInfo) Equal(other GenericInfo) bool

func (GenericInfo) UsedParamType added in v1.2.8

func (g GenericInfo) UsedParamType(n uintptr) reflect.Type

UsedParamType get the type of used parameter in generic function/struct

For example: assume we have generic function "f[int, float64](x int, y T1) T2" and derived type f[int, float64]:

UsedParamType(0) == reflect.TypeOf(int(0))
UsedParamType(1) == reflect.TypeOf(float64(0))

If index n is out of range, or the derived types have more complex structure(for example: define an generic struct in a generic function using generic types, unused parameterized type etc.), this function may return unexpected value or cause unrecoverable runtime error . So it is NOT RECOMMENDED to use this function unless you actually knows what you are doing.

type MockBuilder

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

func Mock

func Mock(target interface{}, opt ...optionFn) *MockBuilder

Mock mocks target function

If target is a generic method or method of generic types, you need add a genericOpt, like this:

func f[int, float64](x int, y T1) T2
Mock(f[int, float64], OptGeneric)

func MockGeneric added in v1.2.5

func MockGeneric(target interface{}) *MockBuilder

MockGeneric mocks generic function

Target must be generic method or method of generic types

func MockUnsafe added in v1.1.0

func MockUnsafe(target interface{}) *MockBuilder

MockUnsafe has the full ability of the Mock function and removes some security restrictions. This is an alternative when the Mock function fails. It may cause some unknown problems, so we recommend using Mock under normal conditions.

func (*MockBuilder) Build

func (builder *MockBuilder) Build() *Mocker

func (*MockBuilder) ExcludeCurrentGoRoutine

func (builder *MockBuilder) ExcludeCurrentGoRoutine() *MockBuilder

func (*MockBuilder) FilterGoRoutine

func (builder *MockBuilder) FilterGoRoutine(filter FilterGoroutineType, gId int64) *MockBuilder

func (*MockBuilder) IncludeCurrentGoRoutine

func (builder *MockBuilder) IncludeCurrentGoRoutine() *MockBuilder

func (*MockBuilder) Origin

func (builder *MockBuilder) Origin(funcPtr interface{}) *MockBuilder

Origin add an origin hook which can be used to call un-mocked origin function

For example:

 origin := Fun // only need the same type
 mock := func(p string) string {
	 return origin(p + "mocked")
 }
 mock2 := Mock(Fun).To(mock).Origin(&origin).Build()

Origin only works when call origin hook directly, target will still be mocked in recursive call

func (*MockBuilder) Return

func (builder *MockBuilder) Return(results ...interface{}) *MockBuilder

func (*MockBuilder) To

func (builder *MockBuilder) To(hook interface{}) *MockBuilder

To declares the hook function that's called to replace the target function.

The hook function must have the same signature as the target function.

The following example would make Fun always return true

func Fun(input string) bool {
	return input == "fun"
}

Mock(Fun).To(func(_ string) bool {return true}).Build()

Note that if the target function is a struct method, you may optionally include the receiver as the first argument of the hook function. For example,

type Foo struct {
	Name string
}
func (f *Foo) Bar(other string) bool {
	return other == f.Name
}
Mock((*Foo).Bar).To(func(f *Foo, other string) bool {return true}).Build()

func (*MockBuilder) When

func (builder *MockBuilder) When(when interface{}) *MockBuilder

When declares the condition hook that's called to determine whether the mock should be executed.

The condition hook function must have the same parameters as the target function.

The following example would execute the mock when input int is negative

func Fun(input int) string {
	return strconv.Itoa(input)
}
Mock(Fun).When(func(input int) bool { return input < 0 }).Return("0").Build()

Note that if the target function is a struct method, you may optionally include the receiver as the first argument of the condition hook function. For example,

type Foo struct {
	Age int
}
func (f *Foo) GetAge(younger int) string {
	return strconv.Itoa(f.Age - younger)
}
Mock((*Foo).GetAge).When(func(f *Foo, younger int) bool { return younger < 0 }).Return("0").Build()

type Mocker

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

func (*Mocker) ExcludeCurrentGoRoutine

func (mocker *Mocker) ExcludeCurrentGoRoutine() *Mocker

func (*Mocker) FilterGoRoutine

func (mocker *Mocker) FilterGoRoutine(filter FilterGoroutineType, gId int64) *Mocker

func (*Mocker) IncludeCurrentGoRoutine

func (mocker *Mocker) IncludeCurrentGoRoutine() *Mocker

func (*Mocker) MockTimes

func (mocker *Mocker) MockTimes() int

func (*Mocker) Origin

func (mocker *Mocker) Origin(funcPtr interface{}) *Mocker

func (*Mocker) Patch

func (mocker *Mocker) Patch() *Mocker

func (*Mocker) Release added in v1.2.3

func (mocker *Mocker) Release() *MockBuilder

func (*Mocker) Return

func (mocker *Mocker) Return(results ...interface{}) *Mocker

func (*Mocker) Times

func (mocker *Mocker) Times() int

func (*Mocker) To

func (mocker *Mocker) To(to interface{}) *Mocker

func (*Mocker) UnPatch

func (mocker *Mocker) UnPatch() *Mocker

func (*Mocker) When

func (mocker *Mocker) When(when interface{}) *Mocker

type MockerVar

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

func MockValue

func MockValue(targetPtr interface{}) *MockerVar

func (*MockerVar) Patch

func (mocker *MockerVar) Patch() *MockerVar

func (*MockerVar) To

func (mocker *MockerVar) To(value interface{}) *MockerVar

func (*MockerVar) UnPatch

func (mocker *MockerVar) UnPatch() *MockerVar

type MockeyPrivate added in v1.2.4

type MockeyPrivate struct{}

type Private added in v1.2.4

type Private interface {
	// contains filtered or unexported methods
}

type SequenceOpt added in v1.2.4

type SequenceOpt interface {
	// Private make sure it is mockey private interface
	Private
	// GetNext is used by mockey, don't use it if you don't know what it does
	GetNext() []interface{}
}

Jump to

Keyboard shortcuts

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