agiledragon / gomonkey

gomonkey is a library to make monkey patching in unit tests easy
MIT License
2k stars 179 forks source link

对接口方法mock报错 call of reflect.flag.mustBeExported on zero Value #18

Closed chengdanhao closed 4 years ago

chengdanhao commented 4 years ago
type Config interface {
        Load() error
        Reload()
        Get(string, interface{}) interface{}
        Unmarshal(interface{}) error
        IsSet(string) bool
        GetInt(string, int) int
        GetInt32(string, int32) int32
        GetFloat64(string, float64) float64
        GetString(string, string) string
        GetBool(string, bool) bool
}

var myConf config.Config

func foo() bool {
    myConf.GetString("test", "1234")
    return true
}

func main() {
    // init myConf
}

对myConf.GetString mock,报错。mock

type mockConf struct{ test int }

func (conf mockConf) Load() error                         { panic("implement me") }
func (conf mockConf) Reload()                             { panic("implement me") }
func (conf mockConf) Get(string, interface{}) interface{} { panic("implement me") }
func (conf mockConf) Unmarshal(interface{}) error         { panic("implement me") }
func (conf mockConf) IsSet(string) bool                   { panic("implement me") }
func (conf mockConf) GetInt(string, int) int              { panic("implement me") }
func (conf mockConf) GetInt32(string, int32) int32        { panic("implement me") }
func (conf mockConf) GetFloat64(string, float64) float64  { panic("implement me") }
func (conf mockConf) GetString(string, string) string     { panic("implement me") }
func (conf mockConf) GetBool(string, bool) bool           { panic("implement me") }

func TestUnit_foo(t *testing.T) {
    mConf := mockConf{test: 231}

    p := gomonkey.ApplyGlobalVar(&myConf, mConf)
    defer p.Reset()  // !!! 注释这一行就正常,不注释报错

    convey.Convey(" case 1", t, func() {
        patch := gomonkey.ApplyMethod(reflect.TypeOf(mConf), "GetString",
            func(_ mockConf, _ string, _ string) string {
                return "hahaha"
            })
        defer patch.Reset()

        result, _ := foo()
        convey.So(result, convey.ShouldBeTrue)
    })
}

报错信息为

2020/04/10 16:25:08 maxprocs: Leaving GOMAXPROCS=28: CPU quota undefined
=== RUN   TestUnit_foo

1 total assertion

--- FAIL: TestUnit_foo (0.00s)
panic: reflect: call of reflect.flag.mustBeExported on zero Value [recovered]
        panic: reflect: call of reflect.flag.mustBeExported on zero Value

goroutine 42 [running]:
testing.tRunner.func1.1(0xcab700, 0xc000392720)
        /home/test/usrpkg/goroot/src/testing/testing.go:941 +0x3d0
testing.tRunner.func1(0xc000394480)
        /home/test/usrpkg/goroot/src/testing/testing.go:944 +0x3f9
panic(0xcab700, 0xc000392720)
        /home/test/usrpkg/goroot/src/runtime/panic.go:967 +0x15d
reflect.flag.mustBeExportedSlow(0x0)
        /home/test/usrpkg/goroot/src/reflect/value.go:222 +0xad
reflect.flag.mustBeExported(...)
        /home/test/usrpkg/goroot/src/reflect/value.go:216
reflect.Value.Set(0xd43a40, 0x17b1fb0, 0x194, 0x0, 0x0, 0x0)
        /home/test/usrpkg/goroot/src/reflect/value.go:1527 +0x56
github.com/agiledragon/gomonkey.(*Patches).Reset(0xc000388600)
        /home/test/usrpkg/gopath/pkg/mod/github.com/agiledragon/gomonkey@v0.0.0-20190517145658-8fa491f7b918/patch.go:131 +0x20d
mygit.com/foo.TestUnit_foo.func1(0xc000394480, 0xc0003842e8, 0xc000388600)
        /mnt/d/foo/foo_test.go:217 +0xab
mygit.com/foo.TestUnit_foo(0xc000394480)
        /mnt/d/foo/foo_test.go:232 +0x432
testing.tRunner(0xc000394480, 0xe494d8)
        /home/test/usrpkg/goroot/src/testing/testing.go:992 +0xdc
created by testing.(*T).Run
        /home/test/usrpkg/goroot/src/testing/testing.go:1043 +0x357
FAIL    mygit.com/foo  0.042s

FAIL
agiledragon commented 4 years ago
  1. 打桩的全局变量没有赋初值,这个是在Reset阶段触发panic的根本原因
  2. 优化建议 2.1 在一个测试中没有必要生成多个patches对象,一个patches对象足矣,其他打桩使用patches对象来操作,这就对应一次Reset 2.2 对于接口的打桩,可以参考apply_interface_reused_test.go,先对工厂函数通过ApplyFunc来打桩,然后再对类的方法通过patches.ApplyMethod来打桩
chengdanhao commented 4 years ago

懂了,谢谢解答。