<?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: Maesi</title>
    <description>The latest articles on DEV Community by Maesi (@marcel_lehmann_31109127df).</description>
    <link>https://dev.to/marcel_lehmann_31109127df</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3119590%2F017be7ab-fffe-4fa3-8abc-6250b5c962e9.png</url>
      <title>DEV Community: Maesi</title>
      <link>https://dev.to/marcel_lehmann_31109127df</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/marcel_lehmann_31109127df"/>
    <language>en</language>
    <item>
      <title>Your Test Suite Shouldn't Depend on APIs You Don't Control</title>
      <dc:creator>Maesi</dc:creator>
      <pubDate>Sun, 07 Jun 2026 10:00:00 +0000</pubDate>
      <link>https://dev.to/marcel_lehmann_31109127df/your-test-suite-shouldnt-depend-on-apis-you-dont-control-1c9e</link>
      <guid>https://dev.to/marcel_lehmann_31109127df/your-test-suite-shouldnt-depend-on-apis-you-dont-control-1c9e</guid>
      <description>&lt;p&gt;You know this feeling. A test passes locally, fails in CI, you rerun it, it passes again. Somewhere a third-party API was slow, or returned something unexpected, or was just briefly down. You move on, but it happens again next week.&lt;/p&gt;

&lt;p&gt;And that's before you even get to the scenarios you can't test at all: rate limits, timeouts, malformed responses. You can't make a live API produce those on demand. So those code paths go untested.&lt;/p&gt;

&lt;p&gt;Mocking solves both problems. &lt;a href="https://mokapi.io" rel="noopener noreferrer"&gt;Mokapi&lt;/a&gt; gives you a spec-validated mock server driven by your OpenAPI or AsyncAPI specs. Every response conforms to the contract. Every request gets validated. Your tests run against something you fully control.&lt;/p&gt;




&lt;h2&gt;
  
  
  The CI Setup
&lt;/h2&gt;

&lt;p&gt;Mokapi runs in Docker during your test job. Your app points at it instead of the real API. That's the whole switch.&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="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Start Mokapi&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;docker run -d --name mokapi \&lt;/span&gt;
      &lt;span class="s"&gt;-p 80:80 \&lt;/span&gt;
      &lt;span class="s"&gt;-v ${{ github.workspace }}/mocks:/mocks \&lt;/span&gt;
      &lt;span class="s"&gt;mokapi/mokapi:latest /mocks&lt;/span&gt;
    &lt;span class="s"&gt;sleep 5&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run Tests&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm test&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Stop Mokapi&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker stop mokapi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your mock specs live in &lt;code&gt;/mocks&lt;/code&gt; in the repo, versioned alongside the code. When the API contract changes, the spec changes, the mock changes, and the tests run against the updated contract automatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  Simulating the Scenarios That Matter
&lt;/h2&gt;

&lt;p&gt;This is where it gets useful beyond just removing the external dependency. With Mokapi's JavaScript API you can control behavior at runtime without restarting anything.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sleep&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mokapi&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;

  &lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/simulations/delay&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PUT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="nx"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;duration&lt;/span&gt;
          &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DELETE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="nx"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;
          &lt;span class="k"&gt;break&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;track&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;delay&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;track&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="nx"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&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;Your test sends a PUT to enable a delay, runs the scenario, sends a DELETE to reset. Same pattern works for forcing 500s, 429 rate limits, or any other condition you want to test against.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;track: true&lt;/code&gt; on the first handler is worth understanding. Mokapi's dashboard shows every HTTP request with the response and all JavaScript handlers that ran for it. The first handler only updates internal state without touching the response, so Mokapi wouldn't know it was relevant unless you tell it explicitly. &lt;code&gt;track: true&lt;/code&gt; keeps it visible in the dashboard, which matters when you're debugging why a test behaved unexpectedly.&lt;/p&gt;




&lt;h2&gt;
  
  
  What You Actually Get
&lt;/h2&gt;

