<?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: Smeet Gohel</title>
    <description>The latest articles on DEV Community by Smeet Gohel (@smeetgohel).</description>
    <link>https://dev.to/smeetgohel</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%2F3988929%2F860dc4c8-8c5f-4ebb-8dbc-708dbc43de19.png</url>
      <title>DEV Community: Smeet Gohel</title>
      <link>https://dev.to/smeetgohel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/smeetgohel"/>
    <language>en</language>
    <item>
      <title>API Error Codes: A Test Suite Pattern I Stole from Stripe</title>
      <dc:creator>Smeet Gohel</dc:creator>
      <pubDate>Fri, 26 Jun 2026 13:39:06 +0000</pubDate>
      <link>https://dev.to/smeetgohel/api-error-codes-a-test-suite-pattern-i-stole-from-stripe-20ce</link>
      <guid>https://dev.to/smeetgohel/api-error-codes-a-test-suite-pattern-i-stole-from-stripe-20ce</guid>
      <description>&lt;p&gt;&lt;em&gt;Read Stripe's API reference for an hour and you'll notice every endpoint has a complete enumerated list of error codes with example payloads. Then look at your own API.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The contrast is hard to ignore.&lt;/p&gt;

&lt;p&gt;Stripe's API documentation treats errors as first-class citizens. Every endpoint clearly documents not only the happy path but also every expected failure, complete with structured error codes, descriptions, HTTP status codes, and example responses.&lt;/p&gt;

&lt;p&gt;Now compare that to many APIs in production.&lt;/p&gt;

&lt;p&gt;You might find a generic list of HTTP status codes somewhere in the documentation, but business-specific errors are often buried inside controller logic, scattered across wiki pages, or simply undocumented. The test suite isn't much better—there are dozens of happy-path tests, but only a handful of negative scenarios.&lt;/p&gt;

&lt;p&gt;That imbalance creates problems for everyone involved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Developers don't know which errors are expected.&lt;/li&gt;
&lt;li&gt;Frontend teams can't reliably handle failures.&lt;/li&gt;
&lt;li&gt;QA engineers miss important negative cases.&lt;/li&gt;
&lt;li&gt;Refactoring accidentally changes error responses without anyone noticing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A few years ago, I borrowed a simple idea from Stripe's documentation and turned it into a testing strategy.&lt;/p&gt;

&lt;p&gt;Instead of treating error responses as exceptions, we created an &lt;strong&gt;error-code catalog&lt;/strong&gt; and made it the foundation of our negative test suite.&lt;/p&gt;

&lt;p&gt;The result wasn't just better &lt;strong&gt;API error code testing&lt;/strong&gt;—it also improved documentation, simplified maintenance, and made API contracts far more consistent.&lt;/p&gt;

&lt;p&gt;Here's how the pattern works.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Error Responses Are Part of the API Contract
&lt;/h1&gt;

&lt;p&gt;When people think about API testing, they naturally focus on successful responses.&lt;/p&gt;

&lt;p&gt;Typical assertions include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTP 200 OK&lt;/li&gt;
&lt;li&gt;HTTP 201 Created&lt;/li&gt;
&lt;li&gt;Correct JSON payload&lt;/li&gt;
&lt;li&gt;Required fields&lt;/li&gt;
&lt;li&gt;Business calculations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Negative testing often gets much less attention.&lt;/p&gt;

&lt;p&gt;Maybe there are a few tests for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Invalid authentication&lt;/li&gt;
&lt;li&gt;Missing required fields&lt;/li&gt;
&lt;li&gt;Unknown resources&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Beyond that, many APIs rely on manual testing or hope that the framework handles everything correctly.&lt;/p&gt;

&lt;p&gt;The problem is that real users encounter failures just as often as successful requests.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Customer account locked&lt;/li&gt;
&lt;li&gt;Payment declined&lt;/li&gt;
&lt;li&gt;Coupon expired&lt;/li&gt;
&lt;li&gt;Inventory unavailable&lt;/li&gt;
&lt;li&gt;Duplicate registration&lt;/li&gt;
&lt;li&gt;Subscription canceled&lt;/li&gt;
&lt;li&gt;Rate limit exceeded&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren't exceptional scenarios.&lt;/p&gt;

