skip to content

How do you test that requests to an unknown path or an unsupported method reach a framework's fallback handlers?

level: middleimportance: should knowfreq 46%

answer

  1. no route matched, so no handler exists
  2. dispatch it, do not call it
  3. two distinct statuses, not one family
  4. assert the body, not just the status
  5. a catch-all can swallow the case

basics

~20 s

Send requests that deliberately miss the routing table: a path no template matches, and a known path with an undeclared method. Assert the exact status for each, the contracted error body, and the header the protocol requires.

solid answer

~40 s

These two cases are answered by the framework itself, without any route matching, so a test must reach them through the dispatcher rather than by calling a handler. Send one request to a path deliberately outside every template, and one to a known path with a method that route does not declare. Assert the exact status for each, and assert the body against the same error contract the service's own failures use - a mismatched body here is the usual drift, because these responses are produced by framework defaults that nobody customised. For the method case, also assert the header the protocol requires on that answer. It is worth pinning a path that once existed and was removed, since a leftover catch-all route or a static-file handler can quietly swallow it.

go deeper

for a junior

Recall the two requests to send - a path nothing matches, and a known path with an undeclared method - and that each has its own status to assert exactly.

for a middle

Explain that no handler is involved, so the test must go through dispatch, and why the response body deserves an assertion of its own.

for a senior

Show how you keep these cases meaningful as the routing surface grows: probing a removed path, guarding against catch-all mounts, and choosing paths relative to protected prefixes.

for a principal

Decide whether fallback answers should be reshaped to the service's error contract across every service, and what test keeps that decision from eroding.

## Why these two cases need their own tests Most of a web-layer suite exercises requests that match a route. The two cases here are the opposite: the request is dispatched and **no route match happens at all** (unknown path), or a route with that path exists but not for the method presented (method mismatch). The response is produced by the framework's fallback handler, not by application code, which has three consequences a test must respect. First, the case cannot be reached by invoking a handler function directly - there is no handler to invoke. The request has to go through the dispatcher, so the test needs the slice that includes routing. Second, the response body is whatever the framework produces by default unless somebody replaced it. That default is almost never the error shape the service publishes for its own failures, so clients that parse errors generically break on exactly these responses. The test's job is to pin the body, not only the status. Third, these answers are the ones most likely to change silently. Adding a catch-all route, mounting a static-file handler at the root, or attaching a prefix group can absorb requests that used to fall through, so the fallback stops firing and nobody notices until a client reports a strange response. ## What each case should assert 1. **Unknown path.** Request a path that no template can match, including under any wildcard or prefix mount. Assert the exact status, the contracted error body with its machine-readable code, the content type, and that no application collaborator was invoked. 2. **Method mismatch.** Request a path a route does declare, using a method that route does not. Assert the exact status - distinct from the unknown-path status - the error body, and the header the protocol mandates on that answer, whose value should list the methods the route really declares. 3. **A removed path.** Request something that existed in an earlier version. This is the regression guard against a catch-all or static mount having grown over it. | Case the test sends | Who produces the answer | The assertion that carries the value | |---|---|---| | Path outside every template | The framework's no-match fallback | Exact status plus the contracted error body | | Known path, undeclared method | The framework's method-mismatch fallback | Exact status plus the protocol-required header | | Path deleted in a past release | Ideally the no-match fallback | That a catch-all or static mount has not absorbed it | ## The traps **Asserting a status family instead of a status.** A test that accepts "any client-error status" cannot tell an unknown path from a method mismatch, which is the one distinction these two cases exist to make. **Choosing a path that is accidentally matchable.** If the suite's unknown path happens to sit under a wildcard segment or a prefix group, the test proves nothing about the fallback and instead exercises whatever matched. Pick a path that is obviously outside the tree and, where the framework can print the routing table, assert against that table too. **Forgetting that a guard may answer first.** On a service that protects a whole prefix, a request to an unknown path *inside* that prefix may be refused by the guard before routing is consulted, so the fallback never runs. That is a legitimate design - it avoids telling an anonymous caller which paths exist - but a test that expected the no-match answer there will be confusing rather than wrong. Choose the path deliberately: outside the protected prefix when the subject is the fallback, inside it when the subject is the guard's reach. **Trusting the default body.** Framework defaults differ: some produce a structured body, some a short text line, some an empty body with only the status. A suite that asserts only a status will not notice that half of the service's error surface does not match the published contract. Where the team has installed custom fallback handlers so that these answers follow the same error shape as everything else, this test is what keeps that installation in place - remove the custom handler and the body assertion fails immediately. ## Where these tests live They belong with the routing configuration they protect rather than scattered through feature suites, because their subject is the application's routing surface as a whole. A small file with the three cases above, plus one per protected prefix where the guard is expected to answer before routing, is usually the whole coverage - and it is the file that fails first when someone mounts a catch-all in the wrong place.

  • Why can these cases not be covered by testing a handler function directly?
    Because there is no handler involved. The answer is produced when matching fails or when the matched route rejects the method, both of which happen in the dispatcher. A test must send the request through routing; anything below that level has already assumed a successful match.
  • Why assert the response body on a fallback and not only the status?
    These bodies come from framework defaults unless somebody replaced them, and those defaults rarely match the error shape the service publishes. Clients that parse errors generically then break on exactly these responses. Asserting the body is also what keeps a custom fallback handler installed, since removing it fails the test.
  • Your unknown-path test suddenly returns a success response. What changed?
    Something now matches that path - most often a catch-all route, a wildcard segment, or a static-file handler mounted at a prefix that covers it. The routing table grew over the gap the test was probing, which is precisely the regression the case exists to catch.

saying these in an interview costs you the question

  • Asserts only that the status is in the client-error family
  • Tries to reach the case by calling a handler directly
  • Assumes the default fallback body matches the service's error contract
  • Picks an unknown path that a wildcard segment quietly matches
  • Expects the no-match answer inside a prefix a guard protects