<?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: Sushant Joshi</title>
    <description>The latest articles on DEV Community by Sushant Joshi (@sushant_joshi_79_).</description>
    <link>https://dev.to/sushant_joshi_79_</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%2F3988729%2F343dd368-bbe5-4b0e-8657-7c1a49a86585.png</url>
      <title>DEV Community: Sushant Joshi</title>
      <link>https://dev.to/sushant_joshi_79_</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sushant_joshi_79_"/>
    <language>en</language>
    <item>
      <title>The Test ID Pattern That Finally Killed Our Flake</title>
      <dc:creator>Sushant Joshi</dc:creator>
      <pubDate>Mon, 29 Jun 2026 13:51:06 +0000</pubDate>
      <link>https://dev.to/sushant_joshi_79_/the-test-id-pattern-that-finally-killed-our-flake-11lh</link>
      <guid>https://dev.to/sushant_joshi_79_/the-test-id-pattern-that-finally-killed-our-flake-11lh</guid>
      <description>&lt;p&gt;&lt;em&gt;Our API test suite went from 4–6% flake to 0.3% the week we changed how we generated entity IDs in test fixtures.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I wish I could tell you we solved our flaky test problem with a fancy testing framework or a massive infrastructure rewrite.&lt;/p&gt;

&lt;p&gt;We didn't.&lt;/p&gt;

&lt;p&gt;The fix was embarrassingly simple.&lt;/p&gt;

&lt;p&gt;We changed how we generated IDs.&lt;/p&gt;

&lt;p&gt;That's it.&lt;/p&gt;

&lt;p&gt;For months, our CI dashboard looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Run #1  ✅
Run #2  ❌
Run #3  ✅
Run #4  ❌
Run #5  ✅
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same code.&lt;/p&gt;

&lt;p&gt;Same tests.&lt;/p&gt;

&lt;p&gt;Different results.&lt;/p&gt;

&lt;p&gt;Eventually, we measured the problem.&lt;/p&gt;

&lt;p&gt;Our API test suite had a &lt;strong&gt;4–6% flake rate&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That meant roughly one out of every twenty pipeline runs failed for no legitimate reason.&lt;/p&gt;

&lt;p&gt;Developers stopped trusting the pipeline.&lt;/p&gt;

&lt;p&gt;Failed builds were retried.&lt;/p&gt;

&lt;p&gt;Warnings were ignored.&lt;/p&gt;

&lt;p&gt;And eventually, real bugs started hiding among false positives.&lt;/p&gt;

&lt;p&gt;The culprit?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Randomly generated IDs.&lt;/strong&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  The Flake Source: Random ID Collisions
&lt;/h1&gt;

&lt;p&gt;A typical test looked like this:&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;customerId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000000&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;createCustomer&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="nx"&gt;customerId&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Seems harmless.&lt;/p&gt;

&lt;p&gt;Until your suite grows.&lt;/p&gt;

&lt;p&gt;Eventually:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hundreds of tests run.&lt;/li&gt;
&lt;li&gt;Multiple pipelines run simultaneously.&lt;/li&gt;
&lt;li&gt;Parallel workers execute the same code.&lt;/li&gt;
&lt;li&gt;Test retries create additional requests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sooner or later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Customer 48291 already exists
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;appears.&lt;/p&gt;

&lt;p&gt;A retry passes.&lt;/p&gt;

&lt;p&gt;Nobody investigates.&lt;/p&gt;

&lt;p&gt;The flake count grows.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Problem Wasn't Just Collisions
&lt;/h1&gt;

&lt;p&gt;Random IDs caused other issues too.&lt;/p&gt;

&lt;p&gt;Suppose a test failed.&lt;/p&gt;

&lt;p&gt;You found this in the database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Customer: 839174
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Questions immediately followed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which test created it?&lt;/li&gt;
&lt;li&gt;Which pipeline run?&lt;/li&gt;
&lt;li&gt;Which branch?&lt;/li&gt;
&lt;li&gt;Is it safe to delete?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nobody knew.&lt;/p&gt;

&lt;p&gt;The data had no meaning.&lt;/p&gt;

&lt;p&gt;It was just another random number.&lt;/p&gt;




&lt;h1&gt;
  
  
  Randomness Is the Enemy of Debugging
&lt;/h1&gt;

&lt;p&gt;Random values make systems harder to reason about.&lt;/p&gt;

&lt;p&gt;If a failure cannot be reproduced easily, it becomes expensive to investigate.&lt;/p&gt;

&lt;p&gt;We wanted IDs that were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Predictable&lt;/li&gt;
&lt;li&gt;Searchable&lt;/li&gt;
&lt;li&gt;Namespaced&lt;/li&gt;
&lt;li&gt;Easy to clean up&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So we introduced a new pattern.&lt;/p&gt;




&lt;h1&gt;
  
  
  The New Pattern
&lt;/h1&gt;

&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;84738291
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we started generating IDs like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test-orders-create-001
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pr-482-users-login-003
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ci-198-payments-refund-001
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The format became:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;prefix&amp;gt;-&amp;lt;test-name&amp;gt;-&amp;lt;counter&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every ID suddenly became self-describing.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Deterministic IDs Help So Much
&lt;/h1&gt;

&lt;p&gt;Now when a failure happened:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Customer already exists:
pr-482-users-create-001
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we immediately knew:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which PR created it&lt;/li&gt;
&lt;li&gt;Which test created it&lt;/li&gt;
&lt;li&gt;Which feature it belonged to&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Debugging became dramatically easier.&lt;/p&gt;




&lt;h1&gt;
  
  
  Example Generator
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildTestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;testName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;sequence&lt;/span&gt;
&lt;span class="p"&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;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;namespace&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;testName&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;sequence&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;Usage:&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;customerId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nf"&gt;buildTestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pr-482&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="s1"&gt;create-user&lt;/span&gt;&lt;span class="dl"&gt;'&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pr-482-create-user-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple.&lt;/p&gt;

&lt;p&gt;Readable.&lt;/p&gt;

&lt;p&gt;Deterministic.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Biggest Improvement: Test Isolation
&lt;/h1&gt;

&lt;p&gt;This change dramatically improved &lt;strong&gt;test isolation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Previously:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Test A
↓
Creates customer 123

Test B
↓
Creates customer 123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Failure.&lt;/p&gt;

&lt;p&gt;Now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pr-482-test-a-001
pr-482-test-b-001
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No collision.&lt;/p&gt;

&lt;p&gt;No interference.&lt;/p&gt;

&lt;p&gt;No flake.&lt;/p&gt;




&lt;h1&gt;
  
  
  Parallel API Tests Become Much Safer
&lt;/h1&gt;

&lt;p&gt;Modern CI systems run tests in parallel.&lt;/p&gt;

&lt;p&gt;Our pipelines used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple workers&lt;/li&gt;
&lt;li&gt;Multiple containers&lt;/li&gt;
&lt;li&gt;Multiple retry attempts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Random IDs weren't enough.&lt;/p&gt;

&lt;p&gt;We needed namespaces.&lt;/p&gt;




&lt;h1&gt;
  
  
  Namespaced IDs
&lt;/h1&gt;

&lt;p&gt;Every test run now receives a namespace.&lt;/p&gt;

