Free tools Windows power users keep installed
One-click scans. No signup required.
Unit tests are neither a cure-all nor inherently wasteful. They are most useful when they check important behavior quickly, especially core logic and edge cases. They become a liability when they are tightly coupled to implementation details, so that ordinary refactoring breaks tests without breaking the product. A practical strategy combines focused unit tests with tests at system boundaries and checks of critical user journeys.
What unit tests are good at—and where they fall short
A unit test checks a small piece of software in isolation, such as a function or use case. Its chief practical advantage is speed: a fast test suite can give developers frequent feedback and make it affordable to cover many edge cases. Abass Ajanaku argues for using unit tests for this kind of focused coverage, rather than expecting them to prove that the whole application works.
As an Amazon Associate I earn from qualifying purchases.
A passing unit test establishes only what its assertions actually check. It may show that a business rule handles a particular input, but it cannot by itself confirm that the database adapter, API, and interface work together or that a user can complete a task from beginning to end.
Should you use unit tests or end-to-end tests?
They answer different questions, so choosing one category to replace the other is usually a false choice. Ajanaku recommends combining test types based on the behavior or connection each needs to verify.
#1 Best Overall
| Test type | What it can help verify | Useful when | Trade-off |
|---|---|---|---|
| Unit | Focused behavior in a small piece of code, including edge cases | You want quick feedback on core logic across many inputs | Isolation can leave application wiring and real user journeys untested |
| Contract or integration | Whether connected components or adapters satisfy an expected boundary | A core component relies on infrastructure such as a database | Tests exercise more than an isolated unit and may take more setup than an in-memory unit test |
| End-to-end | A complete, important flow through the application | You need confidence that a critical user journey works across the system | Running these tests can be slower and require more infrastructure |
The table describes the roles these tests can play, not a universal ranking or fixed ratio. Ajanaku suggests having fewer end-to-end tests than fast unit tests and concentrating them on mission-critical flows; that is a pragmatic recommendation, not a proven test-count formula. Relying only on end-to-end tests can also mean slower feedback and greater infrastructure cost.
How can you write high-value unit tests?
Start with behavior, not the shape of the code. A useful test protects a requirement or meaningful outcome; it should generally continue to pass if an implementation is refactored without changing that outcome.
- Name the behavior: Make clear what requirement or case the test checks.
- Choose meaningful inputs: Include ordinary cases and relevant edge cases that would be cumbersome to exercise repeatedly through a full application.
- Assert the outcome: Prefer observable results over the names, call sequence, or arrangement of private implementation details when those details are not part of the requirement.
- Use failures as information: Ask whether a failing test points to a regression a user or another component would notice, or merely to an internal change.
These questions are a practical review lens, not a guarantee that every behavior belongs in a unit test. A user-visible workflow may be better checked at the system level, while a component boundary may need an integration or contract check.
Recommended Free Tools
Use a shared contract to test business logic without a database
Ajanaku describes a design in which core business logic depends directly on a database. That makes a fast, isolated test harder because the test must bring infrastructure into the setup. One alternative is to make the core depend on a repository contract, then provide different implementations of that same contract.
Rank #3
- Define the boundary: Specify the repository operations the use case needs, without tying the core logic to a particular database.
- Test the use case with an in-memory adapter: Supply a lightweight implementation of the contract so unit tests can check business behavior quickly.
- Use a production adapter: Have the real database-facing implementation satisfy the same contract.
- Check the boundary: Add contract or integration tests to verify that adapters behave as the core expects.
This dependency-inversion design separates business behavior from infrastructure and gives the core a simple test substitute. It also costs more code and adds an abstraction layer. It is most worthwhile when the separation makes tests or changes materially easier, rather than as an architectural ritual.
Why tests can become worse than useless
A test can create maintenance work while offering little confidence if it breaks whenever internal code changes, even though externally visible behavior remains correct. In that case, developers may have to update tests to accommodate a refactor, and a failing test does not reliably signal a product regression.
Dan Abramov recounts this problem in an account of React testing: some tests were coupled to internal module details and became unhelpful during a major rewrite. The team shifted toward tests of public behavior, including examples of reported problems, so that another implementation solving the same problem could still pass. This is a practitioner’s account, not a controlled study or proof that all implementation-focused tests are harmful. The useful lesson is to evaluate what a test protects and whether its failure remains meaningful after a behavior-preserving refactor.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchPC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11A practical way to choose what to test
- Core rule or edge case: Use a unit test when isolated execution provides quick, clear feedback.
- Interaction across an important boundary: Use a contract or integration test to check that connected pieces agree.
- Critical user journey: Use an end-to-end test when confidence depends on verifying the flow through the application.
- Test breaks after refactoring: Check whether the requirement changed. If behavior is unchanged, consider replacing implementation-specific assertions with checks against public behavior.
- Test setup needs substantial infrastructure: Consider whether the behavior can be isolated behind a contract and exercised with an in-memory adapter, while retaining tests for the real boundary.
There is no evidence in the cited accounts establishing a universal number of tests, ideal test ratio, or quantified defect reduction. The decision is therefore about the confidence a test buys, the speed and cost of obtaining it, and how much maintenance it creates—not about maximizing test counts.
Quick Recap
Sources and scope
- Abass Ajanaku, “Unit tests are worse than useless” – Not quite.
- Software Engineering Unlocked, “Inside Facebook: Bad Tests Are Worse Than Product Issues with Dan Abramov”.
- For context on the limits of treating opinions as evidence, see the 2010 Slashdot discussion “Whatever Happened To Programming?”. A commenter’s view there is not a measured result.
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




