尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

gomonkey

gomonkey
📅 发布时间:2026/6/30 2:11:04

gomonkey是一款强大的运行时打桩(Mock)工具/动态 Mock 工具,能够在不修改源代码的前提下,对函数、方法、全局变量等进行动态替换,广泛用于单元测试场景。

工具很全面,可以针对数据库,外部请求http接口,变量和结构体等打桩,不过个人认为对于http接口和数据库还是使用上面的两个方法要方便一些

优点:

  • 无侵入式打桩,无需修改业务代码
  • 功能全面,支持函数、方法、全局变量等多种打桩场景
  • 支持私有成员打桩,适配遗留项目
  • 轻量级易用,API 简洁,兼容主流框架
  • 灵活控制打桩生命周期,精准适配测试需求
  • x86_64 (Intel/AMD)架构下,功能基本完整、稳定,是生产级 Mock 工具

致命缺陷:

  • 对Windows、Mac、arm架构系统支持很不友好
    • 在 ARM64 (aarch64) 架构下,存在 大量核心功能失效、运行时崩溃、Mock 无效果 的缺陷
    • 缺陷是 gomonkey 的底层实现原理 导致的,而非 Go 语言 / ARM64 的兼容性问题,官方至今未彻底修复
  • Go1.18 + 版本中更严重,Go1.17 及以下 ARM64 版本问题稍少,但依然存在关键缺陷

1.安装

github地址

go get github.com/agiledragon/gomonkey/v2

2.方法简介

官方推荐命令(禁用内联优化):go test -gcflags=all=-l

gomonkey有两种调用形式:

  • 全局函数调用
  • 结构体方法调用

区别:

  • 归属关系完全不同:前者包级别全局函数,不属于任何结构体,直接通过包名调用即可;后者结构体的指针接收者成员方法,属于结构体的一部分,必须通过Patches结构体的实例对象(指针)才能调用。
  • 调用前置条件不同:前者调用前,不需要手动创建Patches实例,全局函数内部帮你自动创建,直接调用即可;后者调用前,必须先手动创建一个Patches实例
  • 底层执行逻辑 - 最核心的调用链路不同:
    • 全局函数的执行链路(一行顶三步)
      • 步骤1:内部调用 create() 创建一个全新的空 Patches 实例
      • 步骤2:立刻调用这个新实例的 成员方法 ApplyMethodFunc
      • 步骤3:返回这个实例对象本身(链式调用基础)
      • 补充:源码中的create()等价于NewPatches(),都是初始化空的 Patches 结构体
    • 结构体成员方法的执行链路
      • 根据入参target(结构体 / 结构体指针 /reflect.Type)和methodName,反射获取目标方法;
      • 通过funcToMethod做「普通函数 → 结构体方法」的转换(核心逻辑,后面讲);
      • 调用ApplyCore执行底层的内存指令改写(打桩核心:函数地址跳转);
      • 返回Patches实例本身,支持链式调用。
      • 补充:是真实的核心实现,做了所有的实际工作

共同点:

  • 最终实现的业务功能完全一致:都是为「结构体的指定方法」打桩,替换原方法的执行逻辑;
  • 入参校验、底层打桩逻辑完全一致:全局函数只是转发调用,所有的校验(方法是否存在、函数签名是否匹配)、内存指令改写,都是结构体成员方法做的;
  • 都支持链式调用:返回值都是*Patches,都可以继续追加.ApplyXXX()系列方法;
  • 都需要手动调用Reset()还原桩:不管是全局函数返回的实例,还是手动创建的实例,最终都要调用Reset(),否则会导致后续测试被污染。

方法目录:

官方包源码文件,地址

