Mocking, Stubbing, and Test Isolation Questions
Isolating the unit under test from its dependencies. Covers mocks, stubs, fakes, and spies, when to use test doubles versus real dependencies, and controlling external services and time. Includes designing for isolation so tests are fast, deterministic, and focused.
Explain the differences between mocks, stubs, fakes, spies, and dummies. For each kind of test double, give a concrete example from a typical web application (an HTTP API call, a database, a message queue, or a cache) and a short guideline for when you would prefer that double over the others.
Sample Answer
Direct answer
A test double is a stand-in for a real dependency in a test. The five common kinds differ in what they do when called and what the test asserts about them: a dummy is passed around but never actually used, a stub returns canned data, a fake is a working but simplified implementation, a mock records calls so the test can verify they happened correctly, and a spy wraps a real object while also recording calls to it.
Structured elaboration
| Double | Behavior when called | What the test checks | Typical web-app example |
|---|---|---|---|
| Dummy | Does nothing meaningful; only fills a required parameter slot | Nothing about it directly | Passing a placeholder database-connection object into a constructor that requires one but is never queried on the code path under test |
| Stub | Returns a pre-programmed value | The return value the code under test produces | Making a "get exchange rate" HTTP client return a fixed 1.08 so a pricing calculation is deterministic |
| Fake | Runs real logic, just a lighter-weight implementation | The behavior/output, same as against the real thing | An in-memory key-value store used instead of a real Redis instance |
| Mock | Returns canned data AND records how it was called | That specific calls happened, with specific arguments, in the right order | Verifying that PaymentGateway.charge() was called exactly once with the correct amount |
| Spy | Wraps a real object, forwards calls through, and records them | Both real behavior and the interaction | Wrapping a real email-sending service so you can assert "send was called" while letting a test double catch the actual network call underneath |
The line between stub and mock is really about what the test asserts on: a stub only shapes the INPUT to the code under test (state verification), while a mock is used to verify OUTPUT in terms of interactions (behavior verification). The same test-double library (Mockito, unittest.mock, Sinon) is usually used to build all five; the taxonomy names roles, not library features.
Worked example
For a database example: a stub database client always returns a fixed list of three users for find_active_users(), regardless of what was inserted, so a report-generation test has predictable input. A mock database client would instead be used to verify that save(user) was called exactly once with a User object whose status field is "active", when testing the code that is supposed to activate a user. A fake database would be a real, lightweight in-memory dictionary-backed store that actually persists and retrieves records within the test, useful when the test needs realistic query behavior (like "insert then find") that a stub's fixed answer can't provide.
Trade-offs and pitfalls
Reach for a dummy when a dependency is only required to satisfy a constructor or function signature and is never actually exercised on the path under test; building anything more elaborate (a stub, a fake) for it would be wasted setup effort. Reach for a stub when you need to control an input; reach for a mock when the ACT of calling the dependency (with the right arguments, in the right order) is itself part of the behavior being tested, such as making sure a payment is charged exactly once. Reach for a spy when you want the dependency's real behavior to genuinely happen (unlike a mock, which fully replaces it) while still asserting that a specific interaction occurred, such as confirming a real cache was actually written to while also checking the write call's arguments. Overusing mocks where a stub would do makes tests brittle: every internal refactor that doesn't change observable behavior can still break a mock-heavy test, because the test is coupled to how the code calls its dependency rather than what the code produces.
Give a decision rubric for when to mock an external dependency (a third-party API, a database, a cache) versus calling the real thing in a test. Name the factors your rubric weighs, and explain how the right answer can differ between a local development run, a CI pipeline, and a staging environment.
Sample Answer
Direct answer
Mock a dependency when calling the real one would make the test slow, nondeterministic, expensive, or unsafe to run repeatedly; call the real dependency when the test's whole purpose is to validate the integration itself, or when a fake would hide the exact behavior you're trying to verify.
Structured elaboration
A useful rubric weighs five factors together, not any single one in isolation:
- Speed: a network call to a real third-party service typically costs tens to hundreds of milliseconds; a thousand such tests would make the suite unusably slow. Mocking removes that cost.
- Determinism: real dependencies can be flaky (rate limits, transient errors, clock skew). A test that fails one run in twenty because of the real dependency, not because of a bug, teaches the team to ignore red builds.
- Cost: some real calls cost real money (a payment gateway, a paid data API) or consume a shared, limited quota (a sandboxed third-party account). Running them on every commit is not viable.
- Availability: local development and CI often can't reach an internal-only service, a VPN-gated system, or a partner's staging environment at all.
- Business risk: mocking a critical safety or compliance check (say, a fraud check before a payment) can mean the test never actually exercises the real decision logic, so a regression there ships silently.
Environment changes the answer: local development favors mocks for a fast inner loop; CI usually mixes a large mocked-unit-test layer with a smaller, real-dependency integration-test layer, run less frequently or gated separately; staging is often the first point where the real dependency should be exercised end-to-end, precisely because CI mocked it.
Worked example
A checkout service calls a fraud-scoring API. For the unit tests of the checkout logic (does it block the order when the score is above the threshold), mock the fraud API to return controlled scores like 0.95 and 0.10 so both branches are tested deterministically and instantly. For a smaller number of staging-only tests, call the real fraud API with known test accounts, because a mock can never tell you whether the real API's response schema or business logic changed. If the mock's shape silently drifted from the real API (say, the real API renamed a field from score to riskScore), only the staging test calling the real dependency would catch it.
Trade-offs and pitfalls
A rubric answer that only lists factors without weighing them together isn't complete: high business risk can outweigh speed and cost considerations, which is why safety-critical or compliance-relevant checks deserve at least a thin layer of real-dependency tests even if they're expensive and only run pre-release. The common mistake is mocking everything for speed and never re-validating that the mocks still match reality, which the mock's own definition can't protect against.
Explain approaches to intercept and modify network requests and responses during a Selenium test in order to simulate backend conditions. Compare using an HTTP proxy, browser-level network interception, and a service-virtualization tool, and provide a short example showing how you would stub a single JSON API response.
Sample Answer
Direct answer
Three approaches intercept network traffic during a Selenium test at different layers: an HTTP proxy sits between the browser and the network and can rewrite any request/response; browser-level network interception (via the Chrome DevTools Protocol) hooks directly into the browser's own network stack; and a service-virtualization tool replaces the backend entirely with a configurable fake server the browser talks to normally.
Structured elaboration
| Approach | How it works | Pros | Cons |
|---|---|---|---|
| HTTP proxy (e.g. a local proxy the browser is configured to route through) | Sits outside the browser, intercepts and can rewrite any request/response crossing it | Works across any browser or client, language-agnostic | Extra process to run and configure, and HTTPS interception needs a trusted certificate installed in the browser |
| Browser-level interception via the Chrome DevTools Protocol | The test driver registers request/response handlers directly with the browser's own devtools connection | No separate process, no certificate trust issues, very precise control per-request | Chromium-specific (or needs an equivalent for other engines), and ties the test to the devtools API surface |
| Service virtualization | The backend the app calls is a real, separately configurable server | Exercises the app's real network code path against a realistic backend, reusable across UI and API tests | Slower to set up, requires the app to be pointed at the virtual server's URL instead of production |
A short example stubbing a single JSON API response (shown here with an HTTP-level interception library so it is genuinely runnable without a browser; the identical idea applies whether the interception happens via a proxy or the Chrome DevTools Protocol):
import responses
import requests
def fetch_profile(user_id):
resp = requests.get(f"https://app.example.com/api/profile/{user_id}")
resp.raise_for_status()
return resp.json()
@responses.activate
def test_stub_a_single_json_api_response():
responses.add(
responses.GET,
"https://app.example.com/api/profile/77",
json={"id": 77, "plan": "pro"},
status=200,
)
result = fetch_profile(77)
assert result == {"id": 77, "plan": "pro"}
assert len(responses.calls) == 1
Executed with pytest:
test_s13_network_interception.py::test_stub_a_single_json_api_response PASSED
1 passed in 0.03s
Worked example
Testing a profile page that should show a "Pro" badge only for pro-tier users: stub the GET /api/profile/77 call to return {"plan": "pro"} and assert the badge renders; stub the same endpoint to return {"plan": "free"} in a second test and assert the badge does not render. Neither test depends on a real backend being up, and both are deterministic regardless of what the real profile service currently returns for user 77.
Trade-offs and pitfalls
An HTTP proxy adds a genuinely separate moving part (the proxy process itself, HTTPS certificate trust) to the test environment; browser-level interception avoids that but is coupled to whichever browser engine's devtools protocol you're using; service virtualization is the heaviest but also the most representative of real end-to-end behavior. Whichever layer is chosen, the stubbed response shape needs to be kept realistic, if the real API adds a required field the stub never includes, the UI test can pass while the real integration is actually broken.
That is every published Mocking, Stubbing, and Test Isolation question for Frontend Developer so far. Browse the other topics in this category, or practice this one interactively.