<?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: Goqa Ai</title>
    <description>The latest articles on DEV Community by Goqa Ai (@goqa_ai_c97f712237a7383a5).</description>
    <link>https://dev.to/goqa_ai_c97f712237a7383a5</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%2F3993447%2F0a5376de-79bb-471d-80d8-c7335544ba5a.png</url>
      <title>DEV Community: Goqa Ai</title>
      <link>https://dev.to/goqa_ai_c97f712237a7383a5</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/goqa_ai_c97f712237a7383a5"/>
    <language>en</language>
    <item>
      <title>Writing acceptance criteria a machine can test</title>
      <dc:creator>Goqa Ai</dc:creator>
      <pubDate>Fri, 16 Oct 2026 19:26:28 +0000</pubDate>
      <link>https://dev.to/goqa_ai_c97f712237a7383a5/writing-acceptance-criteria-a-machine-can-test-2jmd</link>
      <guid>https://dev.to/goqa_ai_c97f712237a7383a5/writing-acceptance-criteria-a-machine-can-test-2jmd</guid>
      <description>&lt;p&gt;Most acceptance criteria are written for humans who already share context. That is why they cannot be turned into tests without a conversation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The failure mode
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The user can reset their password.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is not testable. It does not say what happens to an unknown email, whether the reset link expires, whether an old link works after a second request, or whether the response differs for a registered and unregistered address — which is an account-enumeration decision, not a UX one.&lt;/p&gt;

&lt;p&gt;A developer reads it and picks answers. A tester reads it and picks different answers. The bug report is written months later.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shape that works
&lt;/h2&gt;

&lt;p&gt;Given/When/Then is not ceremony. It forces three things into the open: the starting state, the trigger, and the observable result.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gherkin"&gt;&lt;code&gt;&lt;span class="kn"&gt;Scenario&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; reset link expires after one hour
  &lt;span class="nf"&gt;Given &lt;/span&gt;a password reset was requested 61 minutes ago
  &lt;span class="nf"&gt;When &lt;/span&gt;the user opens the reset link
  &lt;span class="nf"&gt;Then &lt;/span&gt;the page explains the link has expired
  &lt;span class="nf"&gt;And &lt;/span&gt;offers to send a new one
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every ambiguity above is now a decision somebody made on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four questions that turn a vague criterion into a testable one
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;What is true before?&lt;/strong&gt; Not 'the user is on the page' — what state is the data in?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What exactly triggers it?&lt;/strong&gt; A click, a timeout, a webhook, a cron?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What can an outsider observe?&lt;/strong&gt; If the only effect is internal, the test has nothing to assert.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What is the nearest failure?&lt;/strong&gt; Every scenario should have a sibling that fails.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That fourth one catches the most. Teams write the happy path and stop, because the happy path is what the ticket described.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then generate
&lt;/h2&gt;

&lt;p&gt;Once criteria are in this shape, generating the suite is mechanical — which is the point. GoQA takes criteria in exactly this form and returns BDD cases you can export to Gherkin, Playwright, or Cypress. But the value is upstream: criteria specific enough to generate from are criteria specific enough to build from.&lt;/p&gt;

&lt;p&gt;If you take one thing: write the sibling that fails.&lt;/p&gt;

</description>
      <category>automation</category>
      <category>softwaredevelopment</category>
      <category>testing</category>
    </item>
    <item>
      <title>The flaky test that was a tenant-isolation bug</title>
      <dc:creator>Goqa Ai</dc:creator>
      <pubDate>Fri, 16 Oct 2026 19:26:20 +0000</pubDate>
      <link>https://dev.to/goqa_ai_c97f712237a7383a5/the-flaky-test-that-was-a-tenant-isolation-bug-1g4a</link>
      <guid>https://dev.to/goqa_ai_c97f712237a7383a5/the-flaky-test-that-was-a-tenant-isolation-bug-1g4a</guid>
      <description>&lt;p&gt;A database test in our suite failed about one run in eight. Here is the full path from symptom to cause, including the three hypotheses that were wrong, because the wrong ones are the useful part.&lt;/p&gt;

&lt;h2&gt;
  
  
  The symptom
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✖ a pending approval is returned with its platform and action type
  AssertionError: 0 !== 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And sometimes, instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  AssertionError: '—' !== 'gmail'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same test, two different failures. That should have been the first clue — one bug producing two shapes usually means the failure is upstream of both assertions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrong hypothesis 1: test contention
&lt;/h2&gt;

&lt;p&gt;The obvious read: something else was hammering the database. We had a dashboard running against the same Postgres container.&lt;/p&gt;

&lt;p&gt;Evidence killed it. The tests use a different database, connections were 7 of 100, and query round trips were 0.07s. Three consecutive runs passed with the dashboard up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrong hypothesis 2: a CREATE DATABASE race
&lt;/h2&gt;

&lt;p&gt;Each test file drops and recreates its database in &lt;code&gt;before&lt;/code&gt;. Fourteen files run in parallel and Postgres serialises &lt;code&gt;CREATE DATABASE&lt;/code&gt;. Plausible.&lt;/p&gt;

