<?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: Imran Al Munyeem</title>
    <description>The latest articles on DEV Community by Imran Al Munyeem (@imranalmunyeem).</description>
    <link>https://dev.to/imranalmunyeem</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%2F4028756%2F2639130a-5e5f-4c62-9ba7-483eb6632df4.png</url>
      <title>DEV Community: Imran Al Munyeem</title>
      <link>https://dev.to/imranalmunyeem</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/imranalmunyeem"/>
    <language>en</language>
    <item>
      <title>Contract Testing in 10 Lines: JSON Schema Validation in Postman</title>
      <dc:creator>Imran Al Munyeem</dc:creator>
      <pubDate>Mon, 10 Aug 2026 10:00:00 +0000</pubDate>
      <link>https://dev.to/imranalmunyeem/contract-testing-in-10-lines-json-schema-validation-in-postman-27ih</link>
      <guid>https://dev.to/imranalmunyeem/contract-testing-in-10-lines-json-schema-validation-in-postman-27ih</guid>
      <description>&lt;p&gt;Here's a bug your test suite probably wouldn't catch.&lt;/p&gt;

&lt;p&gt;A backend developer refactors the user model. The &lt;code&gt;id&lt;/code&gt; field — an integer since forever — starts coming back as a string: &lt;code&gt;"42"&lt;/code&gt; instead of &lt;code&gt;42&lt;/code&gt;. Every value is still "correct". Your assertion &lt;code&gt;pm.expect(user.id).to.eql(42)&lt;/code&gt; fails, sure — but only on the one endpoint you asserted &lt;code&gt;id&lt;/code&gt; on, not the other nine that return users. Meanwhile three client apps that did &lt;code&gt;user.id + 1&lt;/code&gt; are now computing &lt;code&gt;"421"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That's &lt;strong&gt;structural drift&lt;/strong&gt;, and it's what actually breaks API consumers: renamed fields, changed types, properties that quietly vanish. Field-by-field value assertions catch it patchily and by accident. &lt;strong&gt;Schema validation catches it systematically&lt;/strong&gt; — and in Postman it costs about ten lines, because the ajv JSON-schema validator is built into the script sandbox.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ten lines
&lt;/h2&gt;

&lt;p&gt;In &lt;strong&gt;Scripts → Post-response&lt;/strong&gt; on any request that returns a user:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;integer&lt;/span&gt;&lt;span class="dl"&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@&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="nx"&gt;pm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&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="s2"&gt;Response matches the user schema&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;pm&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;pm&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="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;jsonSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userSchema&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;That single test now fails if &lt;code&gt;id&lt;/code&gt; becomes a string, if &lt;code&gt;email&lt;/code&gt; disappears, if &lt;code&gt;name&lt;/code&gt; becomes an object — every structural mutation, whether or not you thought to assert on that field's value.&lt;/p&gt;

&lt;p&gt;For an endpoint returning an array of users:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userListSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;array&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;minItems&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="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userSchema&lt;/span&gt;   &lt;span class="c1"&gt;// reuse the object schema&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;pm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&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="s2"&gt;List matches schema&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;pm&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;pm&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="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;jsonSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userListSchema&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;h2&gt;
  
  
  Share one schema across every endpoint
&lt;/h2&gt;

