[{"data":1,"prerenderedAt":1068},["ShallowReactive",2],{"page-\u002Fintegration-database-and-service-testing\u002Fcontract-testing-for-http-apis\u002Fconsumer-driven-contract-tests-with-pact-python\u002F":3},{"id":4,"title":5,"body":6,"description":1031,"extension":1032,"meta":1033,"navigation":73,"path":1064,"seo":1065,"stem":1066,"__hash__":1067},"content\u002Fintegration-database-and-service-testing\u002Fcontract-testing-for-http-apis\u002Fconsumer-driven-contract-tests-with-pact-python\u002Findex.md","Consumer-Driven Contract Tests with Pact Python",{"type":7,"value":8,"toc":1020},"minimark",[9,13,18,44,48,51,243,246,304,495,499,506,513,517,562,566,569,575,585,591,594,705,708,711,715,718,829,832,930,934,941,949,956,960,966,972,978,982,1011,1016],[10,11,12],"p",{},"Integration tests prove that the consumer works against whatever the provider is running now. Consumer-driven contracts prove something more useful: that the provider still satisfies what its consumers depend on, checked in the provider's own pipeline, so a breaking change fails the build of the team that made it. The mechanism is a pact — a JSON file of request\u002Fresponse pairs written by the consumer's tests and replayed against the real provider.",[14,15,17],"h2",{"id":16},"prerequisites","Prerequisites",[19,20,21,29,32,35],"ul",{},[22,23,24,28],"li",{},[25,26,27],"code",{},"pact-python >= 2.2",", which bundles the Pact mock server and verifier.",[22,30,31],{},"A consumer client that can be pointed at an arbitrary base URL, so the test can aim it at the Pact mock.",[22,33,34],{},"The provider running in its own test pipeline, able to accept a state-setup call.",[22,36,37,38,43],{},"The overview in ",[39,40,42],"a",{"href":41},"\u002Fintegration-database-and-service-testing\u002Fcontract-testing-for-http-apis\u002F","contract testing for HTTP APIs",".",[14,45,47],{"id":46},"solution","Solution",[10,49,50],{},"The consumer side declares interactions and exercises its real client against the mock:",[52,53,58],"pre",{"className":54,"code":55,"language":56,"meta":57,"style":57},"language-python shiki shiki-themes github-light github-dark","import atexit\n\nimport pytest\nfrom pact import Consumer, Like, Provider, Term\n\nfrom checkout.billing import BillingClient\n\npact = Consumer(\"checkout\").has_pact_with(\n    Provider(\"billing\"), host_name=\"localhost\", port=1234, pact_dir=\"pacts\"\n)\npact.start_service()\natexit.register(pact.stop_service)\n\n\ndef test_reads_an_open_invoice():\n    expected = {\n        \"id\": Like(\"inv_123\"),                        # any string\n        \"total_minor\": Like(1234),                    # any integer\n        \"currency\": Term(r\"^[A-Z]{3}$\", \"GBP\"),       # matches the pattern\n        \"status\": \"open\",                              # literal: the consumer branches on it\n    }\n    (pact\n     .given(\"an open invoice exists\")\n     .upon_receiving(\"a request for an invoice\")\n     .with_request(\"GET\", \"\u002Finvoices\u002Finv_123\")\n     .will_respond_with(200, body=expected))\n\n    with pact:                                         # verifies the request was made\n        invoice = BillingClient(base_url=pact.uri).fetch(\"inv_123\")\n\n    assert invoice.is_open\n","python","",[25,59,60,68,75,81,87,92,98,103,109,115,121,127,133,138,143,149,155,161,167,173,179,185,191,197,203,209,215,220,226,232,237],{"__ignoreMap":57},[61,62,65],"span",{"class":63,"line":64},"line",1,[61,66,67],{},"import atexit\n",[61,69,71],{"class":63,"line":70},2,[61,72,74],{"emptyLinePlaceholder":73},true,"\n",[61,76,78],{"class":63,"line":77},3,[61,79,80],{},"import pytest\n",[61,82,84],{"class":63,"line":83},4,[61,85,86],{},"from pact import Consumer, Like, Provider, Term\n",[61,88,90],{"class":63,"line":89},5,[61,91,74],{"emptyLinePlaceholder":73},[61,93,95],{"class":63,"line":94},6,[61,96,97],{},"from checkout.billing import BillingClient\n",[61,99,101],{"class":63,"line":100},7,[61,102,74],{"emptyLinePlaceholder":73},[61,104,106],{"class":63,"line":105},8,[61,107,108],{},"pact = Consumer(\"checkout\").has_pact_with(\n",[61,110,112],{"class":63,"line":111},9,[61,113,114],{},"    Provider(\"billing\"), host_name=\"localhost\", port=1234, pact_dir=\"pacts\"\n",[61,116,118],{"class":63,"line":117},10,[61,119,120],{},")\n",[61,122,124],{"class":63,"line":123},11,[61,125,126],{},"pact.start_service()\n",[61,128,130],{"class":63,"line":129},12,[61,131,132],{},"atexit.register(pact.stop_service)\n",[61,134,136],{"class":63,"line":135},13,[61,137,74],{"emptyLinePlaceholder":73},[61,139,141],{"class":63,"line":140},14,[61,142,74],{"emptyLinePlaceholder":73},[61,144,146],{"class":63,"line":145},15,[61,147,148],{},"def test_reads_an_open_invoice():\n",[61,150,152],{"class":63,"line":151},16,[61,153,154],{},"    expected = {\n",[61,156,158],{"class":63,"line":157},17,[61,159,160],{},"        \"id\": Like(\"inv_123\"),                        # any string\n",[61,162,164],{"class":63,"line":163},18,[61,165,166],{},"        \"total_minor\": Like(1234),                    # any integer\n",[61,168,170],{"class":63,"line":169},19,[61,171,172],{},"        \"currency\": Term(r\"^[A-Z]{3}$\", \"GBP\"),       # matches the pattern\n",[61,174,176],{"class":63,"line":175},20,[61,177,178],{},"        \"status\": \"open\",                              # literal: the consumer branches on it\n",[61,180,182],{"class":63,"line":181},21,[61,183,184],{},"    }\n",[61,186,188],{"class":63,"line":187},22,[61,189,190],{},"    (pact\n",[61,192,194],{"class":63,"line":193},23,[61,195,196],{},"     .given(\"an open invoice exists\")\n",[61,198,200],{"class":63,"line":199},24,[61,201,202],{},"     .upon_receiving(\"a request for an invoice\")\n",[61,204,206],{"class":63,"line":205},25,[61,207,208],{},"     .with_request(\"GET\", \"\u002Finvoices\u002Finv_123\")\n",[61,210,212],{"class":63,"line":211},26,[61,213,214],{},"     .will_respond_with(200, body=expected))\n",[61,216,218],{"class":63,"line":217},27,[61,219,74],{"emptyLinePlaceholder":73},[61,221,223],{"class":63,"line":222},28,[61,224,225],{},"    with pact:                                         # verifies the request was made\n",[61,227,229],{"class":63,"line":228},29,[61,230,231],{},"        invoice = BillingClient(base_url=pact.uri).fetch(\"inv_123\")\n",[61,233,235],{"class":63,"line":234},30,[61,236,74],{"emptyLinePlaceholder":73},[61,238,240],{"class":63,"line":239},31,[61,241,242],{},"    assert invoice.is_open\n",[10,244,245],{},"The provider side replays every interaction against its real implementation:",[52,247,249],{"className":54,"code":248,"language":56,"meta":57,"style":57},"# In the PROVIDER's repository\nfrom pact import Verifier\n\n\ndef test_billing_satisfies_checkout(live_provider_url):\n    verifier = Verifier(provider=\"billing\", provider_base_url=live_provider_url)\n    exit_code, _ = verifier.verify_pacts(\n        \"pacts\u002Fcheckout-billing.json\",\n        provider_states_setup_url=f\"{live_provider_url}\u002F_pact\u002Fprovider-states\",\n    )\n    assert exit_code == 0\n",[25,250,251,256,261,265,269,274,279,284,289,294,299],{"__ignoreMap":57},[61,252,253],{"class":63,"line":64},[61,254,255],{},"# In the PROVIDER's repository\n",[61,257,258],{"class":63,"line":70},[61,259,260],{},"from pact import Verifier\n",[61,262,263],{"class":63,"line":77},[61,264,74],{"emptyLinePlaceholder":73},[61,266,267],{"class":63,"line":83},[61,268,74],{"emptyLinePlaceholder":73},[61,270,271],{"class":63,"line":89},[61,272,273],{},"def test_billing_satisfies_checkout(live_provider_url):\n",[61,275,276],{"class":63,"line":94},[61,277,278],{},"    verifier = Verifier(provider=\"billing\", provider_base_url=live_provider_url)\n",[61,280,281],{"class":63,"line":100},[61,282,283],{},"    exit_code, _ = verifier.verify_pacts(\n",[61,285,286],{"class":63,"line":105},[61,287,288],{},"        \"pacts\u002Fcheckout-billing.json\",\n",[61,290,291],{"class":63,"line":111},[61,292,293],{},"        provider_states_setup_url=f\"{live_provider_url}\u002F_pact\u002Fprovider-states\",\n",[61,295,296],{"class":63,"line":117},[61,297,298],{},"    )\n",[61,300,301],{"class":63,"line":123},[61,302,303],{},"    assert exit_code == 0\n",[305,306,309,491],"figure",{"className":307},[308],"diagram",[310,311,318,319,318,323,318,327,318,345,318,353,318,362,318,372,318,378,318,388,318,394,318,399,318,405,318,411,318,415,318,418,318,423,318,428,318,432,318,437,318,445,318,448,318,452,318,455,318,459,318,462,318,465,318,469,318,472,318,476,318,483,318,487],"svg",{"viewBox":312,"role":313,"ariaLabelledBy":314,"xmlns":317},"0 0 820 268","img",[315,316],"pact-t","pact-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[320,321,322],"title",{"id":315},"The pact lifecycle across two repositories",[324,325,326],"desc",{"id":316},"In the consumer repository, tests exercise the real client against the Pact mock server, which records each interaction into a pact file. The file is published. In the provider repository, the verifier fetches the pact, calls the state-setup endpoint for each interaction, replays the request against the running provider, and compares the response with the consumer's matchers, failing the provider's build on a mismatch.",[328,329,330,331,318],"defs",{},"\n    ",[332,333,340],"marker",{"id":334,"viewBox":335,"refX":336,"refY":337,"markerWidth":338,"markerHeight":338,"orient":339},"pact-a","0 0 10 10","9","5","7","auto-start-reverse",[341,342],"path",{"d":343,"fill":344},"M0 0 L10 5 L0 10 z","#3d405b",[346,347],"rect",{"x":348,"y":348,"width":349,"height":350,"rx":351,"fill":352},"0","820","268","14","#fffdf8",[354,355,361],"text",{"x":356,"y":357,"textAnchor":358,"fontSize":359,"fontWeight":360,"fill":344},"410","28","middle","16","700","Written by the consumer, enforced on the provider",[346,363],{"x":364,"y":365,"width":366,"height":367,"rx":368,"fill":369,"stroke":370,"strokeWidth":371},"26","52","360","190","12","#f4f1de","rgba(61,64,91,0.35)","1.5",[354,373,377],{"x":374,"y":375,"textAnchor":358,"fontSize":376,"fontWeight":360,"fill":344},"206","76","12.5","consumer repository",[346,379],{"x":380,"y":381,"width":382,"height":383,"rx":384,"fill":385,"stroke":386,"strokeWidth":387},"46","92","150","54","10","#e6f0ea","#81b29a","1.8",[354,389,393],{"x":390,"y":391,"textAnchor":358,"fontSize":392,"fontWeight":360,"fill":344},"121","116","11.5","real client",[354,395,398],{"x":390,"y":396,"textAnchor":358,"fontSize":397,"fill":344},"134","10.5","in a test",[63,400],{"x1":401,"y1":402,"x2":403,"y2":402,"stroke":344,"strokeWidth":371,"markerEnd":404},"200","119","222","url(#pact-a)",[346,406],{"x":407,"y":381,"width":408,"height":383,"rx":384,"fill":409,"stroke":410,"strokeWidth":387},"228","138","#f7f0da","#f2cc8f",[354,412,414],{"x":413,"y":391,"textAnchor":358,"fontSize":392,"fontWeight":360,"fill":344},"297","Pact mock",[354,416,417],{"x":413,"y":396,"textAnchor":358,"fontSize":397,"fill":344},"records",[346,419],{"x":420,"y":421,"width":421,"height":365,"rx":384,"fill":352,"stroke":344,"strokeWidth":422},"120","170","1.6",[354,424,427],{"x":425,"y":426,"textAnchor":358,"fontSize":392,"fontWeight":360,"fill":344},"205","194","pact JSON",[354,429,431],{"x":425,"y":430,"textAnchor":358,"fontSize":397,"fill":344},"211","published",[63,433],{"x1":413,"y1":382,"x2":434,"y2":435,"stroke":344,"strokeWidth":436,"markerEnd":404},"240","166","1.4",[63,438],{"x1":439,"y1":440,"x2":441,"y2":440,"stroke":344,"strokeWidth":422,"strokeDashArray":442,"markerEnd":404},"294","196","430",[443,444],"6","4",[346,446],{"x":447,"y":365,"width":366,"height":367,"rx":368,"fill":369,"stroke":370,"strokeWidth":371},"434",[354,449,451],{"x":450,"y":375,"textAnchor":358,"fontSize":376,"fontWeight":360,"fill":344},"614","provider repository",[346,453],{"x":454,"y":381,"width":382,"height":383,"rx":384,"fill":409,"stroke":410,"strokeWidth":387},"454",[354,456,458],{"x":457,"y":391,"textAnchor":358,"fontSize":392,"fontWeight":360,"fill":344},"529","set up state",[354,460,461],{"x":457,"y":396,"textAnchor":358,"fontSize":397,"fill":344},"by name",[346,463],{"x":464,"y":381,"width":382,"height":383,"rx":384,"fill":385,"stroke":386,"strokeWidth":387},"624",[354,466,468],{"x":467,"y":391,"textAnchor":358,"fontSize":392,"fontWeight":360,"fill":344},"699","replay + compare",[354,470,471],{"x":467,"y":396,"textAnchor":358,"fontSize":397,"fill":344},"real provider",[63,473],{"x1":474,"y1":402,"x2":475,"y2":402,"stroke":344,"strokeWidth":371,"markerEnd":404},"608","620",[346,477],{"x":478,"y":421,"width":479,"height":365,"rx":384,"fill":480,"stroke":481,"strokeWidth":482},"534","220","#fbe9e3","#e07a5f","2",[354,484,486],{"x":485,"y":426,"textAnchor":358,"fontSize":392,"fontWeight":360,"fill":344},"644","mismatch fails",[354,488,490],{"x":485,"y":430,"textAnchor":358,"fontSize":397,"fill":489},"#8f3d22","the provider's build",[492,493,494],"figcaption",{},"The dashed arrow is the whole point: the consumer's expectations travel to the repository where a breaking change would be made, and are enforced there.",[14,496,498],{"id":497},"why-this-works","Why this works",[10,500,501,502,505],{},"During the consumer test, the Pact mock server answers requests according to the declared interactions and records exactly what the client sent. If the client sends something not declared, the test fails; if a declared interaction was never exercised, the ",[25,503,504],{},"with pact:"," block fails on exit. The resulting pact therefore describes real client behaviour, not an author's guess about it.",[10,507,508,509,512],{},"On the provider side, the verifier replays each recorded request against the running service after asking it to set up the named state, and compares the response to the consumer's matchers. Matchers are what keep this from becoming brittle: ",[25,510,511],{},"Like(1234)"," passes for any integer, so the provider's test data can differ from the consumer's example without failing verification.",[14,514,516],{"id":515},"edge-cases-and-failure-modes","Edge cases and failure modes",[19,518,519,538,544,550,556],{},[22,520,521,525,526,529,530,533,534,537],{},[522,523,524],"strong",{},"Literal values everywhere."," Every literal in a pact is a claim that the provider must return exactly that value. Use ",[25,527,528],{},"Like",", ",[25,531,532],{},"EachLike"," and ",[25,535,536],{},"Term"," except where the consumer genuinely branches on the value.",[22,539,540,543],{},[522,541,542],{},"Matching the whole response body."," A pact mirroring every field the provider returns makes every additive change a failed verification. Declare only the fields the consumer reads.",[22,545,546,549],{},[522,547,548],{},"Over-specific provider states."," \"Invoice inv_9f3c exists with total 4211 in EUR\" is a setup routine the provider must write for one consumer. Prefer a small vocabulary of general states.",[22,551,552,555],{},[522,553,554],{},"Unimplemented state names."," A provider that silently ignores unknown states verifies interactions against the wrong data. Raise on an unknown state.",[22,557,558,561],{},[522,559,560],{},"Verifying draft pacts."," A consumer's work-in-progress branch publishing pacts can fail the provider's main build. Verify only pacts tagged for deployed consumer versions.",[14,563,565],{"id":564},"choosing-matchers-deliberately","Choosing matchers deliberately",[10,567,568],{},"Matchers are where most pacts go wrong, in both directions. Too strict and verification fails on harmless changes; too loose and the pact stops protecting anything. Four matchers cover nearly every case, and the choice between them follows from how the consumer uses each field.",[10,570,571,574],{},[25,572,573],{},"Like(example)"," says \"a value of this type\". Use it for identifiers, amounts, names and timestamps the consumer passes through or displays without branching on them.",[10,576,577,580,581,584],{},[25,578,579],{},"EachLike(example, minimum=1)"," says \"a list whose items look like this\". Use it for collections; the ",[25,582,583],{},"minimum"," expresses whether the consumer copes with an empty list, which is itself a contract worth stating.",[10,586,587,590],{},[25,588,589],{},"Term(regex, example)"," says \"a string matching this pattern\". Use it for formatted values the consumer parses — currency codes, ISO dates, UUIDs — where the format matters but the value does not.",[10,592,593],{},"A literal says \"exactly this value\". Use it only where the consumer branches: an enum it switches on, a status that drives behaviour. Every literal is a promise the provider must keep, so each one should be a value the consumer genuinely cannot do without.",[305,595,597,694],{"className":596},[308],[310,598,318,603,318,606,318,609,318,613,318,618,318,623,318,628,318,631,318,635,318,638,318,642,318,648,318,651,318,654,318,658,318,660,318,663,318,667,318,670,318,673,318,677,318,680,318,683,318,688,318,691],{"viewBox":599,"role":313,"ariaLabelledBy":600,"xmlns":317},"0 0 800 244",[601,602],"mt-t","mt-d",[320,604,605],{"id":601},"Choosing a matcher from how the consumer uses a field",[324,607,608],{"id":602},"Four rows. A field passed through or displayed uses Like. A collection uses EachLike with a minimum length. A formatted string the consumer parses uses Term with a regular expression. A value the consumer branches on uses a literal, and each literal is a promise the provider must keep exactly.",[346,610],{"x":348,"y":348,"width":611,"height":612,"rx":351,"fill":352},"800","244",[354,614,617],{"x":615,"y":357,"textAnchor":358,"fontSize":616,"fontWeight":360,"fill":344},"400","15.5","Strictness should follow usage",[346,619],{"x":364,"y":620,"width":621,"height":622,"rx":336,"fill":369,"stroke":344,"strokeWidth":371},"48","330","36",[354,624,627],{"x":625,"y":626,"textAnchor":358,"fontSize":392,"fontWeight":360,"fill":344},"191","71","how the consumer uses it",[346,629],{"x":630,"y":620,"width":421,"height":622,"rx":336,"fill":369,"stroke":344,"strokeWidth":371},"366",[354,632,634],{"x":633,"y":626,"textAnchor":358,"fontSize":392,"fontWeight":360,"fill":344},"451","matcher",[346,636],{"x":637,"y":620,"width":407,"height":622,"rx":336,"fill":369,"stroke":344,"strokeWidth":371},"546",[354,639,641],{"x":640,"y":626,"textAnchor":358,"fontSize":392,"fontWeight":360,"fill":344},"660","provider may change",[354,643,647],{"x":644,"y":645,"fontSize":646,"fill":344},"42","112","11","displays or passes through",[354,649,528],{"x":633,"y":645,"textAnchor":358,"fontSize":646,"fill":650},"#2a5f49",[354,652,653],{"x":640,"y":645,"textAnchor":358,"fontSize":646,"fill":344},"the value",[354,655,657],{"x":644,"y":656,"fontSize":646,"fill":344},"146","iterates over a list",[354,659,532],{"x":633,"y":656,"textAnchor":358,"fontSize":646,"fill":650},[354,661,662],{"x":640,"y":656,"textAnchor":358,"fontSize":646,"fill":344},"length and values",[354,664,666],{"x":644,"y":665,"fontSize":646,"fill":344},"180","parses a formatted string",[354,668,536],{"x":633,"y":665,"textAnchor":358,"fontSize":646,"fill":669},"#8a5a00",[354,671,672],{"x":640,"y":665,"textAnchor":358,"fontSize":646,"fill":344},"value, not format",[354,674,676],{"x":644,"y":675,"fontSize":646,"fill":344},"214","branches on the value",[354,678,679],{"x":633,"y":675,"textAnchor":358,"fontSize":646,"fill":489},"literal",[354,681,682],{"x":640,"y":675,"textAnchor":358,"fontSize":646,"fill":489},"nothing",[63,684],{"x1":364,"y1":685,"x2":686,"y2":685,"stroke":687,"strokeWidth":436},"124","774","rgba(61,64,91,0.14)",[63,689],{"x1":364,"y1":690,"x2":686,"y2":690,"stroke":687,"strokeWidth":436},"158",[63,692],{"x1":364,"y1":693,"x2":686,"y2":693,"stroke":687,"strokeWidth":436},"192",[492,695,696,697,700,701,704],{},"Reading a pact's literals is a quick audit of the consumer's real dependencies. Every one should correspond to an ",[25,698,699],{},"if"," or a ",[25,702,703],{},"match"," somewhere in the consumer's code.",[10,706,707],{},"A practical review habit follows. For each literal in a new interaction, find the line of consumer code that branches on it. If there is none, the literal should be a matcher, and turning it into one removes a future false alarm from the provider's pipeline before it ever happens.",[10,709,710],{},"The inverse audit is just as useful on the provider side. When verification fails, the first question is whether the failing assertion is on a literal or a matcher. A failed matcher means the provider really did change a type or remove a field, which is a genuine breaking change to negotiate. A failed literal on a value the consumer does not actually branch on is a pact that was written too strictly, and the fix belongs in the consumer's repository rather than the provider's. Keeping that distinction clear stops contract testing from becoming a source of friction between teams, which is the usual reason it gets abandoned after an initial burst of enthusiasm.",[14,712,714],{"id":713},"implementing-provider-states-that-stay-fast","Implementing provider states that stay fast",[10,716,717],{},"A pact with thirty interactions triggers thirty state setups during verification, and if each one truncates tables and reseeds, verification takes minutes. The provider-state endpoint should use the same transactional isolation as the rest of the provider's tests.",[52,719,721],{"className":54,"code":720,"language":56,"meta":57,"style":57},"# Provider: a test-only endpoint the verifier calls before each interaction.\nfrom fastapi import APIRouter, Depends\n\nrouter = APIRouter()\n\nSTATES = {\n    \"an open invoice exists\": lambda db: db.add(Invoice(id=\"inv_123\", status=\"open\",\n                                                         total_minor=1234, currency=\"GBP\")),\n    \"no invoice exists\": lambda db: None,\n}\n\n\n@router.post(\"\u002F_pact\u002Fprovider-states\")\ndef set_state(body: dict, db=Depends(get_test_session)):\n    state = body[\"state\"]\n    if state not in STATES:\n        # Loud, not silent: an unknown state means a new consumer expectation.\n        raise ValueError(f\"unknown provider state: {state!r}\")\n    db.rollback()                 # discard the previous interaction's data\n    STATES[state](db)\n    db.flush()\n    return {\"ok\": True}\n",[25,722,723,728,733,737,742,746,751,756,761,766,771,775,779,784,789,794,799,804,809,814,819,824],{"__ignoreMap":57},[61,724,725],{"class":63,"line":64},[61,726,727],{},"# Provider: a test-only endpoint the verifier calls before each interaction.\n",[61,729,730],{"class":63,"line":70},[61,731,732],{},"from fastapi import APIRouter, Depends\n",[61,734,735],{"class":63,"line":77},[61,736,74],{"emptyLinePlaceholder":73},[61,738,739],{"class":63,"line":83},[61,740,741],{},"router = APIRouter()\n",[61,743,744],{"class":63,"line":89},[61,745,74],{"emptyLinePlaceholder":73},[61,747,748],{"class":63,"line":94},[61,749,750],{},"STATES = {\n",[61,752,753],{"class":63,"line":100},[61,754,755],{},"    \"an open invoice exists\": lambda db: db.add(Invoice(id=\"inv_123\", status=\"open\",\n",[61,757,758],{"class":63,"line":105},[61,759,760],{},"                                                         total_minor=1234, currency=\"GBP\")),\n",[61,762,763],{"class":63,"line":111},[61,764,765],{},"    \"no invoice exists\": lambda db: None,\n",[61,767,768],{"class":63,"line":117},[61,769,770],{},"}\n",[61,772,773],{"class":63,"line":123},[61,774,74],{"emptyLinePlaceholder":73},[61,776,777],{"class":63,"line":129},[61,778,74],{"emptyLinePlaceholder":73},[61,780,781],{"class":63,"line":135},[61,782,783],{},"@router.post(\"\u002F_pact\u002Fprovider-states\")\n",[61,785,786],{"class":63,"line":140},[61,787,788],{},"def set_state(body: dict, db=Depends(get_test_session)):\n",[61,790,791],{"class":63,"line":145},[61,792,793],{},"    state = body[\"state\"]\n",[61,795,796],{"class":63,"line":151},[61,797,798],{},"    if state not in STATES:\n",[61,800,801],{"class":63,"line":157},[61,802,803],{},"        # Loud, not silent: an unknown state means a new consumer expectation.\n",[61,805,806],{"class":63,"line":163},[61,807,808],{},"        raise ValueError(f\"unknown provider state: {state!r}\")\n",[61,810,811],{"class":63,"line":169},[61,812,813],{},"    db.rollback()                 # discard the previous interaction's data\n",[61,815,816],{"class":63,"line":175},[61,817,818],{},"    STATES[state](db)\n",[61,820,821],{"class":63,"line":181},[61,822,823],{},"    db.flush()\n",[61,825,826],{"class":63,"line":187},[61,827,828],{},"    return {\"ok\": True}\n",[10,830,831],{},"A small dictionary of named states, each a line or two, stays readable as consumers multiply — and a new state appearing in a consumer's pact fails verification with a clear message until someone deliberately adds it here.",[305,833,835,927],{"className":834},[308],[310,836,318,841,318,844,318,847,318,854,318,857,318,860,318,864,318,868,318,871,318,875,318,878,318,882,318,889,318,891,318,895,318,899,318,904,318,908,318,912,318,916,318,919,318,924],{"viewBox":837,"role":313,"ariaLabelledBy":838,"xmlns":317},"0 0 800 234",[839,840],"st-t","st-d",[320,842,843],{"id":839},"State vocabulary shared by many consumers",[324,845,846],{"id":840},"Three consumer pacts each reference a small set of named states such as an open invoice exists and no invoice exists. The provider implements that vocabulary once, as a dictionary of setup functions. A state name not in the vocabulary fails verification with an explicit error rather than being ignored.",[328,848,330,849,318],{},[332,850,852],{"id":851,"viewBox":335,"refX":336,"refY":337,"markerWidth":338,"markerHeight":338,"orient":339},"st-a",[341,853],{"d":343,"fill":386},[346,855],{"x":348,"y":348,"width":611,"height":856,"rx":351,"fill":352},"234",[354,858,859],{"x":615,"y":357,"textAnchor":358,"fontSize":616,"fontWeight":360,"fill":344},"A small vocabulary, many consumers",[346,861],{"x":862,"y":365,"width":401,"height":863,"rx":336,"fill":352,"stroke":370,"strokeWidth":371},"34","40",[354,865,867],{"x":396,"y":866,"textAnchor":358,"fontSize":646,"fill":344},"77","checkout pact",[346,869],{"x":862,"y":870,"width":401,"height":863,"rx":336,"fill":352,"stroke":370,"strokeWidth":371},"104",[354,872,874],{"x":396,"y":873,"textAnchor":358,"fontSize":646,"fill":344},"129","reporting pact",[346,876],{"x":862,"y":877,"width":401,"height":863,"rx":336,"fill":352,"stroke":370,"strokeWidth":371},"156",[354,879,881],{"x":396,"y":880,"textAnchor":358,"fontSize":646,"fill":344},"181","admin pact",[63,883],{"x1":884,"y1":885,"x2":621,"y2":886,"stroke":386,"strokeWidth":887,"markerEnd":888},"238","72","108","1.7","url(#st-a)",[63,890],{"x1":884,"y1":685,"x2":621,"y2":685,"stroke":386,"strokeWidth":887,"markerEnd":888},[63,892],{"x1":884,"y1":893,"x2":621,"y2":894,"stroke":386,"strokeWidth":887,"markerEnd":888},"176","140",[346,896],{"x":897,"y":885,"width":898,"height":870,"rx":646,"fill":385,"stroke":386,"strokeWidth":482},"336","250",[354,900,903],{"x":901,"y":902,"textAnchor":358,"fontSize":368,"fontWeight":360,"fill":344},"461","98","provider STATES",[354,905,907],{"x":906,"y":685,"fontSize":646,"fill":344},"352","an open invoice exists",[354,909,911],{"x":906,"y":910,"fontSize":646,"fill":344},"144","a paid invoice exists",[354,913,915],{"x":906,"y":914,"fontSize":646,"fill":344},"164","no invoice exists",[346,917],{"x":474,"y":381,"width":421,"height":918,"rx":384,"fill":480,"stroke":481,"strokeWidth":482},"64",[354,920,923],{"x":921,"y":922,"textAnchor":358,"fontSize":646,"fontWeight":360,"fill":344},"693","118","unknown name",[354,925,926],{"x":921,"y":408,"textAnchor":358,"fontSize":646,"fill":489},"fails loudly",[492,928,929],{},"When consumers use matchers, three or four general states cover nearly every interaction. A request for a highly specific state is a sign the consumer is asserting on data it should not care about.",[14,931,933],{"id":932},"deciding-whether-a-deploy-is-safe","Deciding whether a deploy is safe",[10,935,936,937,940],{},"The payoff of the full arrangement is a single question answered mechanically before every deploy: have all the consumers currently in production verified against the provider version about to ship? With a broker that records verifications, ",[25,938,939],{},"pact-broker can-i-deploy"," answers it directly and can gate the deploy step.",[10,942,943,944,948],{},"Without a broker the same question can still be answered, less elegantly, by verifying the pacts from each consumer's ",[945,946,947],"em",{},"production"," tag rather than from their main branch. That distinction matters: a consumer's main branch may already depend on a provider change that has not shipped yet, and verifying against it would block the provider for a reason that is not a real incompatibility. Verifying against what consumers actually run is what makes the check trustworthy enough to gate on.",[10,950,951,952,955],{},"Either way, the result is a deploy that fails for a precise, attributable reason — \"checkout 4.12 in production reads ",[25,953,954],{},"legacy_ref",", which this build removes\" — rather than an incident discovered from an error-rate graph. That is the whole argument for consumer-driven contracts, and it is only realised once verification is wired into the step that decides whether a deploy proceeds.",[14,957,959],{"id":958},"frequently-asked-questions","Frequently Asked Questions",[10,961,962,965],{},[522,963,964],{},"Why use matchers instead of literal values in a pact?","\nBecause the consumer depends on the type and presence of a field, not on the particular value in one example. A literal total of 1234 fails verification the moment the provider's fixture uses 1250, which is a false alarm. A type matcher expresses exactly what the consumer needs.",[10,967,968,971],{},[522,969,970],{},"Who writes the provider state setup?","\nThe provider team, in their own repository. The consumer names the state it needs — \"an open invoice exists\" — and the provider decides how to arrange it. Consumers describing how to create provider data would couple them to the provider's internals.",[10,973,974,977],{},[522,975,976],{},"Do I need a Pact Broker?","\nNot for one consumer and one provider; committing the pact file or publishing it as a build artefact works. A broker becomes worthwhile with several consumers, because it records which consumer versions have verified against which provider versions and answers whether a deploy is safe.",[14,979,981],{"id":980},"related","Related",[19,983,984,990,997,1004],{},[22,985,986,989],{},[39,987,988],{"href":41},"Contract Testing for HTTP APIs"," — how pacts relate to schema validation and end-to-end tests.",[22,991,992,996],{},[39,993,995],{"href":994},"\u002Fintegration-database-and-service-testing\u002Fcontract-testing-for-http-apis\u002Fvalidating-responses-against-an-openapi-schema\u002F","Validating Responses Against an OpenAPI Schema"," — the cheaper structural check to run alongside.",[22,998,999,1003],{},[39,1000,1002],{"href":1001},"\u002Fintegration-database-and-service-testing\u002Fdatabase-fixtures-and-transactional-tests\u002F","Database Fixtures & Transactional Tests"," — fast provider-state setup via rolled-back transactions.",[22,1005,1006,1010],{},[39,1007,1009],{"href":1008},"\u002Fproperty-based-fuzz-testing-strategies\u002Fstateful-and-model-based-testing\u002Fmodeling-rest-apis-as-state-machines\u002F","Modeling REST APIs as State Machines"," — exploring sequences of calls the pact does not enumerate.",[10,1012,1013,1014],{},"← Back to ",[39,1015,988],{"href":41},[1017,1018,1019],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":57,"searchDepth":70,"depth":70,"links":1021},[1022,1023,1024,1025,1026,1027,1028,1029,1030],{"id":16,"depth":70,"text":17},{"id":46,"depth":70,"text":47},{"id":497,"depth":70,"text":498},{"id":515,"depth":70,"text":516},{"id":564,"depth":70,"text":565},{"id":713,"depth":70,"text":714},{"id":932,"depth":70,"text":933},{"id":958,"depth":70,"text":959},{"id":980,"depth":70,"text":981},"Write consumer pacts with pact-python, use matchers instead of literals, publish them, and verify them in the provider's pipeline with fast, named provider states.","md",{"slug":1034,"type":1035,"breadcrumb":1036,"datePublished":1037,"dateModified":1037,"faq":1038,"howto":1045},"consumer-driven-contract-tests-with-pact-python","article","Pact Python","2026-09-18",[1039,1041,1043],{"q":964,"a":1040},"Because the consumer depends on the type and presence of a field, not on the particular value in one example. A literal total of 1234 fails verification the moment the provider's fixture uses 1250, which is a false alarm. A type matcher expresses exactly what the consumer needs.",{"q":970,"a":1042},"The provider team, in their own repository. The consumer names the state it needs — 'an open invoice exists' — and the provider decides how to arrange it. Consumers describing how to create provider data would couple them to the provider's internals.",{"q":976,"a":1044},"Not for one consumer and one provider; committing the pact file or publishing it as a build artefact works. A broker becomes worthwhile with several consumers, because it records which consumer versions have verified against which provider versions and answers whether a deploy is safe.",{"name":1046,"description":1047,"steps":1048},"How to write and verify a consumer-driven contract with Pact","Describe each interaction the consumer relies on with matchers, publish the pact, and replay it against the real provider with named states.",[1049,1052,1055,1058,1061],{"name":1050,"text":1051},"Declare the interaction in the consumer test","State the provider state, request and minimum response shape the consumer depends on, using matchers for values.",{"name":1053,"text":1054},"Exercise the real client against the mock","Call the consumer's actual client code inside the pact context so the pact reflects real requests.",{"name":1056,"text":1057},"Publish the generated pact","Commit or upload the JSON file so the provider's pipeline can fetch it.",{"name":1059,"text":1060},"Implement provider states","Map each state name to setup code in the provider, wrapped in a transaction for speed.",{"name":1062,"text":1063},"Verify in the provider's build","Replay every interaction against the running provider and fail the build on any mismatch.","\u002Fintegration-database-and-service-testing\u002Fcontract-testing-for-http-apis\u002Fconsumer-driven-contract-tests-with-pact-python",{"title":5,"description":1031},"integration-database-and-service-testing\u002Fcontract-testing-for-http-apis\u002Fconsumer-driven-contract-tests-with-pact-python\u002Findex","LyxnS1SyNKq9gGbprF1xyk65LhYAJ4P41xJ5_oJ7p9o",1789718767499]