English | 中文
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 monkey or gomonkey.
Mockey makes it easy to replace functions, methods and variables with mocks reducing the need to specify all dependencies as interfaces.
go get github.com/bytedance/mockey@latest
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()) // Try if it's still random?
}
package main_test
import (
"math/rand"
"testing"
. "github.com/bytedance/mockey"
. "github.com/smartystreets/goconvey/convey"
)
// Win function to be tested, input a number, win if it's greater than random number, otherwise lose
func Win(in int) bool {
return in > rand.Int()
}
func TestWin(t *testing.T) {
PatchConvey("TestWin", t, func() {
Mock(rand.Int).Return(100).Build() // mock
res1 := Win(101) // execute
So(res1, ShouldBeTrue) // assert
res2 := Win(99) // execute
So(res2, ShouldBeFalse) // assert
})
}
PatchConvey and PatchRun (automatically release mocks after each test case)GetMethod to handle special cases (e.g., unexported types, unexported method, and methods in nested structs)Mocker for advanced usage (e.g., getting the execution times of target/mock function)Use Mock to mock function/method, use Return to specify the return value, and use Build to make the mock effective:
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()` or use `To` to customize the hook function.
}
Starting from v1.3.0,
Mockexperimentally adds the ability to automatically identify generics (for go1.20+), you can useMockto directly replaceMockGeneric
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!
}
Additionally, Golang generics share implementation for different types with the same underlying type. For example, in type MyString string, MyString and string share one implementation. Therefore, mocking one type will interfere with the other.
package main
import (
"fmt"
. "github.com/bytedance/mockey"
)
type MyString string
func FooGeneric[T any](t T) T {
return t
}
func main() {
MockGeneric(FooGeneric[string]).Return("MOCKED!").Build()
fmt.Println(FooGeneric("anything")) // MOCKED!
fmt.Println(FooGeneric[MyString]("anything")) // MOCKED! | This is due to interference after mocking the string type
}
In v1.3.1, this issue was resolved. We now support mocking generic functions/methods with the same gcshape but different actual types. The example above will behave more as expected:
package main
import (
"fmt"
. "github.com/bytedance/mockey"
)
type MyString string
func FooGeneric[T any](t T) T {
return t
}
func main() {
mocker1 := MockGeneric(FooGeneric[string]).Return("MOCKED!").Build()
fmt.Println(FooGeneric("anything")) // MOCKED!
fmt.Println(FooGeneric[MyString]("anything")) // anything | No longer interferes
mocker2 := MockGeneric(FooGeneric[MyString]).Return("MOCKED2!").Build()
fmt.Println(FooGeneric("anything")) // MOCKED!
fmt.Println(FooGeneric[MyString]("anything")) // MOCKED2! | No longer interferes
// Note: If you need to manually release mockers, be sure to follow the "last-in-first-out" order, otherwise unexpected results such as crashes may occur
mocker2.UnPatch()
fmt.Println(FooGeneric("anything")) // MOCKED!
fmt.Println(FooGeneric[MyString]("anything")) // anything
mocker1.UnPatch()
fmt.Println(FooGeneric("anything")) // anything
fmt.Println(FooGeneric[MyString]("anything")) // anything
}
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!
}
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 (if the receiver is not used, it can be omitted, and mockey is compatible).
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!
}
PatchConvey and PatchRunStarting from v1.4.1,
PatchRunis supported
PatchConvey and PatchRun are tools for managing mock lifecycles. They automatically release mocks after test cases or functions are executed, eliminating the need for defer. Both support nested usage, and each layer only releases its own internal mocks.
Applicable scenarios comparison:
- When you need to use the assertion features and test organization capabilities of the goconvey framework, it is recommended to use PatchConvey; the execution order of nested PatchConvey is the same as Convey, please refer to the goconvey related documentation
- When you don't need goconvey integration, it is recommended to use the more lightweight PatchRun
PatchConvey example as follows:
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) {
PatchConvey("TestXXX", t, func() {
// mock
PatchConvey("mock 1", func() {
Mock(Foo).Return("MOCKED-1!").Build() // mock
res := Foo("anything") // invoke
So(res, ShouldEqual, "MOCKED-1!") // assert
})
// mock released
PatchConvey("mock released", func() {
res := Foo("anything") // invoke
So(res, ShouldEqual, "ori:anything") // assert
})
// mock again
PatchConvey("mock 2", func() {
Mock(Foo).Return("MOCKED-2!").Build() // mock
res := Foo("anything") // invoke
So(res, ShouldEqual, "MOCKED-2!") // assert
})
})
// Tips: Like `Convey`, `PatchConvey` can be nested; each layer of `PatchConvey` will only release its own internal mocks
}
PatchRun example as follows:
package main_test
import (
"testing"
. "github.com/bytedance/mockey"
)
func Foo(in string) string {
return "ori:" + in
}
func TestXXX(t *testing.T) {
// mock
PatchRun(func() {
Mock(Foo).Return("MOCKED-1!").Build() // mock
res := Foo("anything") // call
if res != "MOCKED-1!" {
t.Errorf("expected 'MOCKED-1!', got '%s'", res)
}
})
// mock released
res := Foo("anything") // call
if res != "ori:anything" {
t.Errorf("expected 'ori:anything', got '%s'", res)
}
// mock again
PatchRun(func() {
Mock(Foo).Return("MOCKED-2!").Build() // mock
res := Foo("anything") // call
if res != "MOCKED-2!" {
t.Errorf("expected 'MOCKED-2!', got '%s'", res)
}
})
// mock released
res = Foo("anything") // call
if res != "ori:anything" {
t.Errorf("expected 'ori:anything', got '%s'", res)
}
}
GetMethod to handle special casesIn special cases where direct mocking is not possible or not effective, you can use GetMethod to get the corresponding method before mocking. Please ensure that the passed object is not nil.
Mock method through an instance (including interface type instances):
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`
// Tips: if the instance is an interface type, you can use it the same way
// var ia interface{ Foo(string) string } = new(A)
// Mock(GetMethod(ia, "Foo")).Return("MOCKED!").Build()
Mock(GetMethod(a, "Foo")).Return("MOCKED!").Build()
fmt.Println(a.Foo("anything")) // MOCKED!
}
Mock 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]
// Tips: this is a special case of "mocking methods through instances", where the type corresponding to the instance is unexported
}
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
}
The Go compiler may directly erase the type information of unexported methods, in which case GetMethod will fail to retrieve the method. In such cases, you can use OptUnexportedTargetType to explicitly specify its type:
package main
import (
"bytes"
"fmt"
. "github.com/bytedance/mockey"
)
func main() {
var targetType func() bool // Signature of the unexported `empty` method (excluding receiver)
target := GetMethod(new(bytes.Buffer), "empty", OptUnexportedTargetType(targetType))
Mock(target).Return(true).Build()
buf := bytes.NewBuffer([]byte{1, 2, 3, 4})
b, err := buf.ReadByte()
fmt.Println(b, err) // 0 EOF | `ReadByte` internally calls the `empty` method to check if the buffer is empty and returns io.EOF
}
Mock methods in neste
$ claude mcp add mockey \
-- python -m otcore.mcp_server <graph>