<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: BRUNO SOUZA</title>
    <description>The latest articles on DEV Community by BRUNO SOUZA (@brunohsouza).</description>
    <link>https://dev.to/brunohsouza</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F559371%2Fb41a3018-2901-4fd2-9b8a-ef6e62890f92.png</url>
      <title>DEV Community: BRUNO SOUZA</title>
      <link>https://dev.to/brunohsouza</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/brunohsouza"/>
    <language>en</language>
    <item>
      <title>The Autism Stats In The Tech Industry And The Real Hiring Problem</title>
      <dc:creator>BRUNO SOUZA</dc:creator>
      <pubDate>Wed, 26 Aug 2026 13:05:42 +0000</pubDate>
      <link>https://dev.to/brunohsouza/the-autism-stats-in-the-tech-industry-and-the-real-hiring-problem-1e42</link>
      <guid>https://dev.to/brunohsouza/the-autism-stats-in-the-tech-industry-and-the-real-hiring-problem-1e42</guid>
      <description>&lt;p&gt;You have seen the slide. Someone at a conference puts up "&lt;em&gt;autistic employees are 140% more productive&lt;/em&gt;," the room nods, and everyone goes back to running the same 45-minute whiteboard interview that filtered those people out three years ago.&lt;/p&gt;

&lt;p&gt;I went looking for the source of that number, and of the four or five others that circulate with it. Most of them do not survive contact with a search engine. That bothers me because the underlying problem is real and well documented, and propping it up with marketing statistics makes it trivially easy for a sceptical engineering manager to dismiss the whole conversation.&lt;/p&gt;

&lt;p&gt;So: what actually holds up, and what should you change on Monday?&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers that fall apart
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;"Autistic employees are 140% more productive."&lt;/strong&gt; This traces back to JPMorgan Chase's Autism at Work pilot in 2015. The pilot was a handful of people, four or five depending on which account you read, in one back-office role, measured over six months against colleagues who had been doing the same job for three to ten years. The headline figure from that cohort was 48% faster. The 90–140% range comes from internal claims about specific roles, reported second-hand, never published, never peer reviewed. Four or five people. One task type. No control for the fact that the pilot hires were screened for exactly that task.&lt;/p&gt;

&lt;p&gt;It is still spreading. Pull up any "neurodivergent hiring in 2026" listicle, and the 90–140% claim is right there, sourced to nothing.&lt;/p&gt;

&lt;p&gt;Quote it in a pitch to a CFO and the first person in the room who checks will win the argument, and they will win it against your entire point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"3.7% of developers are autistic, versus 1% of the population. Four times overrepresented."&lt;/strong&gt; The 3.7% comes from a US developer portal called Developer Pitstop, &lt;a href="https://auticon.com/au/code-is-right-when-it-becomes-beautiful-autistic-people-in-software-development/" rel="noopener noreferrer"&gt;repeated by auticon&lt;/a&gt;, and then into everyone's slide deck. Stack Overflow's own 2020 survey put it at &lt;a href="https://www.freecodecamp.org/news/stack-overflow-developer-survey-2020-programming-language-framework-salary-data/" rel="noopener noreferrer"&gt;2.3%&lt;/a&gt;. Both are self-selected samples of people who answer developer surveys, and as far as I can tell Stack Overflow has not published a neurodiversity breakdown since 2022.&lt;/p&gt;

&lt;p&gt;The denominator is worse. The CDC's most recent ADDM data puts prevalence at &lt;a href="https://www.cdc.gov/autism/data-research/index.html" rel="noopener noreferrer"&gt;1 in 31 eight-year-olds, about 3.2%&lt;/a&gt;, up from 1 in 36 in the previous cycle (MMWR Surveillance Summaries, April 2025, using 2022 surveillance data). Diagnosis rates in adults are lower mainly because nobody was screening adults thirty years ago.&lt;/p&gt;

&lt;p&gt;If you are hiring in Ireland, there is now a local denominator. The CSO's &lt;a href="https://www.cso.ie/en/releasesandpublications/ep/p-ihsmr/irishhealthsurvey-mainresults2025/autismandotherneurodiversities/" rel="noopener noreferrer"&gt;Irish Health Survey 2025&lt;/a&gt;, published in May 2026, found that 7.7% of males aged 15 to 24 have an autism diagnosis, against 4.5% of females, and that 38.2% of women in that age band report a diagnosed or suspected neurodivergent condition. Those are the people entering your graduate pipeline.&lt;/p&gt;

&lt;p&gt;So the "four times" multiplier compares a self-reported developer figure against a general-population figure that has roughly tripled since the comparison was first written down. Drop the multiplier. It is doing no work for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Only 22% of autistic adults in the UK are in paid work."&lt;/strong&gt; Stale rather than fabricated: that was ONS data from &lt;a href="https://www.autism.org.uk/what-we-do/news/new-data-on-the-autism-employment-gap" rel="noopener noreferrer"&gt;2021&lt;/a&gt;, and the House of Commons Library now puts it at &lt;a href="https://commonslibrary.parliament.uk/research-briefings/cbp-10389/" rel="noopener noreferrer"&gt;34.0% for 2024/25&lt;/a&gt;, against 55.3% for all disabled people and 82.0% for non-disabled people. Still the worst rate of any disability group, so the current number makes your case without you having to be five years out of date.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Up to 85% of autistic people in Ireland are unemployed or underemployed."&lt;/strong&gt; This one is closer to home, and it is the Irish version of the same problem. It appears in AsIAm campaign material, in the Insurance Ireland employer charter, and in the September 2025 coverage of the Ibec and AsIAm &lt;a href="https://asiam.ie/" rel="noopener noreferrer"&gt;Roadmap for Autism and Employment&lt;/a&gt;, usually as "up to 85%" or "over 80%," attributed to "global research." I have not been able to find the study. What is on the record: Ireland's disability employment rate is 32.6%, roughly 20 points below the EU average, and CSO figures quoted in Irish coverage put 16% of autistic people in full-time work and 32% in some form of paid work. Different claims, different denominators. If you use the 16/32 split, link the CSO release rather than a press piece.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers that survive
&lt;/h2&gt;

&lt;p&gt;The employment gap is the finding, and it replicates. 34% employment against 82% for non-disabled adults, with the &lt;a href="https://gov.uk/government/publications/the-buckland-review-of-autism-employment-report-and-recommendations" rel="noopener noreferrer"&gt;Buckland Review&lt;/a&gt; (February 2024) adding that autistic graduates are twice as likely to be unemployed 15 months after finishing as non-disabled graduates. Only 36% are in full-time work in that window, from the AGCAS &lt;em&gt;What Happens Next?&lt;/em&gt; 2022 data. Graduates. People who already cleared a technical degree.&lt;/p&gt;

&lt;p&gt;The pay gap survives too, and it is the number I would put next to the employment one: autistic employees earn around 27.9% less than non-disabled colleagues, the widest gap of any disability group in the UK data.&lt;/p&gt;

&lt;p&gt;The desire to work is there: the National Autistic Society's figure of 77% of unemployed autistic people wanting a job has held up across surveys for a decade.&lt;/p&gt;

&lt;p&gt;The interview is a measurable choke point. The number everyone reaches for here, 11% of applicants who disclosed on their application were offered any interview adjustment and 3% an alternative process, comes from the NAS surveys of 2016, restated in a &lt;a href="https://journals.sagepub.com/doi/10.1177/13623613241310926" rel="noopener noreferrer"&gt;2025 editorial in &lt;em&gt;Autism&lt;/em&gt;&lt;/a&gt; by NAS's Tim Nicholls. Say 2016 when you quote it. The current comparative work is Davies et al. (2023) in &lt;em&gt;Autism&lt;/em&gt; 27(6), which compared autistic, other neurodivergent and neurotypical adults' experiences of UK hiring processes directly.&lt;/p&gt;

&lt;p&gt;Better than either, there is an actual experiment. Maras et al. (2021), also in &lt;em&gt;Autism&lt;/em&gt;, put 50 adults, 25 autistic and 25 not, through two mock interviews six months apart. Employment experts blind to diagnosis rated the answers. In the first round, the autistic candidates were rated less favourably. In the second round, with questions rewritten to ask explicitly for specific details and examples rather than expecting the candidate to infer what was wanted, ratings improved for everyone, improved most for the autistic candidates, and the gap narrowed. That is a controlled result about a change you can ship this week, and it beats any productivity anecdote.&lt;/p&gt;

&lt;p&gt;The cost objection is dead, but check which edition of the JAN report you are quoting, because I got this wrong myself. JAN surveyed 26,028 employers who contacted it between 2019 and 2024 and received 5,406 responses. Of the 1,425 who gave cost data, &lt;a href="https://askjan.org/topics/costs.cfm" rel="noopener noreferrer"&gt;61% said the accommodation cost nothing, 33% had a one-time cost with a median of $300, and 6% had an ongoing cost with a median of $2,400 a year&lt;/a&gt; (report updated September 2025). The $3,750 annual figure in circulation is from the previous edition covering 2019 to 2022. Both were right for their vintage. Only one is current.&lt;/p&gt;

&lt;p&gt;On the strengths side, be careful. The peer-reviewed work points the other way. Verma, Cruz and Liebel analysed the 2022 Stack Overflow survey, comparing professional engineers with autism (n=374), ADHD (n=1,305) and dyslexia (n=363) against neurotypical engineers, and found that neurodivergent engineers report &lt;em&gt;more&lt;/em&gt; difficulty at work. Superhuman output does not show up in the data. The paper appeared as a &lt;a href="https://arxiv.org/abs/2506.03840" rel="noopener noreferrer"&gt;preprint&lt;/a&gt; in June 2025 and was published at SEAA 2025 (&lt;a href="https://doi.org/10.1007/978-3-032-04207-1_5" rel="noopener noreferrer"&gt;Springer LNCS 16083&lt;/a&gt;), so you can now cite it as reviewed work rather than as an arXiv drop. Alongside it, Gama, Liebel and colleagues published a grounded-theory study at ICSE-SEIS 2025 on how cognitive dysfunctions affect the performance of developers with ADHD and autism, which is the closest thing we have to a mechanism rather than a vibe.&lt;/p&gt;

&lt;p&gt;Interview studies with autistic software engineers do consistently surface pattern recognition, sustained focus and tolerance for detail work as real strengths. They also surface a finding people skip: communication difficulty extends into the code itself, into naming, abstraction boundaries and comments, and not only into standups.&lt;/p&gt;

&lt;p&gt;The honest argument for hiring is that the process you use to reject people is broken. The productivity dividend framing turns a person into a line item, and it collapses the moment someone has an average quarter.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to change in the loop
&lt;/h2&gt;