&lt;p&gt;Consistent test results because nothing outside your control can break them. Coverage of error scenarios that a live API can't reproduce on demand. Faster pipeline runs with no network latency. And because Mokapi validates against the spec, contract violations get caught in CI before they reach production.&lt;/p&gt;




&lt;h2&gt;
  
  
  Full Walkthrough
&lt;/h2&gt;

&lt;p&gt;The complete guide on &lt;a href="https://mokapi.io" rel="noopener noreferrer"&gt;mokapi.io&lt;/a&gt; covers the full GitHub Actions setup, the JavaScript simulation API, and how to structure your mock specs for a real project.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://mokapi.io/resources/blogs/end-to-end-testing-with-mocked-apis" rel="noopener noreferrer"&gt;Read the full guide here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy to answer questions in the comments.&lt;/p&gt;

</description>
      <category>mock</category>
      <category>api</category>
      <category>cicd</category>
      <category>githubactions</category>
    </item>
    <item>
      <title>API Contracts Break Silently. Here's How to Catch Them the Moment They Do.</title>
      <dc:creator>Maesi</dc:creator>
      <pubDate>Sat, 06 Jun 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/marcel_lehmann_31109127df/api-contracts-break-silently-heres-how-to-catch-them-the-moment-they-do-56kn</link>
      <guid>https://dev.to/marcel_lehmann_31109127df/api-contracts-break-silently-heres-how-to-catch-them-the-moment-they-do-56kn</guid>
      <description>&lt;p&gt;A backend developer renames a field. The tests pass. It ships. Three days later the mobile app is crashing and nobody knows why.&lt;/p&gt;

&lt;p&gt;This is how API contract drift works. It's not a discipline problem. It's a tooling problem. When nothing enforces the contract at runtime, you're relying on everyone to manually keep specs, code, and clients in sync across multiple teams and deploy cycles. That doesn't scale.&lt;/p&gt;

&lt;p&gt;I built a validation layer into Mokapi to fix this. The idea is simple: Mokapi sits between your client and backend, validates every request and response against your OpenAPI spec, and gives you an immediate, actionable error the moment something violates the contract.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Forwarding Script
&lt;/h2&gt;