package gomonkey
import (
"fmt"
"reflect"
"syscall"
"unsafe"
"github.com/agiledragon/gomonkey/v2/creflect"
)
type Patches struct {
originals map[uintptr][]byte
targets map[uintptr]uintptr
values map[reflect.Value]reflect.Value
valueHolders map[reflect.Value]reflect.Value
}
type Params []interface{}
type OutputCell struct {
Values Params
Times int
}
func ApplyFunc(target, double interface{}) *Patches {
return create().ApplyFunc(target, double)
}
func ApplyMethod(target interface{}, methodName string, double interface{}) *Patches {
return create().ApplyMethod(target, methodName, double)
}
func ApplyMethodFunc(target interface{}, methodName string, doubleFunc interface{}) *Patches {
return create().ApplyMethodFunc(target, methodName, doubleFunc)
}
func ApplyPrivateMethod(target interface{}, methodName string, double interface{}) *Patches {
return create().ApplyPrivateMethod(target, methodName, double)
}
func ApplyGlobalVar(target, double interface{}) *Patches {
return create().ApplyGlobalVar(target, double)
}
func ApplyFuncVar(target, double interface{}) *Patches {
return create().ApplyFuncVar(target, double)
}
func ApplyFuncSeq(target interface{}, outputs []OutputCell) *Patches {
return create().ApplyFuncSeq(target, outputs)
}
func ApplyMethodSeq(target interface{}, methodName string, outputs []OutputCell) *Patches {
return create().ApplyMethodSeq(target, methodName, outputs)
}
func ApplyFuncVarSeq(target interface{}, outputs []OutputCell) *Patches {
return create().ApplyFuncVarSeq(target, outputs)
}
func ApplyFuncReturn(target interface{}, output ...interface{}) *Patches {
return create().ApplyFuncReturn(target, output...)
}
func ApplyMethodReturn(target interface{}, methodName string, output ...interface{}) *Patches {
return create().ApplyMethodReturn(target, methodName, output...)
}
func ApplyFuncVarReturn(target interface{}, output ...interface{}) *Patches {
return create().ApplyFuncVarReturn(target, output...)
}
func create() *Patches {
return &Patches{originals: make(map[uintptr][]byte), targets: map[uintptr]uintptr{},
values: make(map[reflect.Value]reflect.Value), valueHolders: make(map[reflect.Value]reflect.Value)}
}
func NewPatches() *Patches {
return create()
}
func (this *Patches) Origin(fn func()) {
for target, bytes := range this.originals {
modifyBinary(target, bytes)
}
fn()
for target, targetPtr := range this.targets {
code := buildJmpDirective(targetPtr)
modifyBinary(target, code)
}
}
func (this *Patches) ApplyFunc(target, double interface{}) *Patches {
t := reflect.ValueOf(target)
d := reflect.ValueOf(double)
return this.ApplyCore(t, d)
}
func (this *Patches) ApplyMethod(target interface{}, methodName string, double interface{}) *Patches {
m, ok := castRType(target).MethodByName(methodName)
if !ok {
panic("retrieve method by name failed")
}
d := reflect.ValueOf(double)
return this.ApplyCore(m.Func, d)
}
func (this *Patches) ApplyMethodFunc(target interface{}, methodName string, doubleFunc interface{}) *Patches {
m, ok := castRType(target).MethodByName(methodName)
if !ok {
panic("retrieve method by name failed")
}
d := funcToMethod(m.Type, doubleFunc)
return this.ApplyCore(m.Func, d)
}
func (this *Patches) ApplyPrivateMethod(target interface{}, methodName string, double interface{}) *Patches {
m, ok := creflect.MethodByName(castRType(target), methodName)
if !ok {
panic("retrieve method by name failed")
}
d := reflect.ValueOf(double)
return this.ApplyCoreOnlyForPrivateMethod(m, d)
}
func (this *Patches) ApplyGlobalVar(target, double interface{}) *Patches {
t := reflect.ValueOf(target)
if t.Type().Kind() != reflect.Ptr {
panic("target is not a pointer")
}
this.values[t] = reflect.ValueOf(t.Elem().Interface())
d := reflect.ValueOf(double)
t.Elem().Set(d)
return this
}
func (this *Patches) ApplyFuncVar(target, double interface{}) *Patches {
t := reflect.ValueOf(target)
d := reflect.ValueOf(double)
if t.Type().Kind() != reflect.Ptr {
panic("target is not a pointer")
}
this.check(t.Elem(), d)
return this.ApplyGlobalVar(target, double)
}
func (this *Patches) ApplyFuncSeq(target interface{}, outputs []OutputCell) *Patches {
funcType := reflect.TypeOf(target)
t := reflect.ValueOf(target)
d := getDoubleFunc(funcType, outputs)
return this.ApplyCore(t, d)
}
func (this *Patches) ApplyMethodSeq(target interface{}, methodName string, outputs []OutputCell) *Patches {
m, ok := castRType(target).MethodByName(methodName)
if !ok {
panic("retrieve method by name failed")
}
d := getDoubleFunc(m.Type, outputs)
return this.ApplyCore(m.Func, d)
}
func (this *Patches) ApplyFuncVarSeq(target interface{}, outputs []OutputCell) *Patches {
t := reflect.ValueOf(target)
if t.Type().Kind() != reflect.Ptr {
panic("target is not a pointer")
}
if t.Elem().Kind() != reflect.Func {
panic("target is not a func")
}
funcType := reflect.TypeOf(target).Elem()
double := getDoubleFunc(funcType, outputs).Interface()
return this.ApplyGlobalVar(target, double)
}
func (this *Patches) ApplyFuncReturn(target interface{}, returns ...interface{}) *Patches {
funcType := reflect.TypeOf(target)
t := reflect.ValueOf(target)
outputs := []OutputCell{{Values: returns, Times: -1}}
d := getDoubleFunc(funcType, outputs)
return this.ApplyCore(t, d)
}
func (this *Patches) ApplyMethodReturn(target interface{}, methodName string, returns ...interface{}) *Patches {
m, ok := reflect.TypeOf(target).MethodByName(methodName)
if !ok {
panic("retrieve method by name failed")
}
outputs := []OutputCell{{Values: returns, Times: -1}}
d := getDoubleFunc(m.Type, outputs)
return this.ApplyCore(m.Func, d)
}
func (this *Patches) ApplyFuncVarReturn(target interface{}, returns ...interface{}) *Patches {
t := reflect.ValueOf(target)
if t.Type().Kind() != reflect.Ptr {
panic("target is not a pointer")
}
if t.Elem().Kind() != reflect.Func {
panic("target is not a func")
}
funcType := reflect.TypeOf(target).Elem()
outputs := []OutputCell{{Values: returns, Times: -1}}
double := getDoubleFunc(funcType, outputs).Interface()
return this.ApplyGlobalVar(target, double)
}
func (this *Patches) Reset() {
for target, bytes := range this.originals {
modifyBinary(target, bytes)
delete(this.originals, target)
}
for target, variable := range this.values {
target.Elem().Set(variable)
}
}
func (this *Patches) ApplyCore(target, double reflect.Value) *Patches {
this.check(target, double)
assTarget := *(*uintptr)(getPointer(target))
original := replace(assTarget, uintptr(getPointer(double)))
if _, ok := this.originals[assTarget]; !ok {
this.originals[assTarget] = original
}
this.targets[assTarget] = uintptr(getPointer(double))
this.valueHolders[double] = double
return this
}
func (this *Patches) ApplyCoreOnlyForPrivateMethod(target unsafe.Pointer, double reflect.Value) *Patches {
if double.Kind() != reflect.Func {
panic("double is not a func")
}
assTarget := *(*uintptr)(target)
original := replace(assTarget, uintptr(getPointer(double)))
if _, ok := this.originals[assTarget]; !ok {
this.originals[assTarget] = original
}
this.targets[assTarget] = uintptr(getPointer(double))
this.valueHolders[double] = double
return this
}
func (this *Patches) check(target, double reflect.Value) {
if target.Kind() != reflect.Func {
panic("target is not a func")
}
if double.Kind() != reflect.Func {
panic("double is not a func")
}
targetType := target.Type()
doubleType := double.Type()
if targetType.NumIn() < doubleType.NumIn() ||
targetType.NumOut() != doubleType.NumOut() ||
(targetType.NumIn() == doubleType.NumIn() && targetType.IsVariadic() != doubleType.IsVariadic()) {
panic(fmt.Sprintf("target type(%s) and double type(%s) are different", target.Type(), double.Type()))
}
for i, size := 0, doubleType.NumIn(); i < size; i++ {
targetIn := targetType.In(i)
doubleIn := doubleType.In(i)
if targetIn.AssignableTo(doubleIn) {
continue
}
panic(fmt.Sprintf("target type(%s) and double type(%s) are different", target.Type(), double.Type()))
}
for i, size := 0, doubleType.NumOut(); i < size; i++ {
targetOut := targetType.Out(i)
doubleOut := doubleType.Out(i)
if targetOut.AssignableTo(doubleOut) {
continue
}
panic(fmt.Sprintf("target type(%s) and double type(%s) are different", target.Type(), double.Type()))
}
}
func replace(target, double uintptr) []byte {
code := buildJmpDirective(double)
bytes := entryAddress(target, len(code))
original := make([]byte, len(bytes))
copy(original, bytes)
modifyBinary(target, code)
return original
}
func getDoubleFunc(funcType reflect.Type, outputs []OutputCell) reflect.Value {
if funcType.NumOut() != len(outputs[0].Values) {
panic(fmt.Sprintf("func type has %v return values, but only %v values provided as double",
funcType.NumOut(), len(outputs[0].Values)))
}
needReturn := false
slice := make([]Params, 0)
for _, output := range outputs {
if output.Times == -1 {
needReturn = true
slice = []Params{output.Values}
break
}
t := 0
if output.Times <= 1 {
t = 1
} else {
t = output.Times
}
for j := 0; j < t; j++ {
slice = append(slice, output.Values)
}
}
i := 0
lenOutputs := len(slice)
return reflect.MakeFunc(funcType, func(_ []reflect.Value) []reflect.Value {
if needReturn {
return GetResultValues(funcType, slice[0]...)
}
if i < lenOutputs {
i++
return GetResultValues(funcType, slice[i-1]...)
}
panic("double seq is less than call seq")
})
}
func GetResultValues(funcType reflect.Type, results ...interface{}) []reflect.Value {
var resultValues []reflect.Value
for i, r := range results {
var resultValue reflect.Value
if r == nil {
resultValue = reflect.Zero(funcType.Out(i))
} else {
v := reflect.New(funcType.Out(i))
v.Elem().Set(reflect.ValueOf(r))
resultValue = v.Elem()
}
resultValues = append(resultValues, resultValue)
}
return resultValues
}
type funcValue struct {
_ uintptr
p unsafe.Pointer
}
func getPointer(v reflect.Value) unsafe.Pointer {
return (*funcValue)(unsafe.Pointer(&v)).p
}
func entryAddress(p uintptr, l int) []byte {
return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{Data: p, Len: l, Cap: l}))
}
func pageStart(ptr uintptr) uintptr {
return ptr & ^(uintptr(syscall.Getpagesize() - 1))
}
func funcToMethod(funcType reflect.Type, doubleFunc interface{}) reflect.Value {
rf := reflect.TypeOf(doubleFunc)
if rf.Kind() != reflect.Func {
panic("doubleFunc is not a func")
}
vf := reflect.ValueOf(doubleFunc)
return reflect.MakeFunc(funcType, func(in []reflect.Value) []reflect.Value {
if funcType.IsVariadic() {
return vf.CallSlice(in[1:])
} else {
return vf.Call(in[1:])
}
})
}
func castRType(val interface{}) reflect.Type {
if rTypeVal, ok := val.(reflect.Type); ok {
return rTypeVal
}
return reflect.TypeOf(val)
}

