Have you ever spent an entire afternoon writing a unit test for a five-line setup component?
You mock three context providers, an Auth.js session hook, and a router adapter—only to assert that a button renders in the DOM. At that point, it’s worth asking:
Are we testing our application, or just testing our mocks?
In React applications, not every file benefits equally from isolated unit tests—but figuring out which ones those are isn't always obvious. Over time, I’ve moved away from chasing high coverage numbers for its own sake toward a more pragmatic framework for balancing unit tests (with Vitest or Jest) and End-to-End integration tests (with Playwright).
Here is how I think about testing wiring code without grinding developer velocity to a halt.
What Are "Glue Files"?
"Glue files" isn't an official software engineering term. It's simply the name I use for files whose primary responsibility is connecting parts of an application rather than implementing business logic.
Most React developers will recognize these files instantly in their codebases:
-
Entry & Wiring Files:
main.tsx,App.tsx,providers.tsx,layout.tsx -
Routing & Client Config:
router.tsx,queryClient.ts,middleware.ts -
Store Initialization:
store.ts(when primarily configuring Redux/Zustand rather than implementing custom middleware or complex state logic) -
Auth & Localization Wrappers:
<SignInButton/>wrappers aroundAuth.jsor Firebase, and language switchers wrappingnext-intl -
UI Primitives: Base design system elements (
components/ui/*built over Radix UI or Tailwind primitives)
These files act as the "mortar" holding your external dependencies, global state trees, and visual layout together.
The Hidden Cost of Unit Testing Pure Glue Files
Writing unit tests for pure domain functions is fast and rewarding. Writing unit tests for glue files often leads to three common headaches:
1. 90% Mock Setup, 10% Actual Assertion
To unit test a file that simply instantiates a client or wires up a third-party hook, you often end up writing dozens of lines of mock setup code just to test a few lines of JSX:
// tests/AuthButton.test.tsx
import { render, screen } from '@testing-library/react';
import { vi } from 'vitest';
// Mocking external library hooks and routers
vi.mock('next-auth/react', () => ({
useSession: () => ({ data: null, status: 'unauthenticated' }),
signIn: vi.fn(),
}));
vi.mock('next/navigation', () => ({
useRouter: () => ({ push: vi.fn() }),
}));
test('renders sign-in button', () => {
render(<SignInButton />);
expect(screen.getByText('Sign In')).toBeInTheDocument();
});
When 90% of a test file consists of vi.mock(), you aren't really testing application behavior—you are mostly testing your ability to write mocks.
2. High Fragility with Low Return
Because these tests are tightly bound to the internal signatures of third-party libraries, updating a dependency often breaks your unit tests even if the actual user-facing feature works perfectly fine.
3. Testing Framework Behavior Instead of Your Logic
Maintainers of packages like TanStack Query, Radix UI, or React Router already maintain extensive unit test suites. Writing unit tests to verify that <QueryClientProvider client="{queryClient}"> passes down context means you're often verifying framework behavior rather than your own application's logic.
The Decision Matrix: Does This File Make Decisions?
In my experience, asking this single question eliminates most debates about whether a file deserves unit tests: Does this file make decisions?
┌─────────────────────────────────────┐
│ Does this file make decisions? │
└─────────────────────────────────────┘
│
┌────────┴────────┐
│ │
Yes No
│ │
▼ ▼
┌───────────────┐ ┌────────────────────┐
│ Unit Test │ │ Is it wiring only? │
│ Vitest / Jest │ └─────────┬──────────┘
└───────────────┘ │
┌───────┴────────┐
│ │
Yes No
│ │
▼ ▼
Verify Through Re-evaluate File
Integration / E2E Responsibilities
(Playwright)
Seeing the Matrix in Practice
Let’s look at two concrete examples to see how this decision rule works in real code.
Example 1: queryClient.ts
Pure Wiring (Skip Unit Tests):
import { QueryClient } from '@tanstack/react-query';
export const queryClient = new QueryClient();
- Decision Check: Does this file make decisions? No.
- Strategy: Skip unit testing. Verify data fetching behavior through Playwright integration or end-to-end smoke tests.
Contains Decision Logic (Unit Test It!):
import { QueryClient } from '@tanstack/react-query';
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: process.env.NODE_ENV === 'production' ? 3 : false,
},
},
});
- Decision Check: Does this file make decisions? Yes. It evaluates the current environment to choose retry behavior.
- Strategy: Write a Vitest unit test to verify that retries evaluate correctly across environments.
Example 2: router.tsx
Static Routes (Skip Unit Tests):
export const router = createBrowserRouter([
{ path: '/', element: <Home /> },
{ path: '/dashboard', element: <Dashboard /> },
]);
- Decision Check: Does this file make decisions? No. It's purely declarative static wiring.
Conditional Routing (Unit Test It!):
export function getAppRoutes(userRole: string) {
return createBrowserRouter([
{ path: '/', element: <Home /> },
...(userRole === 'admin' ? [{ path: '/admin', element: <AdminPanel /> }] : []),
]);
}
- Decision Check: Does this file make decisions? Yes. It computes routes based on role permissions.
Quick Checklist: When Should You Unit Test a Glue File?
My default rule is: In most cases, I don't unit test pure glue files because they don't contain business logic.
However, a glue file becomes worth unit testing as soon as it:
- ✅ Contains branching or conditional logic (
if / else, ternary checks) - ✅ Transforms configuration dynamically at runtime
- ✅ Chooses behavior based on environment variables
- ✅ Applies feature flags or user permissions
- ✅ Has custom calculation logic that can fail independently
Otherwise, skip the unit test and verify it at the integration or E2E level.
Final Thoughts
This isn't a rigid rule—it's a heuristic. Every codebase is different, and your testing strategy should reflect your team's priorities, risk tolerance, and architecture.
Coverage tells you how much code executed. Good tests tell you how confident you should be when shipping.
If there's one takeaway to keep in mind when designing your test suite, it's this:
Code deserves tests in proportion to the decisions it makes.
Business logic makes decisions.
Glue code makes connections.
Test them accordingly.
Top comments (0)