&lt;p&gt;Everything below is free, and most of it improves your signal on every candidate you interview. That last part is not a hopeful aside: it is what Maras et al. found, since the adapted questions lifted the non-autistic candidates too.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Send the questions in advance.&lt;/strong&gt; The technical content of your interview is not a secret worth protecting. You are testing whether someone can build software, not whether they can recover from ambush.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask for what you actually want.&lt;/strong&gt; Instead of "tell me about a challenge you faced," ask for the situation, what you personally did, and what the measurable outcome was. This is the single adaptation with experimental support behind it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offer a take-home or a pairing session as a stated alternative&lt;/strong&gt;, on the job ad, before anyone has to ask for it. Asking for an adjustment costs the candidate a disclosure they may not want to make.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Split a four-hour loop into two shorter sessions on different days.&lt;/strong&gt; Sensory and social load compounds; hour four is measuring stamina.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kill the "culture fit" round&lt;/strong&gt;, or define it against written behaviours. Unstructured chat over coffee is a similarity test with a job title.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State the format up front&lt;/strong&gt;: who is in the room, how long, what you will ask, whether cameras are expected.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Day to day, the changes that matter are boring: agendas before meetings, decisions written down after them, explicit deadlines instead of "when you get a chance", async by default, and a real quiet space or a genuine remote option rather than a hot desk beside the sales floor.&lt;/p&gt;

&lt;p&gt;One artefact I would steal outright. Frank Ray, a UK engineer diagnosed at 40 after twenty years of contracts ending badly, published a "How we work" document for his clients and found it attracted &lt;em&gt;better&lt;/em&gt; clients rather than fewer. Put one in your repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# How this team works&lt;/span&gt;

&lt;span class="gu"&gt;## Communication&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Slack is async. No reply expected within the hour.
&lt;span class="p"&gt;-&lt;/span&gt; Anything decided in a call gets written into the ADR or the ticket. If it isn't written, it wasn't decided.
&lt;span class="p"&gt;-&lt;/span&gt; @here is for production incidents.

&lt;span class="gu"&gt;## Meetings&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Agenda in the invite or the meeting is declined, no offence taken.
&lt;span class="p"&gt;-&lt;/span&gt; Cameras optional. Always.
&lt;span class="p"&gt;-&lt;/span&gt; No surprise attendees.

&lt;span class="gu"&gt;## Work&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; "Urgent" means today. Say the date otherwise.
&lt;span class="p"&gt;-&lt;/span&gt; Code review is about the code. Suggestions are prefixed &lt;span class="sb"&gt;`nit:`&lt;/span&gt; when optional.
&lt;span class="p"&gt;-&lt;/span&gt; Deep work blocks in the calendar are load-bearing. Don't book over them.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;None of that is an autism accommodation. It is how a functioning distributed team already operates, which is the point. The adjustments that get labelled special are mostly competent engineering management applied consistently.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Irish and EU angle nobody puts on the slide
&lt;/h2&gt;

&lt;p&gt;The research above is overwhelmingly American and frames everything around the ADA, which is useless to you if you are hiring in Dublin. Here the framework is section 16 of the &lt;a href="https://www.ihrec.ie/factsheets/disability-discrimination-and-the-right-to-reasonable-accommodation-under-the-employment-equality-acts" rel="noopener noreferrer"&gt;Employment Equality Acts 1998–2015&lt;/a&gt;, and it is a positive duty: appropriate measures, unless they impose a disproportionate burden. It covers job applicants, not only employees. The governing authority is the Supreme Court in &lt;em&gt;Nano Nagle School v Daly&lt;/em&gt;, which held that duties can be stripped out of a role as an accommodation, while also making clear that the duty is not infinite.&lt;/p&gt;

&lt;p&gt;The WRC reads the disproportionate-burden defence narrowly, and the recent decisions are worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Mary Tracy v Smurfit Kappa Ireland (Edco)&lt;/em&gt;, January 2025. &lt;a href="https://www.ihrec.ie/news-press/commission-welcomes-equality-rulings-from-the-workplace-relations-commission-in-important-disability-discrimination-case" rel="noopener noreferrer"&gt;€64,000 across two decisions&lt;/a&gt;, largely for never exploring or discussing accommodations at all. IHREC provided the legal representation. Tracy herself had pointed out to her employer that state funding was available.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Paul Hill v Department of Social Protection&lt;/em&gt;, April 2026. €30,000, with discrimination found across a twelve-year period. The Department that lost is the one that administers the State's own workplace accommodation programme.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Lukasz Swiercz v Lidl Ireland&lt;/em&gt;, ADJ-00059764, April 2026. €28,000 for refusing a return to work on light duties.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The exposure is real: compensation runs up to two years' remuneration or €40,000, whichever is greater.&lt;/p&gt;

&lt;p&gt;And the Irish cost objection has a state answer that no American report can give you. &lt;a href="https://www.gov.ie/en/department-of-social-protection/services/work-and-access/" rel="noopener noreferrer"&gt;Work and Access&lt;/a&gt;, which replaced the Reasonable Accommodation Fund and the Disability Awareness Support Scheme, funds a workplace needs assessment up to €2,500, workplace adaptation up to €25,000, and disability equality and inclusion training up to €20,000, with an equipment grant on top. Private sector only, and the public sector is excluded, which is its own conversation.&lt;/p&gt;

&lt;p&gt;I wrote that up separately, because it deserves more than four paragraphs: &lt;strong&gt;&lt;a href="https://dev.toLINK_TO_PART_TWO"&gt;Reasonable Accommodation in Irish Tech: What Section 16 Actually Requires&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part senior engineers get wrong
&lt;/h2&gt;

&lt;p&gt;There is a trap on the other side of hiring that the corporate programme pages never mention. Sustained focus produces output, output gets praised, and an autistic engineer can spend three years being the person who ships while quietly missing that promotion to staff runs on influence, disagreement handled well, and knowing which meeting to be in. Managers who benefit from the output rarely volunteer this. Burnout arrives before the title does.&lt;/p&gt;

&lt;p&gt;If you manage someone like this, say the promotion criteria out loud, in writing, with examples. The implicit ones are the whole barrier.&lt;/p&gt;




&lt;p&gt;What I actually want to know: has anyone reading this changed their interview loop and measured what happened to the pipeline? Or dropped the whiteboard round and found the signal got better? I have opinions about which of the changes above matter most, and almost no data on which ones teams actually keep after six months.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bruno Souza&lt;/strong&gt;&lt;br&gt;
Senior Backend Engineer, Dublin&lt;/p&gt;

</description>
      <category>career</category>
      <category>neurodiversity</category>
      <category>softwareengineering</category>
      <category>inclusion</category>
    </item>
    <item>
      <title>The Singularity Is Nearer: What Ray Kurzweil's Six Epochs Gets Right About Our Future</title>
      <dc:creator>BRUNO SOUZA</dc:creator>
      <pubDate>Mon, 30 Mar 2026 22:15:04 +0000</pubDate>
      <link>https://dev.to/brunohsouza/the-singularity-is-nearer-what-ray-kurzweils-six-epochs-gets-right-about-our-future-1pph</link>
      <guid>https://dev.to/brunohsouza/the-singularity-is-nearer-what-ray-kurzweils-six-epochs-gets-right-about-our-future-1pph</guid>
      <description>&lt;p&gt;Some people say the current AI bubble will soon burst and that AI is not intelligent enough to produce the singularity. Others claim we are very close, or even that we have already achieved AGI. But if AGI is destined to be created, where exactly are we in that process right now?&lt;/p&gt;

&lt;p&gt;Trying to find an answer, or at least a sharper mental model, I've been reading &lt;strong&gt;Ray Kurzweil's *The Singularity Is Nearer&lt;/strong&gt;&lt;em&gt;, his long-awaited follow-up to *The Singularity Is Near&lt;/em&gt; (2005). At its heart, the framework is something every engineer in the AI era should understand.&lt;/p&gt;

&lt;p&gt;Kurzweil structures the entire history and future of intelligence into &lt;strong&gt;Six Epochs&lt;/strong&gt;. Each epoch represents a qualitative leap in how information is processed from the physics of atoms to a universe fully saturated with intelligence. It sounds ambitious, but he grounds it in the same empirical trends that have driven tech progress for decades.&lt;/p&gt;

&lt;p&gt;As a software engineer who's watched distributed systems, cloud computing, and now LLMs reshape what's possible in just a few years, I find this framework surprisingly useful as a mental model.&lt;br&gt;
Let's break it down.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Law of Accelerating Returns (Quick Intro)
&lt;/h2&gt;

&lt;p&gt;Before diving into the epochs, you need to understand the engine driving all of them: &lt;strong&gt;the Law of Accelerating Returns&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The idea is simple: technological and evolutionary progress is not linear. It's &lt;strong&gt;exponential&lt;/strong&gt;. Each innovation builds on previous ones, compounding over time. The doubling of transistor density, the cost curves for compute, the trajectory of LLM capabilities — these aren't coincidences. They reflect a deep underlying dynamic.&lt;/p&gt;

&lt;p&gt;But there's a second, equally important principle that structures the epochs themselves:&lt;br&gt;
&lt;strong&gt;indirection&lt;/strong&gt;. Each epoch doesn't just follow the previous one; it uses the mechanisms of the previous one as raw material to bootstrap the next. Evolution creates DNA; DNA creates brains; brains create technology; technology creates the merger. The epochs are not a list. They're a compounding chain.&lt;/p&gt;

&lt;p&gt;Kurzweil has been tracking these curves since the 80s. The exponential trends he described in 2005 have, remarkably, held.&lt;/p&gt;




&lt;h2&gt;
  
  
  Epoch 1 — Physics and Chemistry
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Information encoded in the structure of matter itself.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The story starts at the origin: atoms and molecules, patterns of matter and energy. The laws of physics, quantum mechanics, thermodynamics, and electrochemistry govern how matter &lt;br&gt;
self-organises.&lt;/p&gt;

&lt;p&gt;Kurzweil's point here is subtle: even at this level, the universe isn't random. The fine-tuned physical constants that allow stable atoms to exist, and enable carbon, hydrogen, nitrogen, and&lt;br&gt;
oxygen bonding in complex ways represents the &lt;strong&gt;first layer of information in the universe&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This raises an uncomfortable question: why are our universe's physical constants so perfectly calibrated for complexity to emerge? Some invoke a divine hand. Others point to the anthropic&lt;br&gt;
principle. Only in a universe that permitted our evolution would we be here to ask the question. &lt;/p&gt;

&lt;p&gt;Kurzweil raises the multiverse theory as a parallel lens: if new universes are constantly generated, each with different rules, most would collapse or stagnate without producing anything&lt;br&gt;
interesting. Ours happened to have exactly the right parameters for increasing order and complexity to take hold. We're the universe that made it.&lt;/p&gt;

&lt;p&gt;No code yet. No DNA. Just physics creating the conditions for everything that follows.&lt;/p&gt;




&lt;h2&gt;
  
  
  Epoch 2 — Biology and DNA
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The universe invents self-replicating, evolvable information storage.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is where it gets interesting for engineers. DNA is, in essence, a &lt;strong&gt;digital information system&lt;/strong&gt;: a four-letter alphabet (A, T, G, C — Adenine, Thymine, Guanine, Cytosine) that&lt;br&gt;
encodes the instructions for building and operating living organisms.&lt;/p&gt;