&lt;p&gt;The real power move: your API returns users from &lt;code&gt;/users&lt;/code&gt;, &lt;code&gt;/users/:id&lt;/code&gt;, &lt;code&gt;/login&lt;/code&gt;, &lt;code&gt;/teams/:id/members&lt;/code&gt;… and they should all be &lt;em&gt;the same shape&lt;/em&gt;. Store the schema once as a collection variable (JSON, stringified), and every request validates against the same contract:&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="c1"&gt;// One-time setup — e.g. in the collection's Pre-request script:&lt;/span&gt;
&lt;span class="nx"&gt;pm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;collectionVariables&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;userSchema&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;integer&lt;/span&gt;&lt;span class="dl"&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In any request's Post-response script:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;collectionVariables&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;userSchema&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="nx"&gt;pm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&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="s2"&gt;User matches the shared contract&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;pm&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;pm&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="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;jsonSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&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;Now when the contract legitimately changes — a new required field, say — you update one variable and every endpoint's test updates with it. That's contract testing: not a new tool, just the discipline of asserting &lt;em&gt;shape&lt;/em&gt; centrally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Schema tips that save real debugging time
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;required&lt;/code&gt; is where the protection lives.&lt;/strong&gt; JSON Schema ignores missing properties unless they're listed in &lt;code&gt;required&lt;/code&gt;. A schema without &lt;code&gt;required&lt;/code&gt; validates &lt;code&gt;{}&lt;/code&gt; happily — the most common reason people think their schema test "isn't working".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Type-check numbers deliberately.&lt;/strong&gt; &lt;code&gt;integer&lt;/code&gt; vs &lt;code&gt;number&lt;/code&gt; matters — prices and quantities have different rules. And remember the drift that motivates all this is usually number→string, so never write &lt;code&gt;type: ["integer", "string"]&lt;/code&gt; to make a flaky test pass. That's deleting the alarm.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Formats catch bad data early.&lt;/strong&gt; &lt;code&gt;pattern&lt;/code&gt; for emails and IDs, &lt;code&gt;enum&lt;/code&gt; for status fields:&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="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;active&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;suspended&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;deleted&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When someone adds a fourth status without telling anyone, you find out from a red test instead of a production incident.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start schemas from reality, not from scratch.&lt;/strong&gt; Send the request once, copy the response, and derive the schema from it (an LLM does this conversion well — &lt;em&gt;then review it against the spec&lt;/em&gt;, especially which fields are truly &lt;code&gt;required&lt;/code&gt;; a schema generated from one happy response will mark optional fields as required and miss fields that were null that day).&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this sits in a strategy
&lt;/h2&gt;

&lt;p&gt;Schema validation is one layer of a complete endpoint checklist — protocol (status, headers, timing), &lt;strong&gt;contract (this)&lt;/strong&gt;, data values, behaviour (state actually changed), and security (auth negatives). It's the layer with the best effort-to-protection ratio in the whole list: ten lines per shape, and an entire category of consumer-breaking regressions becomes impossible to ship silently.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Adapted from Chapter 7 of my free, open-source book *&lt;/em&gt;&lt;a href="https://imranalmunyeem.github.io/api-testing-using-postman/" rel="noopener noreferrer"&gt;API Testing Using Postman: The Practical Guide to Modern API Testing&lt;/a&gt;*&lt;em&gt;. Read online, grab the PDF/EPUB, or contribute on &lt;a href="https://github.com/imranalmunyeem/api-testing-using-postman" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I'm a PhD researcher in Computer Science at Nottingham Trent University working on cybersecurity and AI-assisted security testing. More at &lt;a href="https://imranalmunyeem.com" rel="noopener noreferrer"&gt;imranalmunyeem.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>postmanapi</category>
      <category>json</category>
      <category>qa</category>
    </item>
    <item>
      <title>Still Testing APIs Like It's 2022? Here's Everything That Changed"</title>
      <dc:creator>Imran Al Munyeem</dc:creator>
      <pubDate>Mon, 03 Aug 2026 10:00:00 +0000</pubDate>
      <link>https://dev.to/imranalmunyeem/still-testing-apis-like-its-2022-heres-everything-that-changed-28l8</link>
      <guid>https://dev.to/imranalmunyeem/still-testing-apis-like-its-2022-heres-everything-that-changed-28l8</guid>
      <description>&lt;p&gt;If you learnt Postman a few years ago and have been coasting on that knowledge, an uncomfortable amount of it is now wrong — not "slightly dated" wrong, but "the tab you're looking for doesn't exist" wrong.&lt;/p&gt;

&lt;p&gt;I know because I wrote a Postman guide in 2022 and recently rewrote it as a full book. Here's the changelog of everything that broke, moved, or appeared — the field guide I wish I'd had.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Tests tab is gone
&lt;/h2&gt;

&lt;p&gt;The old &lt;strong&gt;Tests&lt;/strong&gt; and &lt;strong&gt;Pre-request Script&lt;/strong&gt; tabs were merged into a single &lt;strong&gt;Scripts&lt;/strong&gt; tab with two sections: &lt;strong&gt;Pre-request&lt;/strong&gt; (runs before the request) and &lt;strong&gt;Post-response&lt;/strong&gt; (runs after — this is where your tests live now).&lt;/p&gt;

