Rubberducking: More On Mocking
L: I've got it!
🦆: Got what?
L: I've finally figured out why mocking in unit tests sucks.
🦆: Oh?
L: Yeah. It seems obvious, but the issue is that it requires one to assume that you care about the outcome of all dependent methods a service needs to call.
🦆: Could you explain?
L: So lets say you start off with a service method that simply needs to write to a database. You write the unit-test out and mock the database calls, simulating both the happy path and the error cases. Easy, right?
🦆: Yeah, I'd imagine that would be pretty straightforward.
L: Then years go by, and now you need this service method to write a message to a queue. So you add unit-test to verify that that works correctly, both the happy path and the error cases.
🦆: Okay.
L: But you still need to mock out the database call.
🦆: Yeah, so?
L: But those calls add noise to your unit-tests. You now have mock setup code in unit-test that have nothing to do with what you're trying to verify. The database needs to be called before you send the message to the queue, so you need to set those mocks up in your unit-tests about the queue.
🦆: But it's just a call to the database. Is that so much more?
L: Yes, but you're not thinking through the long term it takes to maintain this service. Let's say in 6 months you need to add two more dependent calls. Now that's three additional things you need to setup for every unit-test your writing, along with the setup for the dependency you actually want to verify.
🦆: Why are your service methods making all these calls?
L: Aren't all service methods like this in a micro-service architecture?
🦆: Oh, you're using micro-services. 😧
L: Yeah, although I can't imagine monoliths being much better.
🦆: Could you split this service method up into smaller methods, and just write unit-tests for those? You know, use decomposition?
L: Yeah, but that goes against the principal of writing unit-tests for just the public methods.
🦆: What if you made all these methods public?
L: Nah, that'll just push the problem up a layer.
🦆: Okay, so I see that you the issue of a service method doing a lot, maybe even too much, yet one that needs to be exposed to the user as one operation.
L: Yes.
🦆: And obviously you'd like that service method to be functional, so you're writing unit-tests to make sure they're behaving correctly.
L: Yes, and in order to verify that it's functioning correctly, it needs to verify that it calls out to the dependent services correctly. Hence, all the need for mocking.
🦆: Yeah, but you'll need to verify these somehow. The fact that it's calling out to multiple services doesn't make the act of mocking suck. It sucks that your service method is making all these dependent calls.
L: Oh, yeah. I guess that's true. So how do I make it suck less?
🦆: Well, I guess it depends on your coding practices, but I can think of either:
🦆: 1. Decomposing the large service method into multiple methods, or even services, with each one calling out to one of these dependencies, and simply writing unit-tests for those.
🦆: 2. Have all the mocked calls in a common "setup" method, done in such a way that they're all functioning correctly, with each test somehow "overriding" the setup of a dependency to test the case where that particular dependencies fails.
🦆: 3. Dealing with it.
L: Hmm, some food for thought there. I still don't like mocks and rather use the real implementation instead, but I see that it's not the mocks themselves that's causing all these sucking.
🦆: Well, if it makes sense to use a real database or message queue in your test, then I'm not going to stop you. Just remember there are downsides to that too. I know you've seen those test fail because a Docker container isn't running or a database isn't cleaned up.
L: Yeah, yeah. I know. Really should fix those.
🦆: Well, that's something for later. Just have a think about these approaches and seeing if they help. Learn to "mock smarter."
L: Okay, I will. Next large system I need to work on.
🦆: Oh yeah. And also it's a journey. Seems like there's no silver bullet to this. Just trade-offs. You just need to know which ones you're willing to take. And which will work for the particular project.
L: Yeah, that's some good advice. Okay, cool. I'll let you go.
🦆: Yeah, catch you later. Enjoy your weekend.
L: Yeah, you too.