&lt;p&gt;Evolution is the algorithm. Slow, blind, but astonishingly effective over billions of iterations. It's gradient descent without a loss function you designed. It optimises for survival and reproduction, producing extraordinary complexity from simple rules.&lt;/p&gt;

&lt;p&gt;The key innovation of Epoch 2 isn't just life, it's &lt;strong&gt;self-improving information&lt;/strong&gt;. A system that can modify itself across generations without conscious direction.&lt;/p&gt;




&lt;h2&gt;
  
  
  Epoch 3 — Brains
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Real-time cognition enters the picture.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Epoch 3 is where indirection becomes visible. DNA-guided evolution produced organisms that could detect information through their own sensory organs, and process and store it in their own brains and nervous systems, built entirely on the molecular machinery of Epoch 2, but now operating independently of it.&lt;/p&gt;

&lt;p&gt;The third epoch began with early animals' ability to recognise patterns, a capacity that still accounts for the vast majority of brain activity. The human brain (~86 billion neurons, ~100&lt;br&gt;
trillion synaptic connections) added something further: the ability to build &lt;strong&gt;abstract mental models of the world&lt;/strong&gt;, contemplate alternatives, and act on those simulations. We can redesign&lt;br&gt;
reality in our minds before touching it.&lt;/p&gt;

&lt;p&gt;But Kurzweil is clear-eyed about the brain's limits. It was shaped by evolutionary pressures, not intentional engineering. It's riddled with cognitive biases, constrained memory, slow learning, and inevitable physical decay. The brain is powerful, but it's also the bottleneck.&lt;/p&gt;

&lt;p&gt;And that last capability — abstract modelling, rational contemplation, and the ability to act on ideas — is precisely what triggers the next epoch.&lt;/p&gt;




&lt;h2&gt;
  
  
  Epoch 4 — Technology
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Where we came from (and where we still partly are).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Kurzweil is direct about the link: rational abstract thought, combined with the physical ability to manipulate the world, ushered in the fourth epoch. Technology is Epoch 3's output, built&lt;br&gt;
from Epoch 3's tools, now evolving on its own exponential curve.&lt;/p&gt;

&lt;p&gt;This epoch begins with the first tools and accelerates through writing, mathematics, the printing press, the transistor, the internet, and now AI.&lt;/p&gt;

&lt;p&gt;The defining feature: &lt;strong&gt;technology compounds on itself&lt;/strong&gt;. Unlike biological evolution, each generation of tools enables faster creation of the next. Exponential curves don't just apply to Moore's Law; they also show up in compute costs, memory density, bandwidth, and, increasingly, model capability.&lt;/p&gt;

&lt;p&gt;Kurzweil frames Epoch 4 as the era in which technology is an &lt;em&gt;external&lt;/em&gt; capability. Tools that extend what humans can do, but remain clearly separate from human cognition. By that definition,&lt;br&gt;
most of the 20th century sits squarely here.&lt;/p&gt;

&lt;p&gt;But the tools we're building are starting to blur the line between "external tool" and "cognitive collaborator". And that distinction is exactly what Epoch 5 is about.&lt;/p&gt;




&lt;h2&gt;
  
  
  Epoch 5 — The Merger of Human and Machine Intelligence
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The Singularity. And arguably, it has already started.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the one everyone has heard of and the one most often misunderstood.&lt;/p&gt;

&lt;p&gt;Kurzweil projects the &lt;em&gt;full&lt;/em&gt; merger of human and artificial intelligence to unfold through the 2030s and 2040s, with AI reaching human-level general intelligence around &lt;strong&gt;2029&lt;/strong&gt;. In the strictest definition, Epoch 5 is about physical integration: brain-computer interfaces, nanobots operating at the neuronal level, and the boundary between biology and computation becoming irrelevant at the hardware level.&lt;/p&gt;

&lt;p&gt;By that definition, we're not fully there yet.&lt;/p&gt;

&lt;p&gt;But here's the honest question: &lt;strong&gt;Does the merger have to be physical to be real?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Kurzweil's own answer, buried in the book, is revealing. Alongside brain-computer interfaces and nanobots, he gives significant weight to &lt;strong&gt;virtual worlds&lt;/strong&gt; as a vector for the merge between human and machines, the immersive digital environments where human consciousness doesn't just interact with AI but increasingly &lt;em&gt;inhabits&lt;/em&gt; spaces populated, shared, and shaped by it. That's a different path to Epoch 5 than biological integration, but in his framework, it's equally valid. The merger happens through presence and immersion, not only through hardware in your skull.&lt;/p&gt;

&lt;p&gt;That reframes the question entirely. It's not physical vs. non-physical. It's about &lt;strong&gt;how many vectors the merger is travelling along simultaneously&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Consider what's already happening. Engineers using AI to reason through architecture decisions. Researchers are compressing years of literature review into hours. Doctors using LLMs for&lt;br&gt;
differential diagnosis. And beyond knowledge work: people spending meaningful portions of their cognitive and social lives inside AI-populated digital environments, like games, virtual workspaces,&lt;br&gt;
and persistent AI companions. That's not the tooling in the Epoch 4 sense. That's functional cognitive extension across multiple fronts.&lt;/p&gt;

&lt;p&gt;The epochs don't have clean start dates. Epoch 3 didn't begin on a Tuesday morning, and the merger is likely the same: not a switch that flips in 2029, but a &lt;strong&gt;transition we're already inside&lt;/strong&gt;, advancing on several tracks at once.&lt;/p&gt;

&lt;p&gt;The biological integration piece is nascent (Neuralink, cochlear implants, advanced prosthetics). The virtual integration piece is further along than most people acknowledge. And the functional cognitive merger, especially for knowledge workers, is arguably already a daily reality.&lt;/p&gt;

&lt;p&gt;My read: &lt;strong&gt;we're in the overlap zone&lt;/strong&gt;. Late Epoch 4 bleeding into early Epoch 5, through multiple converging paths. The infrastructure of the merger is being built right now by people&lt;br&gt;
like us, the software engineers.&lt;/p&gt;




&lt;h2&gt;
  
  
  Epoch 6 — The Universe Wakes Up
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The most speculative, and the most philosophically significant.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Having merged with technology and mastered intelligence on Earth, the civilisation emerging from the Singularity begins to expand outward, converting matter and energy throughout the cosmos into increasingly sophisticated computational substrate. Indirection operates at a universal scale.&lt;/p&gt;

&lt;p&gt;The universe began as simple physics. It evolved through chemistry and biology, producing minds capable of comprehending it. In Epoch 6, those minds that had vastly expanded began to fulfil that potential beyond the edges of a single planet.&lt;/p&gt;

&lt;p&gt;Kurzweil acknowledges the speed of light as a hard constraint and concedes this epoch involves genuine unknowns. But his argument follows the same logic as the rest: a universe in which information complexity has increased exponentially at every prior level doesn't simply stop here.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Framework Matters to Engineers
&lt;/h2&gt;

&lt;p&gt;You don't have to buy every prediction to find the Six Epochs useful. What it offers is a &lt;strong&gt;macro-level mental model&lt;/strong&gt; for understanding where we are in the arc of technological progress&lt;br&gt;
and more practically, what we're actually building toward.&lt;/p&gt;

&lt;p&gt;We're at the threshold. The messy, ambiguous overlap between technology as an external tool and technology as a cognitive extension. The distributed architectures, AI inference pipelines, and real-time data platforms we're building today are the substrate on which the full Epoch 5 merger will run.&lt;/p&gt;

&lt;p&gt;A few questions worth sitting with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;If the functional merger is already underway for knowledge workers, what does that mean for how we design systems?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Are we building for users, or for human-AI composites?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;What responsibilities come with being engineers at this particular inflexion point?&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Kurzweil's track record is imperfect, but the trends he identified are real. Whether the Singularity arrives on his exact timeline or not, the direction of travel is hard to argue with, and if you're reading this, you're probably already living in its early chapters.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have you read The Singularity Is Near or The Singularity Is Nearer? Where do you think we actually are? Late Epoch 4,&lt;br&gt;
early Epoch 5, or genuinely in the overlap? Curious what engineers here make of the distinction.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Drop your thoughts in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>technology</category>
      <category>futurism</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>How to Install the AWS CLI and Configure It with IAM Identity Center (SSO)</title>
      <dc:creator>BRUNO SOUZA</dc:creator>
      <pubDate>Mon, 16 Mar 2026 09:30:00 +0000</pubDate>
      <link>https://dev.to/brunohsouza/how-to-install-the-aws-cli-and-configure-it-with-iam-identity-center-sso-14kp</link>
      <guid>https://dev.to/brunohsouza/how-to-install-the-aws-cli-and-configure-it-with-iam-identity-center-sso-14kp</guid>
      <description>&lt;p&gt;Before you can run a single &lt;code&gt;aws s3&lt;/code&gt; command, two things need to be in place: the CLI installed on your machine and credentials configured. So, AWS knows who you are.&lt;/p&gt;

&lt;p&gt;This post covers both (CLI and credentials) in the following OS's: macOS, Linux, and Windows. And uses &lt;strong&gt;IAM Identity Center (formerly AWS SSO)&lt;/strong&gt; for authentication. That means short-lived, automatically refreshed credentials instead of long-lived access keys stored in a plain-text file on your disk.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why IAM Identity Center instead of &lt;code&gt;aws configure&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;The classic &lt;code&gt;aws configure&lt;/code&gt; flow creates a &lt;code&gt;~/.aws/credentials&lt;/code&gt; file with a permanent &lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt; and &lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt;. Those keys never expire on their own. If they leak through a committed &lt;code&gt;.env&lt;/code&gt; file, a misconfigured S3 bucket, or a compromised machine, the window of exposure is unlimited.&lt;/p&gt;

&lt;p&gt;IAM Identity Center issues short-lived session tokens instead. When the session expires, the credentials stop working. You re-authenticate through your browser, and a fresh token is cached locally. It is a fundamentally safer model, and it is what AWS now recommends as the default for developer access.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Prerequisite:&lt;/strong&gt; Your AWS account must have IAM Identity Center enabled. If you are working with a personal account, you can enable it in the IAM Identity Center console. If you are at a company, your AWS administrator will give you a start URL and the name of your permission set.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can find the link for the IAM Identity Center in the bottom left corner of the IAM page: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh5vueefgornt24oihq2x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh5vueefgornt24oihq2x.png" alt="Identity Center Link" width="254" height="371"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1 — Install AWS CLI v2
&lt;/h2&gt;

&lt;h3&gt;
  
  
  macOS
&lt;/h3&gt;

