Hacker News new | ask | show | jobs
by sievebrain 3725 days ago
The problem isn't with the idea of using mocks in testing. The problems start, as often the case, when people start religiously applying a design pattern and end up over using or abusing it.

The problematic thought process goes like this:

1. Gee, my tests are really slow and flaky and hard to run because they talk directly to a database. I know! I'll swap out my real database connection with an in memory database.

2. Things are better, but they're still slower than I'd like. Maybe I can just write a wrapper around my database code, make it implement an interface, and then provide a custom implementation just for my unit tests. Instead of having my class construct a database connection directly, I'll make it passed in as a parameter. Then they'll be nice and fast!

3. Whoa, now my tests run instantly! How great. This mocking pattern is awesome. I want to mock everything! But hmm, I have a lot of objects and they often depend on other objects, like services of some sort. And I was told in school that God Objects and global variables are bad. So I can't use those because I want to be a good programmer and good programmers don't use those. This is making my code kind of a mess because now every time I want to construct an object it takes lots of parameters when before it didn't.

4. <reads some article on hacker news> Hm, this dependency injection library sounds like what I want. Instead of constructing my objects directly, I ask an "injector" to do it for me. And it will then consult a table of bindings, and then find or construct the dependencies, and then build the object I want, recursively. All I have to do is [insert long complicated process here].

5. Meanwhile, some other programmers look at what's going on. Interesting! They say. Those programmers know what they're doing and they're converting their whole codebase to use dependency injection. They claim it's a best practice to make testable code. We want our code to be testable too, we should do the same thing. Plus, we can call this refactoring and code maintenance and get paid for doing essentially brainless work, instead of having to take risks on developing new features or fixing bugs.

The story ends like this: pretty quickly this newfound "best practice" is spreading throughout your codebase like a fire, and now when you deploy your app, you get an error whilst the app is running. You then try to figure out what's causing the error but 25% of the time you try to follow a method call you end up looking at an interface instead of code, and when you try to figure out what implementation of that interface your app called you have to read and decipher tons of binding definitions.

The correct place to stop in the above story was at point two. And the reason is not just code readability. Once you start over-using mock objects, you can start to discover that your mock is implementing what you think that service does, not what it actually does, and you can end up with code that's actually buggier than code written with traditional fat test dependencies would have been. Fail.

1 comments

Thank you, very detailed explanation.