qur / withmock

Automatic Go package mock generation tool
Other
71 stars 9 forks source link

Returning pointer from mocked function fails #43

Closed ereyes01 closed 9 years ago

ereyes01 commented 9 years ago

Disclaimer: I'm new to Go, and it's certainly possible this is PEBKAC, but I can't tell that it is...

Given the following code:

mockexec.go:

package mockexec

import (
    "fmt"
    "os/exec"
)

func DoCommand() {
    command := exec.Command("/bin/true")
    fmt.Println(command)
}

mockexec_test.go:

package mockexec

import (
    "os/exec"
    mockexec "os/exec" // mock
    "testing"

    "code.google.com/p/gomock/gomock"
)

func TestCommand(t *testing.T) {
    mockCtrl := gomock.NewController(t)
    defer mockCtrl.Finish()
    mockexec.MOCK().SetController(mockCtrl)

    mockexec.EXPECT().Command("/bin/true").Return(new(exec.Cmd))
    DoCommand()
}

I get the following when I try to run the tests:

$ withmock go test
--- FAIL: TestCommand (0.00 seconds)
    call.go:83: wrong type of argument 0 to Return for *exec._packageMock.Command: *exec.Cmd is not assignable to *exec.Cmd
    controller.go:158: missing call(s) to *exec._packageMock.Command(is equal to /bin/true)
    controller.go:165: aborting test due to missing call(s)
FAIL
exit status 1
FAIL    mockexec    0.002s

I would think a *exec.Cmd should be assignable to a *exec.Cmd under normal circumstances :-)

Am I doing something wrong? Any insight would be appreciated. Thanks.

qur commented 9 years ago

Due to the way that stdlib packages are mocked mockexec and exec are different packages. You can't mix types between them, even though the types are basically the same (i.e. the type declaration is copied) between the two packages because they are distinct types.

You need to ".Return(new(mockexec.Cmd))" instead so that the types match.

As far as the slightly confusing message goes, the mocked package naturally uses the same package name, so you end up with two "*exec.Cmd" types, but they are from different packages that have the same name.

ereyes01 commented 9 years ago

Thanks, your suggestion worked. That was a pretty subtle issue (for me), mostly due to the misleading error message.

I think I was getting a bit confused by thinking that my tested code's command variable was getting compiled with type *exec.Cmd. That's what led me to believe that I needed to have access to the real os/exec package from the test code- so that I can return an actual *exec.Cmd to the caller.

I now understand, thanks to your explanation, that the entire os/exec package is replaced in the tested code as well. Therefore, the following simplified version of the test works as well, with no local mockexec name needed:

package mockexec

import (
    "os/exec" // mock
    "testing"

    "code.google.com/p/gomock/gomock"
)

func TestCommand(t *testing.T) {
    mockCtrl := gomock.NewController(t)
    defer mockCtrl.Finish()
    exec.MOCK().SetController(mockCtrl)

    exec.EXPECT().Command("/bin/true").Return(new(exec.Cmd))
    DoCommand()
}

It might be helpful to have some examples of mocked stdlib packages in the docs, though I'm not sure if I would've still run into the same problem later. Sometimes the best school is the school of hard knocks :-)

Thanks again for your help.