&lt;p&gt;They're expected business outcomes.&lt;/p&gt;

&lt;p&gt;Treating them as first-class API contracts changes how you design both documentation and tests.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Error-Code Catalog as a Test Input
&lt;/h1&gt;

&lt;p&gt;The first step is creating a centralized catalog of every business error the API can intentionally return.&lt;/p&gt;

&lt;p&gt;A simplified example might look like this:&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;errors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;USER_NOT_FOUND&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;httpStatus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;404&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;User not found&lt;/span&gt;

  &lt;span class="na"&gt;EMAIL_ALREADY_EXISTS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;httpStatus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;409&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Email already exists&lt;/span&gt;

  &lt;span class="na"&gt;INVALID_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;httpStatus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;401&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Invalid authentication token&lt;/span&gt;

  &lt;span class="na"&gt;PAYMENT_DECLINED&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;httpStatus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;402&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Payment declined&lt;/span&gt;

  &lt;span class="na"&gt;ORDER_ALREADY_SHIPPED&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;httpStatus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;409&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Order cannot be modified&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This catalog becomes far more than documentation.&lt;/p&gt;

&lt;p&gt;It becomes an executable specification.&lt;/p&gt;

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

&lt;p&gt;&lt;em&gt;"What errors should this endpoint return?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;the answer already exists in one authoritative location.&lt;/p&gt;

&lt;p&gt;Every new business error must be added here before it reaches production.&lt;/p&gt;

&lt;p&gt;That single requirement dramatically improves consistency.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why a Catalog Helps
&lt;/h2&gt;

&lt;p&gt;Without a catalog:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Documentation drifts.&lt;/li&gt;
&lt;li&gt;Tests become incomplete.&lt;/li&gt;
&lt;li&gt;Frontend teams discover errors by accident.&lt;/li&gt;
&lt;li&gt;Reviewers overlook breaking changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With a catalog:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every error is documented.&lt;/li&gt;
&lt;li&gt;Every error becomes testable.&lt;/li&gt;
&lt;li&gt;Every API consumer sees the same contract.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The catalog becomes the foundation for automation.&lt;/p&gt;




&lt;h1&gt;
  
  
  One Test per Error Code, Generated from the Catalog
&lt;/h1&gt;

&lt;p&gt;Once the catalog exists, generating negative tests becomes surprisingly straightforward.&lt;/p&gt;

&lt;p&gt;Rather than manually writing dozens of repetitive tests, a generator simply iterates through every defined error.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errorCode&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;catalog&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;generateNegativeTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;errorCode&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;Each generated test validates four things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The expected HTTP status&lt;/li&gt;
&lt;li&gt;The error code&lt;/li&gt;
&lt;li&gt;The error message&lt;/li&gt;
&lt;li&gt;The response schema&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Consider &lt;code&gt;EMAIL_ALREADY_EXISTS&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The generated scenario might:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a user.&lt;/li&gt;
&lt;li&gt;Attempt to create the same user again.&lt;/li&gt;
&lt;li&gt;Verify the response:
&lt;/li&gt;
&lt;/ol&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;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EMAIL_ALREADY_EXISTS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Email already exists"&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 implementation differs depending on the framework, but the testing philosophy remains the same:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every documented error deserves exactly one corresponding test.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As new error codes are introduced, new tests appear automatically.&lt;/p&gt;

&lt;p&gt;No engineer has to remember to write them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Scales Better
&lt;/h2&gt;

&lt;p&gt;Imagine your API exposes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;150 endpoints&lt;/li&gt;
&lt;li&gt;90 business error codes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Maintaining those manually quickly becomes tedious.&lt;/p&gt;

&lt;p&gt;Generation solves two maintenance problems simultaneously:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Missing tests&lt;/li&gt;
&lt;li&gt;Duplicate effort&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of asking developers to remember every negative case, the catalog guarantees baseline coverage.&lt;/p&gt;

