-
Notifications
You must be signed in to change notification settings - Fork 182
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature: A method to get the original value #103
Comments
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
} // A private client type that cannot ApplyMethod func (c *client) X() int { func NewClient(x, y int) Client { //test code type FakeClient struct { func (c *FakeClient) X() int { c := &FakeClient{} |
@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 时,就没有很好的解决办法了 |
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 |
@wu-cl It has been merged, 3ks! |
very good! thank you very much |
Thanks for all your work! |
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:
The text was updated successfully, but these errors were encountered: