A React Testing Library test for a Checkout page mocks out every child component (jest.mock('./CartSummary') and friends) so only the page's own markup renders. What does that buy, what does it stop catching, and when is stubbing a child justified?
answer
- isolation bought with coverage
- the seam belongs outside your code
- bugs live in the parent-child join
- stub the dependency, not the component
- nothing left for a role query
basics
~20 sStubbing every child makes the test fast and isolated, but it removes the rendered UI the test was supposed to check, leaving only assertions about props passed to stubs. Stub a child only when it is genuinely untestable in that environment.
solid answer
~50 sWhat it buys is isolation and speed: the page renders without its children's data needs, effects or heavy dependencies, and a bug in a child cannot fail the page's test. What it costs is most of the value. Once `CartSummary` is a stub, the page's rendered output contains no totals, no line items, nothing a user-facing query can reach — so the only assertions left are about the props handed to the stub, which is an implementation assertion in disguise. Every integration bug lives exactly in that seam: wrong prop name, wrong shape, a child that renders nothing for the data given. Stubbing is justified when a child is genuinely hostile to the test environment — a map or chart that needs canvas or WebGL, something that opens a real connection, a third-party widget — and even then the better first move is usually to stub the child's *dependency* rather than the child itself, so the real tree still renders.
code
jsx · 11 lines// Stubbing the child: the page's test can no longer see any real UI
jest.mock('./CartSummary', () => ({
__esModule: true,
default: ({ total }) => <div data-testid="cart-summary">{total}</div>,
}));
// The only assertion left is about the internal prop interface
expect(screen.getByTestId('cart-summary')).toHaveTextContent('42');
// With the real child rendered, the assertion is about what the user sees
expect(screen.getByText('Total: $42.00')).toBeInTheDocument();go deeper
Know that a component test normally renders the real component tree, and that replacing children with stubs means the test no longer sees the text and controls a user would see.
Explain what the assertions degrade into once children are stubbed — prop checks against an internal interface — and name the class of bug that then escapes: mismatched prop names and shapes at the parent-child seam.
Weigh both directions in a real codebase: what isolation buys, why the durable seam sits at the network rather than at your own components, and which specific children genuinely cannot render in the test environment.
Decide the policy: where seams are allowed to exist across a component library, what test level owns composition coverage if page tests stub children, and how to stop mock-everything from becoming the default because setup was painful.
## What the pattern looks like ```jsx jest.mock('./CartSummary', () => ({ __esModule: true, default: ({ total }) => <div data-testid="cart-summary">{total}</div>, })); ``` Repeat for every child, and the page test now renders a skeleton: the page's own wrapper markup plus a handful of placeholder divs. (The same pattern exists in every module-mocking runner — `vi.mock` and friends — the question is not about the API.) ## The case for it It is not a stupid instinct. Mocking children gives you: - **Speed.** Nothing below the page renders, so no child effects, no data hooks, no expensive layout. - **Isolation of failure.** A bug in `CartSummary` fails `CartSummary`'s test and nothing else, so a single defect does not turn twenty suites red. - **Setup relief.** The children's providers, context and fixtures no longer have to be satisfied by the page's test. The last one is the real motive most of the time, and it is worth saying out loud: mocking children is frequently a workaround for painful setup rather than a considered boundary. ## What it takes away The page test's purpose is to check that the page composes its children correctly. Stubbing them removes exactly that. - **No user-facing queries.** There is no "Total: 42.00" in the DOM, no line items, no button labels from children. `getByRole` and `getByText` have nothing to find, so the assertions drift to `expect(screen.getByTestId('cart-summary')).toHaveTextContent('42')` or to inspecting the mock's calls. - **Assertions become prop assertions.** "`CartSummary` was rendered with `total: 42`" is a statement about the internal interface between two components you own. Rename the prop on both sides and the test fails; break the rendering and it does not. That is the wrong sensitivity in both directions. - **Integration bugs go undetected.** The seam between parent and child is precisely where the bugs are: the parent passes `totalCents` and the child reads `total`; the child renders nothing when the array is empty and the page shows a blank panel; the child throws for a shape the parent produces only in one branch. Every one of these is invisible once the child is a stub. - **The accessible tree is gone.** No roles, no labels, no landmarks from below the page — so the test cannot check anything about how the composed page is actually reached. ## The rule of thumb Render the real tree by default; introduce a seam only where you cannot control the world otherwise. In a frontend test the seam that pays for itself is almost always **outside your own code** — the network, the clock, a third-party SDK. Stubbing the network keeps every one of your components real while making responses deterministic. Stubbing your own components does the opposite: it keeps the uncontrollable parts and removes the parts you wrote. ## When stubbing a child is genuinely right - **The child is hostile to the environment.** A chart that draws to canvas, a map that wants WebGL, a video player, an editor that measures layout — things a jsdom-style environment cannot run meaningfully. - **The child is non-deterministic in a way you cannot inject.** Live animation, randomised content, its own polling. - **The child is a third-party black box** whose markup you neither own nor want to assert on. - **The tree is enormous** and this specific test is deliberately narrow, with a broader test covering the composed page elsewhere. That is a legitimate trade *if the broader test exists*. Even then, prefer the smaller cut. If `ExpensiveChart` is heavy because it calls a charting library, stub the library, not the component — the component still renders, still contributes to the accessible tree, and still fails if the page hands it garbage. ## How to answer the scenario in an interview Name the trade in both directions rather than declaring mocking wrong. Something like: "Mocking children buys isolation and speed and costs the composition coverage the page test exists for; I would render the real children and move the seam to the network, then stub individual children only where the environment genuinely cannot run them — and I would check that whatever assertions remain are about rendered output, not about which props a stub received."
- What happens to accessible queries in the parent's test once every child is stubbed?They stop working, because the roles, names and landmarks all came from the children. The test falls back to test ids on the stubs, which is how these two mistakes reinforce each other: the mocked tree makes user-facing queries impossible, and the test-id queries then hide that the page renders nothing meaningful.
- How is stubbing a child component different from stubbing the network for the same page?The network is outside the code you own and is inherently uncontrollable, so a seam there buys determinism without removing anything you wrote — every component still renders and every integration between them is still exercised. Stubbing a child removes your own code from the test, which is where the bugs you are hunting actually live.
- You must stub a chart child because it needs canvas. What do you do to keep some coverage of that seam?Two things. Stub as low as possible — the charting library rather than your wrapper component — so your own code still runs. And make the stub assert its input where it matters, or cover the wrapper in its own focused test with realistic props, so the parent-to-child contract is checked somewhere even if not in the page test.
saying these in an interview costs you the question
- Mocking children makes the test a proper unit test
- Asserting the props a stub received tests the integration
- Children have their own tests, so the page need not render them
- Rendering real children makes the test an end-to-end test
- Any slow child should be mocked out on principle