&lt;p&gt;One JavaScript file is all it takes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mokapi&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mokapi/http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getForwardUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;
      &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Failed to forward request: unknown backend&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;30s&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;

      &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt;
      &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contentType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]?.[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contentType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;// triggers response validation&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;
      &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getForwardUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;backend-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`https://backend1.example.com&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;backend-2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`https://backend2.example.com&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
      &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;undefined&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;code&gt;request.api&lt;/code&gt; contains the &lt;code&gt;info.title&lt;/code&gt; from your OpenAPI spec and is used here purely for routing to the right backend URL.&lt;br&gt;
The choice between &lt;code&gt;response.data&lt;/code&gt; and &lt;code&gt;response.body&lt;/code&gt; is what controls validation: setting &lt;code&gt;response.data&lt;/code&gt; tells Mokapi to validate the response against the spec schema, while setting &lt;code&gt;response.body&lt;/code&gt; skips validation entirely. So for JSON responses you want validated, use &lt;code&gt;response.data&lt;/code&gt;. For anything else, &lt;code&gt;response.body&lt;/code&gt; passes it through as-is.&lt;/p&gt;

&lt;p&gt;No changes to your backend. No changes to your client. Just point the client at Mokapi instead.&lt;/p&gt;




&lt;h2&gt;
  
  
  What It Actually Catches
&lt;/h2&gt;

&lt;p&gt;More than just a renamed field. Mokapi validates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Request method, URL structure, headers, query parameters&lt;/li&gt;
&lt;li&gt;Request body: required fields, data types, format constraints&lt;/li&gt;
&lt;li&gt;Response status codes, headers, and body structure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A backend that starts returning a string where the spec says number. A client sending a request body missing a required field. A response body that doesn't match the documented schema. All caught at the boundary, with a message that tells you exactly what violated what.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where to Use It
&lt;/h2&gt;

&lt;p&gt;It's the same script in every context. Between frontend and backend during local development. Between microservices in CI. In Playwright tests so your test suite becomes a contract test suite automatically. In Kubernetes preview environments before changes reach staging.&lt;/p&gt;




&lt;h2&gt;
  
  
  Full Walkthrough
&lt;/h2&gt;

&lt;p&gt;The complete guide on &lt;a href="https://mokapi.io" rel="noopener noreferrer"&gt;mokapi.io&lt;/a&gt; covers the full setup.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://mokapi.io/resources/blogs/guard-your-api-contracts" rel="noopener noreferrer"&gt;Read the full guide here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy to answer questions in the comments.&lt;/p&gt;

</description>
      <category>mock</category>
      <category>api</category>
      <category>openapi</category>
    </item>
    <item>
      <title>Testing Email Workflows Without Email Server — With Playwright &amp; Mokapi</title>
      <dc:creator>Maesi</dc:creator>
      <pubDate>Fri, 05 Jun 2026 22:07:45 +0000</pubDate>
      <link>https://dev.to/marcel_lehmann_31109127df/testing-email-workflows-without-email-server-with-playwright-mokapi-b25</link>
      <guid>https://dev.to/marcel_lehmann_31109127df/testing-email-workflows-without-email-server-with-playwright-mokapi-b25</guid>
      <description>&lt;p&gt;Email is one of those things that's genuinely hard to test. It goes out through an SMTP server, lands in a real inbox, and you have no programmatic way to check what arrived. So most teams either test it manually or just assert that &lt;code&gt;sendEmail()&lt;/code&gt; was called and call it done.&lt;/p&gt;

&lt;p&gt;Neither tells you if the subject line was right, the link was valid, or the HTML rendered correctly.&lt;/p&gt;

&lt;p&gt;That's why I built email support into &lt;a href="https://mokapi.io" rel="noopener noreferrer"&gt;Mokapi&lt;/a&gt;. Your backend connects to Mokapi's SMTP server exactly as it would a real mail server. Mokapi captures the message. Your test fetches it over HTTP and asserts on the content. No real emails, no inbox polling, no external dependencies.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Config
&lt;/h2&gt;

&lt;p&gt;Unlike Kafka or HTTP, email doesn't have a standard specification format. So Mokapi uses a simple YAML config. This is all you need:&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;mail&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1.0'&lt;/span&gt;
&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Email&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Workflows"&lt;/span&gt;
&lt;span class="na"&gt;servers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;smtp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;:2525&lt;/span&gt;
    &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;smtp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Point your backend at &lt;code&gt;localhost:2525&lt;/code&gt; and Mokapi starts capturing everything.&lt;/p&gt;

&lt;p&gt;Want IMAP too, so you can preview emails in a real mail client during development? Add one line:&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;imap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;:1430&lt;/span&gt;
    &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;imap&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Playwright Test
&lt;/h2&gt;

&lt;p&gt;The test drives a real signup form, then fetches the captured email from Mokapi's HTTP API and asserts on it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Email verification after signup&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;recipient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;randomEmail&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Drive the signup form&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;form&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sign Up&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SuperSecure123!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sign Up&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Check your inbox to verify your email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBeVisible&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Fetch the email from Mokapi&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mails&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`http://localhost:8080/api/services/mail/Email%20Workflows/mailboxes/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/messages?limit=1`&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mailList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;mails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mailList&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;objectContaining&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Verify your email address&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arrayContaining&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;objectContaining&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;noreply@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})]),&lt;/span&gt;
    &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arrayContaining&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;objectContaining&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;recipient&lt;/span&gt; &lt;span class="p"&gt;})])&lt;/span&gt;
  &lt;span class="p"&gt;}));&lt;/span&gt;

  &lt;span class="c1"&gt;// Check the body and verification link&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`http://localhost:8080/api/services/mail/messages/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;mailList&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;messageId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toContain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`verify?email=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&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;Random email per test run keeps things isolated. Playwright's request object handles the Mokapi API calls directly, so the same test that drives the browser also inspects the email. No extra HTTP client needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  The IMAP Bonus