&lt;p&gt;The recommended approach for macOS is Homebrew. It handles updates automatically and keeps the binary on your &lt;code&gt;PATH&lt;/code&gt; without any manual symlinking.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;awscli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you prefer the official &lt;code&gt;.pkg&lt;/code&gt; installer instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://awscli.amazonaws.com/AWSCLIV2.pkg"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"AWSCLIV2.pkg"&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;installer &lt;span class="nt"&gt;-pkg&lt;/span&gt; AWSCLIV2.pkg &lt;span class="nt"&gt;-target&lt;/span&gt; /
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Linux (x86_64)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"awscliv2.zip"&lt;/span&gt;
unzip awscliv2.zip
&lt;span class="nb"&gt;sudo&lt;/span&gt; ./aws/install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For ARM64 (e.g. AWS Graviton, Raspberry Pi):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"awscliv2.zip"&lt;/span&gt;
unzip awscliv2.zip
&lt;span class="nb"&gt;sudo&lt;/span&gt; ./aws/install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;If &lt;code&gt;unzip&lt;/code&gt; is not installed: &lt;code&gt;sudo apt install unzip&lt;/code&gt; (Debian/Ubuntu) or &lt;code&gt;sudo yum install unzip&lt;/code&gt; (Amazon Linux/CentOS/RHEL).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Windows
&lt;/h3&gt;

&lt;p&gt;Open &lt;strong&gt;PowerShell as Administrator&lt;/strong&gt; and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Invoke-WebRequest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://awscli.amazonaws.com/AWSCLIV2.msi"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-OutFile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AWSCLIV2.msi"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;msiexec.exe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;/i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AWSCLIV2.msi&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;/quiet&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively, download and run the &lt;code&gt;.msi&lt;/code&gt; installer manually from &lt;code&gt;https://awscli.amazonaws.com/AWSCLIV2.msi&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verify the installation
&lt;/h3&gt;

&lt;p&gt;On all three platforms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected output (version numbers will vary):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;aws-cli/2.27.41 Python/3.11.6 Linux/6.1.0 botocore/2.0.0
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the command is not found, restart your terminal. On Windows, open a new PowerShell or Command Prompt window.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ WARNING: &lt;code&gt;aws configure sso&lt;/code&gt; only works with &lt;strong&gt;AWS CLI v2&lt;/strong&gt;. If &lt;code&gt;aws --version&lt;/code&gt; shows &lt;code&gt;aws-cli/1.x.x&lt;/code&gt;, you are on v1 and need to upgrade before continuing.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 2 — Gather your IAM Identity Center details
&lt;/h2&gt;