&lt;p&gt;Engineers can then focus on more complex business workflows rather than repetitive validation tests.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Shape Assertion That Prevents Silent Error Drift
&lt;/h1&gt;

&lt;p&gt;One lesson we learned very early was this:&lt;/p&gt;

&lt;p&gt;Checking only the HTTP status is almost useless.&lt;/p&gt;

&lt;p&gt;Imagine an endpoint originally 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;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USER_NOT_FOUND"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"User not found"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"requestId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"abc123"&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;Months later, someone refactors the global exception handler.&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;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"User not found"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The HTTP status is still:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Many tests still pass.&lt;/p&gt;

&lt;p&gt;But every client expecting the original response contract is now broken.&lt;/p&gt;

&lt;p&gt;This is known as &lt;strong&gt;silent error drift&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Nothing appears wrong until consumers start failing.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Solution: Shape Assertions
&lt;/h2&gt;

&lt;p&gt;Every negative test also validates the response structure.&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="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;code&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;String&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;message&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;String&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;requestId&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;String&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;Notice that we're not only validating values.&lt;/p&gt;

&lt;p&gt;We're validating the schema itself.&lt;/p&gt;

&lt;p&gt;That single assertion protects every API consumer from accidental response changes.&lt;/p&gt;




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

&lt;p&gt;Consumers often depend on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Error codes&lt;/li&gt;
&lt;li&gt;Localization keys&lt;/li&gt;
&lt;li&gt;Correlation IDs&lt;/li&gt;
&lt;li&gt;Documentation URLs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Removing any of these fields can become a breaking API change even though the HTTP status remains correct.&lt;/p&gt;

&lt;p&gt;Schema validation catches those problems immediately.&lt;/p&gt;




&lt;h1&gt;
  
  
  Keeping the Catalog in Sync with the Code (Code Generation)
&lt;/h1&gt;

&lt;p&gt;The obvious concern is maintenance.&lt;/p&gt;

&lt;p&gt;If engineers must manually update both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Source code&lt;/li&gt;
&lt;li&gt;Error catalog&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;the catalog eventually becomes outdated.&lt;/p&gt;

&lt;p&gt;The solution is code generation.&lt;/p&gt;

&lt;p&gt;Most applications already define errors centrally.&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 typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;enum&lt;/span&gt; &lt;span class="nx"&gt;ErrorCode&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;USER_NOT_FOUND&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;INVALID_TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;PAYMENT_DECLINED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;EMAIL_ALREADY_EXISTS&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A simple generation step can produce:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API documentation&lt;/li&gt;
&lt;li&gt;OpenAPI components&lt;/li&gt;
&lt;li&gt;Markdown reference tables&lt;/li&gt;
&lt;li&gt;Test inputs&lt;/li&gt;
&lt;li&gt;SDK constants&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All from the same source.&lt;/p&gt;

&lt;p&gt;Now there's only one place where error definitions live.&lt;/p&gt;

&lt;p&gt;Everything else is generated automatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  Benefits of Codegen
&lt;/h2&gt;

&lt;p&gt;This approach creates several advantages:&lt;/p&gt;

&lt;h3&gt;
  
  
  Documentation Never Falls Behind
&lt;/h3&gt;

&lt;p&gt;As soon as a new error appears in code, documentation updates automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generated Tests Stay Current
&lt;/h3&gt;

&lt;p&gt;No manual synchronization required.&lt;/p&gt;

&lt;h3&gt;
  
  
  API Consumers Stay Aligned
&lt;/h3&gt;

&lt;p&gt;Client SDKs can reference the same constants used by the server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code Reviews Become Easier
&lt;/h3&gt;

&lt;p&gt;Adding a new business error becomes highly visible because it affects generated documentation and tests.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Two Error Codes We Deliberately Don't Test (And Why)
&lt;/h1&gt;

