Question

I want to implement Go interfaces with reflection to generate mocks and stubs. But if I look at the reflect package, I get no idea how to do it (maybe it is not possible).

Example: Testing that a func calls WriteHeader(404) on ResponseWriter:

type ResponseWriterMock struct {                              //
  status int                                                  //
}                                                             // How to replace
func (*ResponseWriterMock) Header() Header {}                 // this block with 
func (*ResponseWriterMock) Write([]byte) (i int, e error) {}  // a reflectivly 
func (m *ResponseWriterMock) WriteHeader(status int) {        // generated mock? 
  m.status = status                                           //  
}                                                             //
responseWriterMock := new(ResponseWriterMock)

funcToTest(responseWriterMock)

if responseWriterMock.status != 404 {
    // report error
}

With RhinoMocks (C#) I would write it like this:

var responseWriterMock = MockRepository.GenerateMock<ResponseWriter>();

funcToTest(responseWriterMock);

responseWriterMock.AssertWasCalled(rw => rw.WriteHeader(404));

How can I implement Go interfaces with reflection?

Addendum

Seems like it is not possible today.

Was it helpful?

Solution

To the best of my knowledge and the answers of this related question, it is not possible to create new types at runtime.

You may want to try the go-eval package which should support defining new types in it's universe.

OTHER TIPS

Unfortunately, due to the static nature of the language, the reflection package is a bit limited and there is no way to create dynamic objects at runtime. It might never be possible.

I can propose a few solutions - all involving the creation of any stubs/mocks at design time.

  1. Write your mocks manually.

    I've seen people go down that road and it quickly becomes unbearable once the codebase becomes larger.

    In general, the idea is to write a structure that implements your interface. In each method you add code that increments some counter (in order to track invocations) and often returns a value that has been hardcoded or provided as a configuration to the structure.

  2. Use the testify toolkit.

    At the time of writing, this appears to be the most popular project out there providing mocking support. However, since the project provides other features, it is questionable how many of the likes are due to the mocking capability itself.

    I have not used this toolkit and cannot provide detailed analysis as to whether it is any good.

    Looking at the README, it appears that it takes an approach very similar to the manual mocking one (explained above). It provides some helper code on top but the fake methods still need to be typed manually. Stubbing in the tests is then done by specifying the method via a string, which is going against the static nature of the Go language and might be a problem for some users.

  3. Use the official mock tool from Go.

    This tool seems to have gained popularity since I last wrote about it. Furthermore, as on official Go project, one can expect that this tool will quickly adopt any new Go features. The support it has for go modules is a good example, where other tools lag behind.

    It does take a specific approach to mocking. You need to specify your test expectations at the beginning of the test code. At the end of the test, the framework checks whether all expectations have been met. Unexpected calls cause the framework to fail the test.

    If you are using a custom testing framework like Ginkgo, it might be tricky to cleanly integrate. In my projects, I have written helper functions to bridge the gap.

    While it does generate the fake methods for you, the assertion and expectation methods it generates take interface{} types. This allows the framework to provide more advanced argument matchers but also means that you need to ensure you pass the correct number of arguments and in the correct order.

    In my experience, tests written with this tool tend to be bigger in side and at times more complicated to read. On the plus side, they often test things more thoroughly and are more expressive.

  4. Use the counterfeiter tool.

    This tool has been used a lot in the Cloud Foundry ecosystem and has proven itself as a viable option.

    It allows you to track the number of times a given method has been called, the arguments that were used to call the method, and to fake either the whole method with a custom implementation or specify the result values to be returned.

    What makes it a good choice is that it produces very explicit fakes. Developers don't need to use string names to specify a method, nor do they need to remember the order and number of arguments. The generated helper methods mimic closely the original ones, with arguments of the correct type and order.


Some of the options I provided above require that you run a command line tool to generate the source code for your mocks. As the number of interfaces that need to be mocked can quickly grow, there is a cool trick you could use to keep track of everything.

You can take benefit of the go:generate functionality in Go to easily generate all your stubs/fakes, without having to recall any command parameters or to manually run the tool for each interface.

All you need to do is to add the proper go:generate comment in the file that contains your interface.

mock

//go:generate mockgen -source person.go

type Person interface {
  Name() string
  Age() int
}

counterfeiter

//go:generate counterfeiter ./ Person

type Person interface {
  Name() string
  Age() int
}

You can then run the go generate ./... command in the root of your project and all the stubs/fakes will be regenerated for you. This can be a lifesaver if you are working on a project with multiple collaborators.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top