I think the focus on mocking specifically is not quite right. I would phrase this differently:
Own Your Interfaces
Major functionality should always go through interfaces that you own, even if they’re transparently passing arguments through to a third party. The more complex the interface, the more important this becomes.
When you inevitably need to disentangle yourself from this third party, you will be very glad that you already have a seam for refactoring. This also has major testing benefits, but I think the upside goes far beyond just testing.
I think it’s a balance. Sometimes it’s just YAGNI to own an interface that isn’t going to change and you already have a testing solution for. If adding the interface in later won’t take significantly longer than adding it at creation time, you should wait until you need it.
Waiting until you need it is too late. That is precisely how the problem happens.
I’ll give you a concrete example of what I mean. The ActiveRecord API should never be allowed inside a Controller, or a background worker, or anything that isn’t specifically the data layer. This is basically heresy compared to every Rails guide in existence, but in fourteen years of Ruby I have never seen this not be a disaster.
The AR API is so big, and so complex, that it is impossible to mock effectively. So everyone just resorts to factories or fixtures. This means that you have to do a tremendous amount of data setup to test even the simplest things, and frequently the complexity of the data is strictly unnecessary for the test.
This causes a serious performance problem in your test suite. There is no locus of performance problem, it’s death by a thousand papercuts. You have a painfully slow suite and reversing the problem is basically a full rewrite.
Taking ownership of your interfaces, treating AR like a private API, prevents this from happening.
This also has major testing benefits, but I think the upside goes far beyond just testing.
This is precisely the reason for the rule.
The testing benefits and the design benefits are two sides of the same coin, different lenses to view the same picture. The mocking perspective is just a simple rule that makes the design problem obvious when you’re testing.
I’m the author of the post and I’m trying to interpret your comment as (indirect) feedback. My intention was to take the old principle, add some nuance and come out with your conclusion. Concretely with the both bolded “Key point:” and “Corollary:”. Did I fail at that, or did you just want to rephrase/summarize?
I’ve learned (and re-learned) this lesson the hard way. In my case, it’s frequently tempting to mock things that haven’t been built yet. My hope was that the mocks would become a tacit contract. But it never works out that way. Better to draw up a formal schema with the API developer or just wait until they’re done.
There’s only one exception I’ve found to the rule in the title and it’s precisely the one the author describes:
Every rule and principle can be broken once you’ve fully understood its purpose. For example if an object already does have an idiomatic API, it’s probably not worth wrapping in an identical façade, just so it belongs to you.
Some APIs are simple, stable, and unlikely to change. In such cases, I like to take a representative sample of its output and use that as mocks. The mocks will of course break if the API introduces breaking changes, but in a way, these samples-as-mocks are also a kind of canary in the coal mine, which I’ve found helpful.
Thanks for this. The video (2012) linked at the end was also super helpful. As someone that’s been working on an older go project for a short time, I do feel there’s mocks/fakes/whathaveyou that maybe shouldn’t have been written and went looking for some content akin to this but wasn’t coming up with much.Thanks again.
I think the focus on mocking specifically is not quite right. I would phrase this differently:
Own Your Interfaces
Major functionality should always go through interfaces that you own, even if they’re transparently passing arguments through to a third party. The more complex the interface, the more important this becomes.
When you inevitably need to disentangle yourself from this third party, you will be very glad that you already have a seam for refactoring. This also has major testing benefits, but I think the upside goes far beyond just testing.
I think it’s a balance. Sometimes it’s just YAGNI to own an interface that isn’t going to change and you already have a testing solution for. If adding the interface in later won’t take significantly longer than adding it at creation time, you should wait until you need it.
Waiting until you need it is too late. That is precisely how the problem happens.
I’ll give you a concrete example of what I mean. The ActiveRecord API should never be allowed inside a Controller, or a background worker, or anything that isn’t specifically the data layer. This is basically heresy compared to every Rails guide in existence, but in fourteen years of Ruby I have never seen this not be a disaster.
The AR API is so big, and so complex, that it is impossible to mock effectively. So everyone just resorts to factories or fixtures. This means that you have to do a tremendous amount of data setup to test even the simplest things, and frequently the complexity of the data is strictly unnecessary for the test.
This causes a serious performance problem in your test suite. There is no locus of performance problem, it’s death by a thousand papercuts. You have a painfully slow suite and reversing the problem is basically a full rewrite.
Taking ownership of your interfaces, treating AR like a private API, prevents this from happening.
This is precisely the reason for the rule.
The testing benefits and the design benefits are two sides of the same coin, different lenses to view the same picture. The mocking perspective is just a simple rule that makes the design problem obvious when you’re testing.
Michael Feathers - the deep synergy between testability and good design
With that said, I do like your phrasing.
I’m the author of the post and I’m trying to interpret your comment as (indirect) feedback. My intention was to take the old principle, add some nuance and come out with your conclusion. Concretely with the both bolded “Key point:” and “Corollary:”. Did I fail at that, or did you just want to rephrase/summarize?
I have no issues with the substance, I just think it’s worth calling out that the benefits of this go far beyond just testing.
I got that; I’m just frustrated I failed at conveying that. I’ve added another highlight box spelling it out – sometimes I’m too subtle I guess. :/
I’ve learned (and re-learned) this lesson the hard way. In my case, it’s frequently tempting to mock things that haven’t been built yet. My hope was that the mocks would become a tacit contract. But it never works out that way. Better to draw up a formal schema with the API developer or just wait until they’re done.
There’s only one exception I’ve found to the rule in the title and it’s precisely the one the author describes:
Some APIs are simple, stable, and unlikely to change. In such cases, I like to take a representative sample of its output and use that as mocks. The mocks will of course break if the API introduces breaking changes, but in a way, these samples-as-mocks are also a kind of canary in the coal mine, which I’ve found helpful.
Thanks for this. The video (2012) linked at the end was also super helpful. As someone that’s been working on an older go project for a short time, I do feel there’s mocks/fakes/whathaveyou that maybe shouldn’t have been written and went looking for some content akin to this but wasn’t coming up with much.Thanks again.