Go mockery patching
用于Go单元测试,模拟替换函数、结构体方法的真实调用,屏蔽复杂的调用函数逻辑。
- 代码参考:
- 相关技术博客
-
Go版本 已完成
go1.7
到go1.17
的测试 -
系统架构
x86
、amd64
-
操作系统
macos
、linux
、windows
-
目标函数如果使用内联(inlined),将不能替换,可以使用指令
//go:noinline
或者gcflags=-l
来构建,告诉go编译器禁用内联 -
需要对包含可执行代码的内存页有写权限,一些操作系统可能会限制这种访问
-
不是线程安全的
go get github.com/wenjy/mockery
//go:noinline
func methodA() int { return 1 }
//go:noinline
func methodB() int { return 2 }
func TestPatcher(t *testing.T) {
patch, err := mpatch.PatchMethod(methodA, methodB)
if err != nil {
t.Fatal(err)
}
if methodA() != 2 {
t.Fatal("The patch did not work")
}
err = patch.Unpatch()
if err != nil {
t.Fatal(err)
}
if methodA() != 1 {
t.Fatal("The unpatch did not work")
}
}
//go:noinline
func methodA() int { return 1 }
//go:noinline
func methodB() int { return 2 }
func TestPatcherUsingReflect(t *testing.T) {
reflectA := reflect.ValueOf(methodA)
patch, err := PatchMethodByReflectValue(reflectA, methodB)
if err != nil {
t.Fatal(err)
}
if methodA() != 2 {
t.Fatal("The patch did not work")
}
err = patch.Unpatch()
if err != nil {
t.Fatal(err)
}
if methodA() != 1 {
t.Fatal("The unpatch did not work")
}
}
//go:noinline
func methodA() int { return 1 }
func TestPatcherUsingMakeFunc(t *testing.T) {
reflectA := reflect.ValueOf(methodA)
patch, err := PatchMethodWithMakeFuncValue(reflectA,
func(args []reflect.Value) (results []reflect.Value) {
return []reflect.Value{reflect.ValueOf(42)}
})
if err != nil {
t.Fatal(err)
}
if methodA() != 42 {
t.Fatal("The patch did not work")
}
err = patch.Unpatch()
if err != nil {
t.Fatal(err)
}
if methodA() != 1 {
t.Fatal("The unpatch did not work")
}
}
type myStruct struct {
}
//go:noinline
func (s *myStruct) Method() int {
return 1
}
func TestInstancePatcher(t *testing.T) {
mStruct := myStruct{}
var patch *Patch
var err error
patch, err = PatchInstanceMethod(reflect.TypeOf(mStruct), "Method", func(m *myStruct) int {
patch.Unpatch()
defer patch.Patch()
return 41 + m.Method()
})
if err != nil {
t.Fatal(err)
}
if mStruct.Method() != 42 {
t.Fatal("The patch did not work")
}
err = patch.Unpatch()
if err != nil {
t.Fatal(err)
}
if mStruct.Method() != 1 {
t.Fatal("The unpatch did not work")
}
}
type myStruct struct {
}
//go:noinline
func (s myStruct) ValueMethod() int {
return 1
}
func TestInstanceValuePatcher(t *testing.T) {
mStruct := myStruct{}
var patch *Patch
var err error
patch, err = PatchInstanceMethod(reflect.TypeOf(mStruct), "ValueMethod", func(m myStruct) int {
patch.Unpatch()
defer patch.Patch()
return 41 + m.Method()
})
if err != nil {
t.Fatal(err)
}
if mStruct.ValueMethod() != 42 {
t.Fatal("The patch did not work")
}
err = patch.Unpatch()
if err != nil {
t.Fatal(err)
}
if mStruct.ValueMethod() != 1 {
t.Fatal("The unpatch did not work")
}
}
type myStruct struct {
}
//go:noinline
func (s *myStruct) Method() int {
return 1
}
func TestPatchMethodByReflect(t *testing.T) {
mStruct := myStruct{}
target := reflect.TypeOf(mStruct)
target = reflect.PtrTo(target)
m, _ := target.MethodByName("Method")
var patch *Patch
var err error
patch, err = PatchMethodByReflect(m, func(m *myStruct) int {
patch.Unpatch()
defer patch.Patch()
return 41 + m.Method()
})
if err != nil {
t.Fatal(err)
}
if mStruct.Method() != 42 {
t.Fatal("The patch did not work")
}
err = patch.Unpatch()
if err != nil {
t.Fatal(err)
}
if mStruct.Method() != 1 {
t.Fatal("The unpatch did not work")
}
}
type myStruct struct {
}
//go:noinline
func (s *myStruct) Method() int {
return 1
}
func TestPatchMethodWithMakeFunc(t *testing.T) {
mStruct := myStruct{}
target := reflect.TypeOf(mStruct)
target = reflect.PtrTo(target)
m, _ := target.MethodByName("Method")
var patch *Patch
var err error
patch, err = PatchMethodWithMakeFunc(m, func(args []reflect.Value) (results []reflect.Value) {
return []reflect.Value{reflect.ValueOf(42)}
})
if err != nil {
t.Fatal(err)
}
if mStruct.Method() != 42 {
t.Fatal("The patch did not work")
}
err = patch.Unpatch()
if err != nil {
t.Fatal(err)
}
if mStruct.Method() != 1 {
t.Fatal("The unpatch did not work")
}
}
type myStruct struct {
}
//go:noinline
func (s *myStruct) Method() int {
return 1
}
func TestPatchMethodWithMakeFuncValue(t *testing.T) {
mStruct := myStruct{}
target := reflect.TypeOf(mStruct)
target = reflect.PtrTo(target)
m, _ := target.MethodByName("Method")
var patch *Patch
var err error
patch, err = PatchMethodWithMakeFuncValue(m.Func, func(args []reflect.Value) (results []reflect.Value) {
return []reflect.Value{reflect.ValueOf(42)}
})
if err != nil {
t.Fatal(err)
}
if mStruct.Method() != 42 {
t.Fatal("The patch did not work")
}
err = patch.Unpatch()
if err != nil {
t.Fatal(err)
}
if mStruct.Method() != 1 {
t.Fatal("The unpatch did not work")
}
}
type myStruct struct {
}
//go:noinline
func (s *myStruct) Method() int {
return 1
}
func TestPatchMethodByReflectValue(t *testing.T) {
mStruct := myStruct{}
target := reflect.TypeOf(mStruct)
target = reflect.PtrTo(target)
m, _ := target.MethodByName("Method")
var patch *Patch
var err error
patch, err = PatchMethodByReflectValue(m.Func, func(m *myStruct) int {
patch.Unpatch()
defer patch.Patch()
return 41 + m.Method()
})
if err != nil {
t.Fatal(err)
}
if mStruct.Method() != 42 {
t.Fatal("The patch did not work")
}
err = patch.Unpatch()
if err != nil {
t.Fatal(err)
}
if mStruct.Method() != 1 {
t.Fatal("The unpatch did not work")
}
}