&lt;p&gt;Examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pr-482
build-991
worker-3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;IDs become:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pr-482-worker-1-orders-001
pr-482-worker-2-orders-001
pr-482-worker-3-orders-001
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each worker owns its own space.&lt;/p&gt;

&lt;p&gt;Parallel execution becomes dramatically safer.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Namespaces Matter
&lt;/h1&gt;

&lt;p&gt;Without namespaces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;worker-1 → customer-001
worker-2 → customer-001
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Collision.&lt;/p&gt;

&lt;p&gt;With namespaces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;worker-1-customer-001
worker-2-customer-001
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No collision.&lt;/p&gt;

&lt;p&gt;This single change removed a huge percentage of our flaky failures.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Unexpected Benefit: Easier Debugging
&lt;/h1&gt;

&lt;p&gt;We started seeing logs like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DELETE customer:
pr-512-login-tests-001
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Immediately we knew:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PR number&lt;/li&gt;
&lt;li&gt;Test suite&lt;/li&gt;
&lt;li&gt;Test case&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This sounds small.&lt;/p&gt;

&lt;p&gt;It's actually enormous when debugging CI failures.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Database Cleanup Hook
&lt;/h1&gt;

&lt;p&gt;Deterministic IDs made cleanup incredibly easy.&lt;/p&gt;

&lt;p&gt;Previously:&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;deleteAllTestCustomers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terrifying.&lt;/p&gt;

&lt;p&gt;What if production-like data exists?&lt;/p&gt;

&lt;p&gt;What if another team is using the environment?&lt;/p&gt;

&lt;p&gt;Instead we now do:&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;deleteCustomersByPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pr-482&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or:&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;deleteOrdersByPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;worker-2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Cleanup Became Surgical
&lt;/h1&gt;

&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Delete everything.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Delete only records created
by this test run.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This dramatically reduced accidental interference.&lt;/p&gt;




&lt;h1&gt;
  
  
  Example Cleanup Hook
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;afterAll&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;deleteByPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TEST_NAMESPACE&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;Simple.&lt;/p&gt;

&lt;p&gt;Fast.&lt;/p&gt;

&lt;p&gt;Safe.&lt;/p&gt;




&lt;h1&gt;
  
  
  This Also Helps With Retries
&lt;/h1&gt;

&lt;p&gt;Suppose a pipeline fails halfway through.&lt;/p&gt;

&lt;p&gt;The cleanup never executes.&lt;/p&gt;

&lt;p&gt;Previously:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Random test data remains forever.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DELETE
WHERE id LIKE 'pr-482%'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Done.&lt;/p&gt;

&lt;p&gt;Entire test runs can be cleaned up with one query.&lt;/p&gt;




&lt;h1&gt;
  
  
  Our Flake Numbers
&lt;/h1&gt;

&lt;p&gt;Before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;4–6% flaky failures
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0.3%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The change wasn't perfect.&lt;/p&gt;

&lt;p&gt;But it was transformative.&lt;/p&gt;




&lt;h1&gt;
  
  
  The One Case Where This Pattern Still Loses
&lt;/h1&gt;

&lt;p&gt;Deterministic IDs don't solve every problem.&lt;/p&gt;

&lt;p&gt;They still struggle with:&lt;/p&gt;

&lt;h2&gt;
  
  
  Truly Global Resources
&lt;/h2&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unique email addresses&lt;/li&gt;
&lt;li&gt;Shared queues&lt;/li&gt;
&lt;li&gt;Third-party systems&lt;/li&gt;
&lt;li&gt;Rate-limited APIs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even with namespaced IDs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pr-482-john@example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;might still violate constraints.&lt;/p&gt;




&lt;h1&gt;
  
  
  Our Fallback Strategy
&lt;/h1&gt;

&lt;p&gt;For these scenarios, we combine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Namespace
+
Timestamp
+
Random Suffix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pr-482-user-1721728172-x8a3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This preserves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Searchability&lt;/li&gt;
&lt;li&gt;Cleanup capabilities&lt;/li&gt;
&lt;li&gt;Collision resistance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;while handling globally unique requirements.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Pattern We Use Today
&lt;/h1&gt;

&lt;p&gt;Our current generator looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;namespace&amp;gt;
-
&amp;lt;test-suite&amp;gt;
-
&amp;lt;sequence&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pr-811-orders-001
pr-811-orders-002
pr-811-payments-001
worker-3-users-004
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every piece of test data becomes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Traceable&lt;/li&gt;
&lt;li&gt;Isolated&lt;/li&gt;
&lt;li&gt;Easy to clean up&lt;/li&gt;
&lt;li&gt;Easy to debug&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Why This Matters More Than People Think
&lt;/h1&gt;

&lt;p&gt;Flaky tests aren't just annoying.&lt;/p&gt;

&lt;p&gt;They slowly destroy trust.&lt;/p&gt;

&lt;p&gt;Eventually developers start saying:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Just rerun the pipeline.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's dangerous.&lt;/p&gt;

&lt;p&gt;Because one day:&lt;/p&gt;

&lt;p&gt;A real failure looks exactly like another flaky one.&lt;/p&gt;

&lt;p&gt;And nobody notices.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;I spent a long time assuming flaky tests were caused by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Infrastructure&lt;/li&gt;
&lt;li&gt;Networks&lt;/li&gt;
&lt;li&gt;Containers&lt;/li&gt;
&lt;li&gt;CI providers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sometimes they are.&lt;/p&gt;

&lt;p&gt;But surprisingly often, they're caused by something much simpler:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad test data management.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Changing our ID generation strategy didn't eliminate every flaky test.&lt;/p&gt;

&lt;p&gt;But it removed an enormous category of failures almost overnight.&lt;/p&gt;

&lt;p&gt;And it made our tests easier to debug, easier to clean up, and safer to run in parallel.&lt;/p&gt;

&lt;p&gt;If you're trying to improve reliability in your own suites, spend some time looking at your test data strategy.&lt;/p&gt;

&lt;p&gt;You might discover that your biggest source of flake isn't your infrastructure.&lt;/p&gt;

&lt;p&gt;It's your IDs.&lt;/p&gt;

&lt;p&gt;If you're curious about broader testing metrics beyond flaky failures, here's &lt;strong&gt;&lt;a href="https://totalshiftleft.ai/api-test-coverage" rel="noopener noreferrer"&gt;what we measure for API test coverage&lt;/a&gt;&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;Because sometimes the biggest improvements come from changing something as small as a string format.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>automation</category>
      <category>testing</category>
    </item>
    <item>
      <title>The Only 3 Types of Assertions You Need for REST API Tests</title>
      <dc:creator>Sushant Joshi</dc:creator>
      <pubDate>Fri, 26 Jun 2026 13:36:29 +0000</pubDate>
      <link>https://dev.to/sushant_joshi_79_/the-only-3-types-of-assertions-you-need-for-rest-api-tests-16ek</link>
      <guid>https://dev.to/sushant_joshi_79_/the-only-3-types-of-assertions-you-need-for-rest-api-tests-16ek</guid>
      <description>&lt;p&gt;I went through 2,400 of our team's API test assertions last month. 91% of them fall into three categories.*&lt;/p&gt;

