<?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>What We Learned Generating 12,000 API Tests from OpenAPI Specs</title>
      <dc:creator>Sushant Joshi</dc:creator>
      <pubDate>Wed, 15 Jul 2026 14:04:55 +0000</pubDate>
      <link>https://dev.to/sushant_joshi_79_/what-we-learned-generating-12000-api-tests-from-openapi-specs-1gng</link>
      <guid>https://dev.to/sushant_joshi_79_/what-we-learned-generating-12000-api-tests-from-openapi-specs-1gng</guid>
      <description>&lt;p&gt;&lt;em&gt;We thought generating API tests from an OpenAPI specification would eliminate the repetitive work of writing test cases. It did. What we didn't expect was how quickly it exposed problems in our API specifications instead of our APIs.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After generating more than &lt;strong&gt;12,000 API tests&lt;/strong&gt; from OpenAPI specifications across dozens of projects, one thing became obvious:&lt;/p&gt;

&lt;p&gt;The biggest bottleneck wasn't the test generator.&lt;/p&gt;

&lt;p&gt;It was the API specification itself.&lt;/p&gt;

&lt;p&gt;We expected to spend most of our time improving generation algorithms.&lt;/p&gt;

&lt;p&gt;Instead, we spent most of it fixing incomplete schemas, ambiguous contracts, and inconsistent specifications.&lt;/p&gt;

&lt;p&gt;If you're planning to generate tests from OpenAPI, here's what surprised us the most.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Promise of OpenAPI Test Generation
&lt;/h2&gt;

&lt;p&gt;Most API teams already have an OpenAPI specification.&lt;/p&gt;

&lt;p&gt;Unfortunately, many teams treat it as documentation instead of a contract.&lt;/p&gt;

&lt;p&gt;That's a missed opportunity.&lt;/p&gt;

&lt;p&gt;A good specification can generate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Request validation&lt;/li&gt;
&lt;li&gt;Response validation&lt;/li&gt;
&lt;li&gt;Schema assertions&lt;/li&gt;
&lt;li&gt;Negative test cases&lt;/li&gt;
&lt;li&gt;Authentication scenarios&lt;/li&gt;
&lt;li&gt;Mock servers&lt;/li&gt;
&lt;li&gt;SDKs&lt;/li&gt;
&lt;li&gt;Documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And, of course, automated API tests.&lt;/p&gt;

&lt;p&gt;Generating tests directly from the specification means developers don't have to manually recreate the contract inside Postman collections or code-based frameworks.&lt;/p&gt;

&lt;p&gt;The specification becomes the single source of truth.&lt;/p&gt;

&lt;p&gt;At least, that's the theory.&lt;/p&gt;




&lt;h2&gt;
  
  
  What We Actually Generated
&lt;/h2&gt;

&lt;p&gt;Over several months we processed thousands of endpoints from internal projects, public APIs, and customer specifications.&lt;/p&gt;

&lt;p&gt;Across all of them we generated approximately:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;12,000+ API test cases&lt;/li&gt;
&lt;li&gt;Hundreds of OpenAPI specifications&lt;/li&gt;
&lt;li&gt;Thousands of request/response assertions&lt;/li&gt;
&lt;li&gt;Countless schema validations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The APIs themselves weren't the interesting part.&lt;/p&gt;

&lt;p&gt;The specifications were.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lesson 1: Most OpenAPI Specs Are Only 80% Complete
&lt;/h2&gt;

&lt;p&gt;One pattern appeared immediately.&lt;/p&gt;

&lt;p&gt;Developers document the happy path.&lt;/p&gt;

&lt;p&gt;Everything else gets skipped.&lt;/p&gt;

&lt;p&gt;Typical examples included:&lt;/p&gt;

&lt;p&gt;Missing error responses:&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;responses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;200&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Success&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But no:&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="m"&gt;400&lt;/span&gt;
&lt;span class="m"&gt;401&lt;/span&gt;
&lt;span class="m"&gt;403&lt;/span&gt;
&lt;span class="m"&gt;404&lt;/span&gt;
&lt;span class="m"&gt;500&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generator can only create tests for scenarios that exist in the contract.&lt;/p&gt;

&lt;p&gt;If the specification ignores validation failures, your generated suite will too.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lesson 2: Example Values Matter More Than Expected
&lt;/h2&gt;

&lt;p&gt;OpenAPI supports examples.&lt;/p&gt;

