I'm working on a personal project (meaning clean source code, no legacy dependencies) and attempting to follow best practices regarding unit testing, dependency management, etc.
My company's codebase is littered with code like this:
public Response chargeCard(CreditCard card, Money amount) {
if(Config.isInUnitTests()) {
throw new IllegalStateException("Cannot make credit card API calls in unit tests!");
}
return CreditCardPOS.connect().charge(card, amount);
}
The goal here is to actively prevent dangerous code / code with external dependencies from being executed during testing. I like the notion of failing fast if unit tests do something bad, but I don't like this implementation for a few reasons:
- It leaves hidden dependencies to the static
Config
class scattered throughout our codebase. - It changes the control flow between testing and live behavior, meaning we're not necessarily testing the same code.
- It adds an external dependency to a configuration file, or some other state-holding utility.
- It looks ugly :)
A fair number of the places where we use this in my company's codebase could be avoided by better dependency awareness, which I'm attempting to do, but there are still some places where I'm still struggling to see away around implementing an isInUnitTests()
method.
Using the credit card example above, I can avoid the isInUnitTests()
check on every charge by properly wrapping this up inside a mock-able CardCharger
class, or something similar, but while cleaner I feel like I've only moved the problem up one level - how do I prevent a unit test from constructing a real instance of CardCharger
without putting a check in the constructor / factory method that creates it?
- Is
isInUnitTests()
a code smell? - If so, how can I still enforce that unit tests don't reach external dependencies?
- If not, what's the best way to implement such a method, and what are good practices for when to use / avoid it?
To clarify, I'm trying to prevent unit tests from accessing unacceptable resources, like the database or network. I'm all for test-friendly patterns like dependency injection, but good patterns are useless if they can be violated by a careless developer (i.e. me) - it seems important to me to fail-fast in cases where unit tests do things they aren't supposed to, but I'm not sure the best way to do that.