skip to content

As a lead, how would you decide between rendering one level deep and mounting a real subtree in component tests?

level: principalimportance: nice to knowfreq 40%

answer

  1. children are not instantiated at all
  2. the parent's intent, not the composed result
  3. placeholder assertions break on refactor
  4. full mount by default, exceptions named

basics

~20 s

Make mounting the real subtree the default and treat one-level rendering as a named exception. Rendering one level deep never instantiates the children, so nothing they do on mount happens and no composed output exists — the speed is real, the coverage loss is larger.

solid answer

~50 s

Rendering one level deep asks a component for its output while leaving every child uninstantiated: children appear as placeholders, their setup never runs, their effects never fire, and the composed result never exists. That buys speed, isolation from a heavy child, and a failure that points at one component. It costs the part that usually matters: whether the parent and its children actually fit together, and whether the tree still works after a refactor moves a boundary between them. So I default to mounting the real subtree, and allow one level deep behind named exceptions — a child that cannot run in the test host, or a deliberately expensive boundary — with the composed path covered somewhere else. Then I stop the inflow first and convert by area, rather than freezing feature work for a migration.

go deeper

for a junior

Understand the mechanism first: rendering one level deep leaves children as placeholders, so anything the child would do on mount simply does not happen in that test.

for a middle

Explain the trade in both directions — speed and narrow failures against composed coverage and refactor tolerance — and why placeholder assertions break when a boundary between components moves.

for a senior

Describe converting a real suite: which areas you did first, how you handled the one child that could not mount, and what the change did to runtime and to defects reaching production.

for a principal

Quote the terms of the trade with numbers — suite runtime, refactor breakage rate, contract defects that shipped green — and set a policy with named exceptions rather than a blanket rule either way.

## What rendering one level deep does A one-level render invokes the component under test and stops: where a child would be, the output holds a placeholder naming that child and the inputs it was given. The child is never instantiated. Nothing in its setup runs, none of its effects fire, it renders no output of its own, and it holds no state. That is the whole mechanism, and every consequence follows from it. ## What it buys - **Speed.** One component's render instead of a subtree's, with no child effects and no descendant work to settle. - **Isolation from a heavy child.** A chart, a virtualised list, an editor, or a child that needs a capability the host lacks never runs. - **A narrow failure.** When it goes red, the cause is inside one component, which is easier to read than a failure somewhere in a subtree. - **No collaborator setup for the children.** Whatever a child needs from above is never asked for, because the child never exists. These are genuine. The argument for one-level rendering is not stupid; it is a trade that was priced before refactoring costs were understood. ## What it stops proving | Dimension | One level deep | Full subtree mount | | --- | --- | --- | | Parent's own output | proven | proven | | Composed result | absent | proven | | Child setup and effects | never run | run | | Parent-child contract | asserted from the parent's side only | exercised for real | | Survives moving a boundary between components | no | yes | | Cost per test | lowest | higher | The fourth and fifth rows are the decisive ones. A one-level test asserts the parent's *intention* — that it asked for this child with these inputs. If the child ignores an input, needs one that is not passed, or reads a value of a shape the parent does not send, the test is still green. And because the placeholders are named after the current children, splitting one child into two or lifting a wrapper — changes that alter nothing a user perceives — turn those tests red in bulk. ## How I would set the policy 1. **Default: mount the real subtree.** Composition is the thing being tested; a component framework's whole point is that pieces fit together. 2. **Allow one level deep only behind a named exception** with a written reason: a child that genuinely cannot run in the host, or a boundary expensive enough to distort the suite. 3. **Require the composed path to exist somewhere** for every exception, so nothing depends only on placeholders. 4. **Prefer replacing one child over shallowing the whole tree.** Stand in for the one problem child and keep everything else real; that keeps the composed path for the rest. 5. **Measure before optimising.** If full mounts are slow, find out whether the cost is the framework or a collaborator being constructed in every test, because the second is fixable without losing coverage. ## Migrating without stopping feature work - **Stop the inflow first.** Make the default path for new tests the full mount, so the count stops growing while nothing existing is touched. - **Convert by area as it is touched.** The next change in an area pays for its conversion, which is when someone with context is already reading the tests. - **Start where the contract carries risk** — components with several children and non-trivial inputs — rather than alphabetically. - **Make the remaining count visible** and let the survivors justify themselves in review. Exceptions that nobody can defend tend to disappear on their own. ## The tradeoff to state out loud The honest form of this decision is that one-level rendering optimises for *failure locality and speed*, and full mounting optimises for *integration coverage and refactor tolerance*. There is a real class of codebase where the first matters more: a very large tree with an expensive child on nearly every screen, where a full mount is slow enough that engineers stop running tests at all. A suite nobody runs has no coverage either. So the policy is not a taste; it is a trade whose terms you should be able to quote — how slow the full mount actually is, how often a refactor breaks placeholder tests, and how many contract defects reached production behind green one-level tests.

  • What is a legitimate exception to a full-mount default?
    A child that genuinely cannot run in the test host — it needs a real engine, a device capability or a long-lived connection — and a deliberately expensive boundary such as a heavy chart or a large virtualised list. The better shape is to stand in for that one child, keep the rest of the subtree real, and cover the composed path elsewhere.
  • How would you retire one-level rendering from a large existing suite?
    Stop the inflow first by making full mount the default for new tests, then convert area by area as each area is touched, starting where parent-child contracts carry real risk. Keep the remaining count visible and require surviving exceptions to state their reason in review.
  • When is the argument for one-level rendering actually strong?
    When full mounts are slow enough that people stop running the suite, usually because an expensive child appears on nearly every screen. A suite nobody runs provides no coverage either. Even then, measure first: the cost is often collaborator setup repeated per test rather than the framework's rendering.

saying these in an interview costs you the question

  • Argues one level deep is more unit-like and therefore better
  • Believes child effects still run when children are not instantiated
  • Allows no exception even for a child that cannot run in the host
  • Claims a speed win without measuring either the speed or the loss
  • Treats a parent-child contract as verified by named placeholders