&lt;p&gt;Your code is unchanged — same &lt;code&gt;pm.test&lt;/code&gt;, same Chai assertions, same snippets — only the geography moved:&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="c1"&gt;// Exactly the same as it ever was, just under Scripts → Post-response&lt;/span&gt;
&lt;span class="nx"&gt;pm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&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="s2"&gt;Status code is 200&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;pm&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;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;have&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every tutorial screenshot showing a Tests tab is from the old world. The concepts transfer completely.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Newman has an official successor
&lt;/h2&gt;

&lt;p&gt;Newman — the beloved open-source CLI runner — is no longer the only way to run collections from a terminal. The official &lt;strong&gt;Postman CLI&lt;/strong&gt; authenticates with a Postman API key and can run collections &lt;strong&gt;straight from your workspace, no export step&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;postman login &lt;span class="nt"&gt;--with-api-key&lt;/span&gt; &lt;span class="nv"&gt;$POSTMAN_API_KEY&lt;/span&gt;
postman collection run 12345678-abcd-efgh-ijkl-9876543210ab
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It also pushes results back to Postman's cloud as a shareable run report.&lt;/p&gt;

&lt;p&gt;Newman still works fine and its htmlextra HTML reports remain unmatched — but there's a hard compatibility line to know: &lt;strong&gt;Newman only supports collection format v2/v2.1&lt;/strong&gt;. Postman v12 introduced a v3 (YAML) format for its Git-native workflows, and Newman cannot run it. New pipeline? Start with the Postman CLI. Existing Newman pipeline? It keeps working — just export v2.1.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Your Jenkins tutorial will not work
&lt;/h2&gt;

&lt;p&gt;The classic "install JDK 8, run &lt;code&gt;java -jar jenkins.war&lt;/code&gt;" instructions are dead: &lt;strong&gt;modern Jenkins requires Java 21&lt;/strong&gt; (recent LTS lines dropped Java 17 and older). If you're standing up a CI box today, grab Temurin 21 from adoptium.net first, or Jenkins simply won't start.&lt;/p&gt;