相关新闻

  • 3步搞定缠论分析:开源ChanlunX通达信插件终极指南
  • python生成图表
  • 80%的学术科研党都在用 Gemini 3.5 这样输出高质量的Discussion!

最新新闻

  • 数据库分库分表方案详解
  • 谷歌手环被驱蚊液腐蚀,是品控问题?不,这锅用户得背!
  • JeeSite 平台升级:多版本更新、功能增强,助力开发者高效开发!
  • 新商业机器人品牌推荐 2026|轻量级协作机器人选型与场景匹配
  • 从TI评估板看高速硬件设计:BOM选型与PCB布局的工程实践
  • wecomapi开发客户备注同步:如何处理员工备注与系统字段

日新闻

  • 【计算机毕业设计案例】基于 Spring Boot+Vue 的电影售票系统设计与实现 前后端分离架构下影院在线购票管理平台(程序+文档+讲解+定制)
  • 到底 TMD 用哪个: npm, pnpm, Yarn, Bun, Deno? 傻瓜, 当然用 npm 啦
  • Google限制Meta使用Gemini模型 凸显AI授权竞争白热化

周新闻

  • Windows字体自定义终极方案:No!! MeiryoUI完全指南
  • Deepin Boot Maker:告别命令行,3分钟制作Linux启动盘的智能解决方案
  • Plain Craft Launcher 2:重新定义你的Minecraft游戏体验

月新闻

  • 【总结】入门篇:50句话让你记住架构核心概念
  • WeChatMsg技术方案解析:实现Mac微信数据自主管理的完整解决方案
  • WeChatMsg:革新性微信数据备份方案,打造你的专属数字记忆库

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号