gomonkey is a library to make monkey patching in unit tests easy

gomonkey

gomonkey is a library to make monkey patching in unit tests easy, and the core idea of monkey patching comes from Bouke, you can read this blogpost for an explanation on how it works.

Features

  • support a patch for a function
  • support a patch for a member method
  • support a patch for a interface
  • support a patch for a function variable
  • support a patch for a global variable
  • support patches of a specified sequence for a function
  • support patches of a specified sequence for a member method
  • support patches of a specified sequence for a interface
  • support patches of a specified sequence for a function variable

Notes

  • gomonkey fails to patch a function or a member method if inlining is enabled, please running your tests with inlining disabled by adding the command line argument that is -gcflags=-l(below go1.10) or -gcflags=all=-l(go1.10 and above).
  • gomonkey should work on any amd64 system.
  • A panic may happen when a goroutine is patching a function or a member method that is visited by another goroutine at the same time. That is to say, gomonkey is not threadsafe.
  • go1.6 version of the reflection mechanism supports the query of private member methods, but go1.7 and above does not support it. However, all versions of the reflection mechanism support the query of private functions, so gomonkey will trigger a panic for only patching a private member method when go1.7 and above is used.

Supported Platform:

  • MAC OS X amd64
  • Linux amd64
  • Windows amd64
  • MAC OS X arm64
  • Linux arm64
  • Windows arm64

Installation

$ go get github.com/agiledragon/gomonkey

Test Method

$ cd test 
$ go test -gcflags=all=-l

Using gomonkey

Please refer to the test cases as idioms, very complete and detailed.

