In Mountebank, what does predicateGenerators control on a proxy response?
answer
- only matters when a reply is saved
- it builds the saved stub's predicates
- matches names the request fields copied
- omit it and the stub matches everything
- too tight means proxying forever
basics
~20 sMountebank's predicateGenerators decides which fields of the forwarded request are copied into the predicate on the stub a proxy saves. Set method, path and the query fields that matter; omit it entirely and the saved stub matches everything.
solid answer
~40 s`predicateGenerators` is an array on a Mountebank `proxy` response, and it matters only in `proxyOnce` or `proxyAlways` mode, where the imposter saves each upstream reply as a new stub. A saved reply is useless without predicates, and a generator's `matches` object names the parts of the live request to copy into them: set `method` and `path` to true and nest `query` down to `targetLocale`, and the saved stub is keyed on exactly those three things. Entries also accept `caseSensitive`, `except`, `jsonpath` and `xpath`, which strip a volatile fragment or reach inside a request body. Omit `predicateGenerators` altogether and the saved stub is created with no predicates, so it answers every later request - the commonest reason a proxied glossary imposter starts returning one endpoint's body for another.
code
json · 19 lines{
"responses": [
{
"proxy": {
"to": "https://glossary.example.com",
"mode": "proxyOnce",
"predicateGenerators": [
{
"matches": {
"method": true,
"path": true,
"query": { "targetLocale": true }
}
}
]
}
}
]
}go deeper
Know that Mountebank's predicateGenerators exists on a proxy response and that it shapes the predicates of stubs the proxy saves, not the ones you wrote by hand.
Be able to read a matches object aloud: which request fields it copies, what nesting narrows, and why omitting the generator entirely leaves a saved stub that matches every request.
Show you tune the generator to the fields the response genuinely depends on, and can diagnose both ends - one recorded answer serving every locale, and a proxy that never stops forwarding.
Argue for a house default across many imposters: which fields a generated predicate may key on, which volatile ones must be stripped, and how a team notices a generator has been silently too loose for months.
## The problem `predicateGenerators` solves A Mountebank `proxy` response in `proxyOnce` or `proxyAlways` mode does two things: it forwards the request to the real translation-glossary service, and it saves the reply so the imposter can answer from it later. Saving a reply is only half a stub, though. A Mountebank stub also needs `predicates` — the rules that decide which future requests that saved reply applies to. `predicateGenerators` is the field on the Mountebank `proxy` object that tells the imposter how to build those predicates out of the request it just forwarded. Each entry in the array carries a `matches` object, and every field set to true inside it is copied from the live request into the generated predicate. So a generator whose `matches` sets `method` and `path` produces a saved stub that answers any later request with the same method and path, whatever its query string, headers or body. Nesting narrows it further: `query` nested down to `targetLocale` keys on one query parameter and ignores the rest, and `headers` nested down to `X-Glossary-Revision` keys on one header. ## What happens with no generators at all - With `predicateGenerators` omitted, Mountebank creates the saved stub with **no predicates**, and a stub with no predicates matches every request. - That is the single most common surprise here: a `proxyOnce` stub with no generators makes the imposter answer `GET /v2/languages` with the body it recorded for `GET /v2/glossaries/marine-biology/terms`. - The symptom reads like a matching bug in the specific stubs and is actually a missing generator on the proxy. ## Too loose and too tight `predicateGenerators` is a strictness dial, and both ends hurt in different ways. | generator `matches` | effect on a glossary proxy | |---|---| | omitted | one saved reply answers everything | | `method` + `path` | one reply per endpoint; locale ignored | | `method` + `path` + one query field | one reply per endpoint per target locale | | whole `headers` object | a new saved stub for nearly every request | - **Too loose** collapses distinct calls onto one recorded answer. Two translation requests differing only in `targetLocale` come back identical, and the test that should have caught a locale bug passes. - **Too tight** never stops proxying. Key on the whole `headers` object and a date, a correlation id or a per-run token makes every request unique, so the saved stub never matches, the imposter forwards forever, and Mountebank's `proxyOnce` behaves indistinguishably from `proxyTransparent`. - The rule of thumb is to key on the fields the *response* actually depends on. For a glossary lookup that is the method, the path and the locale parameters, not the transport noise around them. ## The rest of a generator entry A Mountebank generator entry is more than `matches`: 1. `caseSensitive` decides whether the generated comparison respects case, which matters when a client varies header capitalisation between runs. 2. `except` supplies a regular expression whose matches are stripped before comparison, which is how you drop a volatile fragment out of a value you otherwise want to key on. 3. `jsonpath` and `xpath` with a `selector` let a generator key on one field inside a request body rather than the whole document. That is how a proxy in front of `POST /v2/translations` keys on `glossaryId` and ignores a client-generated request id sitting beside it. By default the predicate Mountebank generates is a `deepEquals` over the selected fields, so a nested object is compared in full rather than loosely contained. ## Reading back what you built The practical check is to fetch the imposter and look at the stub the proxy grew: its `predicates` array is the generator's output, written in exactly the syntax you would have typed by hand. If those predicates name a field you did not expect, the generator is the thing to change — not the specific stubs sitting in front of the proxy, and not the mode. ## Why this belongs to selective pass-through Selective pass-through is an arrangement, not a single setting: specific Mountebank stubs answer the glossary paths you faked, and one broad `proxy` stub carries the rest out to the real service. Under `proxyTransparent` that arrangement is stable, because nothing is ever saved. The moment you choose `proxyOnce` or `proxyAlways`, `predicateGenerators` decides how quickly the pass-through half converts itself into more faked stubs, and how coarse those stubs are. A generator one field too loose is exactly how a suite quietly stops testing what its authors believe it tests.
- A Mountebank proxyOnce stub keeps forwarding every request instead of settling down. What do you check first?The generator's `matches` object. If it keys on a field that changes on every call - a whole `headers` object carrying a date or correlation id, or a body containing a client-generated request id - no saved stub can ever match again, so the imposter forwards forever. Narrow `matches` to the fields the response depends on, or use `except` to strip the volatile fragment.
- How would you make a Mountebank proxy key on one field inside a POST body rather than the whole body?Put a `jsonpath` with a `selector` on the generator entry alongside `matches`, so the generated predicate is built from the selected value instead of the whole document. For `POST /v2/translations` that lets the saved stub key on `glossaryId` while ignoring a request id the client regenerates every call.
saying these in an interview costs you the question
- Thinking predicateGenerators filters which requests are proxied
- Assuming an omitted generator produces a sensible default predicate
- Keying on all headers because it feels safest
- Believing generators apply under proxyTransparent
- Confusing a generator's matches with a stub's own predicates