skip to content

How do you write a contract with dynamic values, and why are body matchers important?

level: seniorimportance: should knowfreq 38%

answer

  1. value(consumer(regex), producer(example))
  2. $(stub(...), test(...))
  3. bodyMatchers + jsonPath + byRegex/byType
  4. request = match loosely + example to send
  5. response dynamic → matcher, else literal

basics

~20 s

Use the DSL's dynamic helpers so the request is matched by a pattern (regex) while the stub returns a concrete example. On the response, a fixed value doubles as both the returned value and the assertion; matchers let you assert by pattern instead of exact value.

solid answer

~50 s

A contract must serve two masters: the producer test (which asserts the response) and the consumer stub (which must match incoming requests and return an example). For dynamic parts you use the DSL's value/consumer/producer helpers or bodyMatchers. On the request side you typically match by regex (so the stub accepts any id) but pin a concrete example for the generated producer test. On the response, a literal is used both as the returned stub value and as the producer-test assertion; when a field is non-deterministic (UUID, timestamp) you switch to bodyMatchers with jsonPath + byRegex/byType so the producer test asserts a pattern rather than an exact value, otherwise the generated test would fail on every run. Getting this split right — regex for matching, example for generation — is the core skill of authoring good contracts and avoiding brittle or overly-loose stubs.

code

groovy · 23 lines
groovy
Contract.make {
    request {
        method GET()
        // stub matches any digits; producer test sends 42
        url value(consumer(regex('/users/[0-9]+')), producer('/users/42'))
    }
    response {
        status OK()
        headers { contentType(applicationJson()) }
        body([
            id       : 42,
            name     : 'Ada',
            traceId  : anyUuid(),          // dynamic example
            createdAt: '2026-07-22T10:15:30Z'
        ])
        bodyMatchers {
            // assert by pattern, not exact literal, for volatile fields
            jsonPath('$.traceId',   byRegex('[0-9a-fA-F-]{36}'))
            jsonPath('$.createdAt', byTimestamp())
            jsonPath('$.id',        byEquality())  // strictly 42
        }
    }
}

go deeper

for a junior

Know that dynamic fields need a pattern for matching plus a concrete example for generation.

for a middle

Use value(consumer(...), producer(...)) and basic bodyMatchers with jsonPath byRegex/byType.

for a senior

Articulate the request-loose / response-pattern rule and why literals on volatile fields cause flaky producer tests.

for a principal

Set team conventions for matcher strictness, balance strong enforcement vs stub reusability, and translate between Groovy and YAML matcher semantics.

## Why dynamic values are tricky One contract feeds two consumers: the **producer verification test** (needs a concrete request to send and a concrete/pattern response to assert) and the **WireMock stub** (needs a request *matcher* and a canned response *example*). So many fields need both a **matcher** (for the stub / the producer assertion) and an **example** (for generation). ## DSL helpers In the Groovy DSL: - `value(consumer(...), producer(...))` (aliases `$(...)`, `value(stub(...), test(...))`) — lets you specify different representations for the consumer/stub side vs the producer/test side. E.g. request URL id: `value(consumer(regex('[0-9]+')), producer(42))` means the stub matches any digits, but the generated producer test sends `42`. - Convenience regex helpers: `regex('...')`, plus predefined ones like `anyUuid()`, `anyNumber()`, `anyBoolean()`, `iso8601WithOffset()`. ## bodyMatchers (stubMatchers/testMatchers) For JSON bodies you attach a `bodyMatchers { ... }` block using **jsonPath** expressions: - `jsonPath('$.id', byRegex('[0-9]+'))` — assert/match by pattern. - `jsonPath('$.id', byType())` — match by type only. - `byCommand(...)`, `byEquality()`, `byDate()`, `byTimestamp()` for specialized checks. On the **request** these become WireMock request-matching predicates; on the **response** they become the producer test's assertions (so a UUID/timestamp is asserted by pattern, not a fixed literal that would never match twice). ## The core rule - **Request dynamic fields:** match loosely (regex/byType) so the stub is usable by many calls, but give a concrete `producer(...)` example so the generated producer test can actually send a request. - **Response non-deterministic fields:** use response `bodyMatchers` (byRegex/byType/byTimestamp) so the producer assertion doesn't fail on values it can't control; provide an example body for the stub to return. - **Deterministic response fields:** a plain literal doubles as the returned stub value and the exact-match producer assertion. ## Gotchas - Over-loose matchers (everything `byType`) make the contract meaningless — you verify almost nothing. - Over-tight response literals on dynamic fields (a hardcoded UUID/timestamp) make the producer test flaky/failing because the real controller returns different values. - Regex must be anchored/escaped correctly; a sloppy regex can accept unintended inputs into the stub. - YAML DSL expresses the same with `matchers:` sections (request/response) using `type: by_regex`, `by_type`, etc. - Remember request matchers affect *stub matching*; if too strict, the consumer's real request won't match and WireMock returns 404. ## When to use matchers vs literals Use literals for stable, business-meaningful values you want to strictly enforce. Use matchers for ids, generated keys, UUIDs, timestamps, and anything the producer computes at runtime.

  • What happens if you hardcode a UUID literal in a response instead of using a matcher?
    The generated producer test asserts that exact UUID, but the real controller returns a different one each call, so the test fails every run. A response bodyMatcher (byRegex/byType) asserts the shape while letting the actual value vary.
  • If a consumer's request stops matching the stub, what does WireMock return?
    A 404 (no matching mapping). Over-strict request matchers are a common cause: the real consumer request differs from the pinned matcher, so the stub doesn't match and the consumer test fails as if the endpoint were missing.

saying these in an interview costs you the question

  • Putting a fixed timestamp/UUID literal in a response and expecting the producer test to pass.
  • Making every field byType, so the contract verifies nothing meaningful.
  • Confusing which side (request vs response) a matcher constrains.

context