&lt;p&gt;Although our negative suite covers nearly every business error, there are two categories we intentionally exclude.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Generic Internal Server Errors
&lt;/h2&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;500 Internal Server Error
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These represent unexpected failures.&lt;/p&gt;

&lt;p&gt;They're not part of normal business behavior.&lt;/p&gt;

&lt;p&gt;Rather than intentionally triggering every possible internal exception, we verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sensitive details aren't exposed&lt;/li&gt;
&lt;li&gt;Generic messages are returned&lt;/li&gt;
&lt;li&gt;Correlation IDs exist&lt;/li&gt;
&lt;li&gt;Logging occurs correctly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Testing every possible server failure adds little value.&lt;/p&gt;

&lt;p&gt;Testing the response contract provides much greater return.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Infrastructure Failures
&lt;/h2&gt;

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

&lt;ul&gt;
&lt;li&gt;Database unavailable&lt;/li&gt;
&lt;li&gt;Network partition&lt;/li&gt;
&lt;li&gt;DNS outage&lt;/li&gt;
&lt;li&gt;Message broker failure&lt;/li&gt;
&lt;li&gt;Cloud storage unavailable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These failures belong to resilience testing rather than standard API automation.&lt;/p&gt;

&lt;p&gt;They are better validated using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Chaos engineering&lt;/li&gt;
&lt;li&gt;Fault injection&lt;/li&gt;
&lt;li&gt;Infrastructure testing&lt;/li&gt;
&lt;li&gt;Disaster recovery exercises&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mixing infrastructure scenarios into routine &lt;strong&gt;API negative tests&lt;/strong&gt; usually creates unstable pipelines.&lt;/p&gt;

&lt;p&gt;Keeping them separate results in cleaner and more reliable automation.&lt;/p&gt;




&lt;h1&gt;
  
  
  Additional Benefits We Didn't Expect
&lt;/h1&gt;

&lt;p&gt;Once the catalog became part of our development process, several unexpected improvements appeared.&lt;/p&gt;

&lt;h3&gt;
  
  
  More Consistent APIs
&lt;/h3&gt;

&lt;p&gt;Every endpoint used the same response format.&lt;/p&gt;

&lt;h3&gt;
  
  
  Better Frontend Development
&lt;/h3&gt;

&lt;p&gt;Frontend teams no longer guessed which errors could occur.&lt;/p&gt;

&lt;h3&gt;
  
  
  Simpler Documentation
&lt;/h3&gt;

&lt;p&gt;Error references stayed synchronized automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cleaner Pull Requests
&lt;/h3&gt;

&lt;p&gt;Adding a new error became an explicit design decision rather than an implementation detail.&lt;/p&gt;

&lt;h3&gt;
  
  
  Better QA Coverage
&lt;/h3&gt;

&lt;p&gt;Negative scenarios became just as visible as successful ones.&lt;/p&gt;




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

&lt;p&gt;Most engineering teams invest heavily in testing successful requests while treating failures as secondary concerns.&lt;/p&gt;

&lt;p&gt;Stripe demonstrates a different philosophy.&lt;/p&gt;

&lt;p&gt;Errors are documented, standardized, and treated as an integral part of the public API contract.&lt;/p&gt;

&lt;p&gt;Building an error-code catalog allowed us to adopt that same mindset.&lt;/p&gt;

&lt;p&gt;Instead of manually maintaining dozens of repetitive &lt;strong&gt;error response testing&lt;/strong&gt; scenarios, we generated them from a single source of truth.&lt;/p&gt;

&lt;p&gt;Combined with response schema validation and code generation, the approach dramatically reduced maintenance while increasing confidence that every documented failure behaved exactly as expected.&lt;/p&gt;

&lt;p&gt;If your API already has a growing collection of business errors, consider creating a centralized catalog before the list becomes unmanageable.&lt;/p&gt;

&lt;p&gt;The investment is relatively small, but the payoff in documentation quality, test coverage, and long-term maintainability is substantial.&lt;/p&gt;

