skip to content

How do you wire a component test so the tree renders as if the user arrived at a particular URL, and what can it then assert?

level: middleimportance: must knowfreq 52%

answer

  1. the router owns a history
  2. entries in an array, not the address bar
  3. seed the starting entry
  4. register the redirect's destination too
  5. assert rendered output, then location

basics

~20 s

Wrap the tree in a router whose history is an in-memory list rather than the host's address bar, seeded with the URL under test. The test can then assert which route's content rendered, and where a redirect left the location.

solid answer

~50 s

A router answers "where are we" from a history it owns. In a test you give it an in-memory history — a plain list of entries plus a current index — seeded with the starting URL, so no real navigation and no address bar are involved. Mount inside that and the tree resolves the same route it would for a real visit, with the same path parameters and query. What you then assert is **rendered output plus the resulting location**: that the matched route's content is on screen, that a route that should be protected rendered the sign-in surface instead, that the location ended at the target of a redirect. Navigation is usually asynchronous — resolution or data loading can happen in a later turn — so assert after the tree has settled, and assert on what rendered rather than on the router's internals.

go deeper

for a junior

Know that a test puts the tree at a URL by giving the router an in-memory history seeded with that URL, instead of touching a real address bar.

for a middle

Explain why the router can be run for real in a test, why redirect destinations must be registered, and why assertions wait for resolution.

for a senior

Demonstrate assertion discipline: rendered output as the primary signal, location as confirmation, and route setup complete enough that a failure is unambiguous.

for a principal

Decide how much route wiring the default test setup carries: a full route table makes tests realistic and slow to read, a minimal one makes failures ambiguous.

## Why an in-memory history A client router keeps a list of history entries and an index into it, reads the current entry to match a route, and pushes new entries when the application navigates. In a real page that list is backed by the host's session history, so changing it changes the address bar. A test wants the same matching behaviour with none of that: it wants to say "pretend the user is at `/orders/42?tab=items`" and have the tree resolve accordingly. The standard mechanism is a history implementation whose entries live in an ordinary array in the test's own memory. Nothing leaves the process, several trees can be mounted at different locations in one file, and each test starts from a location it stated explicitly. ## The setup, step by step 1. **Build the history with the starting entry.** One URL is the common case; a list of entries is how you arrange a case that needs a *back* target to exist. 2. **Hand it to the router provider** and wrap the subtree under test in that router. 3. **Give the router the route definitions it needs** — at minimum the route under test, plus any route a redirect or guard sends the user to, otherwise the redirect lands nowhere and the tree renders a not-found surface that looks like a bug in your component. 4. **Mount and let it settle.** Matching may be synchronous, but a route's data loading, code-split boundary, or an async guard resolves in a later turn. 5. **Assert on output first**, and on the resulting location only when the location itself is the outcome. ## What each intent looks like | What the test is about | Setup | Assertion | |---|---|---| | A component reads path or query values | History at a URL containing them, route pattern registered | The rendered output shows values derived from them | | The right route's content renders for a URL | History at that URL, sibling routes registered too | The matched route's content is present and the sibling's is absent | | A visitor without access is sent away | History at the protected URL, protected and destination routes registered | The destination surface rendered, and the current location is the destination | | A link moves the user | Any starting location, both routes registered | After activating the link, the new route's content rendered | | Going back returns to the previous view | History seeded with two entries | After a back navigation, the earlier route's content rendered | ## Traps - **Registering only the route under test.** Every redirect target and fallback route in play must exist, or the test's failure mode is indistinguishable from a genuine matching bug. - **Asserting before settling.** A test that checks output in the same turn as the mount can catch the pre-resolution frame and see a fallback or nothing. - **Asserting on router internals.** Reading a location object is tempting because it is available, but a route that resolves to the wrong content while the location is technically correct still ships a broken page. Prefer the rendered result; use the location as a secondary confirmation for redirect cases. - **Mounting a deep child that needs an ancestor route.** In a nested-route setup the child renders into an ancestor's outlet; a test that mounts the child alone may not establish the parent match the child depends on. Mount from the level that owns the match. - **Leaking the location between tests.** Build the history per test. A history shared across cases carries the previous case's pushes as its back stack. ## Why interviewers ask it A candidate who has only ever tested leaf components will reach for the real address bar, or will try to stub the router's read so the component believes it is somewhere. Both answers miss that the router is a collaborator you can run *for real* in the test — it is in-process, deterministic and fast — and that running it for real is what lets the test cover matching, redirecting and link behaviour instead of your stub's opinion of them. The in-memory history is the small piece of wiring that makes "real, but not in a browser's history" possible. ## Boundaries How a pattern matches a path, and what rule a guard applies to decide, are separate subjects; here the concern is only that the test can put the tree at a URL and observe the consequence. Likewise, faking the responses a route's data loading needs is a separate seam from wiring the router itself.

  • Why must the test register the route a redirect points at, not just the route under test?
    The redirect pushes a location nothing matches, so the tree renders the fallback or not-found surface. The test then fails for a reason that looks like broken matching in the component, and a developer debugs the wrong thing. Registering the destination makes a correct redirect observable as the destination's content appearing.
  • Why prefer asserting the rendered content over reading the router's current location?
    The location says where the router thinks it is; rendered content says what the user would actually see. A route can be matched correctly and still render the wrong subtree, and a redirect can set the right location while the destination fails to render. Assert on output, and use the location as confirmation when the redirect itself is the behaviour under test.
  • When would you seed the in-memory history with more than one entry?
    When the behaviour depends on there being somewhere to go back to — a close button that returns to the previous view, or a flow that pops an entry after a submit. With a single seeded entry there is no back target, so the control either does nothing or falls through to a default, and the test proves nothing about the real case.

saying these in an interview costs you the question

  • Stubs the router's read so the component merely believes it is somewhere
  • Tries to drive the host's real address bar from a component test
  • Registers only the route under test and is surprised by a not-found render
  • Asserts immediately after mount, before route resolution has settled
  • Treats the router's location object as the primary assertion target
  • Reuses one history instance across tests, inheriting the previous back stack