&lt;p&gt;Also wrong — each suite already uses its own database name, so they were never competing for the same one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrong hypothesis 3: test ordering
&lt;/h2&gt;

&lt;p&gt;Maybe a later test decided the approval before the read ran. But the tests are sequential — no concurrency flag — and the seed happens in a &lt;code&gt;before&lt;/code&gt; hook.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual cause
&lt;/h2&gt;

&lt;p&gt;Our repository wraps every tenant-scoped read like this:&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;await&lt;/span&gt; &lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;begin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;select set_config('app.tenant_id', $1, true)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tenantId&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;work&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;commit&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 connection is a single long-lived psql session, not a pool. So when two of these overlap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;begin&lt;/span&gt;          &lt;span class="c1"&gt;-- opens A's transaction&lt;/span&gt;
&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;begin&lt;/span&gt;          &lt;span class="c1"&gt;-- no-op, Postgres warns, B is now inside A's transaction&lt;/span&gt;
&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;commit&lt;/span&gt;         &lt;span class="c1"&gt;-- ends the transaction B is still using&lt;/span&gt;
&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;     &lt;span class="c1"&gt;-- runs with no transaction at all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last line is the bug. &lt;code&gt;set_config(..., true)&lt;/code&gt; is &lt;strong&gt;transaction-local&lt;/strong&gt; — which is exactly what you want, because it stops a pooled connection leaking one tenant's context into another request. But when the transaction ends underneath a caller, the setting goes with it. Every row-level security policy then sees an empty tenant and matches nothing.&lt;/p&gt;

&lt;p&gt;Hence &lt;code&gt;0 !== 1&lt;/code&gt;. And when the outer read landed inside a valid context while the joined read did not, &lt;code&gt;'—' !== 'gmail'&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is not a flaky test
&lt;/h2&gt;

&lt;p&gt;A retry would have made it green. It would also have shipped a path where one caller's query can execute under another caller's tenant context — which is the isolation boundary the whole system rests on.&lt;/p&gt;

&lt;p&gt;The test was not unreliable. It was intermittently reporting a real defect.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;Serialise callers per connection, so a transaction cannot be opened inside another:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;WeakMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SqlConnection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;predecessor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;queues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;release&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&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;turn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;release&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;queues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;turn&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;predecessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;runTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;work&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;release&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;Three details that matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The queue slot is claimed &lt;strong&gt;before&lt;/strong&gt; the first &lt;code&gt;await&lt;/code&gt;, or two callers entering together read the same predecessor and run as one.&lt;/li&gt;
&lt;li&gt;The predecessor's rejection is swallowed. A failed request handed the connection back; inheriting its error would fail an unrelated caller.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;release()&lt;/code&gt; is in a &lt;code&gt;finally&lt;/code&gt;. An unreleased slot stalls every caller behind it — a deadlock is a worse outcome than the bug.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I would do differently
&lt;/h2&gt;

&lt;p&gt;Reproduce first. I asserted 'test contention' twice before running a loop that would have told me, and both times the story was plausible enough that I nearly stopped there.&lt;/p&gt;

&lt;p&gt;The loop that found it is six lines, and it asserts the run completed — because a half-killed test run reports as 'clean' and looks exactly like a pass.&lt;/p&gt;

</description>
      <category>database</category>
      <category>debugging</category>
      <category>postgres</category>
      <category>testing</category>
    </item>
    <item>
      <title>Testing time without waiting for it</title>
      <dc:creator>Goqa Ai</dc:creator>
      <pubDate>Fri, 16 Oct 2026 19:26:10 +0000</pubDate>
      <link>https://dev.to/goqa_ai_c97f712237a7383a5/testing-time-without-waiting-for-it-179m</link>
      <guid>https://dev.to/goqa_ai_c97f712237a7383a5/testing-time-without-waiting-for-it-179m</guid>
      <description>&lt;p&gt;Anything with an expiry has a first failure that arrives weeks after the deploy. Password reset links, sessions, trial periods, cache entries, retention jobs, token refresh. All of them are testable in milliseconds, and almost none of them are tested at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why these go untested
&lt;/h2&gt;

&lt;p&gt;The honest reason is that the obvious test is unbearable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;reset link expires after an hour&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;requestReset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ...no&lt;/span&gt;
  &lt;span class="nf"&gt;expect&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;useLink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;link&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;expired&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So it gets skipped, and the expiry logic ships unverified. Which is how you get links that never expire, or expire immediately, or expire correctly but display the wrong message.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inject the clock
&lt;/h2&gt;

&lt;p&gt;The fix is to stop reading the current time directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Before — untestable&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isExpired&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;issuedAt&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="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;issuedAt&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;ONE_HOUR&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// After — testable, and no less readable&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isExpired&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;issuedAt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;now&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="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;issuedAt&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;ONE_HOUR&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the test is instant:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;reset link expires after an hour&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;issued&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;_000_000&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="nf"&gt;isExpired&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;issued&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;issued&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;59&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;MINUTE&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;false&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="nf"&gt;isExpired&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;issued&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;issued&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;61&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;MINUTE&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;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The boundaries that matter
&lt;/h2&gt;

