Stop writing unit tests for everything, write them for what matters

September 8, 2026

Illustration of a shield covering only part of the code while the rest slips through a net of tests bottom cut

Stop writing unit tests for everything, write them for what matters

We inherited a project with 94% test coverage. Sounds like a dream, right? Then, in the first week, we shipped a bug that miscalculated VAT on orders above a certain amount. Everything green in CI, everything passing. Turns out there was a test that called the pricing function, got a result, and then checked nothing. Literally calculatePrice(order) with not a single expect. The coverage tool painted that line green because it executed. Nobody ever checked whether the result was correct.

That is the core problem with coverage as a goal. Coverage measures whether code ran, not whether anyone verified what it returned. Those are two completely different things, and we have been confusing them for years because one number is easy to put on a dashboard and the other requires thinking.

100% coverage is not a goal, it is a red flag

When I see a team chasing 100% coverage, I do not see discipline. I see a team writing tests to satisfy a number instead of catching mistakes. Because to reach that last twenty percent, you have to test getters, setters, trivial constructors, one-line object mappings, and branches that never happen in practice. Those tests catch nothing. They exist only to make the number look nice.

The worse part comes later. Every such test is a liability. When you change the implementation, thirty tests break, not because you broke anything, but because they are coupled to how something was written rather than what it should do. The team spends half a day patching tests instead of shipping. And then people start to hate tests, which is the worst possible outcome, because now tests are in their way instead of helping.

It is not about the number. A project with 60% coverage that covers all the business logic, edge cases, and the integrations around billing is healthier than a project with 95% that tests getters and skips the places where money actually leaks.

Where tests actually pay off

There is a simple question I ask before writing a test: if this breaks in production, how much does it hurt? If the answer is "nobody would even notice," there is nothing to test here. If the answer is "someone gets the wrong invoice" or "we lose an order," that is where you test to death.

Concretely, these are the places where a test almost always returns the investment:

  • Business logic with edge cases. Price calculations, discounts, taxes, boundaries, rounding. Anything where "plus one" or "minus one" changes the outcome.
  • Anything touching money or user data. Billing, authorization, state changes in the database.
  • The bug you just fixed. Before you fix it, write a test that reproduces the failure. That is the one test you know for certain would catch something, because it already did once.
  • Complex transformations and parsing. Anything where the input can be messy and the output must be exact.

And where not to spend time? On trivial code with no logic, on the framework (you do not test someone else's library), and on things better caught by an integration test than by twenty isolated unit tests with half a dozen mocks. When a test has more mocks than real code, that is a sign you are testing your imagination of the system, not the system itself.

Coverage that lies: the problem with AI-generated tests

This became urgent over the last year and a half. You tell a tool "write me tests for this module" and thirty seconds later you get a nice set that pushes coverage to 90%. It looks like a gift. The problem is that AI is excellent at writing tests that execute but that verify too little, or verify the wrong thing. You end up with a pile of expect(result).toBeDefined() instead of a check for whether the result is actually correct. The code ran, coverage is green, and the test would not catch it even if the function returned a completely wrong number.

This is where coverage as a measure ends and where the thing worth checking begins: mutation testing. The idea is simple and a little brilliant. The tool deliberately breaks your code, changes > to >=, inverts a condition, swaps + for -, and then runs your tests. If the tests still pass on the broken code, they verify nothing. A surviving mutation is proof that the test is theater.

The first time we ran mutation testing (Stryker on a JS project) against a module with 88% coverage, the mutation score was below 50%. Translated: half of our "checks" would not notice the code was broken. That was the moment the whole team stopped treating coverage as a measure of safety. You do not need mutation testing across the entire system, it is too slow and too expensive. But on critical business logic and on anything an AI wrote, it is the only honest way to check whether your tests are real or just green.

What to do on Monday morning

If you have a high-coverage project where bugs keep showing up anyway, do not add more tests. Do the opposite:

  1. Stop targeting a number. Drop the "you must have X% to merge" gate if it pushes you to write tests without assertions. Replace it with a review: does this test check something that can actually break?
  2. Run mutation testing on your most critical module. Just one, to start. The result will sober you up.
  3. For every bug that reaches production, write a test before the fix. That is the only test you have proof works.
  4. Treat AI-generated tests as a draft, not a finished job. Read every assertion. If there is none, the test does not exist, it just takes up space.

The goal was never to have a lot of tests. The goal is to sleep well because you know something will wake you if what matters breaks. That is not measured in percentages.

Related:

If you are not sure whether your tests catch what matters, or just fill a dashboard, we know how to tell the difference. Get in touch and we will look at where your testing is real protection and where it is just expensive theater.

It takes 10 seconds to request a quote.

No strings attached.

computer guy

Subscribe to our Newsletter

Flawless digital

solutions

Our Offices
Check out
facebook twitter linkedin