&lt;p&gt;Many teams ignore them.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;example&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;John Doe&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This improves generated:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requests&lt;/li&gt;
&lt;li&gt;Mock responses&lt;/li&gt;
&lt;li&gt;Documentation&lt;/li&gt;
&lt;li&gt;Test readability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Good examples dramatically improve generated tests.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lesson 3: Nullable Doesn't Mean Optional
&lt;/h2&gt;

&lt;p&gt;This was responsible for an astonishing number of incorrect assertions.&lt;/p&gt;

&lt;p&gt;These are different:&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;nullable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and&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;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;"The property exists but may contain null."&lt;/p&gt;

&lt;p&gt;The other means:&lt;/p&gt;

&lt;p&gt;"The property might not exist at all."&lt;/p&gt;

&lt;p&gt;Generated tests need to distinguish between both.&lt;/p&gt;

&lt;p&gt;Otherwise perfectly valid responses fail incorrectly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lesson 4: Authentication Is Frequently Under-Specified
&lt;/h2&gt;

&lt;p&gt;Many APIs defined:&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;security&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;bearerAuth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Globally.&lt;/p&gt;

&lt;p&gt;Then individual endpoints silently behaved differently.&lt;/p&gt;

&lt;p&gt;Some required API keys.&lt;/p&gt;

&lt;p&gt;Others ignored authentication.&lt;/p&gt;

&lt;p&gt;The specification never reflected reality.&lt;/p&gt;

&lt;p&gt;Test generation immediately exposed those inconsistencies.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lesson 5: Schemas Drift Faster Than Teams Realize
&lt;/h2&gt;

&lt;p&gt;One endpoint 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;"userId"&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="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;Another 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;"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="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;Both represented the same resource.&lt;/p&gt;

&lt;p&gt;Both passed manual testing.&lt;/p&gt;

&lt;p&gt;Neither matched the documented contract consistently.&lt;/p&gt;

&lt;p&gt;Generated tests highlighted schema drift that had accumulated over years.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Generator Could Infer
&lt;/h2&gt;

&lt;p&gt;Modern generators are surprisingly capable.&lt;/p&gt;

&lt;p&gt;They can infer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Required request bodies&lt;/li&gt;
&lt;li&gt;Path parameters&lt;/li&gt;
&lt;li&gt;Query parameters&lt;/li&gt;
&lt;li&gt;Status codes&lt;/li&gt;
&lt;li&gt;JSON schema assertions&lt;/li&gt;
&lt;li&gt;Authentication requirements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From a single specification.&lt;/p&gt;

&lt;p&gt;But they cannot infer business rules.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Still Requires Human Input
&lt;/h2&gt;

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

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

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

&lt;/div&gt;



&lt;p&gt;be valid?&lt;/p&gt;

&lt;p&gt;The schema cannot answer that.&lt;/p&gt;

&lt;p&gt;Should creating an order reduce inventory?&lt;/p&gt;

&lt;p&gt;The schema doesn't know.&lt;/p&gt;

&lt;p&gt;Should duplicate email addresses return 409 or 422?&lt;/p&gt;

&lt;p&gt;Only the business can define that.&lt;/p&gt;

&lt;p&gt;Generated tests complement—not replace—human-designed scenarios.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Biggest Surprise
&lt;/h2&gt;

&lt;p&gt;We expected developers to ask:&lt;/p&gt;

&lt;p&gt;"How accurate is the generator?"&lt;/p&gt;

&lt;p&gt;Instead they asked:&lt;/p&gt;

&lt;p&gt;"Why is the generator failing on my specification?"&lt;/p&gt;

&lt;p&gt;In most cases the answer was simple.&lt;/p&gt;

&lt;p&gt;The specification wasn't actually valid.&lt;/p&gt;

&lt;p&gt;Or it was incomplete.&lt;/p&gt;

&lt;p&gt;The tool simply surfaced issues that had always been there.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Generator Became a Spec Validator
&lt;/h2&gt;

&lt;p&gt;One unexpected outcome was that teams started fixing specifications before generating tests.&lt;/p&gt;

&lt;p&gt;The generator effectively became a quality gate.&lt;/p&gt;

&lt;p&gt;Instead of discovering contract issues in production, developers discovered them during generation.&lt;/p&gt;

&lt;p&gt;That feedback loop was much cheaper.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common Specification Problems We Found
&lt;/h2&gt;