&lt;p&gt;Once time is a parameter, test the edges rather than the middle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Exactly at the boundary.&lt;/strong&gt; Is 3600000ms expired or valid? Pick one deliberately; &lt;code&gt;&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;gt;=&lt;/code&gt; are a real decision.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One unit either side.&lt;/strong&gt; Catches off-by-one in unit conversion, which is the most common bug in this code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Negative elapsed time.&lt;/strong&gt; Clock skew and daylight-saving transitions both produce it. What does your code do when &lt;code&gt;now&lt;/code&gt; is &lt;em&gt;before&lt;/em&gt; &lt;code&gt;issuedAt&lt;/code&gt;? Usually something wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Far future.&lt;/strong&gt; A token issued and checked a year later should be expired, not overflow.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The trap: only injecting it halfway
&lt;/h2&gt;

&lt;p&gt;The common failure is injecting the clock into the check but not the &lt;em&gt;creation&lt;/em&gt;. Then your test controls when the link is validated but not when it was issued, and you are back to real time for half the calculation. Both ends need to be parameters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this generalises
&lt;/h2&gt;

&lt;p&gt;Any non-deterministic input is the same problem: randomness, UUIDs, the filesystem, the network. If a function reads the world directly, it can only be tested in the world. Pass the world in.&lt;/p&gt;

&lt;p&gt;That is the whole of dependency injection, minus the framework.&lt;/p&gt;




&lt;p&gt;If you want the expiry cases enumerated for you, GoQA generates BDD suites from acceptance criteria — including boundary and negative scenarios, which are the ones that get skipped. No sign-up for the demo.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>programming</category>
      <category>softwaredevelopment</category>
      <category>testing</category>
    </item>
    <item>
      <title>Starting my journey in public: fixing test reliability</title>
      <dc:creator>Goqa Ai</dc:creator>
      <pubDate>Mon, 22 Jun 2026 16:09:59 +0000</pubDate>
      <link>https://dev.to/goqa_ai_c97f712237a7383a5/starting-my-journey-in-public-fixing-test-reliability-183c</link>
      <guid>https://dev.to/goqa_ai_c97f712237a7383a5/starting-my-journey-in-public-fixing-test-reliability-183c</guid>
      <description>&lt;p&gt;Hello everyone, &lt;/p&gt;

&lt;p&gt;I’ve spent years watching teams struggle with something that should be simple: trusting their tests.&lt;/p&gt;

&lt;p&gt;On paper, everything looks fine.&lt;/p&gt;

&lt;p&gt;The test suite is large. Coverage is high. CI is green.&lt;/p&gt;

&lt;p&gt;But in reality?&lt;/p&gt;

&lt;p&gt;Pipelines fail for no clear reason.&lt;/p&gt;

&lt;p&gt;Tests break with small UI changes.&lt;/p&gt;

&lt;p&gt;And teams hesitate when it’s time to deploy.&lt;/p&gt;

&lt;p&gt;Not because they didn’t write enough tests but because maintaining them became the real problem.&lt;/p&gt;

&lt;p&gt;This is something my brother and I have spent a lot of time thinking through and working on together.&lt;/p&gt;

&lt;p&gt;That gap stuck with us.&lt;/p&gt;

&lt;p&gt;So we decided to build something about it.&lt;/p&gt;

&lt;p&gt;I’m a co-founder of GoQA AI, an AI-powered quality engineering platform focused on one thing: making tests reliable again.&lt;/p&gt;

&lt;p&gt;We’re building a system that:&lt;br&gt;
• Generates tests from user stories&lt;br&gt;
• Runs them across web and APIs&lt;br&gt;
• Adapts when the product changes (without constant rewrites)&lt;/p&gt;

&lt;p&gt;No hype. No “AI replaces engineers” narrative.&lt;br&gt;
Just solving a problem most teams quietly deal with: test maintenance.&lt;/p&gt;

&lt;p&gt;We’ve started with web and API testing.&lt;br&gt;
"Mobile is next"&lt;/p&gt;

&lt;p&gt;Over the next few weeks, I’ll be sharing what we’re learning as we build:&lt;br&gt;
→ How self-healing actually works&lt;br&gt;
→ Practical ways to reduce flaky tests&lt;br&gt;
→ Where AI helps and where it doesn’t&lt;br&gt;
→ What building this in public really looks like&lt;/p&gt;

&lt;p&gt;If your team has ever questioned whether failing tests actually mean something…&lt;br&gt;
You’re not alone. &lt;br&gt;
And it’s a problem worth fixing.&lt;/p&gt;

&lt;p&gt;Curious to hear from others here, how do you deal with flaky tests today?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>buildinpublic</category>
      <category>startup</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