&lt;/h2&gt;

&lt;p&gt;Automated assertions catch broken content. But they don't catch a layout that looks terrible in Outlook. Because Mokapi supports IMAP, you can connect any real mail client to &lt;code&gt;localhost:1430&lt;/code&gt; during development and see exactly what your users will see. It's the visual check your test suite can't do.&lt;/p&gt;




&lt;h2&gt;
  
  
  Full Walkthrough
&lt;/h2&gt;

&lt;p&gt;This is the short version. The complete guide on my site covers the full config, the Express backend, all the Playwright helpers, and how it all fits together.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://mokapi.io/resources/blogs/testing-email-workflows-with-playwright-and-mokapi" rel="noopener noreferrer"&gt;Read the full guide here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Working repo: &lt;a href="https://github.com/marle3003/mokapi-email-workflow" rel="noopener noreferrer"&gt;mokapi-email-workflow&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy to answer questions in the comments.&lt;/p&gt;

</description>
      <category>api</category>
      <category>mock</category>
      <category>email</category>
      <category>playwright</category>
    </item>
    <item>
      <title>Testing Kafka Workflows Without Kafka — With Playwright &amp; Mokapi</title>
      <dc:creator>Maesi</dc:creator>
      <pubDate>Wed, 20 Aug 2025 20:30:38 +0000</pubDate>
      <link>https://dev.to/marcel_lehmann_31109127df/testing-kafka-workflows-without-kafka-with-playwright-mokapi-29l1</link>
      <guid>https://dev.to/marcel_lehmann_31109127df/testing-kafka-workflows-without-kafka-with-playwright-mokapi-29l1</guid>
      <description>&lt;p&gt;Event-driven systems are powerful — but testing them can feel heavy.  &lt;/p&gt;

&lt;p&gt;In event-driven systems, the contract between producers and consumers is everything. A field gets renamed. A schema evolves. A new required property sneaks in. Any of those can silently break a downstream service, and you won't find out until something blows up in production. &lt;/p&gt;

&lt;p&gt;That's the problem I built &lt;a href="https://mokapi.io" rel="noopener noreferrer"&gt;Mokapi&lt;/a&gt; to solve. Define your Kafka topics in an AsyncAPI spec, and Mokapi enforces that contract automatically. Every message gets validated against the schema, no matter where it comes from. Change the spec and run your tests. If anything is out of line, you'll know immediately.&lt;/p&gt;

&lt;p&gt;Because Mokapi acts as a real Kafka broker on &lt;code&gt;localhost:9092&lt;/code&gt;, your service doesn't need to change at all. No special test mode. No stubbed clients. Just your actual code running against a mock that holds it to the contract.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Idea Behind Mokapi
&lt;/h2&gt;

&lt;p&gt;Mokapi lets you define Kafka topics with an AsyncAPI spec and interact with them over a simple REST API. No broker. No Zookeeper. Your service connects to &lt;code&gt;localhost:9092&lt;/code&gt; exactly like it does in production, but Mokapi is answering. The application code doesn't change at all.&lt;/p&gt;

&lt;p&gt;In this article I'm using Mokapi's HTTP API to inject test messages, but Mokapi actually supports several other ways to produce messages too, including a JavaScript API and OpenAPI-backed endpoints with custom handlers. &lt;/p&gt;

&lt;p&gt;And in all cases, Mokapi validates every message against your AsyncAPI schema by default. You're not just checking that a message arrived, you're checking it's valid according to the contract.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Real Workflow Test
&lt;/h2&gt;