&lt;p&gt;That number surprised me.&lt;/p&gt;

&lt;p&gt;Not because it was low.&lt;/p&gt;

&lt;p&gt;Because it was so high.&lt;/p&gt;

&lt;p&gt;I expected to find dozens of assertion patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Header assertions&lt;/li&gt;
&lt;li&gt;Pagination assertions&lt;/li&gt;
&lt;li&gt;Security assertions&lt;/li&gt;
&lt;li&gt;Performance assertions&lt;/li&gt;
&lt;li&gt;Database validations&lt;/li&gt;
&lt;li&gt;Custom business rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead, almost everything we had written could be grouped into just three buckets.&lt;/p&gt;

&lt;p&gt;When I removed duplicate patterns and categorized the assertions, 91% of them fit into:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Schema Assertions&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Identity Assertions&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Side-Effect Assertions&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The remaining 9%?&lt;/p&gt;

&lt;p&gt;Most of them probably shouldn't exist.&lt;/p&gt;

&lt;p&gt;If you're building or maintaining REST API tests, understanding these three categories will dramatically simplify how you think about testing.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Most API Test Suites Become Hard to Maintain
&lt;/h1&gt;

&lt;p&gt;A lot of test suites grow organically.&lt;/p&gt;

&lt;p&gt;A developer writes:&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;expect&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;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="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another adds:&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;expect&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="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&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;John&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Someone else adds:&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;expect&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="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&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="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eventually the suite contains thousands of assertions.&lt;/p&gt;

&lt;p&gt;Many of them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Duplicate each other&lt;/li&gt;
&lt;li&gt;Validate implementation details&lt;/li&gt;
&lt;li&gt;Add maintenance without adding confidence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal isn't to write more assertions.&lt;/p&gt;

&lt;p&gt;The goal is to write the assertions that actually matter.&lt;/p&gt;




&lt;h1&gt;
  
  
  1. Schema Assertions (The One Most Tests Skip)
&lt;/h1&gt;

&lt;p&gt;This is the most undervalued type of API assertion.&lt;/p&gt;

&lt;p&gt;A schema assertion answers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does the response still match the contract?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Suppose your endpoint returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John Smith"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"john@example.com"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tomorrow someone changes it to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fullName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John Smith"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The endpoint still returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;200 OK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But consumers may immediately break.&lt;/p&gt;

&lt;p&gt;This is why schema assertions matter.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Schema Assertions Validate
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Required fields exist&lt;/li&gt;
&lt;li&gt;Data types are correct&lt;/li&gt;
&lt;li&gt;Fields have not disappeared&lt;/li&gt;
&lt;li&gt;Arrays contain the right structure&lt;/li&gt;
&lt;li&gt;Response contracts remain compatible&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Example JSON Schema Assertion
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;expect&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="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toMatchSchema&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="s1"&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="s1"&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="s1"&gt;name&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="s1"&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="s1"&gt;string&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;h2&gt;
  
  
  Why Teams Skip This
&lt;/h2&gt;

&lt;p&gt;Because checking:&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;expect&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;status&lt;/span&gt;&lt;span class="p"&gt;)&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="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;feels sufficient.&lt;/p&gt;

&lt;p&gt;It isn't.&lt;/p&gt;

&lt;p&gt;The biggest API regressions I see are contract changes that still return successful responses.&lt;/p&gt;

&lt;p&gt;This is why &lt;strong&gt;json schema assertion&lt;/strong&gt; techniques provide so much value.&lt;/p&gt;




&lt;h1&gt;
  
  
  Copy-Paste Template: Schema Assertion
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;expect&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="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toMatchSchema&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or:&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;expect&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="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="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;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  2. Identity Assertions (The Value You Actually Care About)
&lt;/h1&gt;

&lt;p&gt;This is the category most people think of when they hear "API testing."&lt;/p&gt;

&lt;p&gt;An identity assertion answers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Did the API return the correct business value?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"discount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The contract may be valid.&lt;/p&gt;

&lt;p&gt;The endpoint may return &lt;code&gt;200&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But if the expected discount is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"discount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the API is still broken.&lt;/p&gt;




&lt;h2&gt;
  
  
  Identity Assertions Validate
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Business calculations&lt;/li&gt;
&lt;li&gt;Field values&lt;/li&gt;
&lt;li&gt;Sorting&lt;/li&gt;
&lt;li&gt;Filtering&lt;/li&gt;
&lt;li&gt;Authorization decisions&lt;/li&gt;
&lt;li&gt;Domain rules&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Example
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;expect&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="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;discount&lt;/span&gt;&lt;span class="p"&gt;)&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="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or:&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;expect&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="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="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;ACTIVE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Why Identity Assertions Matter
&lt;/h2&gt;

&lt;p&gt;Customers care about values.&lt;/p&gt;

&lt;p&gt;They do not care that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"discount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"integer"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is valid.&lt;/p&gt;

&lt;p&gt;They care that the discount is correct.&lt;/p&gt;




&lt;h1&gt;
  
  
  Copy-Paste Template: Identity Assertion
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;expect&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="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;)&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="nx"&gt;expectedValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or:&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;expect&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="p"&gt;)&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;expectedObject&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  3. Side-Effect Assertions (The Ones That Prove the Work Happened)
&lt;/h1&gt;

&lt;p&gt;This category gets overlooked surprisingly often.&lt;/p&gt;

&lt;p&gt;An API can return:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;200 OK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and still fail completely.&lt;/p&gt;

&lt;p&gt;Consider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /orders
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The endpoint returns success.&lt;/p&gt;

&lt;p&gt;But:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The database row wasn't created.&lt;/li&gt;
&lt;li&gt;The message wasn't published.&lt;/li&gt;
&lt;li&gt;The email wasn't sent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The business process failed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Side-Effect Assertions Validate
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Database writes&lt;/li&gt;
&lt;li&gt;Queue messages&lt;/li&gt;
&lt;li&gt;Event publication&lt;/li&gt;
&lt;li&gt;Emails&lt;/li&gt;
&lt;li&gt;Audit logs&lt;/li&gt;
&lt;li&gt;Third-party integrations&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Example Database Assertion
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orderRepository&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;orderId&lt;/span&gt;&lt;span class="p"&gt;))&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Example Queue Assertion
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orderCreatedEvent&lt;/span&gt;&lt;span class="p"&gt;))&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="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Why Side Effects Matter
&lt;/h2&gt;

&lt;p&gt;Many APIs exist solely to trigger something else.&lt;/p&gt;

&lt;p&gt;The response itself is often the least important part.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /payments
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nobody cares about:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What matters is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Was the payment captured?&lt;/li&gt;
&lt;li&gt;Was the invoice generated?&lt;/li&gt;
&lt;li&gt;Was the receipt sent?&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Copy-Paste Template: Side-Effect Assertion
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;databaseRecord&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toExist&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or:&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;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;publishedEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBeDefined&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  The 9% That Didn't Fit
&lt;/h1&gt;

&lt;p&gt;After categorizing our assertions, around 9% remained.&lt;/p&gt;

