skip to content

In Mountebank, what does predicateGenerators control on a proxy response?

level: middleimportance: should knowfreq 52%

answer

  1. only matters when a reply is saved
  2. it builds the saved stub's predicates
  3. matches names the request fields copied
  4. omit it and the stub matches everything
  5. too tight means proxying forever

basics

~20 s

Mountebank'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
json
{
  "responses": [
    {
      "proxy": {
        "to": "https://glossary.example.com",
        "mode": "proxyOnce",
        "predicateGenerators": [
          {
            "matches": {
              "method": true,
              "path": true,
              "query": { "targetLocale": true }
            }
          }
        ]
      }
    }
  ]
}

go deeper

for a junior

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.

for a middle

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.

for a senior

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.

for a principal

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