&lt;p&gt;Better yet, notice that for many teams the Jenkins box is now optional — a 25-line GitHub Actions workflow runs your collection on every push and every night, with no server to patch:&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;API Tests&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;2&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;postman-tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lts/*"&lt;/span&gt;
      &lt;span class="pi"&gt;-&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 install -g newman newman-reporter-htmlextra&lt;/span&gt;
      &lt;span class="pi"&gt;-&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;newman run collections/MyCollection.postman_collection.json -r cli,htmlextra&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. There's an AI in the footer
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Postbot&lt;/strong&gt; generates tests from plain-English prompts, adds baseline tests to a whole collection in one action, repairs failing scripts, documents requests, and visualises responses. It's genuinely useful and genuinely dangerous in the same specific way: it writes assertions from the response your API &lt;em&gt;currently gives&lt;/em&gt; — so if the current behaviour is a bug, the bug becomes the expected result. Generate freely; review against the spec, not the response. (I wrote a whole piece on this failure mode.)&lt;/p&gt;

&lt;p&gt;Postman also added AI request types (test your LLM-backed endpoints like any other), Agent Mode, and an MCP server — the platform is clearly betting that agents will be first-class API consumers.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Your practice APIs died
&lt;/h2&gt;

&lt;p&gt;Heroku ended its free tier, taking a generation of tutorial APIs with it. If a guide points you at &lt;code&gt;something.herokuapp.com&lt;/code&gt;, expect a dead link. &lt;strong&gt;JSONPlaceholder&lt;/strong&gt; (&lt;code&gt;jsonplaceholder.typicode.com&lt;/code&gt;) remains free, signup-less, and reliable — or create a mock server inside Postman itself from your saved examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. The scripting API moved on politely
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;postman.setNextRequest(null)&lt;/code&gt; still works, but the current form is:&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="nx"&gt;pm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;execution&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setNextRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// stop the run&lt;/span&gt;
&lt;span class="nx"&gt;pm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;execution&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setNextRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Delete user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// or jump to a request&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same story across the &lt;code&gt;pm&lt;/code&gt; API: old code keeps running, new code should use the new names.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Secrets got first-class treatment
&lt;/h2&gt;

&lt;p&gt;Two things every team should know: variables now have a &lt;strong&gt;secret type&lt;/strong&gt; (masked on screen), and — the one that catches people — &lt;strong&gt;initial values sync to Postman's servers and are shared with collaborators, while current values stay on your machine.&lt;/strong&gt; Real credentials go in current values only, or in Postman Vault (encrypted, local, never synced). The classic leak is a Bearer token pasted into an initial value "just for a second," then synced, then forked into a public workspace.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;None of this changed what good API testing &lt;em&gt;is&lt;/em&gt; — five layers of assertions, negative cases, deterministic suites, CI enforcement. What changed is the tooling around it, and the tooling changed enough that 2022 muscle memory now produces broken pipelines and missing tabs. Update the muscle memory; keep the principles.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is the story behind my free, open-source book *&lt;/em&gt;&lt;a href="https://imranalmunyeem.github.io/api-testing-using-postman/" rel="noopener noreferrer"&gt;API Testing Using Postman: The Practical Guide to Modern API Testing&lt;/a&gt;** — a 2022 guide rewritten end-to-end for how we test now. Read online, grab the PDF/EPUB, or contribute on &lt;a href="https://github.com/imranalmunyeem/api-testing-using-postman" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.*&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I'm a PhD researcher in Computer Science at Nottingham Trent University working on cybersecurity and AI-assisted security testing. More at &lt;a href="https://imranalmunyeem.com" rel="noopener noreferrer"&gt;imranalmunyeem.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>testing</category>
      <category>postmanapi</category>
      <category>qa</category>
    </item>
    <item>
      <title>AI Writes Your API Tests Now. Here's the Bug It Introduces.</title>
      <dc:creator>Imran Al Munyeem</dc:creator>
      <pubDate>Mon, 27 Jul 2026 10:00:00 +0000</pubDate>
      <link>https://dev.to/imranalmunyeem/ai-writes-your-api-tests-now-heres-the-bug-it-introduces-3kgf</link>
      <guid>https://dev.to/imranalmunyeem/ai-writes-your-api-tests-now-heres-the-bug-it-introduces-3kgf</guid>
      <description>&lt;p&gt;Ask an AI to write tests for your API endpoint and it will do something subtly dangerous: it will look at the response your API &lt;em&gt;currently gives&lt;/em&gt; and write assertions that lock it in.&lt;/p&gt;

&lt;p&gt;If that response is correct — great, you just saved twenty minutes. If that response is a bug — say, your API wrongly returns &lt;code&gt;200 OK&lt;/code&gt; for an invalid payload — the AI will happily generate &lt;code&gt;pm.test("Status code is 200", ...)&lt;/code&gt; and your bug is now &lt;em&gt;the expected behaviour&lt;/em&gt;, defended by a green test forever.&lt;/p&gt;

&lt;p&gt;That's the whole tension of AI-assisted testing in one sentence: &lt;strong&gt;AI-generated tests validate what a response is, not what it should be.&lt;/strong&gt; The specification lives in your head and your docs; the AI only sees the wire.&lt;/p&gt;

&lt;p&gt;I use AI test generation daily and I'm not telling you to avoid it — I'm telling you how to use it without it quietly rotting your suite.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the AI is genuinely great at
&lt;/h2&gt;

&lt;p&gt;Postman's built-in assistant, Postbot, is the one most API testers will meet first (the pattern applies equally to Copilot, Claude, or whatever you use). Its strongest moves:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Boilerplate generation.&lt;/strong&gt; Open a request, send it once so a response exists, open &lt;strong&gt;Scripts → Post-response&lt;/strong&gt;, and prompt in plain English:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Add tests for the status code, the content type, and that the response time is under 500 ms."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It writes idiomatic &lt;code&gt;pm.test&lt;/code&gt; / &lt;code&gt;pm.expect&lt;/code&gt; code straight into the editor. For an entire collection, &lt;strong&gt;Generate tests&lt;/strong&gt; from the collection menu adds baseline tests to every request at once — hours of boilerplate become minutes of review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fixing broken scripts.&lt;/strong&gt; When a test throws an error, Postbot's &lt;strong&gt;Fix script&lt;/strong&gt; analyses it and proposes a correction. For syntax slips and stale JSON paths, this is close to magic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exploration.&lt;/strong&gt; "What's wrong with this request?", "visualise this response as a table", "document this collection" — context-aware answers that used to mean twenty minutes of reading.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test-case brainstorming.&lt;/strong&gt; Outside Postman, feeding an OpenAPI spec to an LLM and asking for edge cases is genuinely productive: it will suggest boundary values, weird Unicode, and state transitions you hadn't listed. Treat the output as a &lt;em&gt;checklist to evaluate&lt;/em&gt;, not a suite to paste.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it fails, specifically
&lt;/h2&gt;

&lt;p&gt;Three failure modes recur:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The observed-behaviour trap&lt;/strong&gt; (the big one, above). Every generated assertion needs review against the &lt;em&gt;spec&lt;/em&gt;, not the response. The question is never "does this match what came back?" — the AI already guaranteed that. The question is "is what came back correct?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Happy-path bias.&lt;/strong&gt; Ask for tests and you'll get success cases. The negative suite — wrong passwords, missing fields, expired tokens, malformed bodies, the requests an attacker sends — still needs a human's adversarial instinct. My rule of thumb: a real endpoint needs five to ten negative/boundary cases per positive case, and the AI will volunteer roughly none of them unprompted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Plausible nonsense.&lt;/strong&gt; Occasionally the generated assertion is syntactically perfect and semantically empty — checking that an array "exists" rather than that it contains the right things. A suite full of these is worse than a small honest one, because it &lt;em&gt;looks&lt;/em&gt; like coverage.&lt;/p&gt;

&lt;h2&gt;
  
  
  The working protocol
&lt;/h2&gt;

&lt;p&gt;What this adds up to in practice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Learn to write tests by hand first.&lt;/strong&gt; Not for purity — because review is the whole job now, and you cannot review code you couldn't have written. A test you can't read is a test you can't trust.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generate, then interrogate.&lt;/strong&gt; For each AI assertion ask: does the spec say this? If the response were wrong in the most likely way, would this catch it?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add the negatives yourself.&lt;/strong&gt; Budget the time you saved on boilerplate for the adversarial cases the AI skipped.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never paste secrets into prompts.&lt;/strong&gt; Tokens, customer data, internal URLs — they don't belong in any AI conversation unless your plan contractually protects them. (Postbot on enterprise plans excludes inputs from training; check your tier's terms.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Own the suite.&lt;/strong&gt; If a generated test fails six months from now, someone has to understand it in ninety seconds. That someone is you.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The honest framing
&lt;/h2&gt;

&lt;p&gt;Used this way, AI does for test &lt;em&gt;authoring&lt;/em&gt; what Newman did for test &lt;em&gt;execution&lt;/em&gt;: it removes the drudgery and leaves you the judgement. The testers who struggle with AI are the ones who hand over the judgement too — and then wonder why their green suite missed the bug that was green by design.&lt;/p&gt;

&lt;p&gt;Machines for repetition, humans for meaning. That division of labour was true before LLMs and it will be true after whatever comes next.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Adapted from Chapter 14 of my free, open-source book *&lt;/em&gt;&lt;a href="https://imranalmunyeem.github.io/api-testing-using-postman/" rel="noopener noreferrer"&gt;API Testing Using Postman: The Practical Guide to Modern API Testing&lt;/a&gt;** — 15 chapters from your first request to AI-assisted CI/CD pipelines. Read online, grab the PDF/EPUB, or contribute on &lt;a href="https://github.com/imranalmunyeem/api-testing-using-postman" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.*&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I'm a PhD researcher in Computer Science at Nottingham Trent University working on cybersecurity and AI-assisted security testing. More at &lt;a href="https://imranalmunyeem.com" rel="noopener noreferrer"&gt;imranalmunyeem.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>testing</category>
      <category>postmanapi</category>
      <category>qa</category>
    </item>
    <item>
      <title>How to Design an API Test Strategy (Not Just Accumulate Tests)</title>
      <dc:creator>Imran Al Munyeem</dc:creator>
      <pubDate>Mon, 20 Jul 2026 10:00:00 +0000</pubDate>
      <link>https://dev.to/imranalmunyeem/how-to-design-an-api-test-strategy-not-just-accumulate-tests-2bmp</link>
      <guid>https://dev.to/imranalmunyeem/how-to-design-an-api-test-strategy-not-just-accumulate-tests-2bmp</guid>
      <description>&lt;p&gt;Most API test suites are decorative.&lt;/p&gt;

&lt;p&gt;They're green, they run on every build, and they check almost nothing that matters: a wall of &lt;code&gt;status is 200&lt;/code&gt; assertions over happy-path requests. When the API starts returning the wrong data, the wrong errors, or leaking internals in a stack trace, the suite stays green — because nobody ever decided what it was supposed to catch.&lt;/p&gt;

&lt;p&gt;Tools execute tests; testers &lt;em&gt;choose&lt;/em&gt; them. Given an endpoint, what should you actually verify? Here's the framework I use — five layers, three axes of case design, and a way to prioritise when you can't test everything first. Examples use Postman's test scripts, but the framework is tool-agnostic.&lt;/p&gt;

&lt;h2&gt;
  
  
  The layered checklist
&lt;/h2&gt;

&lt;p&gt;For every endpoint that matters, work outside-in through five layers.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Protocol layer
&lt;/h3&gt;

&lt;p&gt;The right status code for the right situation — and &lt;em&gt;specific&lt;/em&gt; failures, not generic ones: 400 for malformed input, 401 for missing credentials, 403 for insufficient rights, 404 for absent resources. Plus the correct &lt;code&gt;Content-Type&lt;/code&gt;, and a sensible response time.&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="nx"&gt;pm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&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="s2"&gt;Status is 200&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;pm&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;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;have&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&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="nx"&gt;pm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&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="s2"&gt;Content-Type is 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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;pm&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;pm&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;headers&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&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="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&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;pm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&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="s2"&gt;Responds within 500 ms&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;pm&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;pm&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;responseTime&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;below&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&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;Cheap to write, catches an entire class of regressions. But if your suite stops here, it's decorative.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Contract layer
&lt;/h3&gt;

&lt;p&gt;The response &lt;em&gt;structure&lt;/em&gt; matches its schema: required fields present, types correct, formats valid. This is what protects the API's consumers — a renamed field or a number that quietly became a string breaks every client, while your value assertions may not even notice.&lt;/p&gt;

&lt;p&gt;Postman ships the ajv JSON-schema validator, so contract testing is ten lines:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;integer&lt;/span&gt;&lt;span class="dl"&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@&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="nx"&gt;pm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&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="s2"&gt;Response matches the user schema&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;pm&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;pm&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="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;jsonSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userSchema&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;h3&gt;
  
  
  3. Data layer
&lt;/h3&gt;

&lt;p&gt;The &lt;em&gt;values&lt;/em&gt; are right: the created user has the name you sent; the filtered list contains only matching records; the total equals the sum of the parts; page 2 is actually different from page 1.&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="kd"&gt;const&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;pm&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="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;pm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&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="s2"&gt;Filter returns only active users&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="o"&gt;=&amp;gt;&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;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;pm&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;user&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="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;active&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Behaviour layer
&lt;/h3&gt;

&lt;p&gt;State actually changed. After DELETE, a follow-up GET returns 404 — deletion that only &lt;em&gt;claims&lt;/em&gt; to work is a classic bug. After PATCH, only the patched fields differ. POSTing the same order twice doesn't charge twice.&lt;/p&gt;

&lt;p&gt;These require chaining requests — capture an ID from one response and use it in the next:&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="c1"&gt;// In the POST's post-response script:&lt;/span&gt;
&lt;span class="nx"&gt;pm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&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="s2"&gt;User created&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;pm&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;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;have&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&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="nx"&gt;pm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;collectionVariables&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;newUserId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pm&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="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Next request: GET {{baseUrl}}/users/{{newUserId}}&lt;/span&gt;
&lt;span class="c1"&gt;// Then: DELETE, then GET again expecting 404&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The behaviour layer is where the most expensive bugs live, and it's the layer happy-path suites never reach.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Security layer
&lt;/h3&gt;

&lt;p&gt;Every negative auth case: no token (expect 401), an expired token (401), a valid token for the &lt;em&gt;wrong&lt;/em&gt; user (403), a valid token with insufficient role (403). Plus: error responses that don't leak stack traces or internal hostnames.&lt;/p&gt;

&lt;p&gt;Authentication isn't plumbing to get through on the way to your tests. It &lt;em&gt;is&lt;/em&gt; a test surface — usually the highest-risk one you have.&lt;/p&gt;

&lt;h2&gt;
  
  
  Positive, negative, and boundary
&lt;/h2&gt;

&lt;p&gt;For each input an endpoint accepts, generate cases along three axes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Positive&lt;/strong&gt; — valid, typical input succeeds. The case everyone writes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Negative&lt;/strong&gt; — invalid input fails &lt;em&gt;correctly&lt;/em&gt;: missing required fields, wrong types, malformed JSON, unknown IDs, illegal state transitions. The assertion is not merely "it fails" but "it fails with the right status and a useful error body." An API that returns 500 for bad input has a bug even though it rejected the request — the server failed to validate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Boundary&lt;/strong&gt; — the edges: empty strings, zero, negative numbers, maximum lengths, page sizes of 0 and 1 and the documented limit and the limit plus one, Unicode in text fields.&lt;/p&gt;

&lt;p&gt;A practical heuristic: for a typical endpoint, expect &lt;strong&gt;one or two positive cases and five to ten negative and boundary cases&lt;/strong&gt;. If your suite is mostly green-path, it is mostly decorative.&lt;/p&gt;

&lt;p&gt;In Postman, the clean way to industrialise this is a data file. One request, one parameterised test, a CSV of cases:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csvs"&gt;&lt;code&gt;&lt;span class="k"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;expectedStatus&lt;/span&gt;
&lt;span class="k"&gt;valid&lt;/span&gt;&lt;span class="kp"&gt;@example&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="k"&gt;com&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;correct&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;pass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;200&lt;/span&gt;
&lt;span class="k"&gt;valid&lt;/span&gt;&lt;span class="kp"&gt;@example&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="k"&gt;com&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;wrong&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;pass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;401&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;correct&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;pass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;400&lt;/span&gt;
&lt;span class="k"&gt;not&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;an&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;correct&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;pass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;400&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;pm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&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="s2"&gt;Status matches the data file&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;pm&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;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;have&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iterationData&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;expectedStatus&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;Adding a case is now a spreadsheet edit — which means anyone on the team can extend coverage, not just the people who write JavaScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prioritising: you cannot test everything first
&lt;/h2&gt;

&lt;p&gt;Order the work by risk:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Endpoints whose failure costs most&lt;/strong&gt; — auth, payment, anything that writes data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Endpoints that change most often&lt;/strong&gt; — change is where regressions come from&lt;/li&gt;
&lt;li&gt;Everything else&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A ten-test suite on the login endpoint beats a hundred tests on a static reference lookup.&lt;/p&gt;

&lt;p&gt;And adopt the single highest-value habit in test automation — the &lt;strong&gt;regression contract&lt;/strong&gt;: every bug found in production earns a permanent test reproducing it. Over a year, this builds the most valuable suite you'll ever own, because it is provably aligned with how your system actually fails.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping the suite honest
&lt;/h2&gt;

&lt;p&gt;Three disciplines keep a growing suite trustworthy:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deterministic tests.&lt;/strong&gt; A test that sometimes fails without a code change is worse than no test — it trains the team to ignore red. Hunt flakiness down; it's usually shared state, missing teardown, or time-dependent assertions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Independent tests where possible.&lt;/strong&gt; Chained workflows are necessary, but keep chains short and self-contained. A suite where request 40 depends on request 3 is unmaintainable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Readable failures.&lt;/strong&gt; Name tests so a red line states the defect. "PATCH ignores read-only fields" tells the developer everything before they even open the tool. "Test 12 failed" tells them nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one-line summary
&lt;/h2&gt;

&lt;p&gt;A test suite earns its keep not by the number of green ticks but by the &lt;em&gt;decisions&lt;/em&gt; behind them: five layers per endpoint, five-to-one negative-to-positive ratio, risk-ordered, deterministic, and readable when red.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article is adapted from Chapter 8 of my free, open-source book *&lt;/em&gt;&lt;a href="https://imranalmunyeem.github.io/api-testing-using-postman/" rel="noopener noreferrer"&gt;API Testing Using Postman: The Practical Guide to Modern API Testing&lt;/a&gt;** — 15 chapters from your first request to AI-assisted CI/CD pipelines, with a 37-question interview FAQ. Read it online, grab the PDF/EPUB, or contribute on &lt;a href="https://github.com/imranalmunyeem/api-testing-using-postman" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.*&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I'm a PhD researcher in Computer Science at Nottingham Trent University working on cybersecurity and AI-assisted security testing. More at &lt;a href="https://imranalmunyeem.com" rel="noopener noreferrer"&gt;imranalmunyeem.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>api</category>
      <category>qa</category>
      <category>postmanapi</category>
    </item>
  </channel>
</rss>
