I remember working in a team that used interfaces for a ton of things. Every single one of those things only ever had one concrete implementation, still to this day. A lot of those things were also purely for mocking in tests too.
Today I don’t use them, unless I need it retrospectively. Which I find is rare.
This is not a knock on interfaces as a language feature. Just inserting a random anecdote about pragmatism..
Something one often sees in Go (which is in my mind an anti-pattern) is the supplier of an object also supplying an equivalent interface, as they would in Java or another nominative adoption system. In Go, the interface belongs to the consumer!
But how do you avoid duplicating the interface in many places?
We've tried both approaches, declaring the interface in one package and importing it where used but also declaring it directly colocated with the consumer.
I've found the first approach worked better when we had to add or modify the interface methods
You don’t. Each consumer of something declares what it needs via an interface, something concrete may be passed that implements it - including fakes for tests.
Of course this doesn’t apply when using interfaces for polymorphic return types in a library, but that is a distinct use case for interfaces to address.
Perhaps some of this is just taste, but this is the way I’ve found interfaces in Go to be most useful.
So you don't test the parts of your system which need mocks to be effectively tested? How do you avoid the maintenance nightmare that causes?
I'm working on a Golang project where we've not yet introduced those kind of tests and, although it can be quicker to code initially, it's slower overall. The project relies heavily on other Open Source systems so the Golang code isn't even that complex.
I can't imagine doing something hard and keeping it working over time without being to isolate dependencies to enable testing and keep the structure clear overall.
I keep hearing this but I don't quite understand what the alternative is? I get that maybe introducing interfaces within your own business domain CAN be overused but certainly any external depedency should be fronted by an interface so the rest of the code can be tested without spinning up test http servers, database containers etc. all over the place. It just feels like a really lightweight, simnple thing to have an interface for some API that you are calling. It also means you are less depedent on the concrete implementation of the API and don't leak types and behaviour from systems you don't control into your codebase
Instead of pushing the external, side-effecting thing down the stack, I put it up the stack and call the logic that generates the data for it from there. Now I don't need to mock an interface, I just need to test data in/out.
If you do this consistently, then you have much of the side effecting code residing next to each other per process/input handler etc.
It's not always feasible or the right thing. But it's a good default way of structuring code and makes testing much more straight forward. And it makes more clear "at a glance" what happens because you can reason more locally about the side effecting parts.
> It also means you are less depedent on the concrete implementation of the API and don't leak types and behaviour from systems you don't control into your codebase
You are always dependent on that. Whether you hide it down the stack or lift it up, you still need to put your data into a specific shape, do the same essential checks etc.
> Instead of pushing the external, side-effecting thing down the stack, I put it up the stack and call the logic that generates the data for it from there.
I've never seen Go code that does anything else. That's not to say it doesn't exist, but it's seemingly not the norm amongst Go developers (probably exactly for the reasons you describe). That doesn't address what the parent is talking about, though. You have the exact same problem spoken of no matter where in the stack you put it.
This is a situation where I enjoy coding in TypeScript. You don’t need to front with interfaces, you just mock objects that map 1:1 with the expected API. So you write your code targeting the standard Fetch API then just pass in a mock when testing.
+1 to this. Especially with go, most of the (non-integration) tests I end up writing are behaviour driven tests. I'm not really sure how one can write these sort of tests without interfaces and mocks.
One could use build tags to provide alternative implementations over a concrete type, I suppose.
But like with everything in life, there is no right solution. Just different tradeoffs. If interfaces best align with the trades you are willing to make, go for it.
To be honest, if I were writing software in Go I would just test it end-to-end.
If the software's logic is too complex and fiddly to lend itself to straightforward end-to-end testing (i.e. enterprise software), I just wouldn't write it in Go. I'd choose a higher level language like Python or Java where mocking (and other kinds of dynamism) are straightforward and require no boilerplate.
I’ve just realised you understood and we’re asking present tense I think. The truth is I also do less unit testing, identifying instead where I know confidence is needed and where it’s not.
Today I don’t use them, unless I need it retrospectively. Which I find is rare.
This is not a knock on interfaces as a language feature. Just inserting a random anecdote about pragmatism..