Owner
Zhang Xiaolong
small step, small win
Zhang Xiaolong
Comments
  • macOS 10.15 syscall.Mprotect panic: permission denied

    macOS 10.15 syscall.Mprotect panic: permission denied

    Hi: After I was update my macOS to about 10.14, It will report panic: permission denied when I run ApplyFunc or ApplyMethod. I searched this problem in google, but I don't have answer. Which permission I should set? Thanks a lot. This is panic stack.

    panic: permission denied [recovered] panic: permission denied

    goroutine 13 [running]: testing.tRunner.func1(0xc4202081e0) /.../goroot/go/src/testing/testing.go:742 +0x29d panic(0x4e697e0, 0xc4205a7c18) /.../goroot/go/src/runtime/panic.go:502 +0x229 github.com/agiledragon/gomonkey.modifyBinary(0x4bf4570, 0xc420229a7c, 0xc, 0xc) /.../gopath/src/github.com/agiledragon/gomonkey/modify_binary_darwin.go:11 +0x198 github.com/agiledragon/gomonkey.replace(0x4bf4570, 0x5099668, 0xc420229b58, 0x59aa580, 0x4dd7500) /.../gopath/src/github.com/agiledragon/gomonkey/patch.go:164 +0x112 github.com/agiledragon/gomonkey.(*Patches).applyCore(0xc420086c40, 0x4dd7560, 0x5099ba8, 0x13, 0x4dd7560, 0x5099668, 0x13, 0x10) /.../gopath/src/github.com/agiledragon/gomonkey/patch.go:140 +0x161 github.com/agiledragon/gomonkey.(*Patches).ApplyFunc(0xc420086c40, 0x4dd7560, 0x5099ba8, 0x4dd7560, 0x5099668, 0x4009d7d) /.../gopath/src/github.com/agiledragon/gomonkey/patch.go:60 +0xbf github.com/agiledragon/gomonkey.ApplyFunc(0x4dd7560, 0x5099ba8, 0x4dd7560, 0x5099668, 0x2ae) /.../gopath/src/github.com/agiledragon/gomonkey/patch.go:22 +0xa2

  • mac m1 used gomockey: undefined: buildJmpDirective

    mac m1 used gomockey: undefined: buildJmpDirective

    my system is mac m1 when I used gomonkey, it reports:

    github.com/agiledragon/gomonkey

    ../../../../pkg/mod/github.com/agiledragon/[email protected]+incompatible/patch.go:160:10: undefined: buildJmpDirective can you help to solve this issue?

  • ApplyMethod针对struct的私有方法无效,报错:retrieve method by name failed

    ApplyMethod针对struct的私有方法无效,报错:retrieve method by name failed

    官方例子:TestApplyPrivate,运行结果,报错:

    GOROOT=/usr/local/go #gosetup
    GOPATH=/Users/devinzeng/go #gosetup
    /usr/local/go/bin/go test -c -o /private/var/folders/h9/2yls4kmx2dj6v1fh2nlkfgww0000gn/T/GoLand/___TestApplyPrivate_in_github_com_agiledragon_gomonkey_v2_test.test github.com/agiledragon/gomonkey/v2/test #gosetup
    /usr/local/go/bin/go tool test2json -t /private/var/folders/h9/2yls4kmx2dj6v1fh2nlkfgww0000gn/T/GoLand/___TestApplyPrivate_in_github_com_agiledragon_gomonkey_v2_test.test -test.v -test.paniconexit0 -test.run ^\QTestApplyPrivate\E$
    === RUN   TestApplyPrivate
    E
    Errors:
    
      * /Users/devinzeng/go/src/github.com/agiledragon/gomonkey/test/apply_private_method_test.go 
      Line 25: - retrieve method by name failed 
      goroutine 18 [running]:
      	/Users/devinzeng/go/pkg/mod/github.com/smartystreets/[email protected]/convey/reporting/reports.go:148 +0x5c
      	/Users/devinzeng/go/pkg/mod/github.com/smartystreets/[email protected]/convey/reporting/reports.go:121 +0x70
      	/Users/devinzeng/go/pkg/mod/github.com/smartystreets/[email protected]/convey/context.go:235 +0x104
      panic({0x104eb5780, 0x104ee2ac0})
      	/usr/local/go/src/runtime/panic.go:1038 +0x21c
      github.com/agiledragon/gomonkey/v2.(*Patches).ApplyMethod(0x14000125160, {0x104eeb398, 0x104ec0500}, {0x104e662c7, 0x2}, {0x104eb7c00, 0x104ee21b8})
      	/Users/devinzeng/go/src/github.com/agiledragon/gomonkey/patch.go:67 +0x1c0
      github.com/agiledragon/gomonkey/v2.ApplyMethod(...)
      	/Users/devinzeng/go/src/github.com/agiledragon/gomonkey/patch.go:27
      github.com/agiledragon/gomonkey/v2/test.TestApplyPrivate.func1.1()
      	/Users/devinzeng/go/src/github.com/agiledragon/gomonkey/test/apply_private_method_test.go:25 +0x160
      	/Users/devinzeng/go/pkg/mod/github.com/smartystreets/[email protected]/convey/discovery.go:80 +0x28
      	/Users/devinzeng/go/pkg/mod/github.com/smartystreets/[email protected]/convey/context.go:261 +0x144
      	/Users/devinzeng/go/pkg/mod/github.com/smartystreets/[email protected]/convey/context.go:163 +0x44
      github.com/jtolds/gls.(*ContextManager).SetValues.func1(0x0)
      	/Users/devinzeng/go/pkg/mod/github.com/jtolds/[email protected]+incompatible/context.go:97 +0x488
      github.com/jtolds/gls.EnsureGoroutineId(0x14000108690)
      	/Users/devinzeng/go/pkg/mod/github.com/jtolds/[email protected]+incompatible/gid.go:19 +0x110
      github.com/jtolds/gls.(*ContextManager).SetValues(0x14000104510, 0x14000108630, 0x140001280a8)
      	/Users/devinzeng/go/pkg/mod/github.com/jtolds/[email protected]+incompatible/context.go:63 +0x180
      	/Users/devinzeng/go/pkg/mod/github.com/smartystreets/[email protected]/convey/context.go:162 +0x364
      	/Users/devinzeng/go/pkg/mod/github.com/smartystreets/[email protected]/convey/doc.go:77 +0x94
      github.com/agiledragon/gomonkey/v2/test.TestApplyPrivate.func1()
      	/Users/devinzeng/go/src/github.com/agiledragon/gomonkey/test/apply_private_method_test.go:22 +0x6c
      	/Users/devinzeng/go/pkg/mod/github.com/smartystreets/[email protected]/convey/discovery.go:80 +0x28
      	/Users/devinzeng/go/pkg/mod/github.com/smartystreets/[email protected]/convey/context.go:261 +0x144
      	/Users/devinzeng/go/pkg/mod/github.com/smartystreets/[email protected]/convey/context.go:110 +0xe0
      github.com/jtolds/gls.(*ContextManager).SetValues.func1(0x0)
      	/Users/devinzeng/go/pkg/mod/github.com/jtolds/[email protected]+incompatible/context.go:97 +0x488
      github.com/jtolds/gls.EnsureGoroutineId.func1()
      	/Users/devinzeng/go/pkg/mod/github.com/jtolds/[email protected]+incompatible/gid.go:24 +0x30
      github.com/jtolds/gls._m(0x0, 0x14000128090)
      	/Users/devinzeng/go/pkg/mod/github.com/jtolds/[email protected]+incompatible/stack_tags.go:108 +0x30
      github.com/jtolds/gls.github_com_jtolds_gls_markS(0x0, 0x14000128090)
      	/Users/devinzeng/go/pkg/mod/github.com/jtolds/[email protected]+incompatible/stack_tags.go:56 +0x30
      github.com/jtolds/gls.addStackTag(...)
      	/Users/devinzeng/go/pkg/mod/github.com/jtolds/[email protected]+incompatible/stack_tags.go:49
      github.com/jtolds/gls.EnsureGoroutineId(0x14000108540)
      	/Users/devinzeng/go/pkg/mod/github.com/jtolds/[email protected]+incompatible/gid.go:24 +0xdc
      github.com/jtolds/gls.(*ContextManager).SetValues(0x14000104510, 0x140001084e0, 0x1400014a080)
      	/Users/devinzeng/go/pkg/mod/github.com/jtolds/[email protected]+incompatible/context.go:63 +0x180
      	/Users/devinzeng/go/pkg/mod/github.com/smartystreets/[email protected]/convey/context.go:105 +0x288
      	/Users/devinzeng/go/pkg/mod/github.com/smartystreets/[email protected]/convey/doc.go:75 +0xb4
      github.com/agiledragon/gomonkey/v2/test.TestApplyPrivate(0x14000113380)
      	/Users/devinzeng/go/src/github.com/agiledragon/gomonkey/test/apply_private_method_test.go:21 +0x84
      testing.tRunner(0x14000113380, 0x104ee21d0)
      	/usr/local/go/src/testing/testing.go:1259 +0x104
      created by testing.(*T).Run
      	/usr/local/go/src/testing/testing.go:1306 +0x328
      
      goroutine 1 [chan receive]:
      testing.(*T).Run(0x140001131e0, {0x104e68b0a, 0x10}, 0x104ee21d0)
      	/usr/local/go/src/testing/testing.go:1307 +0x344
      testing.runTests.func1(0x140001131e0)
      	/usr/local/go/src/testing/testing.go:1598 +0x80
      testing.tRunner(0x140001131e0, 0x14000123d18)
      	/usr/local/go/src/testing/testing.go:1259 +0x104
      testing.runTests(0x14000128048, {0x104fe6ba0, 0xa, 0xa}, {0x0, 0x0, 0x0})
      	/usr/local/go/src/testing/testing.go:1596 +0x3ec
      testing.(*M).Run(0x1400014c000)
      	/usr/local/go/src/testing/testing.go:1504 +0x4fc
      main.main()
      	_testmain.go:61 +0x17c
      
    
    
    1 total assertion
    
    --- FAIL: TestApplyPrivate (0.00s)
    
    FAIL
    
    Process finished with the exit code 1
    
    
    

    环境是Mac M1, go version go1.17 darwin/arm64

  • err:private method feature for all go versions

    err:private method feature for all go versions

    gomonkey:v2.7.0 go version:17.7

    fully support private method

    download gomonkey-2.7.0 , when I run apply_private_method_test.go , err like this:

    GOROOT=C:\Program Files\Go #gosetup GOPATH=C:\Users\cofson\go #gosetup "C:\Program Files\Go\bin\go.exe" test -c -o C:\Users\cofson\AppData\Local\Temp\GoLand__TestApplyPrivateMethod_in_github_com_agiledragon_gomonkey_v2_test__1.test.exe github.com/agiledragon/gomonkey/v2/test #gosetup "C:\Program Files\Go\bin\go.exe" tool test2json -t C:\Users\cofson\AppData\Local\Temp\GoLand__TestApplyPrivateMethod_in_github_com_agiledragon_gomonkey_v2_test__1.test.exe -test.v -test.paniconexit0 -test.run ^\QTestApplyPrivateMethod\E$ #gosetup === RUN TestApplyPrivateMethod x Failures:

    • C:/Users/cofson/GolandProjects/gomonkey/test/apply_private_method_test.go Line 30: Expected: 'I am hungry' Actual: 'I am full' (Should be equal) goroutine 19 [running]: C:/Users/cofson/go/pkg/mod/github.com/smartystreets/[email protected]/convey/reporting/reports.go:143 +0x3c C:/Users/cofson/go/pkg/mod/github.com/smartystreets/[email protected]/convey/reporting/reports.go:103 +0x5d C:/Users/cofson/go/pkg/mod/github.com/smartystreets/[email protected]/convey/context.go:176 +0xa5 C:/Users/cofson/go/pkg/mod/github.com/smartystreets/[email protected]/convey/doc.go:125 +0x5a github.com/agiledragon/gomonkey/v2/test.TestApplyPrivateMethod.func1.1() C:/Users/cofson/GolandProjects/gomonkey/test/apply_private_method_test.go:30 +0x253 C:/Users/cofson/go/pkg/mod/github.com/smartystreets/[email protected]/convey/discovery.go:80 +0x1b C:/Users/cofson/go/pkg/mod/github.com/smartystreets/[email protected]/convey/context.go:261 +0x177 C:/Users/cofson/go/pkg/mod/github.com/smartystreets/[email protected]/convey/context.go:163 +0x2c github.com/jtolds/gls.(*ContextManager).SetValues.func1(0x0) C:/Users/cofson/go/pkg/mod/github.com/jtolds/[email protected]+incompatible/context.go:97 +0x476 github.com/jtolds/gls.EnsureGoroutineId(0xc0000b4960) C:/Users/cofson/go/pkg/mod/github.com/jtolds/[email protected]+incompatible/gid.go:19 +0xfb github.com/jtolds/gls.(*ContextManager).SetValues(0xc000084530, 0xc0000b4900, 0xc000098108) C:/Users/cofson/go/pkg/mod/github.com/jtolds/[email protected]+incompatible/context.go:63 +0x16a C:/Users/cofson/go/pkg/mod/github.com/smartystreets/[email protected]/convey/context.go:162 +0x2dc C:/Users/cofson/go/pkg/mod/github.com/smartystreets/[email protected]/convey/doc.go:77 +0x75 github.com/agiledragon/gomonkey/v2/test.TestApplyPrivateMethod.func1() C:/Users/cofson/GolandProjects/gomonkey/test/apply_private_method_test.go:23 +0x65 C:/Users/cofson/go/pkg/mod/github.com/smartystreets/[email protected]/convey/discovery.go:80 +0x1b C:/Users/cofson/go/pkg/mod/github.com/smartystreets/[email protected]/convey/context.go:261 +0x177 C:/Users/cofson/go/pkg/mod/github.com/smartystreets/[email protected]/convey/context.go:110 +0xce github.com/jtolds/gls.(*ContextManager).SetValues.func1(0x0) C:/Users/cofson/go/pkg/mod/github.com/jtolds/[email protected]+incompatible/context.go:97 +0x476 github.com/jtolds/gls.EnsureGoroutineId.func1() C:/Users/cofson/go/pkg/mod/github.com/jtolds/[email protected]+incompatible/gid.go:24 +0x22 github.com/jtolds/gls._m(0x59b240, 0xc000039d01) C:/Users/cofson/go/pkg/mod/github.com/jtolds/[email protected]+incompatible/stack_tags.go:108 +0x22 github.com/jtolds/gls.github_com_jtolds_gls_markS(0xc0000b4810, 0x0) C:/Users/cofson/go/pkg/mod/github.com/jtolds/[email protected]+incompatible/stack_tags.go:56 +0x19 github.com/jtolds/gls.addStackTag(...) C:/Users/cofson/go/pkg/mod/github.com/jtolds/[email protected]+incompatible/stack_tags.go:49 github.com/jtolds/gls.EnsureGoroutineId(0xc0000b4810) C:/Users/cofson/go/pkg/mod/github.com/jtolds/[email protected]+incompatible/gid.go:24 +0xd8 github.com/jtolds/gls.(*ContextManager).SetValues(0xc000084530, 0xc0000b47b0, 0xc000092480) C:/Users/cofson/go/pkg/mod/github.com/jtolds/gls@v4.
  • privite value method mock failed

    privite value method mock failed

    • platform: mac intel
    • go verison: go1.16.5 darwin/amd64
    • gomonkey version: v2.3.0
    • problem: go test apply_private_method_test.go failed
    • successed code:
    package unit
    
    import (
    	"crypto/md5"
    	"fmt"
    	"github.com/agiledragon/gomonkey/v2"
    	"github.com/stretchr/testify/assert"
    	"reflect"
    	"testing"
    )
    
    type PasswordHelper2 struct {
    }
    
    func (p *PasswordHelper2) encodePassword(password string) string {
    	return fmt.Sprintf("%x", md5.Sum([]byte(password+"test")))
    }
    
    func TestPrivateEncodePassword(t *testing.T) {
    	pHelper := PasswordHelper2{}
    	var s *PasswordHelper2
    	patches := gomonkey.ApplyPrivateMethod(reflect.TypeOf(s), "encodePassword", func(_ *PasswordHelper2, password string) string {
    		return password + "test"
    	})
    	defer patches.Reset()
    
    	assert.Equal(t, pHelper.encodePassword("123456"), "123456test")
    
    }
    
    

    faild code

    package unit
    
    import (
    	"crypto/md5"
    	"fmt"
    	"github.com/agiledragon/gomonkey/v2"
    	"github.com/stretchr/testify/assert"
    	"reflect"
    	"testing"
    )
    
    type PasswordHelper2 struct {
    }
    
    func (p PasswordHelper2) encodePassword(password string) string {
    	return fmt.Sprintf("%x", md5.Sum([]byte(password+"test")))
    }
    
    func TestPrivateEncodePassword(t *testing.T) {
    	pHelper := PasswordHelper2{}
    	var s PasswordHelper2
    	patches := gomonkey.ApplyPrivateMethod(reflect.TypeOf(s), "encodePassword", func(_ PasswordHelper2, password string) string {
    		return password + "test"
    	})
    	defer patches.Reset()
    
    	assert.Equal(t, pHelper.encodePassword("123456"), "123456test")
    
    }
    
    
    
  • panic: permission denied

    panic: permission denied

    I have implemented monkey_arm64.go jmpToFunctionValue. Now trying to compile and execute my code on a Mac M1. I am getting an exception when making a call to syscall.Mprotect (syscall.PROT_WRITE). Do you know how to fix this problem?

    func mprotectCrossPage(addr uintptr, length int, prot int) {
    	pageSize := syscall.Getpagesize()
    	for p := pageStart(addr); p < addr+uintptr(length); p += uintptr(pageSize) {
    		page := rawMemoryAccess(p, pageSize)
    
    	        err := syscall.Mprotect(page, prot) //syscall.PROT_READ|syscall.PROT_WRITE|syscall.PROT_EXEC
    
    		if err != nil {
    			panic(err)
    		}
    	}
    }
    
    func jmpToFunctionValue(to uintptr) []byte {
    	/*return []byte{
    		0x48, 0xBA,
    		byte(to),
    		byte(to >> 8),
    		byte(to >> 16),
    		byte(to >> 24),
    		byte(to >> 32),
    		byte(to >> 40),
    		byte(to >> 48),
    		byte(to >> 56), // movabs rdx,to
    		0xFF, 0x22,     // jmp QWORD PTR [rdx]
    	}*/
    	raw := []uint32{
    		uint32((((to) & 0xffff) << 5) | 0xD2800000),       // MOVZ X0, <bytes 0,1>
    		uint32((((to >> 16) & 0xffff) << 5) | 0xF2A00000), // MOVK X0, <bytes 2,4>, LSL 16
    		uint32((((to >> 32) & 0xffff) << 5) | 0xF2C00000), // MOVK X0, <bytes 4,5>, LSL 32
    		uint32((((to >> 48) & 0xffff) << 5) | 0xF2E00000), // MOVK X0, <bytes 6,7>, LSL 48
    		0xD63F0000, // BLR X0
    	}
    
    	// Get the slice header
    	header := *(*reflect.SliceHeader)(unsafe.Pointer(&raw))
    
    	header.Len *= 4 // 4 bytes in uint32
    	header.Cap *= 4 // 4 bytes in uint32
    
    	// Convert slice header to an []byte
    	data := *(*[]byte)(unsafe.Pointer(&header))
    	return data
    }
    
  • 单例模式无法进行成员方法打桩

    单例模式无法进行成员方法打桩

    我写了一个小 demo 模拟这个问题 首先目录结构如下图 image

    persion.go 代码如下

    package model
    
    import (
    	"strings"
    )
    
    /*
     @Author: zhijian
     @Date: 2021/3/29 18:54
     @Description:
    */
    
    //单例变量
    var One = &Persion{Name: "test"}
    
    //类型为公开的没有任何问题  
    //可是如果这里为私有 persion 那么 ApplyMethod 最后一个参数 double interface{} 因为不在同一个包里面 就会取不到方法的 receiver 的类型 
    //但是为私有 persion 的时候 ApplyMethodSeq  因为不需要显式的填这个取不到的类型 编译可以通过,可以正常使用
    //这个地方有比较优雅的解决方案吗?还是我使用的姿势不对?
    type Persion struct {
    	Name string
    }
    
    func (p *Persion) Echo(age int) (string,error) {
    	var sb strings.Builder
    	sb.WriteString(p.Name)
    	sb.WriteString(string(rune(age)))
    	return sb.String(), nil
    }
    
    func (p *Persion) EchoMaybeDiff(age int) (string,error) {
    	var sb strings.Builder
    	sb.WriteString(p.Name)
    	sb.WriteString(string(rune(age)))
    	return sb.String(), nil
    }
    
    
    

    svc.go 代码如下

    package svc
    
    import "dtstack.com/dtstack/easymatrix/matrix/mock/model"
    
    /*
     @Author: zhijian
     @Date: 2021/3/29 19:08
     @Description:
    */
    
    func BizEcho() string  {
    	echo, err := model.One.Echo(3)
    	if err != nil {
    		panic(err)
    	}
    	return echo
    }
    
    
    func BizEchoMaybeDiff() string  {
    	echo, err := model.One.EchoMaybeDiff(3)
    	if err != nil {
    		panic(err)
    	}
    	return echo
    }
    
    

    svc_test.go 代码如下

    package svc
    
    import (
    	"dtstack.com/dtstack/easymatrix/matrix/mock/model"
    	"fmt"
    	. "github.com/agiledragon/gomonkey"
    	. "github.com/smartystreets/goconvey/convey"
    	"reflect"
    	"testing"
    )
    
    /*
     @Author: zhijian
     @Date: 2021/3/29 19:18
     @Description:
    */
    
    func TestEchoMaybeDiff(t *testing.T) {
    	Convey("test",t,
    		func() {
    			outputs := []OutputCell{
    				{Values: Params{`test1`, nil}},// 模拟函数的第1次输出
    
    				{Values: Params{`test2`, nil}},// 模拟函数的第2次输出
    
    				{Values: Params{`test3`, nil}},// 模拟函数的第3次输出
    			}
    			patches := ApplyMethodSeq(reflect.TypeOf(model.One), "EchoMaybeDiff", outputs)
    			defer patches.Reset()
    			echo1 := BizEchoMaybeDiff()
    			echo2 :=BizEchoMaybeDiff()
    			echo3 :=BizEchoMaybeDiff()
    			fmt.Println(echo1)
    			fmt.Println(echo2)
    			fmt.Println(echo3)
    			ShouldNotBeNil(echo3)
    		})
    }
    
    func TestBizEcho(t *testing.T) {
    	Convey("test",t,
    		func() {
    			patches := ApplyMethod(reflect.TypeOf(model.One), "Echo", func(_ *model.Persion, i int) (string, error) {
    				return "test", nil
    			})
    			defer patches.Reset()
    			bizEcho := BizEcho()
    			fmt.Println(bizEcho)
    			ShouldEqual(bizEcho, "test")
    		})
    }
    
    

    我觉得这种单例模式写法应该挺常见的,是不是我使用的姿势不对?

  • ApplyMethod有时生效,有时不生效

    ApplyMethod有时生效,有时不生效

    the method

    import (
    	"fmt"
    
    	remote "github.com/shima-park/agollo/viper-remote"
    	"github.com/spf13/viper"
    )
    
    var configType = "prop"
    
    func fetchApollo(ip, namespace string, conf *common.Config) error {
    	v := viper.New()
    	v.SetConfigType(configType)
    	err := v.AddRemoteProvider("apollo", ip, namespace)
    	if err != nil {  
    		log.Error(fmt.Sprintf("AddRemoteProvider err : %s", err.Error()))
    		return err
    	}
    	err = v.ReadRemoteConfig()
    	if err != nil {
    		log.Error(fmt.Sprintf("ReadRemoteConfig err : %s", err.Error()))
    		return err
    	}
    	err = v.Unmarshal(conf)
    	if err != nil {
    		log.Error(fmt.Sprintf("unmarshal Config err : %s", err.Error()))
    		return err
    	}
    	return nil
    }
    

    the test case

    import (
    	"errors"
    	"testing"
    
    	"github.com/agiledragon/gomonkey"
    	. "github.com/smartystreets/goconvey/convey"
    	"github.com/spf13/viper"
    )
    
    
    func Test_fetchApollo(t *testing.T) {
    
    	var viperpt *viper.Viper
    	var conf = &common.Config{}
    
    	Convey(casenameprefix, t, func() {
    		Convey(casenameprefix+"error[fetchApollo][Unmarshal]", func() {
    			patch := gomonkey.ApplyMethod(reflect.TypeOf(viperpt), "AddRemoteProvider", func(_ *viper.Viper, provider, endpoint, path string) error {
    				log.Debug("ApplyMethod viper.Viper.AddRemoteProvider success")
    				return nil
    			})
    			patch.ApplyMethod(reflect.TypeOf(viperpt), "ReadRemoteConfig", func(_ *viper.Viper) error {
    				log.Debug("ApplyMethod viper.Viper.ReadRemoteConfig success")
    				return nil
    			})
    			patch.ApplyMethod(reflect.TypeOf(viperpt), "Unmarshal", func(_ *viper.Viper, rawVal interface{}, opts ...viper.DecoderConfigOption) error {
    				log.Debug("ApplyMethod viper.Viper.Unmarshal success")
    				return errors.New("custom errors")
    			})
    			defer patch.Reset()
    
    			fetchApollo(conf)
    		})
    	})
    }
    

    the result

    === RUN   Test_fetchApollo
    
      custom: 
        custom:error[fetchApollo][Unmarshal] {"T":"2020-11-06T09:16:20.694+0800","C":"config/agollo_test.go:69","L":"debug","timestamp":1604625380,"M":"ApplyMethod viper.Viper.AddRemoteProvider success"}
    {"T":"2020-11-06T09:16:20.697+0800","C":"config/agollo.go:51","L":"error","timestamp":1604625380,"M":"ReadRemoteConfig err : Remote Configurations Error: No Files Found"}
    {"T":"2020-11-06T09:16:20.698+0800","C":"config/agollo.go:26","L":"error","timestamp":1604625380,"M":"get fetchApollo err : Remote Configurations Error: No Files Found"}
    
    
    
    0 total assertions
    
    --- PASS: Test_fetchApollo (0.01s)
    PASS
    ok  	config	1.537s
    

    从运行结果来看,只有第一个 method AddRemoteProvider patch成功了,后面的两个方法都没patch上,如果不patch AddRemoteProvider,将测试方法改为如下

    		
    import (
    	"errors"
    	"testing"
    
    	"github.com/agiledragon/gomonkey"
    	. "github.com/smartystreets/goconvey/convey"
    	"github.com/spf13/viper"
    )
    
    
    func Test_fetchApollo(t *testing.T) {
    
    	var viperpt *viper.Viper
    	var conf = &common.Config{}
    
    	Convey(casenameprefix, t, func() {
    		Convey(casenameprefix+"error[fetchApollo][Unmarshal]", func() {
    			//patch := gomonkey.ApplyMethod(reflect.TypeOf(viperpt), "AddRemoteProvider", func(_ *viper.Viper, provider, endpoint, path string) error {
    			//	log.Debug("ApplyMethod viper.Viper.AddRemoteProvider success")
    			//	return nil
    			//})
    			patch := gomonkey.ApplyMethod(reflect.TypeOf(viperpt), "ReadRemoteConfig", func(_ *viper.Viper) error {
    				log.Debug("ApplyMethod viper.Viper.ReadRemoteConfig success")
    				return nil
    			})
    			patch.ApplyMethod(reflect.TypeOf(viperpt), "Unmarshal", func(_ *viper.Viper, rawVal interface{}, opts ...viper.DecoderConfigOption) error {
    				log.Debug("ApplyMethod viper.Viper.Unmarshal success")
    				return errors.New("custom errors")
    			})
    			defer patch.Reset()
    
    			fetchApollo(conf)
    		})
    	})
    }
    
    === RUN   Test_fetchApollo
    
      custom: 
        custom:error[fetchApollo][Unmarshal] {"T":"2020-11-06T09:24:01.800+0800","C":"config/agollo.go:51","L":"error","timestamp":1604625841,"M":"ReadRemoteConfig err : Remote Configurations Error: No Files Found"}
    {"T":"2020-11-06T09:24:01.801+0800","C":"config/agollo.go:26","L":"error","timestamp":1604625841,"M":"get fetchApollo err : Remote Configurations Error: No Files Found"}
    
    
    
    0 total assertions
    
    --- PASS: Test_fetchApollo (0.00s)
    PASS
    ok  	config	1.300s
    

    从结果来看 ReadRemoteConfig 也没有patch成功

    想知道这个是什么原因引起的,看了 github.com/spf13/viper 包中的两个方法,没看出有什么不同

  • ApplyMethod error occurred, target type and double type are different

    ApplyMethod error occurred, target type and double type are different

    My code:

    type sampleStruct struct {
        ctx context.Context
    }
    
    func (ss *sampleStruct) Test(a int, b string) error {
        fmt.Println("this is test")
        // ....
        return nil
    }
    
    mOne := &sampleStruct{}
    
    patchMethodOne := gomonkey.ApplyMethod(reflect.TypeOf(mOne), "Test",
                        func(a int, b string) error {
        // this is patch part
        return nil
    })
    
    defer patchMethodOne.close()
    

    This code will bring out an error which indicates the following content:

    panic: target type(func(*sampleStruct, a int, b string) error) and double type(func(a int, b string) error) are different

    Is there any idea about this problem?

  • couldn't mock generic function

    couldn't mock generic function

    Does gomonkey support generics for now?

    func GeneraicMin[T int|int32](a, b T) T {
      if a <= b {
        return a
      }
      return b
    }
    
    func TestGenericFunc(t *testing.T) {
      assert := assert.New(t)
    
      patches := gomonkey.ApplyFunc(GeneraicMin[int], func(a, b int) int {
        return 100
      })
      defer patches.Reset()
    
      got := GeneraicMin(rand.Int(), rand.Int())
      assert.Equal(100, got) // would fail
    }
    
  • remove

    remove "patch has been existed" check

    Take a look at the following code

    type Repo struct {}
    
    func (r *Repo) A() error { return errors.New("A") }
    
    func (r *Repo) B() error { return errors.New("B") }
    
    type Biz struct {
    	repo *Repo
    }
    
    func (b *Biz) Do() error { 
    	if err := b.repo.A(); err != nil { return err }
    	return b.repo.B()
    }
    
    func Test() {
    	patches := gomonkey.NewPatches()
    	defer patches.Reset()
    	
    	biz := &Biz{repo: &Repo{}}
    	// common patch
    	patches.ApplyMethodReturn(biz.repo, "A", nil)	          
           
            // test a scene
    	patches.ApplyMethodReturn(biz.repo, "B", nil)
    	err := biz.Do()
            So(err, ShouldBeNil)
     
            // test b scene
            // currently it will panic "patch has been existed"
            // what I need is to override the last patch
            patches.ApplyMethodReturn(biz.repo, "B", errors.New("error"))
            err = biz.Do()
            So(err, ShouldNotBeNil)
     }
    
  • ApplyMethod invalid one case

    ApplyMethod invalid one case

    i find a question :

    type Outside struct {
      *Inner
    }
    type Inner struct {
    }
    
    func (gthis *Outside)MethodOut(){
      //...
      gthis.MethodInner()
    }
    
    func (gthis *Inner)MethodInner(){
      
    }
    

    if you

    var p *Outside
    p1 := gomonkey.ApplyMethod(reflect.TypeOf(p), "MethodInner",	func()  {
    	fmt.Printf("applymethod succ")
    	return 
    }). // it will be invalid
    

    but

    var p *Inner
    p1 := gomonkey.ApplyMethod(reflect.TypeOf(p), "MethodInner", func()  {
    	fmt.Printf("applymethod succ")
    	return 
    }). // it will be valid
    
  • Will panic if access interface method's parameters

    Will panic if access interface method's parameters

    Go Version: go version go1.18.2 darwin/amd64 MacOS version: macOS Monterey, version 12.3.1, Intel gomockey version: v2.9.0

    Sample code

    package go_play
    
    import (
    	"fmt"
    	"github.com/agiledragon/gomonkey/v2"
    	"testing"
    )
    
    type Dummy interface {
    	Get(a string) string
    }
    
    type dummy struct {
    }
    
    func (t *dummy) Get(a string) string {
    	return "prefix: " + a
    }
    
    func Test_go(t *testing.T) {
    	patches := gomonkey.NewPatches()
    	defer patches.Reset()
    	patches.ApplyMethod(&dummy{}, "Get", func(_ Dummy, in string) string {
    		fmt.Println("IN: " + in) //Panic
    		return "dummy"
    	})
    	ret := (&dummy{}).Get("hello")
    	fmt.Printf("Actual: " + ret)
    }
    
    
  • panic: permission denied;go test ./main_test.go -gcflags=all=-l

    panic: permission denied;go test ./main_test.go -gcflags=all=-l

    goroutine 4 [running]: testing.tRunner.func1.2({0x100bcf660, 0x100c89988}) /Users//.gvm/gos/go1.18.5/src/testing/testing.go:1389 +0x1c8 testing.tRunner.func1() /Users//.gvm/gos/go1.18.5/src/testing/testing.go:1392 +0x384 panic({0x100bcf660, 0x100c89988}) /Users//.gvm/gos/go1.18.5/src/runtime/panic.go:838 +0x218 github.com/agiledragon/gomonkey/v2.modifyBinary(0x18?, {0x14000018108, 0x18, 0x8?}) /Users//Documents/tap4fun/c6/gs/vendor/github.com/agiledragon/gomonkey/v2/modify_binary_darwin.go:9 +0xac github.com/agiledragon/gomonkey/v2.replace(0x100bc3c60?, 0x100be5b78?) /Users//Documents/tap4fun/c6/gs/vendor/github.com/agiledragon/gomonkey/v2/patch.go:269 +0x78 github.com/agiledragon/gomonkey/v2.(*Patches).ApplyCore(0x1400000c090, {0x100bc3c60?, 0x100be5b70?, 0x14000010480?}, {0x100bc3c60?, 0x100be5b78?, 0x100be68c8?}) /Users//Documents/tap4fun/c6/gs/vendor/github.com/agiledragon/gomonkey/v2/patch.go:213 +0x80 github.com/agiledragon/gomonkey/v2.(*Patches).ApplyFunc(0x14000098ef8?, {0x100bc3c60?, 0x100be5b70?}, {0x100bc3c60, 0x100be5b78}) /Users//Documents/tap4fun/c6/gs/vendor/github.com/agiledragon/gomonkey/v2/patch.go:83 +0x74 github.com/agiledragon/gomonkey/v2.ApplyFunc({0x100bc3c60, 0x100be5b70}, {0x100bc3c60, 0x100be5b78}) /Users//Documents/tap4fun/c6/gs/vendor/github.com/agiledragon/gomonkey/v2/patch.go:25 +0x44 command-line-arguments.TestName(0x0?) /Users//Documents/tap4fun/c6/gs/tmp/main_test.go:20 +0x48 testing.tRunner(0x14000120340, 0x100be5b80) /Users//.gvm/gos/go1.18.5/src/testing/testing.go:1439 +0x118 created by testing.(*T).Run /Users//.gvm/gos/go1.18.5/src/testing/testing.go:1486 +0x2d8 FAIL command-line-arguments 0.085s FAIL

    The test code:

    ` package main

    import ( "testing"

    "github.com/agiledragon/gomonkey/v2"
    

    )

    func TestName(t *testing.T) { Print() p := gomonkey.ApplyFunc(Print, func() { t.Logf("b") }) defer p.Reset()

    Print()
    

    }

    func Print() { }

    `

    image

  • panic: permission denied; While adding export GOARCH=amd64 debugger is  not working. Apple M1 Pro

    panic: permission denied; While adding export GOARCH=amd64 debugger is not working. Apple M1 Pro

    Screen Shot 2022-10-04 at 12 16 26 PM

    While running a test case of monkey patching I am getting the below error (go test ./...) : --- FAIL: TestInquirySuite (0.01s) --- FAIL: TestInquirySuite/TestCore_CreateInquiryController (0.00s) suite.go:77: test panicked: permission denied goroutine 13 [running]: runtime/debug.Stack() /usr/local/go/src/runtime/debug/stack.go:24 +0x68 github.com/stretchr/testify/suite.failOnPanic(0x14000682ea0, {0x1037f8d00, 0x103e5b3a8}) /Users/avinash/Desktop/Backend/quotation-service/vendor/github.com/stretchr/testify/suite/suite.go:77 +0x38 github.com/stretchr/testify/suite.Run.func1.1() /Users/avinash/Desktop/Backend/quotation-service/vendor/github.com/stretchr/testify/suite/suite.go:161 +0x1f0 panic({0x1037f8d00, 0x103e5b3a8}) /usr/local/go/src/runtime/panic.go:838 +0x204 github.com/agiledragon/gomonkey/v2.modifyBinary(0x1034d5650, {0x140001dabd0, 0x18, 0xd695af4ba0afdf5f?}) /Users/avinash/Desktop/Backend/quotation-service/vendor/github.com/agiledragon/gomonkey/v2/modify_binary_darwin.go:9 +0xa4 github.com/agiledragon/gomonkey/v2.replace(0x1034d5650, 0x140004049f0?) /Users/avinash/Desktop/Backend/quotation-service/vendor/github.com/agiledragon/gomonkey/v2/patch.go:256 +0x8c github.com/agiledragon/gomonkey/v2.(*Patches).ApplyCore(0x14000404530, {0x1037cfe80?, 0x1400019cf38?, 0x0?}, {0x1037cfe80?, 0x1038d9d18?, 0x0?}) /Users/avinash/Desktop/Backend/quotation-service/vendor/github.com/agiledragon/gomonkey/v2/patch.go:218 +0xe8 github.com/agiledragon/gomonkey/v2.(*Patches).ApplyMethod(0x0?, {0x1038bd180, 0x10388c720}, {0x10351ae49, 0x26}, {0x1037cfe80?, 0x1038d9d18?}) /Users/avinash/Desktop/Backend/quotation-service/vendor/github.com/agiledragon/gomonkey/v2/patch.go:92 +0x1c4 github.com/agiledragon/gomonkey/v2.ApplyMethod(...) /Users/avinash/Desktop/Backend/quotation-service/vendor/github.com/agiledragon/gomonkey/v2/patch.go:29 quotation-service/inquiry/channel/delphius/core.(*InquiryTestSuite).TestCore_CreateInquiryController(0x140004b9440) /Users/avinash/Desktop/Backend/quotation-service/inquiry/channel/delphius/core/core_test.go:389 +0x1ec reflect.Value.call({0x1400019e9c0?, 0x1400019c560?, 0x13?}, {0x1034f12f3, 0x4}, {0x140000dde68, 0x1, 0x1?}) /usr/local/go/src/reflect/value.go:556 +0x5e4 reflect.Value.Call({0x1400019e9c0?, 0x1400019c560?, 0x140004b9440?}, {0x1400058b668, 0x1, 0x1}) /usr/local/go/src/reflect/value.go:339 +0x98 github.com/stretchr/testify/suite.Run.func1(0x14000682ea0) /Users/avinash/Desktop/Backend/quotation-service/vendor/github.com/stretchr/testify/suite/suite.go:175 +0x3e8 testing.tRunner(0x14000682ea0, 0x140004df170) /usr/local/go/src/testing/testing.go:1439 +0x110 created by testing.(*T).Run /usr/local/go/src/testing/testing.go:1486 +0x300

    While Adding export GOARCH=amd64 Test cases are working fine but the debugger stopped working of GoLand I have tried two ways.

    1. Adding GOARCH=amd64 in the Environment Variable (RUN/DEBUG Configuration).
    2. Add GOARCH=amd64 to the path variable in the zsh file.
  • feature: replace the arguments of a function

    feature: replace the arguments of a function

    Is there a more appropriate way to change the parameters of a function?

    more complicated way....

    
    var patchReadDir = gomonkey.NewPatches()
    var readDir func(name string) ([]os.DirEntry, error)
    readDir = func(name string) ([]os.DirEntry, error) {
            patchReadDir.Reset()
            defer patchReadDir.ApplyFunc(os.ReadDir, readDir)
            return os.ReadDir(filepath.Join(c.baseDir, name))
    }
    patchReadDir.ApplyFunc(os.ReadDir, readDir)
    c.patches = append(c.patches, patchReadDir)
    
    
  • 请问如何实现一个通用的Mock函数?

    请问如何实现一个通用的Mock函数?

    在做单元测试的情况下, 会有一种需求是, 确定调用了某个函数,或者某个接口, 其它的mock框架基本都提供这种能力, gomonkey也可以, 但是需要一个一个的模拟, 有没有可能通过1.18的泛型, 写一个类似这样的Func结构 type Func[T any] struct { callCount int } 然后有这样的方法 MockFunc[T any](ps *Patches, f T) *Func[T]

    那么对于只需要知道方法有没有调用的情况下, 就可以直接用这个通用的Mock结构, 甚至更进一步, 在Func中还可以存储调用的入参, 在单测中就可以精确的知道入参了.

    我尝试着实现了一下我的想法, 但是由于我对反射理解有些欠缺, 所以没能完成这个, 你觉得这个想法能实现吗? 如果可以,能否做为feature加到gomonkey中?