&lt;p&gt;Here's the scenario: a backend consumes a command from &lt;code&gt;document.send-command&lt;/code&gt;, processes it, and publishes a result to &lt;code&gt;document.send-event&lt;/code&gt;. The test acts as the foreign system that sends the command and verifies the outcome.&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%2Fqz04ywesoubarh9g3v7l.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%2Fqz04ywesoubarh9g3v7l.png" alt=" " width="799" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I use Playwright as the test runner (yes, the browser tool, but it's great for async workflows even without a browser).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Kafka document send workflow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;documentId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;doc-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;startOffset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Record current offset&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;startOffset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getPartitionOffset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TOPIC_EVENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Produce command&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;produce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TOPIC_COMMAND&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;documentId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;documentId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alice@mokapi.io&lt;/span&gt;&lt;span class="dl"&gt;'&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="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Wait for event and assert&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;timeout&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;records&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TOPIC_EVENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;startOffset&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;records&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentId&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;documentId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;startOffset&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;records&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&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="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;not&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBeNull&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SENT&lt;/span&gt;&lt;span class="dl"&gt;'&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;The offset tracking is the thing people miss the first time. Without it, you might pick up a stale message from a previous run and get a false positive. Record the offset before you produce, then only read from there.&lt;br&gt;
The backend itself runs completely unchanged, connecting to &lt;code&gt;localhost:9092&lt;/code&gt; as normal. It doesn't know Mokapi is there. That's kind of the whole point.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I Think This Matters
&lt;/h2&gt;

&lt;p&gt;The backend isn't mocked. Your actual consumer and producer code runs against a spec-validated broker. You're testing your logic, not simulating it. And because Mokapi starts in seconds with no infrastructure dependencies, this works fine in CI without any special setup.&lt;/p&gt;




&lt;h2&gt;
  
  
  Full Walkthrough
&lt;/h2&gt;

&lt;p&gt;This is the short version. I wrote a complete guide on my site covering the full AsyncAPI spec, the Node.js backend, all the Playwright helpers, and how to extend this pattern to more complex workflows.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://mokapi.io/resources/blogs/testing-kafka-workflows-playwright" rel="noopener noreferrer"&gt;Read the full guide here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Working repo: &lt;a href="https://github.com/marle3003/mokapi-kafka-workflow" rel="noopener noreferrer"&gt;mokapi-kafka-workflow&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy to answer questions in the comments if you run into anything.&lt;/p&gt;

</description>
      <category>mock</category>
      <category>api</category>
      <category>kafka</category>
      <category>playwright</category>
    </item>
    <item>
      <title>Acceptance Testing with Mokapi: Focus on What Matters</title>
      <dc:creator>Maesi</dc:creator>
      <pubDate>Tue, 08 Jul 2025 11:24:48 +0000</pubDate>
      <link>https://dev.to/marcel_lehmann_31109127df/acceptance-testing-with-mokapi-focus-on-what-matters-4jd6</link>
      <guid>https://dev.to/marcel_lehmann_31109127df/acceptance-testing-with-mokapi-focus-on-what-matters-4jd6</guid>
      <description>&lt;p&gt;In today’s fast-paced development cycles, it’s crucial to ensure your software meets real user expectations. While unit tests validate internal code, acceptance testing answers the bigger question: Does the system behave as users expect? This post explores the value of acceptance tests and how &lt;a href="https://mokapi.io" rel="noopener noreferrer"&gt;Mokapi&lt;/a&gt; supports it by simulating APIs, validating specifications, and enabling robust, realistic test cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Acceptance Testing
&lt;/h2&gt;

&lt;p&gt;Software testing is not merely a box to check—it is a fundamental process to answer one critical question: Is our software releasable?&lt;/p&gt;

&lt;p&gt;Among the various levels of testing, acceptance testing offers the most direct insight into whether the software meets business and user expectations. It bridges the gap between the world of users and the inner workings of the code by turning expectations into precise, executable checks on system behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Nature of Acceptance Tests
&lt;/h3&gt;

&lt;p&gt;At its core, an acceptance test is an executable specification of how a system should behave, written from a user’s perspective. It is not concerned with how the system is implemented, but with what it does—its behavior and its outcomes.&lt;/p&gt;

&lt;p&gt;While unit tests focus on individual components, acceptance tests focus on the system's intent. They clarify what should happen when a user interacts with the software under certain conditions.&lt;/p&gt;

