skip to content

A frontend component test replaces the app's own API-client module with a fake that returns a canned user object. Which parts of the application's code does that test no longer exercise?

level: juniorimportance: should knowfreq 45%

answer

  1. a fake is a cut line
  2. below the line, nothing runs
  3. URL, headers, parsing, error mapping
  4. fixture is a belief, not a fact
  5. component still genuinely tested

basics

~20 s

Everything below the fake goes untested: URL and query building, headers and auth, request serialization, response parsing, status and error mapping, plus any caching or retry logic inside the client. Only the code above the seam actually runs.

solid answer

~50 s

A fake is a cut line through the app: whatever the replaced module normally does simply does not run. So the test proves nothing about how the request is built — the path, the query string, the headers, the body serialization — nor about how the response is read: parsing, field names, status handling, error mapping, and any caching, deduping or retry the client does internally. What the test still proves is real and useful: given data of this shape, the component renders and behaves correctly. The risk is that the canned object and the real payload drift apart, because nothing in this test ever compares them. That gap has to be closed somewhere else — by a test whose fake sits at the network boundary, or by one that talks to a real server.

code

javascript · 9 lines
javascript
// src/api/userClient.js
// Every line in this module is removed from a test that fakes it.
export async function fetchUser(id, { includeOrders = false } = {}) {
  const url = `/api/users/${encodeURIComponent(id)}?orders=${includeOrders}`;
  const res = await fetch(url, { headers: { Accept: 'application/json' } });
  if (res.status === 404) return null;
  if (!res.ok) throw new Error(`user fetch failed: ${res.status}`);
  return res.json();
}

go deeper

for a junior

Be ready to say plainly that a fake replaces real code: whatever that module normally does — building the URL, sending the request, reading the response — simply does not run in that test.

for a middle

Explain the removed layer mechanically: request construction, serialization, status handling, response mapping and any client-level caching or retry all sit below the seam, so a stale fixture keeps the test green forever.

for a senior

Show that you reason about coverage across a suite: name which layer owns URL and error mapping, and say which lower-placed test you rely on to prove that layer still matches the server.

for a principal

Own the tradeoff explicitly: every fake buys speed by declaring a layer out of scope, so argue for where that debt is recorded and which small, funded set of tests is responsible for closing it.

## A fake is a cut line, not an addition When a test substitutes a fake for one of the app's own modules, it is not adding behaviour — it is removing it. Every line inside the replaced module is deleted from that test run and replaced by a value you wrote yourself. The useful mental picture is a horizontal line drawn through the application's layers: the code **above** the line runs for real, the code **below** the line is asserted-by-assumption. That is what makes the choice of seam the central decision in frontend test design. It is not "do I mock?" but "how far down does real code keep running?" ## What sits below an API-client fake A typical data-access module in a frontend app does considerably more than call the network: ```javascript // src/api/userClient.js export async function fetchUser(id, { includeOrders = false } = {}) { const url = `/api/users/${encodeURIComponent(id)}?orders=${includeOrders}`; const res = await fetch(url, { headers: { Accept: 'application/json' } }); if (res.status === 404) return null; if (!res.ok) throw new Error(`user fetch failed: ${res.status}`); return res.json(); } ``` Replace this module with a fake and the following stop being exercised: - **Request construction** — the path, path-parameter encoding, query-string keys and value formats, the method, and the headers (including whatever attaches credentials). - **Body serialization** — how a form object becomes JSON or form data, date formatting, field naming, omitted-versus-null handling. - **Response interpretation** — which status codes mean success, which mean "absent" rather than "failed", how the payload is unwrapped (`data.items` vs `items`), and how server field names are mapped to the app's own shape. - **Error mapping** — turning a status or an error payload into whatever the UI layer expects to catch. - **Client-level behaviour** — caching, request deduplication, cancellation, retry, and any interceptor that rewrites requests or responses globally. None of that is exotic code. It is exactly the code where a typo — `orders` versus `withOrders`, `user.first_name` versus `user.firstName` — silently breaks the feature. ## What the test still proves It is easy to over-correct and conclude the test is worthless. It is not. A test with a module-level fake makes a precise and valuable claim: *given data of this shape, this component renders correctly, reacts correctly to interaction, and handles the loading and empty branches you drove*. That claim is fast to check, stable under refactors of the UI's internals, and pinpoints failures well. The honest way to describe such a test is that it takes the data-access layer as a **premise** rather than as a subject. The premise is only as good as your fixture. ## Why the gap bites The canned object you hand back is a snapshot of your belief about the API, written by hand, on the day you wrote the test. Servers change: a field is renamed, a nullable appears, a list becomes paginated. Nothing in a module-faked test notices, because the code that would have choked on the real payload — the parsing and mapping — was removed from the run. The result is the classic false green: a full suite passing while the feature is broken in the browser. The same is true in reverse for request construction. If the component sends the wrong query parameter, the fake still returns the right answer, because the fake ignores its arguments or only asserts the ones you thought to assert. ## Where the missing coverage has to come from Once you can name the layer you cut away, the fix is a placement decision rather than a testing-philosophy debate: - **Push the seam down to the network boundary.** If the fake answers HTTP requests instead of replacing a module, the client module runs for real: URL, headers, serialization, status handling and parsing are all exercised, and only the server is imaginary. - **Test the client module directly.** A focused test of the data-access layer, with the network faked beneath it, covers request shape and response mapping once, cheaply, for all the component tests that assume it. - **Let a small number of tests reach a real server.** Only a real server proves that your beliefs about the payload are current. Most teams use a combination: a wide band of component tests that take data as a premise, plus a deliberate, much smaller set of tests placed low enough to keep that premise honest. The point of the question is whether you can articulate what your fake removed — the answer "nothing important, it returns the same thing the server would" is the one that fails.

  • If the component sends the wrong query parameter, could a test with a faked client module ever catch it?
    Only if the test explicitly asserts on the arguments the fake was called with, and only against your own expectation of what is correct. The fake still returns the happy-path value regardless, so the render assertions all pass. That is an assertion about the call, not evidence that the server would accept it.
  • Does giving the fake a strongly typed return value close the gap?
    No. Types constrain the fixture to your declared shape, which catches typos against the type, but the type itself is also a hand-written belief about the API. If the server renames a field and nobody updates the type, the fixture and the type agree with each other and both disagree with production.
  • So is a component test with a faked client a bad test?
    No — it is a test with a narrow, explicit claim: given this data, the UI behaves. That is worth having, it is fast, and it fails informatively. It only becomes a problem when a team believes it also covers the data layer, and therefore never funds a test that does.

Handing the component its data directly is like testing a vending machine by passing the customer a drink over the top — the coin slot, the keypad and the dispenser are never tried.

saying these in an interview costs you the question

  • Says mocking the client still tests the request that gets sent
  • Thinks a green suite means the endpoint and payload are correct
  • Assumes a realistic-looking fixture proves the real payload matches
  • Claims TypeScript types make the fake safe from API drift
  • Cannot name a single layer the fake removed from the run

context