skip to content

A frontend team replaces its HTTP client library with a different one and changes no user-visible behavior, yet hundreds of tests fail. What does that tell you about where those tests placed their fakes, and where should the seam have been?

level: seniorimportance: should knowfreq 40%

answer

  1. behaviour unchanged, suite red
  2. what contract does the fake bind to
  3. module path versus URL
  4. external contracts are stable
  5. suite should validate the migration

basics

~20 s

The fakes were bound to the implementation — the client library or the app's own module — so swapping it invalidated them even though behavior was unchanged. A fake placed at the network boundary describes URLs and payloads instead, and survives any client that speaks HTTP.

solid answer

~50 s

Mass test failure with no behavior change is a coupling signal, not a safety signal. Those tests faked something internal — the HTTP library itself, or the app's client module — so the fake's contract was a module path and a function signature, both of which the migration changed. The fix is to place the fake at the boundary the browser actually crosses: intercept the request and answer with a response. That fake is written in terms of URL, method, body and status, which are unchanged by swapping libraries, so the same tests keep running and actually verify the new client builds the same requests as the old one. It is the same principle as querying the DOM by what a user perceives instead of by internal structure: bind the test to the contract, not to the wiring.

go deeper

for a junior

Recognise that tests failing when nothing a user can see has changed is a warning sign about the tests, and know that a fake tied to a specific module or library breaks when that module or library is replaced.

for a middle

Explain each seam's implicit contract — module path and signature versus method, URL and payload — and why only the second survives a swap of the code between the component and the wire.

for a senior

Diagnose it as coupling rather than coverage, and make the stronger point: with the seam at the network boundary the unchanged suite actively proves the new client issues the same requests as the old one.

for a principal

Frame it as refactor tax: seam placement decides what a migration costs across the whole codebase, so set the default, fund an incremental conversion of the worst areas, and state plainly which dependencies stay faked at the module seam and why.

## Read the symptom correctly "Hundreds of tests broke" sounds like the suite doing its job. It is not, when nothing a user can observe changed. A test should fail when behaviour changes and stay green when only structure changes; a suite that inverts this is measuring the shape of the code, and it will tax every refactor for the rest of the project's life. So the first thing to say in an interview is the diagnosis: this is a **coupling defect in the tests**, and the coupling came from where the fakes were placed. ## What each seam binds the test to A fake always has an implicit contract — the thing that must stay true for it to keep working. - **Faking the HTTP library** binds the test to that library's API: its request/response object shapes, its interceptor model, its error semantics. Swapping the library invalidates every one of those fakes by construction. - **Faking the app's own client module** binds the test to a module path and an exported signature. It survives a library swap only if the module's public surface is unchanged — and migrations usually do change it, because error and response shapes differ between libraries. - **Intercepting at the network boundary** binds the test to method, URL, headers, request body and response — a contract owned by the API, not by your wiring. Any client that speaks HTTP produces the same observable request, so the fake keeps working. That third seam is the only one whose contract is external. External contracts are the stable ones. ## The migration becomes a test *subject*, not a test *casualty* The strongest version of the argument is not just that network-level fakes survive the migration. It is that they **verify** it. With the seam at the network boundary, the old client and the new client are both required to produce the same requests and to interpret the same responses identically — and the existing suite proves it, unchanged, before and after. That is precisely the reassurance a library migration needs. With module-level fakes you get the opposite: the tests must be rewritten as part of the migration, which means the safety net is being replaced at the exact moment you are relying on it. Rewritten tests tend to be rewritten until they pass. ## The general principle This is the same rule that governs how tests find elements: query by what the user perceives, not by internal structure, because the perceivable surface is the contract and the structure is an implementation detail. Applied to the data layer, the perceivable surface is the request that leaves the browser. Everything between the component and that request is wiring you should be free to rearrange. A useful check when writing any fake: *what change would break this test?* If the honest answer is "renaming a file", "splitting a module", or "upgrading a library", the seam is too high. ## What this does not solve, and what to do about the current mess Be honest about the limits: - The network seam does not make responses true. You still wrote them, so the suite still cannot notice the server changing. - Not everything is HTTP. Faking a third-party SDK that owns its own transport, or a browser capability, legitimately stays at the module seam — those tests will break on migrations of that dependency, and that is acceptable because the dependency *is* the contract there. - Some clients need their own focused tests. Retry, cancellation, caching and auth-refresh logic inside the client deserve tests aimed at the client itself, again with the network faked beneath it. For recovering a suite that is already in this state, the practical move is not a big-bang rewrite. Migrate the seam where the pain is: start with the tests that broke, re-point them at the network boundary rather than at the new library, and make the network seam the default for anything new. Each converted test becomes immune to the *next* migration, and the conversion is usually mechanical — the fixtures you already wrote become response bodies. ## How to answer Name the diagnosis (coupling, not coverage), name what each seam's contract is, place the seam at the boundary the browser crosses, and add the nuance that the surviving suite actively validates the migration. Then bound the claim: the network seam fixes coupling and raises fidelity, but it does not make your fixtures true, and non-HTTP dependencies stay where they are.

  • Would keeping every call behind a single in-house client module have prevented this instead?
    It reduces the blast radius but does not fix the coupling — it concentrates it. The tests still bind to that module's signature, so any change to its shape, including the error type it throws after the migration, still breaks them. The seam is still inside code you refactor; the network boundary is not.
  • How would you migrate an existing suite of module-level fakes without stopping feature work?
    Incrementally, starting with the tests that already broke. Make the network seam the default for anything new, convert existing fixtures into response bodies as you touch each area, and leave non-HTTP fakes alone. A big-bang rewrite replaces your safety net wholesale, which is the worst moment to be rewriting tests until they pass.
  • Are there tests that legitimately should break when you swap HTTP libraries?
    Yes — the client module's own tests, if they cover library-specific behaviour like interceptors, cancellation semantics or how the library surfaces a network error. Those tests exist to pin that layer down, so a migration rewriting them is correct. What is not correct is component tests breaking for the same reason.

saying these in an interview costs you the question

  • Treats mass breakage on a refactor as proof the tests work
  • Says the fix is a shared test helper wrapping the mocked library
  • Believes any fake, wherever placed, has the same refactor cost
  • Claims network-level fakes are immune to all future change
  • Rewrites the broken tests during the migration and calls the migration verified

context