&lt;p&gt;When we get the level of abstraction right, acceptance tests become clear, precise, and maintainable. They reflect scenarios that matter to users, expressed in a way that is both easy to read and easy to execute. They serve as living documentation that evolves with the system and its requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solving Ambiguity and Ensuring Reproducibility
&lt;/h3&gt;

&lt;p&gt;One of the most challenging aspects of software development is not writing code—it is understanding the problem clearly and expressing it precisely. Programs are, after all, specifications of what we want the system to do. But unlike natural languages, which are rich in context and ambiguity, software requires unambiguous clarity.&lt;/p&gt;

&lt;p&gt;This is where acceptance testing shines.&lt;/p&gt;

&lt;p&gt;By expressing requirements in an executable form, acceptance tests remove ambiguity. They define exactly what needs to happen. Rather than relying on vague documentation or human interpretation, developers, testers, and stakeholders can rely on concrete, shared understanding. The tests describe behavior, not implementation details, which makes them robust to changes in how the system is built, as long as it continues to behave as expected.&lt;/p&gt;

&lt;p&gt;Moreover, acceptance tests are reproducible. They can be run automatically, as often as needed, to ensure the software continues to meet its defined expectations. This reproducibility is crucial for modern CI pipelines, where tests are run continuously to catch regressions early&lt;/p&gt;

&lt;h3&gt;
  
  
  A Contract of Trust
&lt;/h3&gt;

&lt;p&gt;In a collaborative development environment, acceptance tests serve as a contract between the business and the development team. When everyone agrees on the specifications encoded in the tests, there is no room for misinterpretation. The business gets what it asked for, and developers have a reliable guide to follow.&lt;/p&gt;

&lt;p&gt;This contract is especially important in agile and iterative processes, where requirements evolve and features are delivered incrementally. With acceptance tests in place, the team always has a clear picture of what "done" means.&lt;/p&gt;

&lt;h3&gt;
  
  
  Making Software Releasable
&lt;/h3&gt;

&lt;p&gt;Acceptance testing answers a key question: Is the software ready to release? It gives us confidence that the features meet user needs and work correctly. It also checks that we built what was actually requested. Most importantly, it does this in a way that’s automated, repeatable, and reliable.&lt;/p&gt;

&lt;p&gt;By aligning development with user intent, removing ambiguity, and validating results, acceptance testing becomes an essential practice—not just for verifying software, but for clearly understanding and specifying it in the first place.&lt;/p&gt;

&lt;h3&gt;
  
  
  The problem with acceptance testing and 3rd party APIs
&lt;/h3&gt;

&lt;p&gt;Unstable – External APIs can fail randomly, whether due to bugs or release workflows.&lt;br&gt;
No test environment – Many APIs don’t offer a dedicated environment for testing.&lt;br&gt;
Uncontrollable state – It’s often impossible to set up the external system in the desired state for your tests.&lt;/p&gt;

&lt;p&gt;These limitations can make acceptance testing brittle and unreliable—unless you can simulate the external system reliably.&lt;/p&gt;
&lt;h2&gt;
  
  
  How Mokapi Supports Acceptance Testing
&lt;/h2&gt;

&lt;p&gt;That’s where &lt;a href="https://mokapi.io" rel="noopener noreferrer"&gt;Mokapi&lt;/a&gt; comes in. It lets you simulate realistic API behavior in a way that’s fast, accurate, and under your full control.&lt;br&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%2Fm8skadwhq08g0t994186.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%2Fm8skadwhq08g0t994186.png" alt=" " width="800" height="429"&gt;&lt;/a&gt;&lt;br&gt;
Acceptance testing becomes significantly more effective with the right tools—especially in complex environments with multiple APIs, microservices, and evolving interfaces. Mokapi was developed to address precisely these challenges. It provides a powerful, flexible, and specification-oriented approach to API simulation, enabling high-quality acceptance testing in a wide variety of scenarios.&lt;/p&gt;
&lt;h3&gt;
  
  
  Acceptance Testing Across Boundaries
