petergtz / pegomock

Pegomock is a powerful, yet simple mocking framework for the Go programming language
Apache License 2.0
253 stars 28 forks source link

Params passed by reference not updated for mocked responses #73

Closed jpopadak closed 5 years ago

jpopadak commented 5 years ago

Writing a When().Then() response allows for a user to modify the response object based upon input parameters. However, when updating the input params []pegomock.Param objects does not get reflected in the response.

Example: Create an interface like the following:

type Inter interface {
   func DoStuff(param1 interface{}) bool
}

Run pegomock to generate a mocked object. Call DoStuff() with an address of a slice of structs.

var input = []Entity{}
mock.DoStuff(&input)

In the Then response, update param[0] itself by either appending or setting the value to a new slice.

Doing this all updates the params object and sends it back to the MockInter mocked object code. In the mocked method DoStuff(), the params object (the one with the combined input params) has the updated data. However, the _param0 object is not updated.

Due to this, one cannot successfully mock what a method does as one can pass a reference to a slice inside of an empty interface interface{} type.

I do not know the best way to solve this or how Go would deal with this.

jpopadak commented 5 years ago

It turns out setting the values of params to each individual value of _paramX works properly and successfully propagates the value up (if the value is passed by pointer).

Here is an example that works properly:

func (mock *MockDatabase) GetSingle(_param0 interface{}, _param1 interface{}) serviceerror.ServiceError {
    if mock == nil {
        panic("mock must not be nil. Use myMock := NewMockDatabase().")
    }
    params := []pegomock.Param{_param0, _param1}
    result := pegomock.GetGenericMockFrom(mock).Invoke("GetSingle", params, []reflect.Type{reflect.TypeOf((*serviceerror.ServiceError)(nil)).Elem()})
    var ret0 serviceerror.ServiceError
    if len(result) != 0 {
        _param0 = params[0]
        _param1 = params[1]
        if result[0] != nil {
            ret0 = result[0].(serviceerror.ServiceError)
        }
    }
    return ret0
}
petergtz commented 5 years ago

Hi @jpopadak, thanks for reporting this. It's an interesting use case I haven't thought of (not using out args very often). So yes, I think we can treat this as a bug.

2 Things:

jpopadak commented 5 years ago

@petergtz - So after going through this more and debugging it all through, it turns out it is not a bug in the library, but a bug in my code. 👎

If passed by a pointer, or passing an interface type to the empty interface, it successfully updates the value if you are operating directly on it instead of setting the param itself like this:

This does not work:

Whenever(database.GetSingle(argmatchers.AnyType(), argmatchers.AnyType())).Then(func(params []pegomock.Param) pegomock.ReturnValues {
         param[0] = &group.Entity{}
         param[1] = &group.Entity{}
    return pegomock.ReturnValues{nil}
})

This works because we are modifying the object via a pointer:

Whenever(database.GetSingle(argmatchers.AnyType(), argmatchers.AnyType())).Then(func(params []pegomock.Param) pegomock.ReturnValues {
    where := params[0].(*group.Entity)
    entity := params[1].(*group.Entity)
    entity.UUID = where.UUID
    entity.Description = description
    entity.ResourceGroupTypeUUID = groupTypeUuid
    entity.CreatedAt = time.Now()
    entity.UpdatedAt = time.Now()
    return pegomock.ReturnValues{nil}
})

This works because we are copying an empty object into the already created one:

Whenever(database.GetSingle(argmatchers.AnyType(), argmatchers.AnyType())).Then(func(params []pegomock.Param) pegomock.ReturnValues {
         zero := params[0].(*group.Entity)
         *zero = group.Entity{}

         one := params[1].(*group.Entity)
         *one = group.Entity{}
    return pegomock.ReturnValues{nil}
})
petergtz commented 5 years ago

Okay, cool. Yea, I just confirmed this as well :-D :

type Entity struct{ i int }
var input = []Entity{}
When(func() { display.InterfaceParam(AnyInterface()) }).Then(func(params []pegomock.Param) pegomock.ReturnValues {
    *params[0].(*[]Entity) = append(*params[0].(*[]Entity), Entity{3})
    return nil
})
fmt.Printf("%#v\n", input)
display.InterfaceParam(&input)
fmt.Printf("%#v\n", input)

prints:

[]pegomock_test.Entity{}
[]pegomock_test.Entity{pegomock_test.Entity{i:3}}

So I guess this can be closed, can it?