gostub is a library to make stubbing in unit tests easy

gostub gostub is a library to make stubbing in unit tests easy. Getting started Import the following package: github.com/prashantv/gostub Click here t

Dec 28, 2022
How we can run unit tests in parallel mode with failpoint injection taking effect and without injection race

This is a simple demo to show how we can run unit tests in parallel mode with failpoint injection taking effect and without injection race. The basic

Oct 31, 2021
Rr-e2e-tests - Roadrunner end-to-end tests repository
Rr-e2e-tests - Roadrunner end-to-end tests repository

RoadRunner end-to-end plugins tests License: The MIT License (MIT). Please see L

Dec 15, 2022
go websocket client for unit testing of a websocket handler

wstest A websocket client for unit-testing a websocket server The gorilla organization provides full featured websocket implementation that the standa

Dec 21, 2022
Easier way to unit test terraform

Unit testing terraform (WIP) Disclaimer Currently, the only way to compare values is using JSON query path and all types are strings. want := terraf

Aug 16, 2022
A mock of Go's net package for unit/integration testing

netmock: Simulate Go network connections netmock is a Go package for simulating net connections, including delays and disconnects. This is work in pro

Oct 27, 2021
Go Unit Testing Clean Arch

Golang Unit Testing Tutorial melakukan unit testing di Golang yang sudah menerapkan clean architecture Menjalankan Service PSQL_HOST=<IP Database Serv

