The Dogma of Mocks and Protocols
This post is a personal opinionated piece around unit testing and the dogmas of our industry. Be sure to come here with an open mind and respectul toughts. I will also link to some Pointfree content so make sure you check them out.
The abstraction dogma
We all know that unit testing the logic of your code is important. Over the years the industry has created tons of material explaining how abstracting your types with protocols (a.k.a. interfaces) is the best way to accomplish maintainable and testable code.
Although some of it is objectively true. There is a lot of dogma and close mindedness in a lot of the narrative surrounding tests and architecture. I've personally always been in two minds on this topic (and many others, which is hard in today's extremist world).
Yes, I do love a good unit testing suite but no, I don't want to force my code into a complex net of systems just to make unit testing easier.
Many will argue that unit testing is a tool that helps shaping and designing good code, and again, I agree partially. When they teach you how to do unit testing and use interfaces they often forget to tell you that every interface you add to your code increases its complexity. There is more stuff to keep in your head, and more files you need to click trough to finally read the code that really matters.
The more code I need to maintain the more I appreciate simplicity. As a young engineer I bought into the OOP dogma and tended to implement future proof systems with protocols and abstractions all over the place. I was so naive.
We've seen this attitude get into the mindset of iOS developers with things like VIPER that are so overly complicated that everybody runs away just by hearing its name. And again, I'm pretty sure there are places where deploying a system like this is worth the complexity that it adds.
Personally, nowadays, if I can use a concrete type and avoid having protocols all over the place I'm a much happier programmer, and so is the rest of the team.
The useless abstraction
Abstractions are good when they are good abstractions. When they create a new language that helps us talk in a higher level way about a problem. In those occasions a protocol becomes a good abstraction, when it emerges from the design of a solution to a problem. But that's not what protocols required by unit testing are. And in Swift we tend to make a lot of those since runtime hackery is not at the level that we need to fully mock all objects.
That's why I'm so happy to see an impactful site like Pointfree talk about this. I'm not gonna put words on their mouth, maybe I interpreted too much on their words, but their current series of episodes felt like a liberating chant. Finally!
But, just because it’s popular doesn’t mean it can’t be improved. - PointFree about protocol-oriented problems.
The problem with the protocols that show up from unit testing is that they are only necessary for unit testing. If you have a very complex piece of software that require multiple implementations that can be used trough a single interface, then yes, by all means, use protocols!
I'm gonna leave here a quote from Pointfree's video about dependencies. It summaries very well the thoughts that I've been having for years but that are so hard to merry with the dogma that permeates our industry.
There is something a little strange about what we have come up with so far. We created a protocol to abstract away the interface of getting weather results, but only created two conformances: a live one that makes an API request and a mock one that synchronously returns mocked data. - PointFree about protocol-oriented problems.
It has always smelled badly to me the fact that we're so happy just making a protocol to make unit testing happy.
A protocol that only has two conformances is not a strong form of abstraction. There is not a single protocol that Apple gives us that is meant to only have two conformances. For example, the
Collectionprotocols have dozens of conformances. - PointFree about protocol-oriented problems.
When reality strikes again
But of course, as with many topics that I talk about in this blog, reality is here to stay. It's very likely that you are working in a codebase that requires this unit testing protocols because there is no other way of doing it. Even if adopting some of Pointfree's ideas wouldn't take too long we already have work to do and we can't stop the printing press just for this.
So here's my tip, something that I've been doing recently.
Embrace that those protocols must exist and accept that, on their own, they don't add anything valuable to your codebase. They are just there for unit testing. They are just there to create boilerplate. And what do we do with boilerplate? We summon a sorcerer!
I used Sourcery to generate all the boilerplate necessary to have nice unit tests.
// sourcery: AutoProtocol, AutoMockable final class AuthenticationDataManager
Just with that annotation you get a protocol that reflects the public interface of the type and a mock implementation with a lot of helpers to know what method has been called, to fake return values, etc.
First because AutoInterface naming is too close to Java for my taste. I don't want to see IType in a Swift codebase. We prefer to use
Protocol as a suffix.
And second, because these templates are thought to be used individually. You can get a protocol from a class. And you can generate a mock from a protocol. But what I wanted is that from a concrete type that I write, to first get a protocol for it, and from that protocol get a mock. This is not hard to do but it requires modifications and some duplication in the templates.
This is not perfect. You still need to be aware of the existence of the protocol because that's what you need to use on your consuming types. But at least you don't have to write the boilerplate and it makes sure that everything is always in sync.
I leave the templates that I'm using in this Gist.
As usually, just have an open mind and don't follow dogmas blindly. Just with that you will be a much happier programmer, and human being ^^.
Protocols are nice and powerful but don't over use them. Especially if they don't add any value as an abstraction.
And finally check out Pointfree. I'm in no way affiliated to them (apart from that link being an affiliate link LUL) but their content is so good that I can't miss any opportunity to recommend it.
Thanks for reading.