DEV Community

Writing Testable Code: Common Anti-Patterns and How to Fix Them

Mark Adel on April 26, 2026

When code is hard to test, it is usually a design problem. Code becomes difficult to test for many of the same reasons it becomes difficult to main...
Collapse
 
laura_ashaley_be356544300 profile image
Laura Ashaley

Great topic testable code is really about clean structure, low coupling, and clear responsibilities. Most anti-patterns come from trying to optimize for speed over maintainability.

Collapse
 
steve-oh profile image
Steve Schafer

The "fix" for global mutable state is only applicable if the value is incidentally mutable. That is, it just happens to be mutable, and its mutability is not part of the design of the code. If the mutability is part of how the code works, then you still need to fix it, but the fix is almost certainly going to be much more involved.

Collapse
 
markadel profile image
Mark Adel

Interesting take. Could you please provide an example where having a global mutable state would be part of the design?

Collapse
 
steve-oh profile image
Steve Schafer

Well, prior to about 1970, pretty much all non-trivial programs included global mutable state. COBOL doesn't have any other option, for example. And in Fortran, COMMON blocks, which were a standard way to pass data among subroutines, are an example global mutable state.

Moving to more modern times, global flags are commonly used in many programming languages, and are often mutable at runtime (as opposed to program startup).

Have you ever written JavaScript code, or used a JavaScript library, that modified an object's prototype? If so, you've mutated global state.

Collapse
 
anirseven profile image
Anirban Majumdar

The framing of "hard to test = design problem, not a testing problem" is something every dev should internalize early — it reframes the whole conversation.
The anti-pattern that trips up teams the most in my experience is #5, mixing business logic with I/O. It seems harmless at first, but it quietly makes entire service layers untestable without a running network. And the fix is so clean once you see it.
The note on interfaces is also refreshingly honest — "inject infrastructure, not pure business logic" is a much better mental model than the blanket "everything needs an interface" advice you see everywhere. Solid, practical guide

Collapse
 
markadel profile image
Mark Adel

Glad you found it useful. Thank you!

Collapse
 
itskondrat profile image
Mykola Kondratiuk

started noticing this during code reviews - the code that was hardest to read was always hardest to test too. same root cause: too much responsibility per function.

Collapse
 
markadel profile image
Mark Adel

Absolutely right! But the opposite is not always true. For example, a method that directly depends on a random number generator can be easy to read, but impossible to test.

Collapse
 
itskondrat profile image
Mykola Kondratiuk

fair - non-determinism is its own category. inject the rng and it's usually testable again. what gets you is when both problems compound.

Collapse
 
leob profile image
leob

Great overview, good points, clear examples, well done!

Collapse
 
markadel profile image
Mark Adel

Thank you!

Collapse
 
techhub profile image
Tech Hub

Well done!

Collapse
 
markadel profile image
Mark Adel

Thank you!