如何使用反射实现或模拟Go接口?

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.

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.

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 code base becomes larger.

  2. Use the official mock tool from Go.

    I've had very limited experience with this framework. What I did not like about it is the fact that it forces you to use their way of doing assertions. I personally like to us the Gomega matcher library and prefer to have it raise and report any errors.

    Additionally (at the time of writing), it did not handle sub-interface scenarios properly. (Where you have an interface nested inside another interface and they come from different packages). You would have to explicitly declare the package of the nested interface at generation time.

  3. Use the counterfeiter tool.

    I've played around a lot with this tool and I can say it has proven itself in some larger projects.

    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.

    That said, I have also found some bugs in the tool which remain open to this day. For one of the bugs (sub-interface scenario) it might need a major redesign to solve.

  4. Use the gostub tool.

    This is a tool that I have written which is heavily influenced by the counterfeiter tool. In fact, through the usage of command line flags, you should be able to generate matching fakes.

    I have also solved some of the bugs and issues that the counterfeiter tool has, by writing the tool from the ground up.

    That said, aside from the suite of unit and acceptance tests that I have written, the tool has barely any mileage. It is just as possible that I have introduced other bugs and limitations.

There is a really cool way through which you could use the last three options I have given. You can take benefit of the go:generate functionality in Go to easily generate your stubs/fakes without having to manually execute 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.

gostub

//go:generate gostub Person

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.