&lt;p&gt;If you'd like to explore how automated API testing can support this approach, you can &lt;strong&gt;&lt;a href="https://totalshiftleft.ai/free-trial-signup" rel="noopener noreferrer"&gt;spin up a free trial to try this catalog pattern&lt;/a&gt;&lt;/strong&gt; and see how generated negative tests, schema validation, and API contracts work together in practice.&lt;/p&gt;




</description>
      <category>ai</category>
      <category>testing</category>
      <category>api</category>
      <category>opensource</category>
    </item>
    <item>
      <title>5 OpenAPI Mistakes That Break Every Test Generator I've Tried</title>
      <dc:creator>Smeet Gohel</dc:creator>
      <pubDate>Thu, 25 Jun 2026 13:28:37 +0000</pubDate>
      <link>https://dev.to/smeetgohel/5-openapi-mistakes-that-break-every-test-generator-ive-tried-f2b</link>
      <guid>https://dev.to/smeetgohel/5-openapi-mistakes-that-break-every-test-generator-ive-tried-f2b</guid>
      <description>&lt;p&gt;&lt;em&gt;I fed the same OpenAPI spec into four different test generators last weekend. All four failed on the same five things.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That wasn't supposed to happen.&lt;/p&gt;

&lt;p&gt;The generators came from different vendors. They used different parsing engines, different test-generation strategies, and different AI capabilities. Some focused on contract testing. Others emphasized intelligent test creation and edge-case discovery.&lt;/p&gt;

&lt;p&gt;Yet all four stumbled on the exact same sections of the specification.&lt;/p&gt;

&lt;p&gt;The surprising part wasn't that the tools failed.&lt;/p&gt;

&lt;p&gt;The surprising part was that every failure could be traced back to issues inside the OpenAPI document itself.&lt;/p&gt;

&lt;p&gt;Over the years, I've learned that most API test generation problems aren't actually tool problems. They're specification quality problems.&lt;/p&gt;

&lt;p&gt;A clean OpenAPI specification can generate hundreds of useful tests automatically.&lt;/p&gt;

&lt;p&gt;A flawed specification can confuse even the smartest generator.&lt;/p&gt;

&lt;p&gt;If you're using OpenAPI to generate tests, SDKs, mocks, documentation, or validation suites, these are the five most common &lt;strong&gt;OpenAPI mistakes&lt;/strong&gt; I've seen repeatedly—and why they cause so much trouble.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Missing Required Fields on Nested &lt;code&gt;$ref&lt;/code&gt; Objects
&lt;/h2&gt;

&lt;p&gt;This is easily one of the most overlooked issues in OpenAPI specifications.&lt;/p&gt;

&lt;p&gt;Consider the following schema:&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;Customer&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;object&lt;/span&gt;
  &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;id&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;integer&lt;/span&gt;
    &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/Address'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The referenced schema looks like:&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;Address&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;object&lt;/span&gt;
  &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;street&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;city&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;Looks harmless.&lt;/p&gt;

&lt;p&gt;Now imagine the actual API requires:&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;"street"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Main Street"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"city"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"London"&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 the schema never specifies:&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="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;street&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;city&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generator now assumes both fields are optional.&lt;/p&gt;




&lt;h3&gt;
  
  
  Why Test Generators Struggle
&lt;/h3&gt;

&lt;p&gt;When generating positive and negative test cases, the tool needs to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which fields must exist&lt;/li&gt;
&lt;li&gt;Which fields may be omitted&lt;/li&gt;
&lt;li&gt;Which omissions should trigger validation failures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without required declarations, generators often create:&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;"address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and treat the payload as valid.&lt;/p&gt;

&lt;p&gt;The resulting tests provide little value because they don't reflect real application behavior.&lt;/p&gt;




&lt;h3&gt;
  
  
  Best Practice
&lt;/h3&gt;

&lt;p&gt;Always define required fields explicitly at every schema level.&lt;/p&gt;

&lt;p&gt;Even when schemas are reused via &lt;code&gt;$ref&lt;/code&gt;, each referenced object should clearly identify mandatory properties.&lt;/p&gt;

