Open Nomango opened 1 year ago
it's a good proposal
What is your business secnario? I haven't get your problem yet.
@agiledragon An example I find difficult to implement
// An interface with many methods
type Client interface {
X() int
// ...
}
// A private client type that cannot ApplyMethod
type client struct {
x, y int
}
func (c *client) X() int {
return c.x
}
func NewClient(x, y int) Client {
return &client{x: x, y: y}
}
gomonkey.ApplyFunc(NewClient, func(x, y int) Client {
return NewClient(1, y) // want a fixed x, using original NewClient
})
And my actual scenario if you need
// Expected usage for my utility package mockredis
patch := mockredis.Patch{
Target: redis.NewClient,
GenerateDouble: func(mockedCli *mockredis.Client) interface{} {
return func(opts ...redis.Option) *redis.Client {
opts = append(opts, redis.WithAddr(mockedCli.Addr)) // want a fixed addr, using original redis.NewClient
return redis.NewClient(opts)
}
},
}
reset := mockredis.Init()
defer reset()
// package mockredis
var mockedCli *Client
func Init(patches ...Patch) context.CancelFunc {
once.Do(func() {
mockedCli = NewXXX()
})
monkeyPatches := gomonkey.NewPatches()
for _, p := range patches {
monkeyPatches.ApplyFunc(p.Target, p.GenerateDouble(mockedCli))
}
// ...
}
Version without original value
patch1 := mockredis.Patch{
Target: redis.NewClient,
CreateInstance: func(mockedCli *mockredis.Client) (returns []interface{}) {
cli := redis.NewClient(redis.WithAddr(mockedCli.Addr)) // we lost option parameters
return []interface{}{cli}
},
}
patch2 := mockredis.Patch{
Target: redis.NewFailoverClient, // Another func with same implementation. Although we won't use it in practice, the instance will still be created
CreateInstance: func(mockedCli *mockredis.Client) (returns []interface{}) {
cli := redis.NewClient(redis.WithAddr(mockedCli.Addr))
return []interface{}{cli}
},
}
reset := mockredis.Init(patch1, patch2)
defer reset()
// package mockredis
var mockedCli *Client
func Init(patches ...Patch) context.CancelFunc {
once.Do(func() {
mockedCli = NewXXX()
})
monkeyPatches := gomonkey.NewPatches()
for _, p := range patches {
returns := p.CreateInstance(mockedCli) // create instances, whatever need or not
monkeyPatches.ApplyFuncReturn(p.Target, returns...)
}
// ...
}
// An interface with many methods type Client interface { X() int
// ...
}
// A private client type that cannot ApplyMethod type client struct { x, y int }
func (c *client) X() int { return c.x }
func NewClient(x, y int) Client { return &client{x: x, y: y} }
//test code
type FakeClient struct { X, Y int }
func (c *FakeClient) X() int { return c.X }
c := &FakeClient{} patches := ApplyFunc(NewClient, func(x, y int) Client { return &FakeClient(X:1, Y: y) }) defer patches.Reset() patches.ApplyMethod(c, "X", func(_ *Client) int { return 2 }) ....
@agiledragon 直接中文沟通吧,这个方法不行,因为FakeClient只是 mock 了一个X()方法,实际 client 是有很多参数和方法的,而且这个 fixed x 并不会生效,因为整个 Client 都是 fake 的,就算FakeClient里面放一个真的client进去,fixed x 也是没办法生效的
比如
type client struct {
x int
}
func (c *client) A() {
// do something with c.x
}
func (c *client) B() {
// do something with c.x
}
// and method C、D、E...
// 如何实现 FakeClient?
当 NewClient 返回的是个interface(没办法mock method),且有一个希望固定的入参 x 和一个透传的入参 y 时,就没有很好的解决办法了
What is your business secnario? I haven't get your problem yet.
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.valueHolders[double] = double
return this
}
I think this feature is aslo very useful in unittests. For example, I need to make sure that a function call another function with correct arguments, but I want to keep calling the original callee.
Here is a testcase to show what I need.
Convey("one func call origin", func() {
var patches *Patches
patches = ApplyFunc(fmt.Sprintf, func(format string, a ...interface{}) string {
patches.Reset()
So(format, ShouldEqual, "%s")
return fmt.Sprintf(format, a...)
})
output := fmt.Sprintf("%s", "foobar")
So(output, ShouldEqual, "foobar")
})
I want to call the original fmt.Sprintf without calling patches.Reset().
As a solution for this feature, I introduce a package similar to gomonkey: bytedance/mockey.
Quick example:
origin := Fun
mock := mockey.Mock(Fun).
Origin(&origin).
To(func(p string) string {
return origin(p + "mocked")
}).
Build()
defer mock.UnPatch()
Welcome to submit PR @Nomango
@agiledragon #166
@wu-cl It has been merged, 3ks!
@Nomango You can check #166
@agiledragon #166
very good! thank you very much
Example
Calling NewClient to create a instance before patch may solve this problem, but it is not convenient to do so in my scenario.
So I would like to use a GetOriginal method to do this, like: