awesome-everything RU
↑ Back to the climb

Engineering Practice

Contract testing: code and contract reading

Crux Read real Pact consumer tests, a provider verification setup, and a contract-break diff, then predict the behaviour and pick the highest-leverage fix.
Your altitude — climbing toward senior
ZeroJuniorMiddleSenior
You are at senior altitude — in orbit
◷ 14 min

Contracts are read in code and diffs, not in prose. Read the consumer test, the provider verification wiring, and a real contract-break, then choose the fix a senior engineer would make first.

Goal

Practise the loop you run in every contract incident: read the test, predict what the generated pact will assert, and reach for the fix that keeps the gate precise — without re-coupling the two teams.

Snippet 1 — the consumer test

// checkout-web consumer test (Pact JS)
const { like, integer, string } = MatchersV3;

provider
  .given("a price with id 42 exists")
  .uponReceiving("a request for price 42")
  .withRequest({ method: "GET", path: "/prices/42" })
  .willRespondWith({
    status: 200,
    body: like({
      amount_cents: integer(1299),
      currency: string("USD"),
    }),
  });

await provider.executeTest(async (mock) => {
  const res = await pricingClient(mock.url).getPrice(42);
  expect(res.amountCents).toBe(1299);   // reads amount_cents
});
Quiz

What does the pact this test generates actually assert about amount_cents, and what is the consequence?

Snippet 2 — the provider verification setup

// pricing provider verification (Pact JS)
await new Verifier({
  provider: "pricing",
  providerBaseUrl: "http://localhost:8080",
  pactBrokerUrl: process.env.PACT_BROKER_URL,
  publishVerificationResult: true,
  providerVersion: process.env.GIT_SHA,
  // stateHandlers: { ... }   // <-- intentionally not wired
}).verifyProvider();
Quiz

The consumer pact above uses given('a price with id 42 exists'), but this verifier has no stateHandlers. What happens, and what is the fix?

Snippet 3 — the contract-break diff

  // pricing provider response serializer
  {
    "id": price.id,
-   "amount_cents": price.amountCents,
+   "price_minor_units": price.amountCents,
    "currency": price.currency
  }
Quiz

Three deployed consumers read amount_cents. This rename ships in one PR. The provider's own unit tests are green. What does the contract gate do, and how should the rename actually be shipped?

Snippet 4 — the can-i-deploy gate

# in the consumer's deploy pipeline, keyed to the commit being shipped
pact-broker can-i-deploy \
  --pacticipant checkout-web --version "$GIT_SHA" \
  --to-environment production
# exit 0 => deploy; non-zero => blocked
# ...then, at the very end of a successful rollout:
pact-broker record-deployment \
  --pacticipant checkout-web --version "$GIT_SHA" \
  --environment production
Quiz

Why must record-deployment run at the very end of a successful rollout, and why is can-i-deploy keyed to the git SHA rather than a semver?

Recap

Every contract incident is read in code: a consumer test’s type matchers define what the pact actually asserts (the type, not the example value); a missing stateHandler fails verification for a precondition the provider never seeded, not for a broken contract; a one-PR rename of a consumed field fails verification for every consumer and must ship as expand-then-contract; and the deploy gate only works when versions are git SHAs and record-deployment runs at the end of a successful rollout so the matrix reflects reality. Read the test and the diff, predict the gate’s behaviour, then fix at the level that keeps the gate both precise and decoupled.

Continue the climb ↑Contract testing: build the gate and survive a breaking change
shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.