&lt;p&gt;Examples included:&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;expect&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="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&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="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="mi"&gt;5&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="nf"&gt;expect&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="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;)&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;2026-07-01&lt;/span&gt;&lt;span class="dl"&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="nf"&gt;expect&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="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;)&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;1.2.8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Many of these were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Brittle&lt;/li&gt;
&lt;li&gt;Overly specific&lt;/li&gt;
&lt;li&gt;Tied to implementation details&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Why Most of Them Should Be Deleted
&lt;/h1&gt;

&lt;p&gt;Ask yourself:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If this assertion failed tomorrow, would users actually notice?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the answer is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Probably not.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Delete it.&lt;/p&gt;

&lt;p&gt;A surprising amount of maintenance comes from assertions that don't provide meaningful confidence.&lt;/p&gt;




&lt;h1&gt;
  
  
  Bad Assertions
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;expect&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="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="mi"&gt;183&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="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;itemCount&lt;/span&gt;&lt;span class="p"&gt;)&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="mi"&gt;17&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="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;)&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2026-07-28T08:00:00Z&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These tend to break constantly.&lt;/p&gt;




&lt;h1&gt;
  
  
  Better Assertions
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;expect&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBeLessThan&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;/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="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;itemCount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBeGreaterThan&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;/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="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBeDefined&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are more resilient.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Assertion Pyramid I Recommend
&lt;/h1&gt;

&lt;p&gt;Whenever I write a new API test, I ask three questions.&lt;/p&gt;




&lt;h2&gt;
  
  
  First
&lt;/h2&gt;

&lt;p&gt;Does the response still match the contract?&lt;/p&gt;

&lt;p&gt;→ Schema Assertion.&lt;/p&gt;




&lt;h2&gt;
  
  
  Second
&lt;/h2&gt;

&lt;p&gt;Did the API return the correct business value?&lt;/p&gt;

&lt;p&gt;→ Identity Assertion.&lt;/p&gt;




&lt;h2&gt;
  
  
  Third
&lt;/h2&gt;

&lt;p&gt;Did the system actually perform the work?&lt;/p&gt;

&lt;p&gt;→ Side-Effect Assertion.&lt;/p&gt;




&lt;p&gt;Most useful tests contain at least one of these categories.&lt;/p&gt;

&lt;p&gt;Many contain all three.&lt;/p&gt;

&lt;p&gt;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="nf"&gt;expect&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="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toMatchSchema&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="nf"&gt;expect&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="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&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;John&lt;/span&gt;&lt;span class="dl"&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;databaseUser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toExist&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three assertions.&lt;/p&gt;

&lt;p&gt;Three different guarantees.&lt;/p&gt;

&lt;p&gt;High confidence.&lt;/p&gt;

&lt;p&gt;Low maintenance.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;When we reduced our thousands of assertions down to categories, we realized something important:&lt;/p&gt;

&lt;p&gt;Most API testing is simpler than we make it.&lt;/p&gt;

&lt;p&gt;The majority of valuable assertions answer only three questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Is the contract still valid?&lt;/li&gt;
&lt;li&gt;Is the business value correct?&lt;/li&gt;
&lt;li&gt;Did the side effect happen?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Everything else should justify its existence.&lt;/p&gt;

&lt;p&gt;If an assertion doesn't increase confidence or protect against meaningful regressions, it may not belong in the suite.&lt;/p&gt;

&lt;p&gt;That's why I now start every API test by deciding which of these three categories I'm actually trying to validate.&lt;/p&gt;

&lt;p&gt;If you'd like to go deeper into building maintainable API suites, I highly recommend &lt;strong&gt;&lt;a href="https://totalshiftleft.ai/blog/rest-api-testing-best-practices" rel="noopener noreferrer"&gt;the REST API testing best practices guide&lt;/a&gt;&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;Because the best API tests aren't the ones with the most assertions.&lt;/p&gt;

&lt;p&gt;They're the ones with the right assertions.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>startup</category>
      <category>testing</category>
    </item>
    <item>
      <title>API Versioning Tests: 3 Cases Almost Every Team Misses</title>
      <dc:creator>Sushant Joshi</dc:creator>
      <pubDate>Thu, 25 Jun 2026 13:28:32 +0000</pubDate>
      <link>https://dev.to/sushant_joshi_79_/api-versioning-tests-3-cases-almost-every-team-misses-4d1l</link>
      <guid>https://dev.to/sushant_joshi_79_/api-versioning-tests-3-cases-almost-every-team-misses-4d1l</guid>
      <description>&lt;p&gt;Our v2 API broke v1 clients in production on a Wednesday because nobody had tested the case where a v1 client received a v2-shaped response after a load-balancer routing rule was tweaked.*&lt;/p&gt;

&lt;p&gt;The outage lasted less than an hour.&lt;/p&gt;

&lt;p&gt;The damage lasted much longer.&lt;/p&gt;

&lt;p&gt;A mobile application started failing silently because it expected a field named:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"customerName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John Smith"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but suddenly received:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fullName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John Smith"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"customerStatus"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Active"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing was technically wrong with the v2 API.&lt;/p&gt;

&lt;p&gt;The issue was that some v1 clients unexpectedly started receiving v2 responses.&lt;/p&gt;

&lt;p&gt;The routing change looked harmless during deployment.&lt;/p&gt;

&lt;p&gt;No tests covered that scenario.&lt;/p&gt;

&lt;p&gt;This experience taught us an important lesson:&lt;/p&gt;

&lt;p&gt;Versioning an API isn't just about creating &lt;code&gt;/v2&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It's about ensuring every version continues behaving exactly as its consumers expect.&lt;/p&gt;

&lt;p&gt;Most teams write happy-path tests for each version independently. Very few teams write tests that validate interactions between versions, backward compatibility guarantees, and accidental cross-version behavior.&lt;/p&gt;

&lt;p&gt;These are the three categories of &lt;strong&gt;API versioning tests&lt;/strong&gt; that almost every team misses.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Versioning Tests Matter More Than Ever
&lt;/h1&gt;

&lt;p&gt;Modern APIs evolve constantly.&lt;/p&gt;

&lt;p&gt;New versions introduce:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Additional fields&lt;/li&gt;
&lt;li&gt;Renamed properties&lt;/li&gt;
&lt;li&gt;New authentication requirements&lt;/li&gt;
&lt;li&gt;Performance improvements&lt;/li&gt;
&lt;li&gt;Business logic changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these are inherently bad.&lt;/p&gt;

&lt;p&gt;The risk comes from assuming:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Since v2 works, v1 must still work too."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That assumption causes many production incidents.&lt;/p&gt;

&lt;p&gt;Consumers don't upgrade immediately.&lt;/p&gt;

&lt;p&gt;Some clients may continue using older versions for months or years.&lt;/p&gt;

&lt;p&gt;A single breaking change can impact:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mobile applications&lt;/li&gt;
&lt;li&gt;Partner integrations&lt;/li&gt;
&lt;li&gt;Third-party SDKs&lt;/li&gt;
&lt;li&gt;Internal microservices&lt;/li&gt;
&lt;li&gt;Reporting systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why &lt;strong&gt;API version testing&lt;/strong&gt; requires more than endpoint-level validation.&lt;/p&gt;

&lt;p&gt;It requires contract validation across versions.&lt;/p&gt;




&lt;h1&gt;
  
  
  1. The Dual-Version Contract Test
