In a Karate mock feature, what does `Scenario: pathMatches('/cats/{id}')` match, and where does the `id` value become available?
answer
- Curly braces capture, not a regex
- Segment counts must line up
- Query string is cut off first
- It fills a map the body reads
- Captured values arrive as strings
basics
~20 sIt matches a request path of exactly two segments whose first is literally cats, capturing the second under the name id. On a successful call the helper fills the pathParams map, so the scenario body reads pathParams.id as a string.
solid answer
~40 s`pathMatches()` is a segment-by-segment comparison, not a regex and not a prefix test. Karate strips any query string, splits both the pattern and the request path on `/`, discards empty segments, and requires the **same number of segments**. Literal segments must be equal; a `{name}` segment matches exactly one segment and captures it. So `/cats/{id}` matches `/cats/42` and `/cats/42/` but **not** `/cats` and **not** `/cats/42/toys`. When the call succeeds it populates the `pathParams` map, which the scenario body reads as `pathParams.id` — always a **string**, so `pathParams.id == 42` is false where `pathParams.id == '42'` is true. `pathParams` is only filled by an actual `pathMatches()` call, so you have to call it — normally in the scenario name — before the body can unpack anything.
code
gherkin · 4 linesScenario: pathMatches('/cats/{catId}/toys/{toyId}') && methodIs('get')
* def toy = toys[pathParams.catId][pathParams.toyId]
* def response = toy
* def responseStatus = toy ? 200 : 404go deeper
Recall the two halves: curly braces name a segment, and the body reads it back from pathParams. Writing one get-by-id stub from memory is enough at this level.
Explain the algorithm — query string cut, both sides split on slash, equal segment counts required, literals compared, braces captured — and why there is no wildcard.
Watch for stubs that silently stop matching when a route gains a segment, and for scenarios relying on a pathParams map that an earlier scenario's matcher populated.
Set the convention for how paths are stubbed across a suite, since segment-exact matching means every route variant costs a scenario and drift shows up only as a 404.
## What the helper actually does `pathMatches()` is deliberately small. Given a pattern and the request path it: 1. cuts the request path at the first `?`, so a query string never takes part in the comparison; 2. splits both the pattern and the path on `/`, dropping empty segments (which is why a leading or trailing slash makes no difference); 3. fails immediately if the two lists are **not the same length**; 4. walks the segments pairwise — equal literals pass, a segment written `{name}` captures the corresponding value under `name`, and anything else fails the match; 5. on success, publishes the captured values as the `pathParams` map and returns `true`. There is no regular expression anywhere in that list, and no wildcard segment. ## The consequences worth memorising | Pattern | Request path | Result | |---|---|---| | `/cats/{id}` | `/cats/42` | matches, `pathParams.id` is `'42'` | | `/cats/{id}` | `/cats/42/` | matches — the empty trailing segment is dropped | | `/cats/{id}` | `/cats/42?full=true` | matches — the query string is cut first | | `/cats/{id}` | `/cats` | no match — one segment against two | | `/cats/{id}` | `/cats/42/toys` | no match — three segments against two | | `/cats/{catId}/toys/{toyId}` | `/cats/42/toys/7` | matches, two captures | The "same number of segments" rule is the one people get wrong. A stub written `pathMatches('/cats/{id}')` will *not* answer a nested resource, and a stub written `pathMatches('/cats')` will *not* answer `/cats/42`. Two scenarios are the normal answer; there is no `/cats/**`. ## Reading the captured values `pathParams` is a variable, not a function, and it holds strings: ```gherkin Scenario: pathMatches('/cats/{id}') && methodIs('get') * def cat = cats[pathParams.id] * def response = cat * def responseStatus = cat ? 200 : 404 ``` Two details bite in practice: - **Everything is a string.** `pathParams.id` from `/cats/42` is `'42'`. Use it as a map key (as above) and you are fine; compare it to a number and you are not. `parseInt(pathParams.id)` or `~~pathParams.id` when you need arithmetic. - **It is filled by the call, not by the match.** `pathParams` is only ever populated as a side effect of a successful `pathMatches()` call. If no scenario name in this request has called it, there is nothing to read. And because the helper sets it the moment the path shape fits, a name like `pathMatches('/cats/{id}') && methodIs('get')` leaves `pathParams` populated even when `methodIs` sends the scenario on — a later scenario in the same request can therefore see values it never extracted itself. Call `pathMatches()` in your own name rather than trusting an inherited map. ## When the pattern is not enough The name is JavaScript, so the escape hatch is JavaScript rather than a richer pattern language. For a prefix, a suffix or anything genuinely irregular, match on the request variables directly: - `requestPath.startsWith('/cats/')` — any depth below `/cats`; - `requestPath.indexOf('/toys') != -1` — a segment anywhere in the path; - `requestUri.startsWith('/cats?')` — the path *with* its query string, if you really need it; - `pathMatches('/cats/{id}') && paramExists('full')` — combine the helper with the rest of the request. Note that `requestPath` is the path without the query string and `requestUri` is the path with it; picking the wrong one is a routine source of a stub that never fires. ## How a `pathMatches` stub goes quiet Segment-exact matching means a stub is coupled to the exact shape of the route, and the failures all look the same — the scenario simply stops answering: - the service gains a version prefix and `/cats/{id}` now faces `/v1/cats/42`; - a client starts sending `/cats/42/` from a URL builder that appends a slash — this one is safe, because empty segments are dropped, but the neighbouring `/cats//42` is not; - a sub-resource appears and `/cats/{id}` is asked to serve `/cats/42/toys`; - the id itself contains an encoded slash, which splits into two segments and breaks the count. None of these produces an error at startup or in the file. The evidence is a request answered by the catch-all, or the handler's 404, so a catch-all that echoes `requestPath` pays for itself. ## Interview framing The good answer names three things without prompting: the segment-count rule, `pathParams` as the output, and the fact that the values are strings. The very good answer adds that the pattern language stops there — one placeholder, one segment, no wildcards — and that the remedy is to drop into plain JavaScript over `requestPath`, because the whole matcher is an expression rather than a mapping DSL.
- How would you make one Karate mock scenario answer both `/cats` and `/cats/42`?You would not — `pathMatches()` compares segment counts, so one pattern cannot cover both. Write two scenarios (`pathMatches('/cats')` and `pathMatches('/cats/{id}')`), or, if the two really share a body, match on `requestPath.startsWith('/cats')` and branch inside the scenario on `requestPath`.
- Why does `pathParams.id == 42` fail in a Karate mock when the request was `/cats/42`?Captured path parameters are always strings, so the value is `'42'`. Compare against `'42'`, or convert with `parseInt(pathParams.id)` before doing arithmetic. Using it as a map key works either way, because a JavaScript object key is a string too.
saying these in an interview costs you the question
- Calls the pattern a regular expression
- Expects a two-segment pattern to match three segments
- Thinks a placeholder can span several segments
- Assumes the query string takes part in the match
- Compares a captured value to a number