&lt;p&gt;Never assume the generator will infer business intent.&lt;/p&gt;

&lt;p&gt;Schemas should be unambiguous.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. &lt;code&gt;anyOf&lt;/code&gt; With No Discriminator (The One That Hurts Most)
&lt;/h2&gt;

&lt;p&gt;If I had to choose the single most problematic specification pattern, this would be it.&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 yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Pet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;anyOf&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/Cat'&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/Dog'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells the generator:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"The payload may match Cat or Dog."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Sounds reasonable.&lt;/p&gt;

&lt;p&gt;The problem is that the generator has no reliable way to determine which schema should be used for a specific test case.&lt;/p&gt;




&lt;h3&gt;
  
  
  Why It Breaks Generation
&lt;/h3&gt;

&lt;p&gt;Imagine:&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;Cat&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="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;Dog&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="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;Now the payload:&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;"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;"Buddy"&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;matches both schemas.&lt;/p&gt;

&lt;p&gt;The generator faces several questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is this a Cat?&lt;/li&gt;
&lt;li&gt;Is this a Dog?&lt;/li&gt;
&lt;li&gt;Should both test paths be created?&lt;/li&gt;
&lt;li&gt;Which validation rules apply?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Different tools make different assumptions.&lt;/p&gt;

&lt;p&gt;Most assumptions are wrong.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Fix
&lt;/h3&gt;

&lt;p&gt;Use discriminators whenever possible.&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 yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Pet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;oneOf&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/Cat'&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/Dog'&lt;/span&gt;
  &lt;span class="na"&gt;discriminator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;propertyName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;petType&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now responses become:&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;"petType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Dog"&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;"Buddy"&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 ambiguity disappears.&lt;/p&gt;

&lt;p&gt;Test generation becomes deterministic.&lt;/p&gt;

&lt;p&gt;This single change often improves generated coverage dramatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Untyped Query Parameters (String vs Integer Ambiguity)
&lt;/h2&gt;

&lt;p&gt;Another surprisingly common issue involves query parameters.&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 yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;page&lt;/span&gt;
    &lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;query&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks simple.&lt;/p&gt;

&lt;p&gt;Unfortunately, the schema never specifies the data type.&lt;/p&gt;




&lt;h3&gt;
  
  
  Why This Creates Problems
&lt;/h3&gt;

&lt;p&gt;A generator needs type information to create meaningful tests.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;?page=1
&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;?page=abc
&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;?page=true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without explicit typing, every option becomes theoretically valid.&lt;/p&gt;

&lt;p&gt;Different generators handle this differently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Some assume strings&lt;/li&gt;
&lt;li&gt;Some guess based on naming&lt;/li&gt;
&lt;li&gt;Some generate everything&lt;/li&gt;
&lt;li&gt;Some skip validation entirely&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these approaches are ideal.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Hidden Impact
&lt;/h3&gt;

&lt;p&gt;This issue affects more than positive tests.&lt;/p&gt;

&lt;p&gt;It also impacts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Boundary testing&lt;/li&gt;
&lt;li&gt;Negative testing&lt;/li&gt;
&lt;li&gt;Fuzz testing&lt;/li&gt;
&lt;li&gt;Schema validation&lt;/li&gt;
&lt;/ul&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;schema&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;integer&lt;/span&gt;
  &lt;span class="na"&gt;minimum&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;maximum&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;enables generators to automatically create:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;0&lt;/li&gt;
&lt;li&gt;1&lt;/li&gt;
&lt;li&gt;100&lt;/li&gt;
&lt;li&gt;101&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without typing, those valuable edge cases disappear.&lt;/p&gt;




&lt;h3&gt;
  
  
  Best Practice
&lt;/h3&gt;

&lt;p&gt;Always define parameter schemas completely.&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 yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;page&lt;/span&gt;
    &lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;query&lt;/span&gt;
    &lt;span class="na"&gt;schema&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;integer&lt;/span&gt;
      &lt;span class="na"&gt;minimum&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The more constraints provided, the better the generated tests become.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Example Values That Don't Match the Schema