&lt;/h1&gt;

&lt;p&gt;This is the most valuable test we added after our routing incident.&lt;/p&gt;

&lt;p&gt;The concept is simple.&lt;/p&gt;

&lt;p&gt;Every request that exists in both versions should be tested against both versions simultaneously.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /customers/123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is executed against:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/v1/customers/123
/v2/customers/123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal isn't necessarily to make responses identical.&lt;/p&gt;

&lt;p&gt;The goal is to validate contractual guarantees.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Test Checks
&lt;/h2&gt;

&lt;p&gt;The test should answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does v1 still return every promised field?&lt;/li&gt;
&lt;li&gt;Are field types unchanged?&lt;/li&gt;
&lt;li&gt;Are required properties still present?&lt;/li&gt;
&lt;li&gt;Are deprecated properties still available?&lt;/li&gt;
&lt;li&gt;Are status codes consistent?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This catches situations where implementation changes accidentally impact previous versions.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Cross-Version Routing Test
&lt;/h2&gt;

&lt;p&gt;Now comes the scenario most teams never test.&lt;/p&gt;

&lt;p&gt;What happens if:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;v1 request → v2 service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;v2 request → v1 service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sounds unlikely.&lt;/p&gt;

&lt;p&gt;In distributed systems, it happens more often than people expect.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Load-balancer misconfigurations&lt;/li&gt;
&lt;li&gt;Canary deployments&lt;/li&gt;
&lt;li&gt;Service discovery issues&lt;/li&gt;
&lt;li&gt;Proxy rules&lt;/li&gt;
&lt;li&gt;API gateway changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your system should either:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Continue functioning safely.&lt;/li&gt;
&lt;li&gt;Return a clear version error.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It should never fail unpredictably.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example Test Matrix
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Request&lt;/th&gt;
&lt;th&gt;Expected Response&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;v1 → v1&lt;/td&gt;
&lt;td&gt;Success&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v2 → v2&lt;/td&gt;
&lt;td&gt;Success&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v1 → v2&lt;/td&gt;
&lt;td&gt;Graceful handling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v2 → v1&lt;/td&gt;
&lt;td&gt;Graceful handling&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Most teams only test the first two rows.&lt;/p&gt;

&lt;p&gt;The last two are where incidents happen.&lt;/p&gt;




&lt;h1&gt;
  
  
  2. Deprecated-Field Assertions (Yes, You Should Assert They Exist)
&lt;/h1&gt;

&lt;p&gt;This recommendation often surprises people.&lt;/p&gt;

&lt;p&gt;Many engineers believe deprecated fields should gradually disappear from tests.&lt;/p&gt;

&lt;p&gt;In reality, the opposite is usually true.&lt;/p&gt;

&lt;p&gt;If a field is marked as deprecated but remains part of the contract, your tests should explicitly assert its existence.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Real Example
&lt;/h2&gt;

&lt;p&gt;Version 1 returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"customerName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John Smith"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Version 2 introduces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fullName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John Smith"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The team marks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;customerName
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;as deprecated.&lt;/p&gt;

&lt;p&gt;Everything seems fine.&lt;/p&gt;

&lt;p&gt;Six months later, an engineer removes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;customerName
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;assuming nobody uses it anymore.&lt;/p&gt;

&lt;p&gt;Unfortunately:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mobile apps still depend on it.&lt;/li&gt;
&lt;li&gt;A partner integration still parses it.&lt;/li&gt;
&lt;li&gt;Several reports still consume it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Production breaks.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Better Approach
&lt;/h2&gt;

&lt;p&gt;Your test should explicitly verify:&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;expect&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;customerName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBeDefined&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even though the field is deprecated.&lt;/p&gt;

&lt;p&gt;Deprecation means:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Planned for removal."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It does not mean:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Safe to remove today."&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Add Expiration Dates
&lt;/h2&gt;

&lt;p&gt;The best practice is to combine assertions with metadata.&lt;/p&gt;

&lt;p&gt;For example:&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;deprecated&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;field&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;customerName&lt;/span&gt;
  &lt;span class="na"&gt;remove_after&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2027-01-01&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now tests can remind teams:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Field still exists.&lt;/li&gt;
&lt;li&gt;Deprecation window is active.&lt;/li&gt;
&lt;li&gt;Removal date is approaching.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This creates a controlled migration process.&lt;/p&gt;




&lt;h1&gt;
  
  
  3. The Header-Based vs URL-Based Version Test Pattern
&lt;/h1&gt;

&lt;p&gt;API versioning strategies vary significantly.&lt;/p&gt;

&lt;p&gt;The two most common approaches are:&lt;/p&gt;

&lt;h3&gt;
  
  
  URL Versioning
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/api/v1/orders
/api/v2/orders
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Header Versioning
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Accept: application/vnd.company.v1+json
Accept: application/vnd.company.v2+json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both approaches require different testing strategies.&lt;/p&gt;




&lt;h2&gt;
  
  
  URL-Based Testing
&lt;/h2&gt;

&lt;p&gt;This approach is relatively straightforward.&lt;/p&gt;

&lt;p&gt;Test suites can easily target:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/v1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/v2&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/v3&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Contract separation is clear.&lt;/p&gt;

&lt;p&gt;However, routing issues become important.&lt;/p&gt;

&lt;p&gt;You need tests that verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;URL rewrites&lt;/li&gt;
&lt;li&gt;Gateway behavior&lt;/li&gt;
&lt;li&gt;Redirect handling&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Header-Based Testing
&lt;/h2&gt;

&lt;p&gt;Header-based versioning creates additional complexity.&lt;/p&gt;

&lt;p&gt;The same endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /orders
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;can return completely different responses depending on headers.&lt;/p&gt;

&lt;p&gt;Testing must validate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Missing version headers&lt;/li&gt;
&lt;li&gt;Invalid versions&lt;/li&gt;
&lt;li&gt;Default versions&lt;/li&gt;
&lt;li&gt;Unsupported versions&lt;/li&gt;
&lt;li&gt;Version negotiation behavior&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  A Test Pattern We Like
&lt;/h2&gt;

&lt;p&gt;For every endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;No header
v1 header
v2 header
invalid version header
future version header
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This often reveals unexpected behavior.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Silent fallbacks&lt;/li&gt;
&lt;li&gt;Incorrect defaults&lt;/li&gt;
&lt;li&gt;Cached responses&lt;/li&gt;
&lt;li&gt;Improper content negotiation&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Snapshot Diffs Between v1 and v2 Responses
&lt;/h1&gt;

&lt;p&gt;One of the simplest and most effective versioning techniques is snapshot comparison.&lt;/p&gt;

&lt;p&gt;For a representative request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /customers/123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Capture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;v&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;_response.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;v&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="err"&gt;_response.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then generate structural diffs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example Diff
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;{
&lt;span class="gd"&gt;- "customerName": "John Smith",
&lt;/span&gt;&lt;span class="gi"&gt;+ "fullName": "John Smith",
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gi"&gt;+ "customerStatus": "Active"
&lt;/span&gt;}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This provides immediate visibility into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Added fields&lt;/li&gt;
&lt;li&gt;Removed fields&lt;/li&gt;
&lt;li&gt;Type changes&lt;/li&gt;
&lt;li&gt;Structural changes&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why Snapshots Matter
&lt;/h2&gt;