Feb 12, 2022
A simple yet intuitive golang unit test framework.

gotest Intuitive and simple golang testing framework which helps in writing unit tests in a way which improves the readability of the test. Here is an

Jan 1, 2022
Vault mock - Mock of Hashicorp Vault used for unit testing

vault_mock Mock of Hashicorp Vault used for unit testing Notice This is a person

Jan 19, 2022
Benchmarking deferent Fibonacci functions and algorithms with running unit test

GoFibonacciBench Benchmarking deferent Fibonacci functions and algorithms with running unit test ... Introduction: Fibonacci numbers are special kinds

Feb 27, 2022
This repository includes consumer driven contract test for provider, unit test and counter api.

This repository includes consumer driven contract test for provider, unit test and counter api.

Feb 1, 2022
Belajar golang unit test

perintah eksekusi di root folder : go test -v ./... assertion ambil dari framewo

Feb 3, 2022
Terratest is a Go library that makes it easier to write automated tests for your infrastructure code.

Terratest is a Go library that makes it easier to write automated tests for your infrastructure code. It provides a variety of helper functions and patterns for common infrastructure testing tasks,

Dec 30, 2022
A simple and expressive HTTP server mocking library for end-to-end tests in Go.

mockhttp A simple and expressive HTTP server mocking library for end-to-end tests in Go. Installation go get -d github.com/americanas-go/mockhttp Exa