&lt;/h2&gt;

&lt;p&gt;This problem creates some of the most confusing failures.&lt;/p&gt;

&lt;p&gt;Imagine:&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;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;integer&lt;/span&gt;
&lt;span class="na"&gt;example&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;123"&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 yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;boolean&lt;/span&gt;
&lt;span class="na"&gt;example&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The examples look correct at first glance.&lt;/p&gt;

&lt;p&gt;But they violate the schema definition.&lt;/p&gt;




&lt;h3&gt;
  
  
  Why This Matters
&lt;/h3&gt;

&lt;p&gt;Most generators rely heavily on example values.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Request payloads&lt;/li&gt;
&lt;li&gt;Mock data&lt;/li&gt;
&lt;li&gt;Positive test cases&lt;/li&gt;
&lt;li&gt;Sample assertions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When examples contradict schemas, the generator receives conflicting instructions.&lt;/p&gt;

&lt;p&gt;The schema says:&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;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;integer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The example says:&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="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;123"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which one should the tool trust?&lt;/p&gt;




&lt;h3&gt;
  
  
  What Usually Happens
&lt;/h3&gt;

&lt;p&gt;Different generators respond differently:&lt;/p&gt;

&lt;h4&gt;
  
  
  Generator A
&lt;/h4&gt;

&lt;p&gt;Uses schema definition.&lt;/p&gt;

&lt;h4&gt;
  
  
  Generator B
&lt;/h4&gt;

&lt;p&gt;Uses example value.&lt;/p&gt;

&lt;h4&gt;
  
  
  Generator C
&lt;/h4&gt;

&lt;p&gt;Attempts to coerce data types.&lt;/p&gt;

&lt;h4&gt;
  
  
  Generator D
&lt;/h4&gt;

&lt;p&gt;Fails validation completely.&lt;/p&gt;

&lt;p&gt;The result is inconsistent behavior across platforms.&lt;/p&gt;




&lt;h3&gt;
  
  
  Best Practice
&lt;/h3&gt;

&lt;p&gt;Treat examples as executable documentation.&lt;/p&gt;

&lt;p&gt;Every example should validate successfully against its schema.&lt;/p&gt;

&lt;p&gt;A useful review process is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validate schema&lt;/li&gt;
&lt;li&gt;Validate examples&lt;/li&gt;
&lt;li&gt;Validate generated payloads&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All three should agree.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Auth Scheme Defined Globally but Overridden Per-Path
&lt;/h2&gt;

&lt;p&gt;Authentication definitions often create subtle specification issues.&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 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;This applies globally.&lt;/p&gt;

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

&lt;p&gt;Later, a specific endpoint introduces:&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;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;/public-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This explicitly removes authentication.&lt;/p&gt;

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

&lt;p&gt;The trouble begins when specifications contain dozens or hundreds of endpoints.&lt;/p&gt;




&lt;h3&gt;
  
  
  Why Test Generators Fail Here
&lt;/h3&gt;

&lt;p&gt;Generators must determine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which endpoints require authentication&lt;/li&gt;
&lt;li&gt;Which endpoints are public&lt;/li&gt;
&lt;li&gt;Which credentials to attach&lt;/li&gt;
&lt;li&gt;Which negative auth scenarios to generate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Conflicting security definitions make this surprisingly difficult.&lt;/p&gt;

&lt;p&gt;I've seen generators:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add tokens to public endpoints&lt;/li&gt;
&lt;li&gt;Skip tokens on protected endpoints&lt;/li&gt;
&lt;li&gt;Generate invalid authentication tests&lt;/li&gt;
&lt;li&gt;Ignore overrides completely&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  The Real Problem
&lt;/h3&gt;

&lt;p&gt;Many teams inherit APIs over several years.&lt;/p&gt;

&lt;p&gt;Security definitions evolve.&lt;/p&gt;

&lt;p&gt;Documentation evolves.&lt;/p&gt;

&lt;p&gt;Endpoints move between versions.&lt;/p&gt;