&lt;p&gt;Humans often miss small changes during code review.&lt;/p&gt;

&lt;p&gt;Examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- "123"
&lt;/span&gt;&lt;span class="gi"&gt;+ 123
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This appears minor.&lt;/p&gt;

&lt;p&gt;For some consumers, it's a breaking change.&lt;/p&gt;

&lt;p&gt;Snapshot testing catches these differences immediately.&lt;/p&gt;




&lt;h2&gt;
  
  
  Contract Snapshots Are Better Than Payload Snapshots
&lt;/h2&gt;

&lt;p&gt;Avoid comparing exact values whenever possible.&lt;/p&gt;

&lt;p&gt;Compare:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Field names&lt;/li&gt;
&lt;li&gt;Types&lt;/li&gt;
&lt;li&gt;Required properties&lt;/li&gt;
&lt;li&gt;Structure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This reduces noise while preserving meaningful change detection.&lt;/p&gt;




&lt;h1&gt;
  
  
  The CI Gate That Catches Accidental Breaking Changes
&lt;/h1&gt;

&lt;p&gt;The most effective versioning tests don't run only during releases.&lt;/p&gt;

&lt;p&gt;They run continuously.&lt;/p&gt;

&lt;p&gt;Every pull request should answer one question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does this change accidentally break a previous contract?&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What the CI Gate Should Check
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Schema Changes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Required fields removed&lt;/li&gt;
&lt;li&gt;Data types changed&lt;/li&gt;
&lt;li&gt;Enum values changed&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Response Changes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Deprecated fields removed&lt;/li&gt;
&lt;li&gt;Status codes changed&lt;/li&gt;
&lt;li&gt;Error structures modified&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Version Behavior
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Routing changes&lt;/li&gt;
&lt;li&gt;Header handling&lt;/li&gt;
&lt;li&gt;Content negotiation&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Example CI Pipeline
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Build
↓
Run version contract tests
↓
Generate snapshots
↓
Compare against baseline
↓
Fail if breaking changes detected
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The process is simple.&lt;/p&gt;

&lt;p&gt;The impact is enormous.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Breaking Changes You Want to Catch
&lt;/h2&gt;

&lt;p&gt;Examples include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- customerId: integer
&lt;/span&gt;&lt;span class="gi"&gt;+ customerId: string
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- status: Active
&lt;/span&gt;&lt;span class="gi"&gt;+ state: Active
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- customerName
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These changes may look harmless to developers.&lt;/p&gt;

&lt;p&gt;For API consumers, they can be catastrophic.&lt;/p&gt;




&lt;h1&gt;
  
  
  Building a Sustainable Version Testing Strategy
&lt;/h1&gt;

&lt;p&gt;A practical strategy usually includes four layers.&lt;/p&gt;




&lt;h2&gt;
  
  
  Layer 1: Endpoint Tests
&lt;/h2&gt;

&lt;p&gt;Validate each version independently.&lt;/p&gt;




&lt;h2&gt;
  
  
  Layer 2: Contract Tests
&lt;/h2&gt;

&lt;p&gt;Validate compatibility between versions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Layer 3: Snapshot Diffs
&lt;/h2&gt;

&lt;p&gt;Highlight structural changes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Layer 4: CI Gates
&lt;/h2&gt;

&lt;p&gt;Prevent accidental regressions from reaching production.&lt;/p&gt;




&lt;p&gt;This layered approach dramatically reduces version-related incidents.&lt;/p&gt;

&lt;p&gt;It also provides confidence when introducing new versions.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Teams Still Miss These Tests
&lt;/h1&gt;

&lt;p&gt;Versioning issues often occur because teams think in terms of endpoints.&lt;/p&gt;

&lt;p&gt;Consumers think in terms of contracts.&lt;/p&gt;

&lt;p&gt;Those perspectives are different.&lt;/p&gt;

&lt;p&gt;An endpoint can function perfectly while still breaking consumers.&lt;/p&gt;

&lt;p&gt;The goal of version testing is not simply:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Does the endpoint return 200?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The goal is:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Can every supported consumer continue working safely?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That question requires broader testing.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;The most painful API incidents I've seen were not caused by servers crashing or databases failing.&lt;/p&gt;

&lt;p&gt;They were caused by small compatibility assumptions.&lt;/p&gt;

&lt;p&gt;A renamed field.&lt;/p&gt;

&lt;p&gt;A removed property.&lt;/p&gt;

&lt;p&gt;A routing rule.&lt;/p&gt;

&lt;p&gt;A header negotiation bug.&lt;/p&gt;

&lt;p&gt;These changes rarely trigger alarms immediately.&lt;/p&gt;

&lt;p&gt;Instead, they quietly break integrations that nobody remembered to test.&lt;/p&gt;

&lt;p&gt;Strong &lt;strong&gt;backward compatibility tests&lt;/strong&gt; and thoughtful &lt;strong&gt;deprecation testing&lt;/strong&gt; dramatically reduce those risks.&lt;/p&gt;

&lt;p&gt;Versioning isn't just about introducing new APIs.&lt;/p&gt;

&lt;p&gt;It's about preserving trust with every client that still depends on older ones.&lt;/p&gt;

&lt;p&gt;If your team is building or maintaining multiple API versions, adopting &lt;strong&gt;&lt;a href="https://totalshiftleft.ai/api-contract-testing" rel="noopener noreferrer"&gt;the contract-testing approach we use for versioned APIs&lt;/a&gt;&lt;/strong&gt; can provide a much safer foundation for change:&lt;/p&gt;

&lt;p&gt;Because in versioned systems, the bugs that hurt the most are usually the ones nobody thought to test.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>testing</category>
      <category>api</category>
      <category>postman</category>
    </item>
    <item>
      <title>Building a Self-Healing Test Suite ~ My Honest Version</title>
      <dc:creator>Sushant Joshi</dc:creator>
      <pubDate>Thu, 18 Jun 2026 11:59:56 +0000</pubDate>
      <link>https://dev.to/sushant_joshi_79_/building-a-self-healing-test-suite-my-honest-version-3hkl</link>
      <guid>https://dev.to/sushant_joshi_79_/building-a-self-healing-test-suite-my-honest-version-3hkl</guid>
      <description>&lt;p&gt;"Self-healing" is one of those phrases that means three different things depending on which vendor's homepage you read last.*&lt;/p&gt;

&lt;p&gt;In one product, it means updating locators when a button moves.&lt;/p&gt;

&lt;p&gt;In another, it means regenerating entire test cases from production traffic.&lt;/p&gt;

&lt;p&gt;In yet another, it means using AI to automatically rewrite assertions whenever tests fail.&lt;/p&gt;

&lt;p&gt;The problem isn't that these claims are entirely wrong.&lt;/p&gt;

&lt;p&gt;The problem is that "self-healing" has become a catch-all marketing term that often creates unrealistic expectations.&lt;/p&gt;

&lt;p&gt;Teams hear "self-healing tests" and imagine a future where test failures magically disappear while quality remains intact.&lt;/p&gt;

&lt;p&gt;Reality is more nuanced.&lt;/p&gt;

