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?
How do I not use an interface when mocking?
1.2k Views Asked by imagineerThat At
3
There are 3 best solutions below
0

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

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.
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 ofnet.Listener
is easy to mock, yes, but that's not why you implementnet.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 ahttp.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 ownGetCertificate
function (among others). But thetls.Config
struct also has sensible defaults so you don't have to configure every piece every time. By passing a specialized version oftls.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.