&lt;/h3&gt;

&lt;p&gt;Mokapi makes acceptance testing easier across flexible system boundaries—whether you're focusing on a single microservice or validating your entire architecture—by mocking the APIs they depend on.&lt;br&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%2Fxluy255s1g29y21ciutg.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%2Fxluy255s1g29y21ciutg.png" alt=" " width="800" height="429"&gt;&lt;/a&gt;&lt;br&gt;
Powerful Flexibility for Real-World Scenarios&lt;br&gt;
With Mokapi, you're not limited to ideal cases. The JavaScript-based mock handlers enable the simulation of a wide variety of real-world scenarios, including negative tests, edge cases, and error conditions—all essential for developing robust software. You can programmatically control responses based on request data, headers, or business logic, giving you precise control over your mock's behavior during a test.&lt;/p&gt;

&lt;p&gt;This flexibility helps you answer not only the question "Does it work?" but also "Does it work when third-party systems don't."&lt;/p&gt;
&lt;h3&gt;
  
  
  Specification-Driven Confidence
&lt;/h3&gt;

&lt;p&gt;One of Mokapi's key strengths is its close alignment with API specifications. Mocks are continuously validated against OpenAPI or AsyncAPI definitions, ensuring they accurately reflect the APIs being simulated. This becomes especially valuable as APIs evolve.&lt;/p&gt;

&lt;p&gt;When a provider releases a new API version, it can be easily updated in Mokapi. If mock data or API calls no longer conform to the updated specification, Mokapi returns errors that can be caught through acceptance testing. This supports automated API version updates and acts as an early warning system for potential production issues.&lt;/p&gt;
&lt;h3&gt;
  
  
  Smart Mock Customization with Patch Mechanism
&lt;/h3&gt;

&lt;p&gt;Mokapi supports a patching mechanism for both API specifications and mock data. Patching the specification helps you adopt new API versions more easily while keeping track of your own modifications to the original spec. Patching mock data, on the other hand, allows you to avoid over-mocking—keeping your tests stable and focused, so changes are only required when they’re truly relevant.&lt;/p&gt;

&lt;p&gt;Here's an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;patch&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mokapi&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;fake&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mokapi/faker&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Open the OpenAPI pet store specification, resolving all $ref references&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pet-store-api.yaml&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;as&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;resolved&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Generate a random pet object based on the OpenAPI schema&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fake&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;components&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;schemas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pet&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Use the generated pet but override the name to 'Odie'&lt;/span&gt;
        &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Odie&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="c1"&gt;// Alternatively, patch Mokapi's autogenerated response and just set the name&lt;/span&gt;
        &lt;span class="c1"&gt;// response.data = patch(response.data, { name: 'Odie' });&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;This example demonstrates how you can generate valid mock data that conforms to the API specification and then customize only the parts relevant to your test—such as setting a specific pet name—without redefining the entire response structure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Making Acceptance Testing Sustainable
&lt;/h3&gt;

&lt;p&gt;By combining flexible mocks, specification validation, and a smart patching mechanism, Mokapi makes acceptance testing more resilient, more focused, and easier to maintain. It helps your team test confidently—whether you're building a new feature in isolation, validating complex integrations, or preparing for a production release.&lt;/p&gt;

&lt;p&gt;Mokapi doesn’t just enable acceptance testing—it makes it practical, maintainable, and deeply aligned with your evolving system and its requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ready to get started?
&lt;/h2&gt;

&lt;p&gt;Learn how to set up acceptance tests with Mokapi in your CI/CD pipeline:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://mokapi.io/docs/resources/tutorials/running-mokapi-in-a-ci-cd-pipeline" rel="noopener noreferrer"&gt;Running Mokapi in a CI/CD Pipeline&lt;/a&gt;&lt;br&gt;
👉 &lt;a href="https://mokapi.io/docs/guides/welcome" rel="noopener noreferrer"&gt;Mokapi Documentation&lt;/a&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>mocking</category>
      <category>api</category>
    </item>
  </channel>
</rss>