&lt;p&gt;Modern self-healing technology can dramatically reduce maintenance effort, especially for API and integration testing. However, there are clear boundaries between what can be safely repaired and what still requires human judgment.&lt;/p&gt;

&lt;p&gt;This article explores the practical reality of building a self-healing test suite, including what it can fix, what it cannot fix, and where automation should stop and ask for approval.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Self-Healing Actually Covers (And the 3 Things It Can't Fix)
&lt;/h2&gt;

&lt;p&gt;Before discussing implementations, it's important to define what self-healing means in practice.&lt;/p&gt;

&lt;p&gt;At its core, a self-healing system attempts to determine whether a test failed because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The application changed legitimately.&lt;/li&gt;
&lt;li&gt;The test became outdated.&lt;/li&gt;
&lt;li&gt;The application is actually broken.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The goal is to automatically repair tests only in scenario #2.&lt;/p&gt;

&lt;p&gt;The challenge is distinguishing between all three.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Self-Healing Can Usually Fix
&lt;/h3&gt;

&lt;p&gt;The most effective self-healing systems focus on structural changes.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Renamed JSON fields&lt;/li&gt;
&lt;li&gt;Additional optional response properties&lt;/li&gt;
&lt;li&gt;Endpoint path updates&lt;/li&gt;
&lt;li&gt;Schema version changes&lt;/li&gt;
&lt;li&gt;Authentication token format changes&lt;/li&gt;
&lt;li&gt;Header name changes&lt;/li&gt;
&lt;li&gt;Parameter renaming&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These changes often represent intentional application evolution rather than defects.&lt;/p&gt;

&lt;p&gt;Because they are structural, they can frequently be analyzed and corrected automatically.&lt;/p&gt;




&lt;h3&gt;
  
  
  What Self-Healing Cannot Reliably Fix
&lt;/h3&gt;

&lt;p&gt;There are three categories that remain extremely difficult to automate safely.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Business Logic Changes
&lt;/h4&gt;

&lt;p&gt;Consider an API that calculates discounts.&lt;/p&gt;

&lt;p&gt;Yesterday:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"discount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Today:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"discount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Did the business rule change?&lt;/p&gt;

&lt;p&gt;Or is there a bug?&lt;/p&gt;

&lt;p&gt;The test failure alone cannot answer that question.&lt;/p&gt;

&lt;p&gt;A healing engine should never guess.&lt;/p&gt;




&lt;h4&gt;
  
  
  2. Missing Business Outcomes
&lt;/h4&gt;

&lt;p&gt;Imagine a checkout API that suddenly stops creating orders.&lt;/p&gt;

&lt;p&gt;The response format remains identical.&lt;/p&gt;

&lt;p&gt;All fields still exist.&lt;/p&gt;

&lt;p&gt;Yet the core business outcome is gone.&lt;/p&gt;

&lt;p&gt;No amount of structural healing can identify the intended business behavior.&lt;/p&gt;




&lt;h4&gt;
  
  
  3. Security-Related Failures
&lt;/h4&gt;

&lt;p&gt;Authentication, authorization, and access-control failures should never be auto-corrected.&lt;/p&gt;

&lt;p&gt;If an API suddenly returns sensitive data to unauthorized users, automatic healing could accidentally hide a critical security issue.&lt;/p&gt;

&lt;p&gt;Security failures require investigation, not repair.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Worked Example: A Renamed JSON Field
&lt;/h2&gt;

&lt;p&gt;Let's look at a realistic scenario.&lt;/p&gt;

&lt;p&gt;An API originally returned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"customerId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"customerName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John Smith"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Active"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test asserted:&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;expect&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;customerName&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;John Smith&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Several months later, the API team introduces a naming convention update.&lt;/p&gt;

&lt;p&gt;The response becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"customerId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fullName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John Smith"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Active"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test now fails.&lt;/p&gt;

&lt;p&gt;Traditional automation requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Failure investigation&lt;/li&gt;
&lt;li&gt;Root cause analysis&lt;/li&gt;
&lt;li&gt;Test update&lt;/li&gt;
&lt;li&gt;Code review&lt;/li&gt;
&lt;li&gt;Commit&lt;/li&gt;
&lt;li&gt;Redeployment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a single field rename, that's a surprisingly expensive workflow.&lt;/p&gt;




&lt;h3&gt;
  
  
  What a Self-Healing Engine Sees
&lt;/h3&gt;

&lt;p&gt;A modern healing engine analyzes several signals.&lt;/p&gt;

&lt;p&gt;It notices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Response schema remains largely unchanged&lt;/li&gt;
&lt;li&gt;Field value still exists&lt;/li&gt;
&lt;li&gt;Data type matches&lt;/li&gt;
&lt;li&gt;Object structure remains identical&lt;/li&gt;
&lt;li&gt;Similar semantic meaning between names&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It may calculate:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Confidence&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Data type match&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Position similarity&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Value pattern match&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Semantic similarity&lt;/td&gt;
&lt;td&gt;92%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Overall confidence&lt;/td&gt;
&lt;td&gt;96%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The system now has strong evidence that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;customerName
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;became&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fullName
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;rather than the application breaking.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Diff the Healer Proposed and the Diff I Accepted
&lt;/h2&gt;

&lt;p&gt;One of the biggest misconceptions about &lt;strong&gt;auto-fix API tests&lt;/strong&gt; is that they should operate silently.&lt;/p&gt;

&lt;p&gt;In reality, silent modifications can become dangerous very quickly.&lt;/p&gt;

&lt;p&gt;A better approach is proposing changes first.&lt;/p&gt;

&lt;p&gt;The failing assertion looked like this:&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;expect&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;customerName&lt;/span&gt;&lt;span class="p"&gt;)&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;John Smith&lt;/span&gt;&lt;span class="dl"&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 healer proposed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- expect(response.customerName)
&lt;/span&gt;&lt;span class="gi"&gt;+ expect(response.fullName)
&lt;/span&gt;    .toEqual("John Smith");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first glance, this appears obvious.&lt;/p&gt;

&lt;p&gt;However, the review process still matters.&lt;/p&gt;

&lt;p&gt;The engineer can quickly verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Was the field intentionally renamed?&lt;/li&gt;
&lt;li&gt;Is the value equivalent?&lt;/li&gt;
&lt;li&gt;Does the business meaning remain unchanged?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this case, the answer was yes.&lt;/p&gt;

&lt;p&gt;The change was accepted.&lt;/p&gt;

&lt;p&gt;The test passed immediately.&lt;/p&gt;

&lt;p&gt;Total maintenance effort: less than one minute.&lt;/p&gt;




&lt;h3&gt;
  
  
  Why Human Approval Still Matters
&lt;/h3&gt;

&lt;p&gt;Now imagine this proposed change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- accountBalance
&lt;/span&gt;&lt;span class="gi"&gt;+ availableCredit
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those fields may look similar.&lt;/p&gt;

&lt;p&gt;They are not the same thing.&lt;/p&gt;

&lt;p&gt;Automatically accepting that modification could introduce serious defects into the test suite.&lt;/p&gt;

&lt;p&gt;Self-healing should reduce human work, not eliminate human oversight.&lt;/p&gt;