Dec 19, 2021
Package for comparing Go values in tests

Package for equality of Go values This package is intended to be a more powerful and safer alternative to reflect.DeepEqual for comparing whether two

Dec 29, 2022
Extremely flexible golang deep comparison, extends the go testing package and tests HTTP APIs
Extremely flexible golang deep comparison, extends the go testing package and tests HTTP APIs

go-testdeep Extremely flexible golang deep comparison, extends the go testing package. Latest news Synopsis Description Installation Functions Availab

Dec 22, 2022
Record and replay your HTTP interactions for fast, deterministic and accurate tests

go-vcr go-vcr simplifies testing by recording your HTTP interactions and replaying them in future runs in order to provide fast, deterministic and acc

Dec 25, 2022
Go testing in the browser. Integrates with `go test`. Write behavioral tests in Go.
Go testing in the browser. Integrates with `go test`. Write behavioral tests in Go.

GoConvey is awesome Go testing Welcome to GoConvey, a yummy Go testing tool for gophers. Works with go test. Use it in the terminal or browser accordi

Dec 30, 2022
Testing framework for Go. Allows writing self-documenting tests/specifications, and executes them concurrently and safely isolated. [UNMAINTAINED]

GoSpec GoSpec is a BDD-style testing framework for the Go programming language. It allows writing self-documenting tests/specs, and executes them in p

Nov 28, 2022