&lt;p&gt;You need two values before running the setup wizard:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Where to find it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SSO Start URL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;IAM Identity Center console → Dashboard → &lt;strong&gt;Issuer URL&lt;/strong&gt; (or ask your AWS admin)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SSO Region&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The AWS region where IAM Identity Center is enabled (e.g. &lt;code&gt;eu-west-1&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You can also find both by signing in to your AWS access portal, selecting your permission set, clicking &lt;strong&gt;Access keys&lt;/strong&gt;, and choosing the &lt;strong&gt;IAM Identity Center credentials&lt;/strong&gt; tab.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F70vc9aivgqmscpx6hfez.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F70vc9aivgqmscpx6hfez.png" alt="Identity Center - Settings Summary" width="800" height="706"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3 — Configure a profile with &lt;code&gt;aws configure sso&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Run the interactive wizard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws configure sso
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will be prompted for the following. Use the values you gathered above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="err"&gt;SSO&lt;/span&gt; &lt;span class="err"&gt;session&lt;/span&gt; &lt;span class="err"&gt;name&lt;/span&gt; &lt;span class="err"&gt;(Recommended):&lt;/span&gt; &lt;span class="err"&gt;my-dev-session&lt;/span&gt;
&lt;span class="err"&gt;SSO&lt;/span&gt; &lt;span class="err"&gt;start&lt;/span&gt; &lt;span class="err"&gt;URL&lt;/span&gt; &lt;span class="err"&gt;[None]:&lt;/span&gt; &lt;span class="py"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s"&gt;//d-xxxxxxxxxx.awsapps.com/start&lt;/span&gt;
&lt;span class="err"&gt;SSO&lt;/span&gt; &lt;span class="err"&gt;region&lt;/span&gt; &lt;span class="err"&gt;[None]:&lt;/span&gt; &lt;span class="err"&gt;eu-west-1&lt;/span&gt; 
&lt;span class="err"&gt;SSO&lt;/span&gt; &lt;span class="err"&gt;registration&lt;/span&gt; &lt;span class="err"&gt;scopes&lt;/span&gt; &lt;span class="err"&gt;[&lt;/span&gt;&lt;span class="py"&gt;sso&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s"&gt;account:access]: sso:account:access&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CLI will open your browser to complete authentication. Once authorised, it returns to the terminal and asks for a few more values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="err"&gt;AWS&lt;/span&gt; &lt;span class="err"&gt;account&lt;/span&gt; &lt;span class="py"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;123456789012&lt;/span&gt;
&lt;span class="err"&gt;Role&lt;/span&gt; &lt;span class="py"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PowerUserAccess&lt;/span&gt;
&lt;span class="err"&gt;CLI&lt;/span&gt; &lt;span class="err"&gt;default&lt;/span&gt; &lt;span class="err"&gt;client&lt;/span&gt; &lt;span class="err"&gt;Region&lt;/span&gt; &lt;span class="err"&gt;[None]:&lt;/span&gt; &lt;span class="err"&gt;eu-west-1&lt;/span&gt;
&lt;span class="err"&gt;CLI&lt;/span&gt; &lt;span class="err"&gt;default&lt;/span&gt; &lt;span class="err"&gt;output&lt;/span&gt; &lt;span class="err"&gt;format&lt;/span&gt; &lt;span class="err"&gt;[None]:&lt;/span&gt; &lt;span class="err"&gt;json&lt;/span&gt;
&lt;span class="err"&gt;CLI&lt;/span&gt; &lt;span class="err"&gt;profile&lt;/span&gt; &lt;span class="err"&gt;name&lt;/span&gt; &lt;span class="err"&gt;[PowerUserAccess-123456789012]:&lt;/span&gt; &lt;span class="err"&gt;my-dev-profile&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can use the region you want. I am using the &lt;code&gt;eu-west-1&lt;/code&gt; as I am based in Ireland. The default region is &lt;code&gt;us-east-1&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;The profile name is what you will pass to every &lt;code&gt;--profile&lt;/code&gt; flag. Pick something short and descriptive — &lt;code&gt;dev&lt;/code&gt;, &lt;code&gt;personal&lt;/code&gt;, or &lt;code&gt;work&lt;/code&gt; are all fine.&lt;/p&gt;

&lt;h3&gt;
  
  
  What gets written to your config file
&lt;/h3&gt;

&lt;p&gt;The wizard creates two entries in &lt;code&gt;~/.aws/config&lt;/code&gt; (macOS/Linux) or &lt;code&gt;%USERPROFILE%\.aws\config&lt;/code&gt; (Windows):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[sso-session my-dev-session]&lt;/span&gt;
&lt;span class="py"&gt;sso_start_url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;https://d-xxxxxxxxxx.awsapps.com/start&lt;/span&gt;
&lt;span class="py"&gt;sso_region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;eu-west-1&lt;/span&gt;
&lt;span class="py"&gt;sso_registration_scopes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;sso:account:access&lt;/span&gt;

&lt;span class="nn"&gt;[profile my-dev-profile]&lt;/span&gt;
&lt;span class="py"&gt;sso_session&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;my-dev-session&lt;/span&gt;
&lt;span class="py"&gt;sso_account_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;123456789012&lt;/span&gt;
&lt;span class="py"&gt;sso_role_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;PowerUserAccess&lt;/span&gt;
&lt;span class="py"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;eu-west-1&lt;/span&gt;
&lt;span class="py"&gt;output&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice there are &lt;strong&gt;no access keys&lt;/strong&gt; anywhere in this file. Credentials are fetched and cached at login time under &lt;code&gt;~/.aws/sso/cache/&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4 — Log in and verify
&lt;/h2&gt;

&lt;p&gt;Log in to start a session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws sso login &lt;span class="nt"&gt;--profile&lt;/span&gt; my-dev-profile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your browser opens, you approve the request, and the CLI caches a short-lived token locally. Then verify the profile is working:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws sts get-caller-identity &lt;span class="nt"&gt;--profile&lt;/span&gt; my-dev-profile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"UserId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AROA...:user@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Account"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"123456789012"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Arn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:sts::123456789012:assumed-role/PowerUserAccess/user@example.com"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you see your account ID and role name, you are fully configured.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5 — Set a default profile (optional but useful)
&lt;/h2&gt;

&lt;p&gt;Passing &lt;code&gt;--profile my-dev-profile&lt;/code&gt; to every command gets repetitive. Export it as an environment variable for your current session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# macOS / Linux&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AWS_PROFILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;my-dev-profile

&lt;span class="c"&gt;# Windows PowerShell&lt;/span&gt;
&lt;span class="nv"&gt;$env&lt;/span&gt;:AWS_PROFILE &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-dev-profile"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make it permanent, add the &lt;code&gt;export&lt;/code&gt; line to your &lt;code&gt;~/.zshrc&lt;/code&gt;, &lt;code&gt;~/.bashrc&lt;/code&gt;, or PowerShell profile.&lt;/p&gt;

&lt;p&gt;Now you can run commands without the flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3 &lt;span class="nb"&gt;ls
&lt;/span&gt;aws sts get-caller-identity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Day-to-day workflow
&lt;/h2&gt;

&lt;p&gt;Once everything is set up, your daily routine is just two commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Start your working session&lt;/span&gt;
aws sso login &lt;span class="nt"&gt;--profile&lt;/span&gt; my-dev-profile

&lt;span class="c"&gt;# When you are done&lt;/span&gt;
aws sso &lt;span class="nb"&gt;logout&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sessions expire automatically (typically after 8–12 hours, depending on your IAM Identity Center configuration). When credentials expire mid-session, the CLI will prompt you to re-run &lt;code&gt;aws sso login&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;aws: command not found&lt;/code&gt; after install&lt;/strong&gt;&lt;br&gt;
Restart your terminal. On macOS, run &lt;code&gt;brew doctor&lt;/code&gt; to check your PATH. On Linux, confirm &lt;code&gt;/usr/local/bin&lt;/code&gt; is in your &lt;code&gt;$PATH&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Browser does not open on login&lt;/strong&gt;&lt;br&gt;
Run with the device code fallback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws sso login &lt;span class="nt"&gt;--profile&lt;/span&gt; my-dev-profile &lt;span class="nt"&gt;--use-device-code&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CLI will print a URL and a code — open the URL manually and enter the code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Error when retrieving token from sso&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
Your cached token has expired. Re-run &lt;code&gt;aws sso login --profile my-dev-profile&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;AccessDenied&lt;/code&gt; on a command&lt;/strong&gt;&lt;br&gt;
Your permission set may not include the IAM action you are calling. Check the permissions attached to your IAM Identity Center permission set, or ask your AWS admin. The previous post in this series covers exactly how to map CLI commands to the IAM actions they require.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;AWS CLI v2 is required for &lt;code&gt;aws configure sso&lt;/code&gt; (v1 will not work).&lt;/li&gt;
&lt;li&gt;IAM Identity Center issues short-lived tokens. There are no long-lived keys stored on disk.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;sso-session&lt;/code&gt; block in &lt;code&gt;~/.aws/config&lt;/code&gt; handles token refresh automatically.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AWS_PROFILE&lt;/code&gt; environment variable saves you from typing &lt;code&gt;--profile&lt;/code&gt; on every command.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aws sts get-caller-identity&lt;/code&gt; is your fastest way to confirm a profile is working.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Previous in this series: &lt;a href="https://dev.to/brunohsouza/aws-iam-s3-demystified-map-every-s3-cli-command-to-its-required-permission-5bmd"&gt;AWS IAM Demystified — Map Every S3 CLI Command to Its Exact Permission&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>iam</category>
      <category>devops</category>
      <category>cloud</category>
    </item>
    <item>
      <title>AWS IAM + S3 Demystified: Map Every S3 CLI Command to Its Required Permission</title>
      <dc:creator>BRUNO SOUZA</dc:creator>
      <pubDate>Fri, 13 Mar 2026 17:57:57 +0000</pubDate>
      <link>https://dev.to/brunohsouza/aws-iam-s3-demystified-map-every-s3-cli-command-to-its-required-permission-5bmd</link>
      <guid>https://dev.to/brunohsouza/aws-iam-s3-demystified-map-every-s3-cli-command-to-its-required-permission-5bmd</guid>
      <description>&lt;p&gt;If you have ever stared at an &lt;code&gt;AccessDenied&lt;/code&gt; error and played permission roulette, adding one action at a time until something works, this post is for you.&lt;/p&gt;

&lt;p&gt;We are going to take a short, real S3 bash script, map &lt;strong&gt;every single CLI command&lt;/strong&gt; to the exact IAM permission it requires, and write the smallest possible policy that grants only those actions.&lt;/p&gt;




&lt;h2&gt;
  
  
  What IAM is (in 60 seconds)
&lt;/h2&gt;

&lt;p&gt;AWS IAM (Identity and Access Management) answers three questions for every API call:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;IAM concept&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Who is calling?&lt;/td&gt;
&lt;td&gt;Identity (user, role, or group)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What are they allowed to do?&lt;/td&gt;
&lt;td&gt;Policy (JSON allow/deny rules)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;On which resource?&lt;/td&gt;
&lt;td&gt;Resource ARN&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The evaluation logic is simple but strict:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Default deny&lt;/strong&gt; — everything is blocked unless explicitly allowed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explicit Allow&lt;/strong&gt; — grants access&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explicit Deny&lt;/strong&gt; — always wins, even over an Allow&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That third rule is why least-privilege matters: the more permissions you hand out, the harder it is to audit which Deny you actually need.&lt;/p&gt;




&lt;h2&gt;
  
  
  The script we are dissecting
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# create a bucket&lt;/span&gt;
aws s3api create-bucket &lt;span class="nt"&gt;--bucket&lt;/span&gt; hello-bucket &lt;span class="nt"&gt;--region&lt;/span&gt; eu-west-1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--create-bucket-configuration&lt;/span&gt; &lt;span class="nv"&gt;LocationConstraint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;eu-west-1

aws s3api put-bucket-acl &lt;span class="nt"&gt;--bucket&lt;/span&gt; hello-bucket &lt;span class="nt"&gt;--acl&lt;/span&gt; public-read

&lt;span class="c"&gt;# upload a file to the bucket&lt;/span&gt;
aws s3 &lt;span class="nb"&gt;cp &lt;/span&gt;hello.txt s3://hello-bucket/hello.txt

&lt;span class="c"&gt;# list the contents of the bucket&lt;/span&gt;
aws s3 &lt;span class="nb"&gt;ls &lt;/span&gt;s3://hello-bucket/

&lt;span class="c"&gt;# delete the file from the bucket&lt;/span&gt;
aws s3 &lt;span class="nb"&gt;rm &lt;/span&gt;s3://hello-bucket/hello.txt

&lt;span class="c"&gt;# download the file from the bucket&lt;/span&gt;
aws s3 &lt;span class="nb"&gt;cp &lt;/span&gt;s3://hello-bucket/hello.txt ./hello.txt

&lt;span class="c"&gt;# delete the bucket&lt;/span&gt;
aws s3api delete-bucket &lt;span class="nt"&gt;--bucket&lt;/span&gt; hello-bucket
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Seven commands. Seven IAM permission checks. Let us go through each one.&lt;/p&gt;




&lt;h2&gt;
  
  
  Command-to-permission map
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;IAM action required&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;aws s3api create-bucket&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;s3:CreateBucket&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;aws s3api put-bucket-acl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;s3:PutBucketAcl&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;aws s3 cp&lt;/code&gt; (upload)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;s3:PutObject&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;code&gt;aws s3 ls&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;s3:ListBucket&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;code&gt;aws s3 rm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;s3:DeleteObject&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;aws s3 cp&lt;/code&gt; (download)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;s3:GetObject&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;&lt;code&gt;aws s3api delete-bucket&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;s3:DeleteBucket&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One thing most beginners miss: S3 permissions split across &lt;strong&gt;two resource levels&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bucket-level&lt;/strong&gt; actions (&lt;code&gt;CreateBucket&lt;/code&gt;, &lt;code&gt;ListBucket&lt;/code&gt;, &lt;code&gt;PutBucketAcl&lt;/code&gt;, &lt;code&gt;DeleteBucket&lt;/code&gt;) apply to &lt;code&gt;arn:aws:s3:::bucket-name&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Object-level&lt;/strong&gt; actions (&lt;code&gt;PutObject&lt;/code&gt;, &lt;code&gt;GetObject&lt;/code&gt;, &lt;code&gt;DeleteObject&lt;/code&gt;) apply to &lt;code&gt;arn:aws:s3:::bucket-name/*&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Getting this wrong is one of the most common sources of &lt;code&gt;AccessDenied&lt;/code&gt; errors, even when the right action is listed.&lt;/p&gt;




&lt;h2&gt;
  
  
  The minimal IAM policy
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Replace &lt;code&gt;hello-bucket&lt;/code&gt; with your actual bucket name.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"BucketLevelAccess"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"s3:CreateBucket"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"s3:ListBucket"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"s3:PutBucketAcl"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"s3:DeleteBucket"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::hello-bucket"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ObjectLevelAccess"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"s3:PutObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"s3:DeleteObject"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::hello-bucket/*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No wildcards. No &lt;code&gt;s3:*&lt;/code&gt;. Just the seven actions the script actually calls.&lt;/p&gt;




&lt;h2&gt;
  
  
  Line-by-line walkthrough
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Create a bucket
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3api create-bucket &lt;span class="nt"&gt;--bucket&lt;/span&gt; hello-bucket &lt;span class="nt"&gt;--region&lt;/span&gt; eu-west-1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--create-bucket-configuration&lt;/span&gt; &lt;span class="nv"&gt;LocationConstraint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;eu-west-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;IAM check:&lt;/strong&gt; &lt;code&gt;s3:CreateBucket&lt;/code&gt; on &lt;code&gt;arn:aws:s3:::hello-bucket&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;LocationConstraint&lt;/code&gt; is required for every region except &lt;code&gt;us-east-1&lt;/code&gt;, which is the global default&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Set a public ACL
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3api put-bucket-acl &lt;span class="nt"&gt;--bucket&lt;/span&gt; hello-bucket &lt;span class="nt"&gt;--acl&lt;/span&gt; public-read
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;IAM check:&lt;/strong&gt; &lt;code&gt;s3:PutBucketAcl&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;⚠️ &lt;strong&gt;2026 note:&lt;/strong&gt; AWS now enforces &lt;strong&gt;Object Ownership&lt;/strong&gt; with ACLs disabled by default on new buckets. This command will throw &lt;code&gt;AccessControlListNotSupported&lt;/code&gt; unless you explicitly set Object Ownership to &lt;code&gt;BucketOwnerPreferred&lt;/code&gt; or &lt;code&gt;ObjectWriter&lt;/code&gt;. For demos, skip the ACL step entirely and use bucket policies if you need public access.&lt;/li&gt;
&lt;li&gt;Public ACLs are also blocked by &lt;strong&gt;Block Public Access&lt;/strong&gt; settings at the account level — double-check yours.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Upload an object
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3 &lt;span class="nb"&gt;cp &lt;/span&gt;hello.txt s3://hello-bucket/hello.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;IAM check:&lt;/strong&gt; &lt;code&gt;s3:PutObject&lt;/code&gt; on &lt;code&gt;arn:aws:s3:::hello-bucket/*&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Make sure the local file exists before running the script&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. List objects
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3 &lt;span class="nb"&gt;ls &lt;/span&gt;s3://hello-bucket/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;IAM check:&lt;/strong&gt; &lt;code&gt;s3:ListBucket&lt;/code&gt; on &lt;code&gt;arn:aws:s3:::hello-bucket&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Note the resource is the &lt;strong&gt;bucket&lt;/strong&gt;, not the objects — a common policy mistake&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Delete an object
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3 &lt;span class="nb"&gt;rm &lt;/span&gt;s3://hello-bucket/hello.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;IAM check:&lt;/strong&gt; &lt;code&gt;s3:DeleteObject&lt;/code&gt; on &lt;code&gt;arn:aws:s3:::hello-bucket/*&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6. Download an object
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3 &lt;span class="nb"&gt;cp &lt;/span&gt;s3://hello-bucket/hello.txt ./hello.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;IAM check:&lt;/strong&gt; &lt;code&gt;s3:GetObject&lt;/code&gt; on &lt;code&gt;arn:aws:s3:::hello-bucket/*&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  7. Delete the bucket
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3api delete-bucket &lt;span class="nt"&gt;--bucket&lt;/span&gt; hello-bucket
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;IAM check:&lt;/strong&gt; &lt;code&gt;s3:DeleteBucket&lt;/code&gt; on &lt;code&gt;arn:aws:s3:::hello-bucket&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The bucket &lt;strong&gt;must be empty&lt;/strong&gt; before this succeeds. S3 will return a &lt;code&gt;BucketNotEmpty&lt;/code&gt; error otherwise.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  A production-ready version of the script
&lt;/h2&gt;

&lt;p&gt;The original script has a few rough edges worth smoothing out before using it in a real environment. The corrected version below uses a timestamp suffix to avoid S3's global naming conflicts, ensures the file is downloaded before it is deleted, and removes the ACL step that fails by default on modern AWS accounts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;BUCKET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"hello-bucket-&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%s&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"eu-west-1"&lt;/span&gt;
&lt;span class="nv"&gt;LOCAL_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"test.txt"&lt;/span&gt;
&lt;span class="nv"&gt;OBJECT_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"hello.txt"&lt;/span&gt;

&lt;span class="c"&gt;# 1. Create bucket&lt;/span&gt;
aws s3api create-bucket &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--bucket&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BUCKET&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REGION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--create-bucket-configuration&lt;/span&gt; &lt;span class="s2"&gt;"LocationConstraint=&lt;/span&gt;&lt;span class="nv"&gt;$REGION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# 2. Upload file&lt;/span&gt;
aws s3 &lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOCAL_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"s3://&lt;/span&gt;&lt;span class="nv"&gt;$BUCKET&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$OBJECT_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# 3. List objects&lt;/span&gt;
aws s3 &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="s2"&gt;"s3://&lt;/span&gt;&lt;span class="nv"&gt;$BUCKET&lt;/span&gt;&lt;span class="s2"&gt;/"&lt;/span&gt;

&lt;span class="c"&gt;# 4. Download file (before deleting it)&lt;/span&gt;
aws s3 &lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="s2"&gt;"s3://&lt;/span&gt;&lt;span class="nv"&gt;$BUCKET&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$OBJECT_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"./downloaded-&lt;/span&gt;&lt;span class="nv"&gt;$OBJECT_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# 5. Remove the object, then the bucket&lt;/span&gt;
aws s3 &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="s2"&gt;"s3://&lt;/span&gt;&lt;span class="nv"&gt;$BUCKET&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$OBJECT_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
aws s3api delete-bucket &lt;span class="nt"&gt;--bucket&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BUCKET&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The mental model I use before every AWS CLI command
&lt;/h2&gt;

&lt;p&gt;Before writing or running any AWS CLI call, I ask three questions:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Which exact API action is this command calling?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Is the permission needed at the bucket level, object level, or both?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;What is the narrowest resource ARN I can use?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That habit is what keeps IAM policies auditable. The moment you write &lt;code&gt;s3:*&lt;/code&gt; or use &lt;code&gt;"Resource": "*"&lt;/code&gt;, you lose the ability to reason about what the policy actually permits.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Every AWS CLI command maps to at least one IAM action — knowing the mapping is the core skill&lt;/li&gt;
&lt;li&gt;S3 permissions split across two resource ARN patterns: bucket (&lt;code&gt;:::name&lt;/code&gt;) and objects (&lt;code&gt;:::name/*&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Modern AWS accounts disable ACLs by default — do not rely on &lt;code&gt;put-bucket-acl&lt;/code&gt; in new scripts&lt;/li&gt;
&lt;li&gt;Least privilege is not just a best practice; it is the only way to write policies you can actually audit&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>iam</category>
      <category>s3</category>
      <category>devops</category>
    </item>
    <item>
      <title>Mocking HTTP Services in PHP with Phiremock</title>
      <dc:creator>BRUNO SOUZA</dc:creator>
      <pubDate>Tue, 03 Mar 2026 18:57:49 +0000</pubDate>
      <link>https://dev.to/brunohsouza/mocking-http-services-in-php-with-phiremock-aa7</link>
      <guid>https://dev.to/brunohsouza/mocking-http-services-in-php-with-phiremock-aa7</guid>
      <description>&lt;p&gt;This article is part of a series on mock servers for backend developers. Part 1 covers language-agnostic tools (Postman, WireMock, and Pact Stub Server). This article focuses on Phiremock, the first of three PHP-specific tools covered in the series.&lt;/p&gt;

&lt;p&gt;When your PHP application integrates with third-party APIs — payment gateways, logistics providers, marketplaces — testing those integrations reliably is a real challenge. Hitting real sandboxes is slow, fragile, and sometimes billable. Phiremock solves this by running a real HTTP server that your application calls instead, returning whatever response you define.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Phiremock?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Phiremock is a standalone HTTP mock server written in PHP, heavily inspired by WireMock. You run it as a separate process (or Docker container) and configure it via JSON expectation files or programmatically through its PHP client.&lt;/p&gt;

&lt;p&gt;Current version: &lt;strong&gt;&lt;em&gt;v1.5.1&lt;/em&gt;&lt;/strong&gt; (released December 2024)&lt;br&gt;
PHP compatibility: &lt;strong&gt;&lt;em&gt;^7.2 | ^8.0&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/mcustiel/phiremock" rel="noopener noreferrer"&gt;https://github.com/mcustiel/phiremock&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The key advantage over in-process mocking libraries is that Phiremock runs as a real HTTP server. Your application doesn't know it's talking to a mock; it makes real HTTP calls, making the tests more realistic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Installation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Since version 2, Phiremock is split into two packages: the server and the client. Install both as dev dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require &lt;span class="nt"&gt;--dev&lt;/span&gt; mcustiel/phiremock-server mcustiel/phiremock-client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you also need Guzzle as the HTTP client (default):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require &lt;span class="nt"&gt;--dev&lt;/span&gt; mcustiel/phiremock-server mcustiel/phiremock-client guzzlehttp/guzzle:^6.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Starting the Server&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Start Phiremock from the command line after installing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vendor/bin/phiremock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, it listens on 0.0.0.0:8086. To customise:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vendor/bin/phiremock &lt;span class="nt"&gt;--port&lt;/span&gt; 8089 &lt;span class="nt"&gt;--ip&lt;/span&gt; 127.0.0.1 &lt;span class="nt"&gt;--debug&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Configuration File&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For a consistent setup, create a .phiremock file at your project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; 
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="s1"&gt;'port'&lt;/span&gt;             &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;8089&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s1"&gt;'ip'&lt;/span&gt;               &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'0.0.0.0'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s1"&gt;'expectations-dir'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'./tests/expectations'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s1"&gt;'debug'&lt;/span&gt;            &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Phiremock searches for this file automatically at startup. Command-line arguments take priority over the config file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Docker Compose&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For a shared dev/CI environment, run Phiremock alongside your application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;PAYMENT_API_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://phiremock:8086&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;phiremock&lt;/span&gt;

  &lt;span class="na"&gt;phiremock&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mcustiel/phiremock&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8086:8086"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./tests/expectations:/var/www/phiremock/expectations&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once running, update your application config to point to &lt;em&gt;&lt;a href="http://localhost:8086" rel="noopener noreferrer"&gt;http://localhost:8086&lt;/a&gt;&lt;/em&gt; (or the Docker service name) instead of the real service URL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Defining Expectations&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An expectation tells Phiremock: "&lt;em&gt;When you receive this request, return this response&lt;/em&gt;". You can define expectations in two ways: as JSON files loaded on startup, or programmatically via the PHP client at runtime.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JSON Expectations (Declarative)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Place JSON files in your expectations-dir directory. They are loaded automatically when Phiremock starts.&lt;br&gt;
Match a GET request and return a product:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"on"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"isSameString"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"isEqualTo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/api/products/1"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"then"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"statusCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: 1, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Gift Box&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;price&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: 49.99}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/json"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Match a POST with a URL regex and simulate a 100ms delay:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"on"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"isSameString"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"POST"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"matches"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"~/api/orders/?~"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"then"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"delayMillis"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"statusCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;orderId&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;abc-123&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;status&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;processing&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/json"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simulate a 503 service unavailable&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"on"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"isSameString"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"isEqualTo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/api/inventory/check"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"then"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"statusCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Service temporarily unavailable&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/json"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Programmatic Expectations (PHP Client)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For dynamic test scenarios, define expectations in code using the Phiremock client. This is particularly useful in PHPUnit tests where each test needs a different response.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setting up the client:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Mcustiel\Phiremock\Client\Connection\Host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Mcustiel\Phiremock\Client\Connection\Port&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Mcustiel\Phiremock\Client\Factory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Mcustiel\Phiremock\Client\Phiremock&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Mcustiel\Phiremock\Client\Utils\A&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Mcustiel\Phiremock\Client\Utils\Is&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Mcustiel\Phiremock\Client\Utils\Respond&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Factory&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;createDefault&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;createPhiremockClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'localhost'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Port&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8086&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Creating the expectation&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;createExpectation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;Phiremock&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;A&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;getRequest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Is&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;equalTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/api/products/1'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;Respond&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;withStatusCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'{"id": 1, "name": "Gift Box", "price": 49.99}'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Content-Type'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'application/json'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Matching by body content:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;createExpectation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;Phiremock&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;A&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;postRequest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Is&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;equalTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/api/orders'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Is&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;containing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'"productId":1'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;Respond&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;withStatusCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'{"orderId": "abc-123"}'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Content-Type'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'application/json'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Matching by header:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;createExpectation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;Phiremock&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;A&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;getRequest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Is&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;equalTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/api/products'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Authorization'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Is&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;matching&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/^Bearer .+/'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;Respond&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;withStatusCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'[{"id": 1, "name": "Gift Box"}]'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Content-Type'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'application/json'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Using with PHPUnit&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here is a complete PHPUnit test class that resets Phiremock state between tests and uses programmatic expectations:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Defining a config to run before the tests:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Tests&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Mcustiel\Phiremock\Client\Connection\Host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Mcustiel\Phiremock\Client\Connection\Port&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Mcustiel\Phiremock\Client\Factory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Mcustiel\Phiremock\Client\Phiremock&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Mcustiel\Phiremock\Client\Utils\A&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Mcustiel\Phiremock\Client\Utils\Is&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Mcustiel\Phiremock\Client\Utils\Respond&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;PHPUnit\Framework\TestCase&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductServiceTest&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;TestCase&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;Phiremock&lt;/span&gt; &lt;span class="nv"&gt;$phiremock&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;ProductService&lt;/span&gt; &lt;span class="nv"&gt;$productService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;setUpBeforeClass&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nv"&gt;$phiremock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Factory&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;createDefault&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;createPhiremockClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'localhost'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Port&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8086&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Defining the set up for the tests:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;setUp&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Clear all expectations and request history before each test&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nv"&gt;$phiremock&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;clearExpectations&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nv"&gt;$phiremock&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;resetRequestsLog&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Point your service at the mock server&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;productService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ProductService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'http://localhost:8086'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Writing the functions to test:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;[&lt;em&gt;Positive Scenario&lt;/em&gt;]&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;testFetchesProductSuccessfully&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nv"&gt;$phiremock&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;createExpectation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;Phiremock&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nc"&gt;A&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;getRequest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Is&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;equalTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/api/products/1'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nc"&gt;Respond&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;withStatusCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'{"id": 1, "name": "Gift Box", "price": 49.99}'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Content-Type'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'application/json'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;productService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Gift Box'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;49.99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'price'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;[&lt;em&gt;Negative Scenario&lt;/em&gt;]&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;testHandles404Gracefully&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nv"&gt;$phiremock&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;createExpectation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;Phiremock&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nc"&gt;A&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;getRequest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Is&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;equalTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/api/products/999'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nc"&gt;Respond&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;withStatusCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'{"error": "Not found"}'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Content-Type'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'application/json'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;expectException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ProductNotFoundException&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;productService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;999&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;testHandlesServiceTimeout&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nv"&gt;$phiremock&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;createExpectation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;Phiremock&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nc"&gt;A&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;getRequest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Is&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;equalTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/api/products/1'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nc"&gt;Respond&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;withStatusCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'{"id": 1, "name": "Gift Box"}'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andDelayInMillis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// simulate a slow response&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;expectException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ServiceTimeoutException&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;productService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Verifying Calls&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One of Phiremock's most useful features for strict test assertions is verifying that your application actually made the expected calls — not just that it returned the right result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;testCreatesOrderAndCallsPaymentGateway&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nv"&gt;$phiremock&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;createExpectation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;Phiremock&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;A&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;postRequest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Is&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;equalTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/api/payments/charge'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;Respond&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;withStatusCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'{"chargeId": "ch_abc123", "status": "succeeded"}'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;orderService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'productId'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'quantity'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="c1"&gt;// Assert the payment endpoint was called exactly once&lt;/span&gt;
    &lt;span class="nv"&gt;$executions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nv"&gt;$phiremock&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;countExecutions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;A&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;postRequest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;andUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Is&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;equalTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/api/payments/charge'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$executions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Stateful Scenarios&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Phiremock supports scenarios (sequences of responses that transition state depending on how many times an endpoint has been called). This is useful for simulating async workflows, such as order processing or job polling.&lt;br&gt;
Define two JSON expectations for the same endpoint with different scenario states:&lt;/p&gt;

&lt;p&gt;First call — returns "&lt;strong&gt;processing&lt;/strong&gt;":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scenarioName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"order-lifecycle"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"on"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"scenarioStateIs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Scenario.START"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"isSameString"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"isEqualTo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/api/orders/abc-123"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"then"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"newScenarioState"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"order-completed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"statusCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;orderId&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;abc-123&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;status&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;processing&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/json"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Second call — returns "&lt;strong&gt;completed&lt;/strong&gt;":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scenarioName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"order-lifecycle"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"on"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"scenarioStateIs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"order-completed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"isSameString"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"isEqualTo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/api/orders/abc-123"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"then"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"statusCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;orderId&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;abc-123&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;status&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;completed&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/json"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first time your application calls GET &lt;em&gt;/api/orders/abc-123&lt;/em&gt;, it receives a &lt;em&gt;processing&lt;/em&gt; status. The second call returns &lt;em&gt;completed&lt;/em&gt; — no code changes needed between calls.&lt;br&gt;
You can reset all scenarios to their initial state between tests via the client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nv"&gt;$phiremock&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;resetScenarios&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pros &amp;amp; Cons&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pros:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Supports PHP 8, actively maintained (v1.5.1, December 2024)&lt;/li&gt;
&lt;li&gt;Runs as a real HTTP server — tests are realistic end-to-end&lt;/li&gt;
&lt;li&gt;Rich matching API: method, URL, body, headers, regex, exact match&lt;/li&gt;
&lt;li&gt;Network latency simulation with delayMillis&lt;/li&gt;
&lt;li&gt;Stateful scenarios for async workflows&lt;/li&gt;
&lt;li&gt;Verify call counts for strict assertion&lt;/li&gt;
&lt;li&gt;Docker image available for shared environments&lt;/li&gt;
&lt;li&gt;Works with any PHP HTTP client&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requires a running server process — adds infrastructure overhead&lt;/li&gt;
&lt;li&gt;No built-in GUI (unlike Postman or WireMock)&lt;/li&gt;
&lt;li&gt;The client requires Guzzle v6 by default (an older version)&lt;/li&gt;
&lt;li&gt;More setup than in-process libraries for simple unit tests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When to Use Phiremock&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Phiremock is the right choice when you need a mock server that multiple test suites, developers, or services can share, especially in a Docker Compose environment. If you need stateful scenarios, call verification, or realistic end-to-end HTTP testing where your app makes actual network connections to a real server, Phiremock is the strongest PHP-native option available.&lt;br&gt;
For simpler, inline mocks that live entirely inside a single PHPUnit test class, take a look at the next tools in this series.&lt;/p&gt;

&lt;p&gt;This article is part of a series on mock servers. Read &lt;em&gt;&lt;a href="https://dev.to/brunohsouza/to-mock-or-not-to-mock-a-practical-guide-to-mock-servers-for-apis-jli"&gt;Part 1: Language-Agnostic Tools (Postman, WireMock, Pact Stub Server)&lt;/a&gt;&lt;/em&gt;, or continue with the next PHP tool in the series.&lt;/p&gt;

&lt;p&gt;If you have questions or want to share how your team uses Phiremock, drop a comment below!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;— Bruno Souza | Senior Software Engineer | &lt;a href="https://www.linkedin.com/in/bruno-souza-software-engineer/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>testing</category>
      <category>phiremock</category>
      <category>api</category>
    </item>
    <item>
      <title>To Mock or Not to Mock: A Practical Guide to Mock Servers For APIs</title>
      <dc:creator>BRUNO SOUZA</dc:creator>
      <pubDate>Wed, 25 Feb 2026 10:00:00 +0000</pubDate>
      <link>https://dev.to/brunohsouza/to-mock-or-not-to-mock-a-practical-guide-to-mock-servers-for-apis-jli</link>
      <guid>https://dev.to/brunohsouza/to-mock-or-not-to-mock-a-practical-guide-to-mock-servers-for-apis-jli</guid>
      <description>&lt;p&gt;When you're building a backend that integrates with multiple third-party services, such as marketplaces, payment gateways, shipping providers, and 3PLs, your test suite quickly becomes a hostage to availability, rate limits, and unpredictable responses. I hit this problem hard while working on an integration platform that connected to external partners.&lt;/p&gt;

&lt;p&gt;In this article, I'll walk through what mock testing is, why it matters, and give a practical overview of three language-agnostic tools: &lt;strong&gt;Postman Mock Server&lt;/strong&gt;, &lt;strong&gt;WireMock&lt;/strong&gt;, and the &lt;strong&gt;Pact Stub Server&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is the first article in a three-part series:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Part 1 (this article):&lt;/strong&gt; Language-agnostic tools — Postman, WireMock, and Pact Stub Server&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 2:&lt;/strong&gt; PHP-specific tools — Phiremock, HTTP Mock, and Mock Webserver&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 3:&lt;/strong&gt; Node.js-specific tools — Nock and MSW&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tools in this article don't care what language your application is written in. Whether you're running PHP, Node.js, Java, Python, or Go — these tools will work because they operate at the HTTP level, not at the code level. They're also the best choice when multiple services in different languages all need to share the same set of mocks.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is Mock Testing?
&lt;/h2&gt;

&lt;p&gt;Mock testing is the practice of emulating external services so your application can be tested against any scenario in complete isolation, without touching the real service.&lt;/p&gt;

&lt;p&gt;The key advantages are that developers and testers get expected results without depending on a real (possibly unstable) external service, there's no risk of polluting production or staging data on third-party systems, you have full control over responses to simulate timeouts, 500 errors, malformed payloads, and edge cases, and tests run faster with no real network overhead.&lt;/p&gt;

&lt;p&gt;The goal, in practical terms, is to intercept every outbound HTTP call your application makes and return a pre-defined, predictable response. Your code doesn't know the difference, and that's exactly the point.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tools at a Glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;How it runs&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Postman Mock Server&lt;/td&gt;
&lt;td&gt;Cloud-based GUI&lt;/td&gt;
&lt;td&gt;Cloud (no setup)&lt;/td&gt;
&lt;td&gt;Prototyping, team sharing, frontend collaboration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WireMock&lt;/td&gt;
&lt;td&gt;HTTP stub server&lt;/td&gt;
&lt;td&gt;Docker / standalone JAR&lt;/td&gt;
&lt;td&gt;Powerful integration test environments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pact Stub Server&lt;/td&gt;
&lt;td&gt;HTTP stub server&lt;/td&gt;
&lt;td&gt;Binary / Docker&lt;/td&gt;
&lt;td&gt;Serving pre-written pact JSON files as a live mock&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Postman Mock Server
&lt;/h2&gt;

&lt;p&gt;If your team already uses Postman for API documentation and manual testing, spinning up a mock server from an existing collection is remarkably quick — and since it runs in the cloud, any service in any language can call it.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to Create One
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Step 1 — Add an example to a request.&lt;/strong&gt;&lt;br&gt;
Open a request inside a Collection, send it once to capture a real response, click the &lt;code&gt;...&lt;/code&gt; menu to the right of the request, and choose &lt;strong&gt;Add Example&lt;/strong&gt;. Postman pre-fills it with the actual response you received.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faqe07d6i5a0z17cer3ga.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faqe07d6i5a0z17cer3ga.png" alt="Creating a Postman Mock Server" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2 — Create the mock server.&lt;/strong&gt;&lt;br&gt;
Go to the Collection settings and click &lt;strong&gt;+ New&lt;/strong&gt; next to the &lt;em&gt;Mock Servers&lt;/em&gt; section. Set the mock server name, pick an environment (e.g. &lt;code&gt;API_LOCAL&lt;/code&gt;), optionally check &lt;em&gt;Save the mock server URL as an environment variable&lt;/em&gt;, and click &lt;strong&gt;Create Mock Server&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbd0c89j42icruna58e21.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbd0c89j42icruna58e21.png" alt="Postman - Creating Mock Server in Collection" width="800" height="313"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiii028n73b99gn3vh0rw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiii028n73b99gn3vh0rw.png" alt="Showing Mock Server in the Collection" width="800" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Postman generates a unique URL like &lt;code&gt;https://xxxxxxxx.mock.pstmn.io&lt;/code&gt;. Any request matching your saved examples will return the mocked response. In your application config, swap the base URL to point to the testing URL. No code changes needed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0hxn321on2vgo58askb0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0hxn321on2vgo58askb0.png" alt="Postman - Mock Server Details" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Simulating Different Scenarios
&lt;/h3&gt;

&lt;p&gt;You can add multiple examples to the same request, each with a different response body or status code. Postman selects the matching example based on query params, headers, or path.&lt;/p&gt;

&lt;p&gt;For example, having two examples for &lt;code&gt;GET /api/products/:id&lt;/code&gt; — one returning &lt;code&gt;200&lt;/code&gt; with a product, another returning &lt;code&gt;404&lt;/code&gt; with an error — lets different consumers test both paths just by hitting different URLs or passing different headers.&lt;/p&gt;
&lt;h3&gt;
  
  
  Pros &amp;amp; Cons
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; Zero configuration code, excellent GUI, language-independent (any service can call it), great for sharing mocks across teams, free tier available.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt; Responses must be captured from a real endpoint first — you can't fabricate a response that doesn't yet exist without manually editing the example body. Limited dynamic manipulation compared to code-based tools. Requires a Postman account.&lt;/p&gt;


&lt;h2&gt;
  
  
  WireMock
&lt;/h2&gt;

&lt;p&gt;WireMock is the battle-tested standalone HTTP mock server originally from the Java ecosystem — but it's fully language-agnostic and works perfectly from any project. You run it as a Docker container or a standalone JAR, and interact with it via a REST API or one of its official client libraries for Node.js, Python, Go, and others.&lt;/p&gt;

&lt;p&gt;It's the right choice when you need a powerful, long-lived mock server shared across multiple services in a Docker Compose environment, or when you need advanced features like stateful scenarios and response templating.&lt;/p&gt;
&lt;h3&gt;
  
  
  Starting WireMock via Docker
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; wiremock &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:8080 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nv"&gt;$PWD&lt;/span&gt;/wiremock:/home/wiremock &lt;span class="se"&gt;\&lt;/span&gt;
  wiremock/wiremock:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Once running, WireMock exposes &lt;code&gt;http://localhost:8080&lt;/code&gt; for your application to call, and &lt;code&gt;http://localhost:8080/__admin&lt;/code&gt; for its management API.&lt;/p&gt;
&lt;h3&gt;
  
  
  Defining Stubs via the REST API
&lt;/h3&gt;

&lt;p&gt;You can create stubs with a plain HTTP POST — any language, any tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8080/__admin/mappings &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "request": {
      "method": "GET",
      "url": "/api/products/1"
    },
    "response": {
      "status": 200,
      "body": "{\"id\": 1, \"name\": \"Gift Box\", \"price\": 49.99}",
      "headers": { "Content-Type": "application/json" }
    }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Stub Files (Declarative Approach)
&lt;/h3&gt;

&lt;p&gt;Alternatively, drop JSON files into &lt;code&gt;./wiremock/mappings/&lt;/code&gt; and they are loaded on startup — great for version-controlling your mock definitions alongside your code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"urlPattern"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/api/orders"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"bodyPatterns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"matchesJsonPath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$.productId"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"matches"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer .+"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"jsonBody"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"orderId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"abc-123"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/json"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"fixedDelayMilliseconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Stateful Scenarios
&lt;/h3&gt;

&lt;p&gt;One of WireMock's most powerful features is &lt;strong&gt;scenarios&lt;/strong&gt; — mocks that change state based on how many times they've been called. This is perfect for simulating async workflows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scenarioName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Order Processing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"requiredScenarioState"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Started"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"newScenarioState"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Processing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/api/orders"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;202&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"jsonBody"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"processing"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scenarioName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Order Processing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"requiredScenarioState"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Processing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/api/orders/abc-123"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"jsonBody"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"completed"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Response Templating
&lt;/h3&gt;

&lt;p&gt;WireMock can also extract data from the request and inject it into the response — useful when your code echoes back submitted data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/api/products"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;{{randomValue type='UUID'}}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;{{{jsonPath request.body '$.name'}}}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/json"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"transformers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"response-template"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pros &amp;amp; Cons
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; Extremely feature-rich (stateful scenarios, response templating, fault simulation, proxying); runs in Docker so it's shareable across a whole team or CI environment; battle-tested at enterprise scale; excellent documentation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt; Requires a running Docker container or a Java process — more infrastructure overhead than in-process solutions; initial learning curve with the mapping DSL; the stub format is verbose for simple cases.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pact Stub Server
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/pact-foundation/pact-stub-server" rel="noopener noreferrer"&gt;Pact Stub Server&lt;/a&gt; is a standalone HTTP mock server that serves responses defined in pact-format JSON files. You write your interactions (request/response pairs) as JSON, point the stub server at them, and it starts serving those responses immediately — no code required, no language SDK to install.&lt;/p&gt;

&lt;p&gt;It's built in Rust, distributed as a single binary or a Docker image, and fully language-agnostic. The stub server is conceptually similar to WireMock's declarative mapping files, but uses the &lt;a href="https://github.com/pact-foundation/pact-specification/tree/version-4" rel="noopener noreferrer"&gt;Pact V4 specification&lt;/a&gt; JSON format instead.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This is not the same as Pact contract testing. The stub server simply serves pre-written responses — there is no consumer/provider verification involved. Contract testing is a separate topic we'll cover in a future article.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Via Docker (recommended):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker pull pactfoundation/pact-stub-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Via binary download:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Download the latest release for your platform from the &lt;a href="https://github.com/pact-foundation/pact-stub-server/releases" rel="noopener noreferrer"&gt;GitHub releases page&lt;/a&gt; (supports Linux, macOS, and Windows on both x86_64 and arm64).&lt;/p&gt;

&lt;h3&gt;
  
  
  Writing a Pact Stub File
&lt;/h3&gt;

&lt;p&gt;The stub server reads standard pact JSON files. You can write these by hand — they're just JSON files describing which request should return which response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"consumer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ProductService"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"provider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ProductAPI"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"interactions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"get product 1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/api/products/1"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/json"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Gift Box"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;49.99&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"get unknown product"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/api/products/999"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/json"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Not found"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"create an order"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/api/orders"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/json"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/json"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"orderId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"abc-123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"processing"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save this as &lt;code&gt;pacts/ProductService-ProductAPI.json&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running the Stub Server
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;From a single pact file:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pact-stub-server &lt;span class="nt"&gt;--file&lt;/span&gt; pacts/ProductService-ProductAPI.json &lt;span class="nt"&gt;--port&lt;/span&gt; 8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;From an entire directory of pact files:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pact-stub-server &lt;span class="nt"&gt;--dir&lt;/span&gt; pacts/ &lt;span class="nt"&gt;--port&lt;/span&gt; 8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Via Docker, mounting a local pacts folder:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:8080 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/pacts:/app/pacts"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  pactfoundation/pact-stub-server &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 8080 &lt;span class="nt"&gt;-d&lt;/span&gt; /app/pacts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once running, your application points to &lt;code&gt;http://localhost:8080&lt;/code&gt; instead of the real service. The stub server matches incoming requests by method, path, and query parameters, then returns the matching response body and headers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Docker Compose Integration
&lt;/h3&gt;

&lt;p&gt;For a shared dev/test environment, add the stub server alongside your application services:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;PRODUCT_API_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://stub:8080&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;stub&lt;/span&gt;

  &lt;span class="na"&gt;stub&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pactfoundation/pact-stub-server&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8080:8080"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./pacts:/app/pacts&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-p"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8080"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-d"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/app/pacts"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Serving Multiple Scenarios with Provider States
&lt;/h3&gt;

&lt;p&gt;If your application needs the same endpoint to return different responses depending on the test scenario (e.g. an existing product vs. an out-of-stock one), use &lt;strong&gt;provider states&lt;/strong&gt;. Each interaction can declare a &lt;code&gt;providerState&lt;/code&gt;, and you filter which state the stub server uses at runtime.&lt;/p&gt;

&lt;p&gt;Add multiple interactions for the same endpoint with different provider states in your pact file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"consumer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ProductService"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"provider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ProductAPI"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"interactions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"get product 1 - in stock"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"providerState"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"product 1 is in stock"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/api/products/1"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Gift Box"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"available"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"get product 1 - out of stock"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"providerState"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"product 1 is out of stock"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/api/products/1"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Gift Box"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"available"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start the stub server filtered to a specific state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pact-stub-server &lt;span class="nt"&gt;--dir&lt;/span&gt; pacts/ &lt;span class="nt"&gt;--port&lt;/span&gt; 8080 &lt;span class="nt"&gt;--provider-state&lt;/span&gt; &lt;span class="s2"&gt;"in stock"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, if you need the server to handle multiple states in a single run, pass the desired state as a request header. Start the server with &lt;code&gt;--provider-state-header-name&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pact-stub-server &lt;span class="nt"&gt;--dir&lt;/span&gt; pacts/ &lt;span class="nt"&gt;--port&lt;/span&gt; 8080 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--provider-state-header-name&lt;/span&gt; &lt;span class="s2"&gt;"X-Provider-State"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then your test sends the header to tell the stub which interaction to match:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Returns the "in stock" response&lt;/span&gt;
curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-Provider-State: product 1 is in stock"&lt;/span&gt; http://localhost:8080/api/products/1

&lt;span class="c"&gt;# Returns the "out of stock" response&lt;/span&gt;
curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-Provider-State: product 1 is out of stock"&lt;/span&gt; http://localhost:8080/api/products/1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Watch Mode
&lt;/h3&gt;

&lt;p&gt;During development, pact files change frequently. The &lt;code&gt;--watch&lt;/code&gt; flag tells the stub server to monitor files and reload automatically without a restart:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pact-stub-server &lt;span class="nt"&gt;--dir&lt;/span&gt; pacts/ &lt;span class="nt"&gt;--port&lt;/span&gt; 8080 &lt;span class="nt"&gt;--watch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any time you add, edit, or delete a pact file in the watched directory, the stub server picks up the changes immediately.&lt;/p&gt;

&lt;h3&gt;
  
  
  Loading Pacts from a URL or Pact Broker
&lt;/h3&gt;

&lt;p&gt;The stub server can also fetch pact files from remote sources, which is useful in CI environments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Load from a URL&lt;/span&gt;
pact-stub-server &lt;span class="nt"&gt;--url&lt;/span&gt; https://your-server/pacts/ProductService-ProductAPI.json &lt;span class="nt"&gt;--port&lt;/span&gt; 8080

&lt;span class="c"&gt;# Load from a Pact Broker (authenticated)&lt;/span&gt;
pact-stub-server &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--broker-url&lt;/span&gt; https://your-pact-broker &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--token&lt;/span&gt; your-bearer-token &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--provider-name&lt;/span&gt; ProductAPI &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--port&lt;/span&gt; 8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  CORS Support
&lt;/h3&gt;

&lt;p&gt;For browser-based consumers (e.g. a React app calling the stub during development), enable CORS with the &lt;code&gt;-o&lt;/code&gt; flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pact-stub-server &lt;span class="nt"&gt;--dir&lt;/span&gt; pacts/ &lt;span class="nt"&gt;--port&lt;/span&gt; 8080 &lt;span class="nt"&gt;--cors&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This automatically responds to &lt;code&gt;OPTIONS&lt;/code&gt; preflight requests and adds the &lt;code&gt;Access-Control-Allow-Origin: *&lt;/code&gt; header to all responses.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pros &amp;amp; Cons
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; Single binary with zero runtime dependencies — no JVM, no Node.js, no PHP needed; language-agnostic (any service can call it); JSON stub files are plain text and version-control friendly; watch mode makes development smooth; can pull pacts directly from a Pact Broker in CI; multi-platform Docker image available.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt; Matching is limited to method, path, and query params — no body-matching or conditional response logic like WireMock offers; response templating (injecting request data into responses) is not supported; designed primarily for prototyping rather than complex integration test scenarios.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;The three tools in this article sit at different points on the effort/power spectrum. Postman gets you a shareable mock in minutes with no code, making it ideal for early prototyping and frontend collaboration. WireMock gives you the full power of a production-grade stub server with stateful scenarios and response templating, making it the right choice for complex integration test environments. The Pact Stub Server sits between the two — a single binary driven entirely by plain JSON files, with no infrastructure overhead and zero language dependencies.&lt;/p&gt;

&lt;p&gt;In practice, they're not mutually exclusive: Postman works great during early development when the API isn't stable yet, the Pact Stub Server is a lightweight option for dev and CI environments where you want file-based stub definitions, and WireMock handles scenarios that need more sophisticated matching or stateful behaviour.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is Part 1 of a three-part series on mock servers. Continue with Part 2: PHP Tools and Part 3: Node.js Tools.&lt;/em&gt; (To be published soon)&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Article based on an internal presentation I gave at a previous company. If you have questions or want to share how your team approaches this problem, drop a comment below!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;— Bruno Souza | Senior Software Engineer | &lt;a href="https://www.linkedin.com/in/bruno-souza-software-engineer/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>pact</category>
      <category>mock</category>
      <category>stub</category>
      <category>api</category>
    </item>
  </channel>
</rss>