&lt;p&gt;Across hundreds of specifications, the same issues appeared repeatedly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Missing required properties&lt;/li&gt;
&lt;li&gt;Incorrect data types&lt;/li&gt;
&lt;li&gt;Invalid examples&lt;/li&gt;
&lt;li&gt;Inconsistent naming&lt;/li&gt;
&lt;li&gt;Missing response schemas&lt;/li&gt;
&lt;li&gt;Undefined authentication&lt;/li&gt;
&lt;li&gt;Duplicate models&lt;/li&gt;
&lt;li&gt;Ambiguous nullable fields&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most of these weren't testing problems.&lt;/p&gt;

&lt;p&gt;They were specification problems.&lt;/p&gt;




&lt;h2&gt;
  
  
  What We Changed in Our Workflow
&lt;/h2&gt;

&lt;p&gt;Today our process 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;Design API

↓

Write OpenAPI Specification

↓

Validate Specification

↓

Generate Tests

↓

Add Business Assertions

↓

Run in CI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice what's missing?&lt;/p&gt;

&lt;p&gt;Manual recreation of test cases.&lt;/p&gt;

&lt;p&gt;The contract drives almost everything.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where Human Testing Still Wins
&lt;/h2&gt;

&lt;p&gt;Automatic generation is fantastic for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Schema validation&lt;/li&gt;
&lt;li&gt;Required fields&lt;/li&gt;
&lt;li&gt;Authentication&lt;/li&gt;
&lt;li&gt;Parameter combinations&lt;/li&gt;
&lt;li&gt;Status codes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Humans still write tests for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Business rules&lt;/li&gt;
&lt;li&gt;Complex workflows&lt;/li&gt;
&lt;li&gt;Performance&lt;/li&gt;
&lt;li&gt;Security&lt;/li&gt;
&lt;li&gt;User journeys&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The two approaches work best together.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Biggest Productivity Gain
&lt;/h2&gt;

&lt;p&gt;The biggest benefit wasn't writing fewer tests.&lt;/p&gt;

&lt;p&gt;It was eliminating duplicated work.&lt;/p&gt;

&lt;p&gt;Previously developers described the API three times:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OpenAPI specification&lt;/li&gt;
&lt;li&gt;Postman collection&lt;/li&gt;
&lt;li&gt;Automated tests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now the specification generates much of the repetitive work automatically.&lt;/p&gt;

&lt;p&gt;Teams spend more time testing behavior instead of recreating contracts.&lt;/p&gt;




&lt;h2&gt;
  
  
  What We'd Recommend to Teams Starting Today
&lt;/h2&gt;

&lt;p&gt;If you're adopting OpenAPI-driven testing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Treat the specification as code.&lt;/li&gt;
&lt;li&gt;Validate it continuously.&lt;/li&gt;
&lt;li&gt;Keep examples realistic.&lt;/li&gt;
&lt;li&gt;Separate nullable from optional fields.&lt;/li&gt;
&lt;li&gt;Add business assertions after generation.&lt;/li&gt;
&lt;li&gt;Run generated tests in every CI pipeline.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The better the specification becomes, the better the generated tests become.&lt;/p&gt;




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

&lt;p&gt;Generating &lt;strong&gt;12,000 API tests&lt;/strong&gt; taught us something unexpected.&lt;/p&gt;

&lt;p&gt;The hardest part wasn't automation.&lt;/p&gt;

&lt;p&gt;It was creating clear, accurate API contracts.&lt;/p&gt;

&lt;p&gt;OpenAPI isn't just documentation.&lt;/p&gt;

&lt;p&gt;Done well, it becomes the foundation for documentation, SDK generation, mock servers, contract validation, and automated testing.&lt;/p&gt;

&lt;p&gt;The quality of your generated tests will almost always reflect the quality of your specification.&lt;/p&gt;

&lt;p&gt;If you're interested in this approach, we have documented more about &lt;strong&gt;&lt;a href="https://totalshiftleft.ai/openapi-test-automation" rel="noopener noreferrer"&gt;OpenAPI test automation&lt;/a&gt;&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;The best API tests don't start with a testing framework.&lt;/p&gt;

&lt;p&gt;They start with a well-written contract.&lt;/p&gt;

</description>
      <category>api</category>
      <category>ai</category>
      <category>testing</category>
      <category>automation</category>
    </item>
    <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>