&lt;p&gt;Eventually, the specification contains multiple overlapping security patterns.&lt;/p&gt;

&lt;p&gt;Humans can usually understand the intent.&lt;/p&gt;

&lt;p&gt;Generators often cannot.&lt;/p&gt;




&lt;h3&gt;
  
  
  Best Practice
&lt;/h3&gt;

&lt;p&gt;Maintain a clear authentication strategy.&lt;/p&gt;

&lt;p&gt;Review:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Global security settings&lt;/li&gt;
&lt;li&gt;Path-level overrides&lt;/li&gt;
&lt;li&gt;Operation-level overrides&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whenever possible, minimize exceptions.&lt;/p&gt;

&lt;p&gt;The fewer special cases you create, the easier automated tooling becomes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why These Mistakes Keep Appearing
&lt;/h2&gt;

&lt;p&gt;What's interesting is that none of these issues are technically invalid OpenAPI.&lt;/p&gt;

&lt;p&gt;Many specifications containing these patterns will still:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Render documentation correctly&lt;/li&gt;
&lt;li&gt;Generate SDKs successfully&lt;/li&gt;
&lt;li&gt;Pass schema validation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The problems emerge only when advanced automation enters the picture.&lt;/p&gt;

&lt;p&gt;Test generation requires much higher precision than documentation generation.&lt;/p&gt;

&lt;p&gt;Documentation can tolerate ambiguity.&lt;/p&gt;

&lt;p&gt;Automated testing cannot.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Simple Validation Checklist
&lt;/h2&gt;

&lt;p&gt;Before feeding an OpenAPI document into any generator, review the following:&lt;/p&gt;

&lt;h3&gt;
  
  
  Schema Definitions
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Are required fields defined?&lt;/li&gt;
&lt;li&gt;Are nested references complete?&lt;/li&gt;
&lt;li&gt;Are enums constrained properly?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Polymorphism
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Does every &lt;code&gt;anyOf&lt;/code&gt; or &lt;code&gt;oneOf&lt;/code&gt; include a discriminator?&lt;/li&gt;
&lt;li&gt;Can payload types be identified deterministically?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Parameters
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Are query parameters typed?&lt;/li&gt;
&lt;li&gt;Are constraints defined?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Examples
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Do examples validate against schemas?&lt;/li&gt;
&lt;li&gt;Are example payloads realistic?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Authentication
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Are security rules consistent?&lt;/li&gt;
&lt;li&gt;Are overrides intentional?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This five-minute review can save hours of debugging later.&lt;/p&gt;




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

&lt;p&gt;After testing multiple generators against the same API specification, one conclusion became obvious:&lt;/p&gt;

&lt;p&gt;Most test-generation failures begin long before the generator runs.&lt;/p&gt;

&lt;p&gt;They begin when the specification is written.&lt;/p&gt;

&lt;p&gt;The better your OpenAPI document, the better your generated tests, mocks, SDKs, documentation, and validation suites become.&lt;/p&gt;

&lt;p&gt;Tools continue to improve every year.&lt;/p&gt;

&lt;p&gt;AI-assisted generation is becoming increasingly capable.&lt;/p&gt;

&lt;p&gt;Yet even the most advanced platform struggles when the specification contains ambiguity, conflicting definitions, or incomplete schema information.&lt;/p&gt;

&lt;p&gt;If you're planning to generate automated API tests from your OpenAPI specification, spending time on specification quality is often the highest-return investment you can make.&lt;/p&gt;

&lt;p&gt;For a deeper walkthrough on &lt;strong&gt;how to generate API tests from OpenAPI without these traps&lt;/strong&gt;, visit:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://totalshiftleft.ai/blog/how-to-generate-api-tests-from-openapi" rel="noopener noreferrer"&gt;https://totalshiftleft.ai/blog/how-to-generate-api-tests-from-openapi&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A clean specification doesn't just improve documentation.&lt;/p&gt;

&lt;p&gt;It becomes the foundation for every automation layer built on top of it.&lt;/p&gt;

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