On this page10 sections

Pact Consumer Contract

Design and document consumer-driven contract tests for the ShopFlow Orders service — specifying the minimum interactions it needs from the User Profile provider, writing Pact-style interactions with provider states and type matchers, and explaining the CDC workflow.

Role

SDET

Difficulty

Advanced

Time limit

2 hr

Category

microservices contract

Pact (PactJS / Pact-JVM / pact-python — choose the language you know)Any HTTP client for reference (Postman or curl)

Scenario

The ShopFlow Orders service depends on the User Profile service for two pieces of information: it calls GET /users/{userId} to retrieve the user's role before approving a purchase (a user with role 'customer' or 'vendor' can place orders; role 'admin' cannot), and it expects a 404 response when a userId does not exist so it can return an appropriate error to the caller. The teams are adopting consumer-driven contract testing using Pact. Your task is to author the consumer side of the contract: define the minimum interactions that the Orders service needs, write the Pact interactions in your language of choice (or as structured pseudocode), and produce the supporting documentation. You are writing tests that describe what the Orders service expects from the User Profile service — not tests of the User Profile service's internal logic.

Requirements

  • 1.Map the consumer's actual dependencies: identify exactly which HTTP call(s) the Orders service makes, which response fields it reads, and why — do not include fields that Orders never uses
  • 2.Write at least three distinct Pact interactions covering: (1) GET /users/{userId} where the user exists and has an order-eligible role, (2) GET /users/{userId} where the user exists but has a non-eligible role (admin), and (3) GET /users/{userId} where the user does not exist
  • 3.For each interaction define: the consumer name, provider name, provider state description, request (method, path, headers), and minimum response (status code and only the fields the consumer actually reads)
  • 4.Use type matchers (e.g. like(), eachLike(), integer(), string()) rather than exact values for dynamic fields such as id — explain the matcher choice for each field
  • 5.Document required vs optional fields from the consumer's perspective: which fields must be present and of the correct type, which fields Orders ignores entirely
  • 6.Write a README-style explanation (300–500 words) of how consumer-driven contract testing differs from testing against a live provider, and what the workflow looks like: consumer writes tests → Pact file generated → provider verifies → result shared
  • 7.List at least three things the consumer contract intentionally does NOT test and explain why each is the provider's responsibility, not the consumer's

Starter data

  • Consumer service: Orders service (Node.js / Java / Python — your choice). It calls User Profile via HTTP.
  • Provider service: User Profile service. Relevant endpoint: GET /users/{userId}
  • UserProfile response shape as documented by the provider team: { id: integer, username: string, email: string, role: string, created_at: string, preferences: object }
  • Fields Orders actually reads from the response: id (to log for audit), username (to display in order confirmation), role (to gate order eligibility)
  • Order-eligibility rule in Orders service: if role is 'customer' or 'vendor', allow the order to proceed; if role is 'admin', reject with 403; if the user is not found (404), reject with 404
  • Authentication: Orders sends Authorization: Bearer <token> on every request to User Profile — include this in the request headers of each interaction
  • Provider states will be set up on the provider side — you define the state name, the provider team implements the hook

Expected deliverables

  • A consumer-dependency map: a table or list stating which HTTP calls Orders makes, which endpoint and method, and which response fields it reads and why
  • At least three Pact interactions in your chosen format (code in any Pact-supported language, or structured pseudocode with consumer, provider, providerState, request, and response clearly labelled)
  • A matcher justification table: for each response field in the contract, state whether it is matched by type (e.g. integer()), by regex, by exact value, or ignored — with a one-sentence rationale
  • Required vs optional fields matrix from the consumer's perspective
  • A README-style CDC workflow explanation (300–500 words): what the consumer tests produce, how the Pact file is shared with the provider, and what the provider does with it
  • A 'not our job' list: at least three provider behaviours that the consumer contract explicitly does not test, with a one-sentence explanation of why each belongs on the provider side

Evaluation rubric

DimensionWhat reviewers look for
Consumer-first thinkingDoes the contract include only the fields and behaviours that the Orders service actually needs? A consumer contract that mirrors the full UserProfile schema (copying all six fields) demonstrates provider-centric thinking — the candidate is testing the provider's contract, not the consumer's needs. The contract should contain only id, username, and role in the response body, and no other fields.
Interaction correctness and completenessAre all three interaction scenarios present (eligible user, non-eligible user, user not found)? Does each interaction include a named provider state, the correct request path and headers (including Authorization), and a response with only the minimum required fields? A common gap is omitting the admin-role interaction — testing only happy-path and 404 misses a distinct eligibility branch that Orders must handle.
Matcher selection and justificationAre type matchers used for dynamic fields (id should be integer(), not the literal value 123)? Is an exact string used for role ('customer') where the exact value drives the consumer's branching logic — as opposed to a generic string() matcher that would allow any role value? The key insight is: match by type where the consumer only cares about the type; match by exact value where the consumer uses the value in a conditional.
Provider state definitionAre provider state names descriptive enough for a provider-side developer to implement the data-setup hook without asking questions? 'a user exists' is too vague. 'user with ID 42 exists with role customer' gives the provider team the exact data precondition. Each state should be a complete sentence that unambiguously describes the data condition required for the interaction to succeed.
CDC workflow understandingDoes the README correctly describe the direction of contract flow? Contracts derive from the CONSUMER's expectations; the consumer tests generate the Pact file; the PROVIDER team verifies their implementation against it — not the other way around. A README that says 'the provider writes the contract' or 'we test against the live provider' indicates a fundamental misunderstanding of CDC.
Not-our-job list accuracyAre the excluded items genuinely provider-side responsibilities? Valid examples: validating that the User Profile service enforces username uniqueness; testing that the provider correctly updates a user's role on PATCH /users/{id}; testing the provider's auth token issuance. Invalid examples: 'we don't test 404' (the consumer must test the 404 interaction because Orders has logic that depends on it) or excluding the admin-role scenario (same reason).