&lt;h2&gt;
  
  
  Confidence Thresholds — When to Auto-Apply vs Ask
&lt;/h2&gt;

&lt;p&gt;The most effective &lt;strong&gt;resilient test suite&lt;/strong&gt; implementations use confidence scoring.&lt;/p&gt;

&lt;p&gt;Not every proposed repair deserves the same level of trust.&lt;/p&gt;

&lt;p&gt;A useful model might look like this:&lt;/p&gt;

&lt;h3&gt;
  
  
  Auto-Apply (95–100%)
&lt;/h3&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Added optional response fields&lt;/li&gt;
&lt;li&gt;Header renames&lt;/li&gt;
&lt;li&gt;Query parameter aliases&lt;/li&gt;
&lt;li&gt;Non-breaking schema extensions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Risk is extremely low.&lt;/p&gt;

&lt;p&gt;Automation can safely proceed.&lt;/p&gt;




&lt;h3&gt;
  
  
  Request Approval (75–95%)
&lt;/h3&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Field renames&lt;/li&gt;
&lt;li&gt;Schema restructures&lt;/li&gt;
&lt;li&gt;Endpoint migrations&lt;/li&gt;
&lt;li&gt;Nested object movement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These changes are usually safe but deserve a quick review.&lt;/p&gt;

&lt;p&gt;A human can validate them in seconds.&lt;/p&gt;




&lt;h3&gt;
  
  
  Require Manual Investigation (&amp;lt;75%)
&lt;/h3&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Value changes&lt;/li&gt;
&lt;li&gt;Business rule differences&lt;/li&gt;
&lt;li&gt;Calculation differences&lt;/li&gt;
&lt;li&gt;Authorization changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this point, the system lacks enough confidence.&lt;/p&gt;

&lt;p&gt;Automatic repair becomes risky.&lt;/p&gt;




&lt;h3&gt;
  
  
  Why Confidence Matters More Than AI
&lt;/h3&gt;

&lt;p&gt;Many discussions focus on whether healing uses AI, machine learning, or rules.&lt;/p&gt;

&lt;p&gt;The more important question is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How confident is the system in the proposed repair?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Even sophisticated models make mistakes.&lt;/p&gt;

&lt;p&gt;A strong healing framework acknowledges uncertainty and surfaces it rather than hiding it.&lt;/p&gt;

&lt;p&gt;The goal isn't to appear intelligent.&lt;/p&gt;

&lt;p&gt;The goal is to avoid masking defects.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Category of Failure Where You Should Never Auto-Heal
&lt;/h2&gt;

&lt;p&gt;If there is one principle every engineering team should adopt, it's this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Never auto-heal business assertions.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's examine why.&lt;/p&gt;

&lt;p&gt;Suppose a tax calculation API should return:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tax"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;15.25&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A deployment causes the API to return:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tax"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;12.75&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test fails.&lt;/p&gt;

&lt;p&gt;A dangerous healing engine might decide:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"The value changed. Let's update the expected result."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The test now passes.&lt;/p&gt;

&lt;p&gt;The bug survives.&lt;/p&gt;

&lt;p&gt;The entire purpose of testing has been defeated.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Cost of False Positives
&lt;/h3&gt;

&lt;p&gt;Many organizations focus on reducing false failures.&lt;/p&gt;

&lt;p&gt;That's important.&lt;/p&gt;

&lt;p&gt;However, the larger risk is introducing false success.&lt;/p&gt;

&lt;p&gt;A false failure wastes time.&lt;/p&gt;

&lt;p&gt;A false success ships defects.&lt;/p&gt;

&lt;p&gt;Given the choice, every mature QA organization should prefer a small amount of investigation over silently hiding a production issue.&lt;/p&gt;




&lt;h3&gt;
  
  
  Where Self-Repair Works Best
&lt;/h3&gt;

&lt;p&gt;The strongest use cases for &lt;strong&gt;test self repair&lt;/strong&gt; involve maintenance-heavy changes that provide little business value.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Schema evolution&lt;/li&gt;
&lt;li&gt;Endpoint versioning&lt;/li&gt;
&lt;li&gt;Contract updates&lt;/li&gt;
&lt;li&gt;Response restructuring&lt;/li&gt;
&lt;li&gt;Naming convention changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These changes generate noise rather than insight.&lt;/p&gt;

&lt;p&gt;Removing that noise allows engineers to focus on genuine quality risks.&lt;/p&gt;




&lt;h2&gt;
  
  
  Building a Practical Self-Healing Strategy
&lt;/h2&gt;

&lt;p&gt;Organizations often ask whether every test should be self-healing.&lt;/p&gt;

&lt;p&gt;The answer is no.&lt;/p&gt;

&lt;p&gt;A layered approach works far better.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 1: Contract Tests
&lt;/h3&gt;

&lt;p&gt;Allow healing.&lt;/p&gt;

&lt;p&gt;These tests validate structure.&lt;/p&gt;

&lt;p&gt;They're ideal candidates for automated repair.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 2: Integration Tests
&lt;/h3&gt;

&lt;p&gt;Allow limited healing with approval.&lt;/p&gt;

&lt;p&gt;These tests validate interactions between services.&lt;/p&gt;

&lt;p&gt;Some repairs are safe.&lt;/p&gt;

&lt;p&gt;Others require review.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 3: Business Validation Tests
&lt;/h3&gt;

&lt;p&gt;No automatic healing.&lt;/p&gt;

&lt;p&gt;These tests exist specifically to detect behavioral changes.&lt;/p&gt;

&lt;p&gt;Their assertions should remain under human control.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 4: Security Tests
&lt;/h3&gt;

&lt;p&gt;Never heal automatically.&lt;/p&gt;

&lt;p&gt;Security failures should always trigger investigation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;The promise of self-healing tests isn't that failures disappear.&lt;/p&gt;

&lt;p&gt;The real value is that teams spend less time fixing tests that were never providing meaningful feedback in the first place.&lt;/p&gt;

&lt;p&gt;A renamed field should not consume hours of engineering effort.&lt;/p&gt;

&lt;p&gt;A schema evolution should not trigger dozens of manual pull requests.&lt;/p&gt;

&lt;p&gt;A version upgrade should not create a maintenance backlog.&lt;/p&gt;

&lt;p&gt;Modern self-healing systems can eliminate much of that friction while preserving confidence in the test suite.&lt;/p&gt;

&lt;p&gt;The key is understanding where automation helps and where human judgment remains essential.&lt;/p&gt;

&lt;p&gt;If a healing engine is modifying assertions tied to business outcomes, it's probably going too far.&lt;/p&gt;

&lt;p&gt;If it's repairing structural changes while providing transparency and confidence scoring, it's likely delivering real value.&lt;/p&gt;

&lt;p&gt;For a deeper technical explanation of &lt;strong&gt;&lt;a href="https://totalshiftleft.ai/blog/self-healing-api-tests-how-they-work" rel="noopener noreferrer"&gt;how self-healing actually works under the hood&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The most effective self-healing strategy isn't about making tests smarter.&lt;/p&gt;

&lt;p&gt;It's about making maintenance quieter while keeping quality signals loud.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>software</category>
      <category>testing</category>
      <category>api</category>
    </item>
  </channel>
</rss>
