There's a common misconception that writing tests is purely about verifying that your code works. That's true, but it's only half the story. Tests also apply pressure to your code structure and design. Code that is hard to test is usually hard to reason about, hard to reuse, and hard to debug, for the same underlying reason: it's too coupled to its context.
This is my story about a test coverage gap that revealed a design flaw, and how fixing the design fixed the coverage, along with some other things I hadn't noticed were broken.
The Setup
Imagine a React form component that manages a list of shipping destinations. Each destination has fields like address, weight limit, and delivery zone. When the user submits with incomplete destinations, a toast notification should appear telling them to fill out all the required fields.
The validation error-checking logic lives inside a useEffect:
// ShippingForm.tsx
useEffect(() => {
const destinationErrors = errors?.destinations as DestinationError[];
const hasErrors =
destinationErrors?.some((entry) => {
if (!entry) return false;
return Object.values(entry).some((field) => field?.message);
}) ?? false;
if (hasErrors) {
toast.error('Please expand and fill all destination fields');
}
}, [errors]);
The logic does three things:
- Guards against
nullentries in the errors array (if (!entry) return false) - Checks whether any field in the entry has an error message
- Shows a toast if any errors are found
This looks reasonable. It works in the browser. Surely the tests will should be straightforward or so I thought.
The Coverage Gap
When writing tests for this component, the happy path and most error paths were straightforward to cover. But the coverage report flagged one specific line as unreachable:
if (!entry) return false;
The instinct was to find a way to trigger this branch through the component, submit the form with one valid destination and one invalid one, so the errors array would contain a null entry at the first index and an error object at the second.
The problem? React Hook Form never actually produces that.
RHF stores field array errors as a sparse object internally: { 1: { address: {...} } }.
When that gets cast to an array and iterated with .some(), JavaScript only visits keys that exist. Index 0 is never visited. The callback never receives a falsy value. The if (!entry) return false branch is, in practice, unreachable through normal form interaction.
At this point there are two roads:
-
Road A: Find a subtle workaround, spy on
useForm, overrideformState, inject a synthetic errors object with anullentry just to hit the branch. - Road B: Ask why the branch is unreachable, and fix.
PS: I, like a couple of other people, I know and do not know, detest rewriting codes we didn’t write, but if you have a boss and mentor like mine who will drag you like 'tiger gen' with the statements like “i ga na e make use of your upper”, “i na-ede some funny funny things”, (find an Igbo person to translate, lol), “why exactly do we have this here?”(me in my mind: swear you don’t know why we have it 😩😭😂), you will not choose A.
You'll be forced to learn to find joy in doing things the right way. Anyways I digress 🤲🏽😅.
I chose B cause the reason for those statements was beginning to make sense after hearing them multiple times over the past 1+ years.
Road B is always the right answer, it often reveals more with a simpler approach.
What the Unreachable Branch Was Actually Saying
The if (!entry) return false guard is defensive code. It's protecting against a null value appearing in the array. But that situation can only arise if the errors object is constructed manually, it never happens in the actual running system.
So why is it there? Either:
- It was added out of caution without confirming it was reachable
- It was copy-pasted from somewhere where it made sense
- It was written to handle a case we thought could happen, but can't
In any of those cases, the logic is either dead code or it's solving a hypothetical problem. And it's doing so inside a useEffect that also owns the side effect of calling toast.error. That's two jobs in one place.
And that is the real problem the test was pointing at.
The Fix: Extract the Logic
The solution is to pull the error-checking logic out of the useEffect and into a standalone pure function:
// utils/destination-field-errors.ts
type ErrorField = {
message: string;
type: string;
};
type DestinationError = {
address?: ErrorField;
weightLimit?: ErrorField;
deliveryZone?: ErrorField;
} | null;
export const hasDestinationFieldErrors = (
destinations: DestinationError[] | undefined
): boolean => {
if (!destinations) return false;
return destinations.some((entry) => {
if (!entry) return false;
return Object.values(entry).some((field) => !!field?.message);
});
};
The component's useEffect becomes a clean one-liner:
// shipping-form.tsx
useEffect(() => {
if (hasDestinationFieldErrors(errors?.destinations as DestinationError[])) {
toast.error('Please expand and fill all destination fields');
}
}, [errors]);
Now the Tests Are Trivial
With the logic extracted, every branch is reachable by passing plain values directly to the function. No component. No form submission. No React Hook Form internals.
Every branch is hit. Every test is readable. No mocking of framework internals required.
But the Actual Benefits Go Beyond Coverage
You will agree with me on this. Here's what else improved by making this change, none of which the test coverage report would say.
1. Single Responsibility
Before the extraction, the useEffect was doing two jobs: computing whether errors exist AND reacting to that result by firing the toast. Those are different concerns.
After extraction, each piece has one job:
- the function answers a question,
- the effect acts on the answer.
2. The Logic Became Visible
When logic lives inside a useEffect, it's hidden inside React's machinery. To understand what "checking for destination errors" means, you have to mentally parse the hook, the dependency array, and the inline logic all at once.
A named function hasDestinationFieldErrors documents intent at the call site. The name itself becomes part of the codebase's vocabulary, searchable, referenceable, and self-explanatory.
3. Reusability
If another component, hook, or validation flow ever needs the same logic, it can import the function directly. Without the extraction, the only option is to copy-paste, which is how subtle bugs and inconsistencies spread across a codebase.
4. Easier Debugging
When a bug is reported about error toasts not appearing, or appearing incorrectly, there is now a precise entry point. You can test hasDestinationFieldErrors in isolation with the exact inputs from the bug report, without needing to reproduce the full form state in a browser.
The Bigger Lesson
Tests didn't cause this improvement. The improvement was always available. But without the discipline of writing tests, there was no mechanism to feel the resistance that the design was creating.
This is what good test coverage actually does. It doesn't just verify that the code works. It applies constant, honest pressure to the structure of the code itself.
A function that is hard to test is a function that probably is doing too much, coupled to too much, or hiding in a place it doesn't really belong.
If you can't write a clean test for something, just maybe the code is telling you something.
The coverage gap was never really about a missing test. It was a signal that a piece of logic had no identity of its own, it existed only as a side note inside a React hook. Giving it an identity (a name, a file, a signature, a test) forced it to stand on its own, and in doing so, made the entire surrounding system cleaner.
That, my friend, is one the many reasons why testing matters. Not just to catch bugs, but to keep asking the question: Is this code as simple and clear as it could be?
Are you team test or no test?
Have you encountered a coverage gap that pointed to a deeper code structure or design issue?
Lemme hear from you...

Top comments (0)