How do I not use an interface when mocking?

1.2k Views Asked by At

I am trying to avoid using an interface because my use cases do not require it (similarly mentioned in https://www.ardanlabs.com/blog/2016/10/avoid-interface-pollution.html). However, to create a mock (using testify), wouldn't I need an interface to mock? I could create one, but it seems tedious. All the places my code is used would require writing interfaces for their mocking, not for their actual usage. Is there a workaround?

3

There are 3 best solutions below

1
On BEST ANSWER

If the only possible use of an interface is for testing, I agree, that's probably a bad interface, and you should avoid that.

The best way is to refactor your system to rely on a small, useful interfaces rather than one-off "mocks" of over-complicated structs. A very good example of this is the net.Listener interface. A system built on top of net.Listener is easy to mock, yes, but that's not why you implement net.Listener. You use the interface because it lets you swap in many possible implementations, one of which happens to be for testing.

Another powerful approach is chain together functions rather than hard-coding functionality as methods. http.HandlerFunc is a great example of this that also demonstrates great interface design. See the many "click-together" functions that return a http.Handler, rather than one, massive "Handler" struct that you'd then have to mock for testing. This is Go at its best IMO.

Remembering that functions are first-class in Go, you can get a lot of flexibility by passing and returning functions rather than tying them to interfaces. When you do that, you also can bundle those together as a struct. This can give a lot of power that also happens to be useful for testing. For example see, tls.Config which allows you to include your own GetCertificate function (among others). But the tls.Config struct also has sensible defaults so you don't have to configure every piece every time. By passing a specialized version of tls.Config, you can test TLS functionality without "mocking" anything.

The consistent theme here is to make your system flexible about its implementations, and as a nice side-effect, that makes testing easier. In my experience, this is a much better way to think about the problem than mocking.

0
On

To reduce the amount of repetition with mocking larger interfaces, it appears one could use something like vectra/mockery to generate the mocks. Not a solution to removing the need for an interface, however, but I think Rob mentions some good ideas for that below.

1
On

You need an interface to mock because the mock is your second implementation, so if you're using mocks in unit tests, then your use case does require it. The interface serves as a placeholder to be filled with some implementation of that functionality. You're planning to have at least two such implementations: one mock, one production. In order to be able to use them interchangeably for testing, you must use an interface.