How does a virtual service decide which canned response to return, and what should it do with an unmatched request?
answer
- A lookup, not a fixed answer
- Predicates over method, path, headers, body
- Two rules match - what decides?
- Loose gives false passes, strict false failures
- Nothing matched should never be success
basics
~20 sIt evaluates matching predicates over the incoming request - method, path, query, headers, body - and serves the matching rule's canned response. An unmatched request must fail loudly with a distinctive status and a logged diagnosis, never a blanket success.
solid answer
~50 sA virtual service holds an ordered set of rules. Each rule carries predicates over the incoming request - method, path or path template, query parameters, selected headers, and predicates on the body such as a field equalling a value or matching a pattern - plus the canned response to serve. Requests are resolved by explicit priority or by most-specific-wins; either way, an overlap between two rules should be settled deterministically rather than by declaration order nobody thought about. Matching that is too loose is the dangerous end: a rule answering any request on a path with a success accepts a wrong body happily, so the case proves nothing. Matching that is too strict - pinned to a generated identifier or a timestamp - gives brittle false failures. An unmatched request should return a distinctive error and record what arrived plus the closest near-miss.
code
pseudocode · 12 lines// too loose - green even when the amount is wrong
when(method = "POST", path = "/returns")
.respond(status = 202, body = { "receiptId": "R-84417" })
// matches on what the case is about, ignores what must vary
when(method = "POST", path = "/returns",
header("Authorization").present(),
body.field("taxDue.minorUnits").equals(148230),
body.field("submittedAt").ignored())
.respond(status = 202, body = { "receiptId": "R-84417" })
onNoMatch.respond(status = 418, body = describeNearestMiss(request))go deeper
Be ready to list what a rule can match on - method, path, query, headers, body - and to say why answering every unmatched request with a success is the worst possible default.
Explain the resolution policy when two rules overlap, and be able to contrast the two failure directions: loose matching gives silent false passes, strict matching gives noisy false failures. Interviewers want you to name which one is more dangerous and why.
Show the operational habits: near-miss diagnostics on no-match, failing a run on a non-zero unmatched count, scrubbing volatile fields at record time, and using the request journal so the case asserts on the outgoing call as well as the reply.
Own the convention across teams - where rules live, who reviews them, how a shared stand-in avoids one team's catch-all swallowing another's narrow case, and whether rule definitions are generated from the provider's published schema rather than hand-written twice.
## The matching model A virtual service is, at heart, a lookup: an incoming request arrives, a set of rules is evaluated against it, and the first or best matching rule supplies the response. Understanding the two halves - the predicates, and the resolution policy when more than one fits - is what separates someone who has configured a stand-in from someone who has only seen one work. ### Predicates Typical predicates, from coarsest to finest: - **Method and path.** The floor. Paths are usually templates, so a variable segment can be captured rather than enumerated. - **Query parameters.** Present, absent, or equal to a value. - **Headers.** Content type, a version or tenant header, an authorization scheme. Matching on the *presence* of an authorization header is often worth doing, because it catches a client that forgot to attach one - a defect a path-only rule would never see. - **Body predicates.** Equality on a whole document, equality ignoring insignificant ordering or whitespace, a value at a particular field position, a pattern, or a schema check. This is the layer people skip, and it is the layer that makes a stand-in an oracle rather than a doorman. ### Resolution When two rules match, something has to decide. Tools differ: some take the first declared, some the last, some the most specific, most allow an explicit priority number. The point for an interview is that you should not leave it implicit. A broad catch-all plus narrow overrides is a perfectly good pattern - as long as the overrides are given priority explicitly, so that adding a rule at the bottom of a file cannot silently change which response an existing case receives. ## Too loose, and why it is the dangerous direction The tax-filing wizard's 340-case regression pack ran against a stand-in whose submission rule matched any request to the submission path and returned a receipt. It stayed green through a refactor that changed how the wizard rounded a monetary field before sending it - because nothing on the stand-in ever looked at the body. The returns filed against the real gateway carried a silently corrupted amount, and the pack had asserted nothing about the request at all: it had asserted that the wizard could reach a URL. That is the shape of the loose-matching defect. The rule is an oracle for *nothing*, and the test's green is unrelated to the behaviour it claims to cover. The fix is to make the rule assert the contract of the request: match on the fields the case is about, and let a wrong body fall through to no match. ## Too strict, and why it is merely expensive The opposite error is a rule pinned to values the client legitimately varies: a generated correlation identifier, a submission timestamp, a nonce, a trace header, a field order that serialization does not guarantee. Now an innocent change breaks 27 cases at once, and the team's response - loosening every rule to path-only in a hurry - converts an expensive problem into a dangerous one. The discipline is to match *semantically*: on the values the case is about, with ignore rules for the ones that must vary, rather than on an entire recorded document byte for byte. ## Unmatched requests The default behaviour for a request that matches nothing is a design decision, and the wrong choice hides defects: - **Blanket success.** Never. A stand-in that answers everything with a success turns every client mistake into a passing test. - **A distinctive failure status plus a diagnostic body.** The right default. The response should be recognisable as *no rule matched* rather than a plausible business error, and the body should echo the request that arrived plus the closest near-miss and which predicate failed on it. Most of the debugging time on a stand-in is spent asking why a rule did not fire, and near-miss output collapses that to seconds. - **Pass-through to the real service.** Useful in a recording mode or during migration, dangerous as a standing default, because a missing rule then silently becomes a live call. A related habit: fail the test run if the unmatched-request count is non-zero, and watch that number over time. On one migration the rate sat at 8.3 percent of all requests, which meant one call in twelve was quietly not being tested at all. ## The other half of the assertion Matching decides what comes back. Verification decides whether what went out was right: a virtual service keeps a journal of received requests, and a case can assert that the submission was sent exactly once, with these fields, in this order relative to another call. Matching plus verification together give you an oracle on both directions of the exchange; matching alone only shapes the reply.
- How do you assert that the client sent the right request, not merely that it received a response?Use the stand-in's request journal. After the case runs, query it for the calls that arrived and assert on count, ordering and payload - that the submission was sent once rather than twice, that the retry carried the same idempotency value, that no call was made at all on the validation-failure path. Matching shapes the reply; verification is what proves the outgoing side.
- Two rules both match an incoming request. How should that be resolved?Deterministically and visibly: an explicit priority on the narrow rule, or a documented most-specific-wins policy. Relying on declaration order means a rule appended by someone else can silently change which response an existing case gets. If the tool cannot express priority, keep the catch-all in a separate file loaded last and treat overlapping narrow rules as a defect to be split.
- A recorded interaction matches on the full body, including a generated correlation identifier. What do you change?Relax the predicate to ignore the fields that legitimately vary - identifiers, timestamps, nonces, trace headers - and keep equality on the fields the case is actually about. Scrubbing volatile values at record time is better than doing it per rule afterwards, because it fixes every recording at once and stops the same brittleness reappearing on the next capture.
saying these in an interview costs you the question
- Matches only on path, then calls the case an integration test
- Returns a blanket success for anything unmatched
- Pins a rule to a timestamp or generated identifier
- Relies on declaration order when two rules overlap
- Never inspects what request the client actually sent
- Loosens every rule to path-only after one brittle failure