Sample solution outline

  • Consumer-dependency map: Orders service makes one call — GET /users/{userId} with Authorization: Bearer <token>. It reads: id (string for audit log), username (string for display), role (string for eligibility gate). It ignores: email, created_at, preferences.
  • Interaction 1 — eligible user: providerState: 'user with ID 42 exists with role customer'; request: GET /users/42, Authorization: Bearer <token>; response: 200, { id: integer(42), username: string('alice'), role: 'customer' } — role matched exactly because Orders branches on its value
  • Interaction 2 — non-eligible user: providerState: 'user with ID 99 exists with role admin'; request: GET /users/99, Authorization: Bearer <token>; response: 200, { id: integer(99), username: string('bob'), role: 'admin' } — same structure, different role value triggers the 403 branch in Orders
  • Interaction 3 — user not found: providerState: 'no user with ID 9999 exists'; request: GET /users/9999, Authorization: Bearer <token>; response: 404, body: {} or { code: string() } — Orders only checks the status code to decide to return 404 to its caller; body content is not consumed
  • Matcher table: id → integer() (Orders only needs to confirm it is an integer for logging); username → string() (Orders displays it; exact value irrelevant); role → exact value in each interaction ('customer', 'admin') because the consumer's if-statement branches on the literal string
  • CDC workflow (excerpt): The Orders service team writes Pact tests using the consumer DSL. Running the test suite generates a pact.json file containing all three interactions. This file is published to a Pact Broker (or shared as an artefact). The User Profile team then runs their verification step, which replays each interaction against their real service (with provider states set up by hooks) and asserts the actual response matches the contracted response. The result — pass or fail — is reported back. No live cross-service call is made from the consumer's CI pipeline.
  • Not-our-job list: (1) Testing that POST /users correctly creates a user — Orders never calls POST /users; (2) Testing that the provider enforces username uniqueness — that is a provider business rule, not something Orders depends on; (3) Testing that GET /users/{id} returns email in the correct format — Orders ignores the email field entirely

Common mistakes

  • Including all six UserProfile fields in the consumer contract instead of only the three that Orders actually reads — this is the most common mistake and makes the contract fragile: any provider-side change to email, created_at, or preferences will break the consumer's tests even though Orders doesn't use those fields
  • Using exact-value matchers for dynamic fields like id (writing id: 42 instead of integer(42)) — this makes the interaction fail against any test-data setup where the user's ID happens to be different
  • Using a generic type matcher for role (writing string() instead of 'customer') — the consumer's eligibility logic branches on the literal role value, so the exact value must be in the contract for the test to prove the right behaviour
  • Omitting the Authorization header from interactions — if the provider requires a Bearer token, an interaction without it will fail provider verification with an unexpected 401 that looks like a contract failure
  • Writing provider states that are too vague to implement ('a valid user exists') — the provider team cannot write a data-setup hook without knowing which user ID the request path references
  • Describing CDC as 'the provider defines the contract and the consumer validates against it' — this is standard schema testing, not CDC; in CDC the consumer defines what it needs and the provider verifies it can satisfy that need
  • Testing provider business logic from the consumer side (e.g. writing an interaction that tests what happens when the provider rejects an invalid email) — the consumer should only test the interactions that its own code depends on

Submission checklist

  • Consumer-dependency map listing the HTTP call, endpoint, method, and each response field the consumer reads with a reason
  • At least three Pact interactions, each with: consumer name, provider name, provider state, request (method, path, headers), and minimum response (status and body)
  • Matcher justification table: for each response field — matcher type, exact value or not, one-sentence rationale
  • Required vs optional fields matrix from the consumer's perspective
  • README-style CDC workflow explanation (300–500 words) that correctly describes the consumer-generates-contract direction
  • Not-our-job list with at least three excluded provider behaviours and a one-sentence explanation for each
  • No provider-side internal logic tested from the consumer contract

Extension ideas

  • +Implement the three interactions as runnable Pact consumer tests in your language of choice and verify that the generated pact.json file contains the correct interactions
  • +Add a fourth interaction covering a vendor-role user (role: 'vendor') and verify that a vendor-role eligibility test case exists in the Orders service unit tests that depends on it
  • +Set up a mock Pact Broker locally using the pact-broker Docker image and publish your generated pact.json to it, then retrieve it from the 'provider' side to simulate the full broker workflow