<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Testing on WebDevStation</title>
    <link>https://webdevstation.com/categories/testing/</link>
    <description>4 articles in the Testing category — tutorials, code examples and notes from building real systems, newest first.</description>
    <generator>Hugo</generator>
    <language>en</language>
    <managingEditor>Alex</managingEditor>
    <webMaster>Alex</webMaster>
    <copyright>© 2026 WebDevStation</copyright>
    <lastBuildDate>Tue, 01 Sep 2026 09:20:00 +0200</lastBuildDate>
    <atom:link href="https://webdevstation.com/categories/testing/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>How to Test Go Code That Calls an LLM</title>
      <link>https://webdevstation.com/posts/testing-go-code-that-calls-an-llm/</link>
      <pubDate>Tue, 01 Sep 2026 09:20:00 +0200</pubDate>
      <author>Alex</author>
      <guid isPermaLink="true">https://webdevstation.com/posts/testing-go-code-that-calls-an-llm/</guid>
      <description>Non-determinism does not have to mean untestable. Wrapping the model behind an interface, faking it in unit tests, golden files for prompt assembly, and the small…</description>
      <content:encoded><![CDATA[<p>The first question everyone asks about a feature backed by a language model is &ldquo;how do you even test that?&rdquo; — usually with a shrug, as though non-determinism were a get-out-of-jail card for the whole test suite. It is not. Almost all of the code you write around a model is perfectly deterministic, and the small part that is not can be pinned down with a different kind of test. Here is how I split it.</p>
<h2 id="three-things-tested-three-ways">Three Things, Tested Three Ways</h2>
<p>The confusion comes from treating &ldquo;the LLM feature&rdquo; as one indivisible thing. It is three:</p>
<table>
	<thead>
			<tr>
					<th>What</th>
					<th>Deterministic?</th>
					<th>How to test it</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Prompt assembly, parsing, tool handlers, the loop</td>
					<td>Yes, completely</td>
					<td>Ordinary unit tests</td>
			</tr>
			<tr>
					<td>Wiring to the real API — auth, streaming, errors</td>
					<td>Yes enough</td>
					<td>A few integration tests, run on demand</td>
			</tr>
			<tr>
					<td>Output quality — is the answer any good?</td>
					<td>No</td>
					<td>Evals, scored not asserted</td>
			</tr>
	</tbody>
</table>
<p>The first row is 90% of your code and needs no model at all. Get that boundary right and the rest is manageable.</p>
<h2 id="put-an-interface-in-front-of-the-model">Put an Interface in Front of the Model</h2>
<p>Do not scatter <code>client.Messages.New</code> through your handlers. Define the narrowest interface your code actually needs and depend on that:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// Completer turns a prompt into text. It is deliberately narrow — narrower</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// than the SDK — so tests can implement it in five lines.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Completer</span> <span style="color:#66d9ef">interface</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Complete</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">req</span> <span style="color:#a6e22e">Request</span>) (<span style="color:#a6e22e">Response</span>, <span style="color:#66d9ef">error</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Request</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">System</span>   <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Messages</span> []<span style="color:#a6e22e">Message</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Tools</span>    []<span style="color:#a6e22e">Tool</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Response</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Text</span>       <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">ToolCalls</span>  []<span style="color:#a6e22e">ToolCall</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">StopReason</span> <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Usage</span>      <span style="color:#a6e22e">Usage</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>This is just the dependency-inversion habit that makes any external service testable, and it earns its keep faster here than almost anywhere else. Two things fall out of it:</p>
<p><strong>Your business logic never imports the SDK.</strong> It imports your <code>Request</code> and <code>Response</code> types. When the SDK&rsquo;s union types change shape, one adapter file changes.</p>
<p><strong>The fake is trivial.</strong> No HTTP, no fixtures, no mocking library:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">fakeCompleter</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">responses</span> []<span style="color:#a6e22e">Response</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">err</span>       <span style="color:#66d9ef">error</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">calls</span>     []<span style="color:#a6e22e">Request</span> <span style="color:#75715e">// recorded for assertions</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">f</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">fakeCompleter</span>) <span style="color:#a6e22e">Complete</span>(<span style="color:#a6e22e">_</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">req</span> <span style="color:#a6e22e">Request</span>) (<span style="color:#a6e22e">Response</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">f</span>.<span style="color:#a6e22e">calls</span> = append(<span style="color:#a6e22e">f</span>.<span style="color:#a6e22e">calls</span>, <span style="color:#a6e22e">req</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">f</span>.<span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">Response</span>{}, <span style="color:#a6e22e">f</span>.<span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> len(<span style="color:#a6e22e">f</span>.<span style="color:#a6e22e">responses</span>) <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">Response</span>{}, <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">New</span>(<span style="color:#e6db74">&#34;fakeCompleter: no responses left&#34;</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">resp</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">f</span>.<span style="color:#a6e22e">responses</span>[<span style="color:#ae81ff">0</span>]
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">f</span>.<span style="color:#a6e22e">responses</span> = <span style="color:#a6e22e">f</span>.<span style="color:#a6e22e">responses</span>[<span style="color:#ae81ff">1</span>:]
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">resp</span>, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Returning a queue rather than a single value is what lets you test multi-turn loops: the first call returns a tool request, the second returns the final answer.</p>
<h2 id="test-the-loop-not-the-model">Test the Loop, Not the Model</h2>
<p>An agent loop has plenty of logic worth testing, none of which needs a model. Does it stop when it should? Does it return a result for every tool call? Does it hand a tool failure back rather than aborting?</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">TestLoopReturnsResultForFailedTool</span>(<span style="color:#a6e22e">t</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">T</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">fake</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">fakeCompleter</span>{<span style="color:#a6e22e">responses</span>: []<span style="color:#a6e22e">Response</span>{
</span></span><span style="display:flex;"><span>        {<span style="color:#a6e22e">StopReason</span>: <span style="color:#e6db74">&#34;tool_use&#34;</span>, <span style="color:#a6e22e">ToolCalls</span>: []<span style="color:#a6e22e">ToolCall</span>{
</span></span><span style="display:flex;"><span>            {<span style="color:#a6e22e">ID</span>: <span style="color:#e6db74">&#34;t1&#34;</span>, <span style="color:#a6e22e">Name</span>: <span style="color:#e6db74">&#34;lookup&#34;</span>, <span style="color:#a6e22e">Input</span>: []byte(<span style="color:#e6db74">`{&#34;id&#34;:&#34;missing&#34;}`</span>)},
</span></span><span style="display:flex;"><span>        }},
</span></span><span style="display:flex;"><span>        {<span style="color:#a6e22e">StopReason</span>: <span style="color:#e6db74">&#34;end_turn&#34;</span>, <span style="color:#a6e22e">Text</span>: <span style="color:#e6db74">&#34;I couldn&#39;t find that record.&#34;</span>},
</span></span><span style="display:flex;"><span>    }}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">agent</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">New</span>(<span style="color:#a6e22e">fake</span>, <span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">string</span>]<span style="color:#a6e22e">ToolFunc</span>{
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;lookup&#34;</span>: <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, []<span style="color:#66d9ef">byte</span>) (<span style="color:#66d9ef">string</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;&#34;</span>, <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">New</span>(<span style="color:#e6db74">&#34;not found&#34;</span>)
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">got</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">agent</span>.<span style="color:#a6e22e">Run</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>(), <span style="color:#e6db74">&#34;look up record missing&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Fatalf</span>(<span style="color:#e6db74">&#34;Run() error = %v, want nil&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">got</span> <span style="color:#f92672">!=</span> <span style="color:#e6db74">&#34;I couldn&#39;t find that record.&#34;</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;got %q&#34;</span>, <span style="color:#a6e22e">got</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// The failure must reach the model as a tool result, not abort the loop.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">second</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">fake</span>.<span style="color:#a6e22e">calls</span>[<span style="color:#ae81ff">1</span>]
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">result</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">findToolResult</span>(<span style="color:#a6e22e">t</span>, <span style="color:#a6e22e">second</span>, <span style="color:#e6db74">&#34;t1&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">result</span>.<span style="color:#a6e22e">IsError</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#e6db74">&#34;tool failure was not marked as an error result&#34;</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>That test catches a real bug — a loop that drops the failed call instead of reporting it — and runs in microseconds with no API key.</p>
<p>The same approach covers the rest of the loop&rsquo;s contract — <a href="/posts/tool-use-in-go-agent-loop/">the invariants from the agent-loop article</a>: that it stops at <code>MaxIterations</code>, that every <code>tool_use</code> block gets exactly one result, that results come back in one message. Table-driven tests fit this beautifully, since each case is just a different queue of canned responses.</p>
<h2 id="golden-files-for-prompt-assembly">Golden Files for Prompt Assembly</h2>
<p>Prompt building is string manipulation, and it drifts. Someone adds a field, reorders a section, changes a heading — and unlike code, a prompt regression produces no compile error and no failing assertion, just slightly worse output that nobody attributes to the change.</p>
<p>Golden files make the diff visible in review:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">update</span> = <span style="color:#a6e22e">flag</span>.<span style="color:#a6e22e">Bool</span>(<span style="color:#e6db74">&#34;update&#34;</span>, <span style="color:#66d9ef">false</span>, <span style="color:#e6db74">&#34;update golden files&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">TestBuildSystemPrompt</span>(<span style="color:#a6e22e">t</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">T</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">tests</span> <span style="color:#f92672">:=</span> []<span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">name</span>   <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">cfg</span>    <span style="color:#a6e22e">Config</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">golden</span> <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>    }{
</span></span><span style="display:flex;"><span>        {<span style="color:#e6db74">&#34;default&#34;</span>, <span style="color:#a6e22e">Config</span>{}, <span style="color:#e6db74">&#34;system_default.txt&#34;</span>},
</span></span><span style="display:flex;"><span>        {<span style="color:#e6db74">&#34;with_tools&#34;</span>, <span style="color:#a6e22e">Config</span>{<span style="color:#a6e22e">Tools</span>: <span style="color:#a6e22e">allTools</span>}, <span style="color:#e6db74">&#34;system_with_tools.txt&#34;</span>},
</span></span><span style="display:flex;"><span>        {<span style="color:#e6db74">&#34;terse_mode&#34;</span>, <span style="color:#a6e22e">Config</span>{<span style="color:#a6e22e">Terse</span>: <span style="color:#66d9ef">true</span>}, <span style="color:#e6db74">&#34;system_terse.txt&#34;</span>},
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">tt</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">tests</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Run</span>(<span style="color:#a6e22e">tt</span>.<span style="color:#a6e22e">name</span>, <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">t</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">T</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">got</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">BuildSystemPrompt</span>(<span style="color:#a6e22e">tt</span>.<span style="color:#a6e22e">cfg</span>)
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">path</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">filepath</span>.<span style="color:#a6e22e">Join</span>(<span style="color:#e6db74">&#34;testdata&#34;</span>, <span style="color:#a6e22e">tt</span>.<span style="color:#a6e22e">golden</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">update</span> {
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">WriteFile</span>(<span style="color:#a6e22e">path</span>, []byte(<span style="color:#a6e22e">got</span>), <span style="color:#ae81ff">0</span><span style="color:#a6e22e">o644</span>); <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>                    <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Fatal</span>(<span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">want</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">ReadFile</span>(<span style="color:#a6e22e">path</span>)
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Fatal</span>(<span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">diff</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">cmp</span>.<span style="color:#a6e22e">Diff</span>(string(<span style="color:#a6e22e">want</span>), <span style="color:#a6e22e">got</span>); <span style="color:#a6e22e">diff</span> <span style="color:#f92672">!=</span> <span style="color:#e6db74">&#34;&#34;</span> {
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;prompt changed (-want +got):\n%s&#34;</span>, <span style="color:#a6e22e">diff</span>)
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        })
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Run <code>go test ./... -update</code> to accept a change deliberately. The point is not that the golden file is correct — it is that changing it requires saying so out loud, in a diff a reviewer can read.</p>
<p>This also catches the caching bugs from the <a href="/posts/prompt-caching-llm-cost/">prompt caching article</a> before they cost you anything. A golden test on the system prompt fails the moment somebody interpolates <code>time.Now()</code> into it, because the output differs on every run.</p>
<p>Which suggests a second, blunter test worth having:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">TestPromptAssemblyIsDeterministic</span>(<span style="color:#a6e22e">t</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">T</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Go randomises map iteration order, so a prompt built by ranging over a</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// map differs run to run — and silently never caches.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">first</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">BuildSystemPrompt</span>(<span style="color:#a6e22e">cfg</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">i</span> &lt; <span style="color:#ae81ff">20</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">got</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">BuildSystemPrompt</span>(<span style="color:#a6e22e">cfg</span>); <span style="color:#a6e22e">got</span> <span style="color:#f92672">!=</span> <span style="color:#a6e22e">first</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Fatalf</span>(<span style="color:#e6db74">&#34;prompt is not deterministic on run %d&#34;</span>, <span style="color:#a6e22e">i</span>)
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Twenty iterations is enough to make a randomised map order fail essentially every time.</p>
<h2 id="integration-tests-behind-a-build-tag">Integration Tests, Behind a Build Tag</h2>
<p>You do need a handful of tests that touch the real API — enough to catch an SDK upgrade that changed a union type, a model id that no longer resolves, or streaming that broke. But they cost money and need a key, so they must not run on every <code>go test ./...</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">//go:build integration</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">llm_test</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">TestStreamingReturnsCompleteMessage</span>(<span style="color:#a6e22e">t</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">T</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Getenv</span>(<span style="color:#e6db74">&#34;ANTHROPIC_API_KEY&#34;</span>) <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;&#34;</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Skip</span>(<span style="color:#e6db74">&#34;no API key; skipping integration test&#34;</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// ... real call, assert on shape rather than content</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>go test ./...                    <span style="color:#75715e"># fast, free, no key</span>
</span></span><span style="display:flex;"><span>go test -tags<span style="color:#f92672">=</span>integration ./...  <span style="color:#75715e"># the real thing, on demand</span>
</span></span></code></pre></div><p>Assert on <strong>shape</strong>, never on wording. <code>resp.Text</code> being non-empty, <code>StopReason</code> being <code>end_turn</code>, <code>Usage.OutputTokens</code> being greater than zero, a streamed message accumulating to the same content as a non-streamed one. Those hold across model versions. &ldquo;The answer contains the word Paris&rdquo; does not, and a flaky test that fails once a month teaches your team to ignore failures.</p>
<p>There is a middle option that gets you a long way for free: point the SDK at a local <code>httptest.Server</code> via the <code>ANTHROPIC_BASE_URL</code> environment variable and serve canned JSON. That exercises the real SDK — its parsing, its retry behaviour, its streaming decoder — without a key or a bill. It is the closest analogue to what <a href="/posts/how-to-test-database-interactions-go/">go-sqlmock</a> does for the database layer: a real driver, a fake server.</p>
<p>It is also the only sane way to test the paths you cannot easily provoke on purpose:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// Rate limiting: does the caller back off, or hammer?</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">mux</span>.<span style="color:#a6e22e">HandleFunc</span>(<span style="color:#e6db74">&#34;/v1/messages&#34;</span>, <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">Header</span>().<span style="color:#a6e22e">Set</span>(<span style="color:#e6db74">&#34;Retry-After&#34;</span>, <span style="color:#e6db74">&#34;1&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">WriteHeader</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusTooManyRequests</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Fprint</span>(<span style="color:#a6e22e">w</span>, <span style="color:#e6db74">`{&#34;type&#34;:&#34;error&#34;,&#34;error&#34;:{&#34;type&#34;:&#34;rate_limit_error&#34;,&#34;message&#34;:&#34;...&#34;}}`</span>)
</span></span><span style="display:flex;"><span>})
</span></span></code></pre></div><p>Do the same for a 500, a connection dropped mid-stream, and a malformed body. Those are the failures that actually page you, and they are trivial to simulate and nearly impossible to trigger on demand against the real API.</p>
<h2 id="the-refusal-case-nobody-tests">The Refusal Case Nobody Tests</h2>
<p>Worth its own paragraph because it is so easy to miss: a safety refusal comes back as <strong>HTTP 200</strong> with <code>StopReason</code> set to <code>refusal</code>. Your error handling never fires. Code that goes from <code>if err != nil</code> straight to reading the first content block treats it as an empty answer.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">TestRefusalIsNotTreatedAsEmptyAnswer</span>(<span style="color:#a6e22e">t</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">T</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">fake</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">fakeCompleter</span>{<span style="color:#a6e22e">responses</span>: []<span style="color:#a6e22e">Response</span>{
</span></span><span style="display:flex;"><span>        {<span style="color:#a6e22e">StopReason</span>: <span style="color:#e6db74">&#34;refusal&#34;</span>, <span style="color:#a6e22e">Text</span>: <span style="color:#e6db74">&#34;&#34;</span>},
</span></span><span style="display:flex;"><span>    }}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">New</span>(<span style="color:#a6e22e">fake</span>, <span style="color:#66d9ef">nil</span>).<span style="color:#a6e22e">Run</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>(), <span style="color:#e6db74">&#34;...&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">Is</span>(<span style="color:#a6e22e">err</span>, <span style="color:#a6e22e">ErrDeclined</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;got %v, want ErrDeclined&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>One line in the fake, and you have covered a branch most production code does not have at all. The sentinel-error pattern behind <code>ErrDeclined</code> is the one from <a href="/posts/error-handling-in-go/">error handling in Go</a>.</p>
<h2 id="evals-for-quality-not-correctness">Evals: For Quality, Not Correctness</h2>
<p>Everything above tests whether your <em>code</em> is right. None of it tells you whether the answers are any good. That needs a different instrument, and the mistake is trying to force it into <code>go test</code>.</p>
<p>An eval is a fixed set of inputs, run against the real model, scored rather than asserted:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">//go:build eval</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">TestClassificationAccuracy</span>(<span style="color:#a6e22e">t</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">T</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">cases</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">loadEvalCases</span>(<span style="color:#a6e22e">t</span>, <span style="color:#e6db74">&#34;testdata/eval/classification.jsonl&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">correct</span> <span style="color:#66d9ef">int</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">c</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">cases</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">got</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">classify</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>(), <span style="color:#a6e22e">realClient</span>, <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Input</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Fatal</span>(<span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">got</span> <span style="color:#f92672">==</span> <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Want</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">correct</span><span style="color:#f92672">++</span>
</span></span><span style="display:flex;"><span>        } <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Logf</span>(<span style="color:#e6db74">&#34;MISS: input=%q got=%q want=%q&#34;</span>, <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Input</span>, <span style="color:#a6e22e">got</span>, <span style="color:#a6e22e">c</span>.<span style="color:#a6e22e">Want</span>)
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">accuracy</span> <span style="color:#f92672">:=</span> float64(<span style="color:#a6e22e">correct</span>) <span style="color:#f92672">/</span> float64(len(<span style="color:#a6e22e">cases</span>))
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Logf</span>(<span style="color:#e6db74">&#34;accuracy: %.1f%% (%d/%d)&#34;</span>, <span style="color:#a6e22e">accuracy</span><span style="color:#f92672">*</span><span style="color:#ae81ff">100</span>, <span style="color:#a6e22e">correct</span>, len(<span style="color:#a6e22e">cases</span>))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// A floor, not an equality check. Below this, something regressed.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">accuracy</span> &lt; <span style="color:#ae81ff">0.90</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;accuracy %.1f%% below the 90%% floor&#34;</span>, <span style="color:#a6e22e">accuracy</span><span style="color:#f92672">*</span><span style="color:#ae81ff">100</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Two things make this useful rather than annoying. <strong>The threshold is a floor</strong>, not a target — you are detecting regression, not demanding perfection. And <strong>the misses get logged</strong>, because the list of what it got wrong is the actual output; the pass/fail is almost incidental.</p>
<p>Run evals when you change a prompt, a model, or an effort setting — not on every commit. They cost money and take minutes.</p>
<h2 id="what-not-to-do">What Not to Do</h2>
<p><strong>Do not assert on model wording.</strong> <code>strings.Contains(resp, &quot;Paris&quot;)</code> passes today and fails after a model update that phrases it differently. It is not testing your code.</p>
<p><strong>Do not set temperature to zero and call it deterministic.</strong> Sampling parameters are not even accepted on current models, and identical output was never guaranteed regardless.</p>
<p><strong>Do not mock the SDK&rsquo;s types.</strong> Mocking <code>anthropic.Message</code> and its union blocks is a lot of work to test the adapter you wrote to avoid exactly that. Fake your own interface instead.</p>
<p><strong>Do not let integration tests run by default.</strong> A test suite that needs an API key is a test suite that new contributors cannot run, and CI cost that grows with every push.</p>
<p><strong>Do not skip testing the error paths</strong> because they are &ldquo;just the SDK&rsquo;s job&rdquo;. Rate limits, refusals and mid-stream disconnects are the failures you will actually see in production, and they are the cheapest things in this article to cover.</p>
<h2 id="checklist">Checklist</h2>
<ul>
<li>A narrow interface between your logic and the SDK; business code never imports the SDK.</li>
<li>A fake with a queue of canned responses for multi-turn loops.</li>
<li>Unit tests for the loop&rsquo;s contract: one result per tool call, errors handed back, iteration cap honoured.</li>
<li>Golden files for prompt assembly, plus a determinism test.</li>
<li>Integration tests behind <code>//go:build integration</code>, asserting on shape not wording.</li>
<li>An <code>httptest.Server</code> for 429s, 500s and truncated streams.</li>
<li>A test for the refusal path.</li>
<li>Evals behind their own tag, scored against a floor, misses logged.</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>&ldquo;You cannot test LLM code&rdquo; conflates the model with the code around it. The code around it — the prompt builder, the parser, the loop, the tool handlers, the error branches — is ordinary Go, and it becomes easy to test the moment there is an interface between it and the SDK. Push non-determinism out to the edges, cover the edges with evals scored against a floor, and the rest of your suite stays fast, free and green.</p>]]></content:encoded>
      <category>AI Engineering</category>
      <category>Go Programming</category>
      <category>Testing</category>
    </item>
    <item>
      <title>Error Handling in Go: Wrapping, Sentinel Errors and errors.Is/As</title>
      <link>https://webdevstation.com/posts/error-handling-in-go/</link>
      <pubDate>Tue, 21 Jul 2026 09:30:00 +0200</pubDate>
      <author>Alex</author>
      <guid isPermaLink="true">https://webdevstation.com/posts/error-handling-in-go/</guid>
      <description>A practical guide to Go error handling — wrapping with %w, sentinel errors, custom error types, errors.Is and errors.As, and where each one belongs in a real…</description>
      <content:encoded><![CDATA[<p>Every Go codebase I have joined had the same weak spot: errors. Not the <code>if err != nil</code> part — everybody writes that — but everything after it. Errors get logged three times on the way up, lose their context somewhere in the middle, or get compared with <code>err.Error() == &quot;not found&quot;</code>. In this post I want to share the small set of rules I now apply everywhere, and the standard library features that make them work.</p>
<h2 id="the-one-rule-that-fixes-most-of-it">The One Rule That Fixes Most of It</h2>
<p>Handle an error <strong>once</strong>. Everywhere else, add context and pass it up.</p>
<p>&ldquo;Handling&rdquo; means doing something a caller cannot: returning a 404, retrying, falling back to a default, logging and moving on. If you are not doing one of those, you are just a link in the chain — and a link&rsquo;s only job is to explain where it sits.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// Bad: the error is logged here and returned, so it will be logged again</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// by every caller on the way up.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">s</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Store</span>) <span style="color:#a6e22e">User</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">id</span> <span style="color:#66d9ef">int64</span>) (<span style="color:#f92672">*</span><span style="color:#a6e22e">User</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">u</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">query</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">id</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Printf</span>(<span style="color:#e6db74">&#34;failed to load user: %v&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>, <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">u</span>, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Good: add what this layer knows, and let the caller decide.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">s</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Store</span>) <span style="color:#a6e22e">User</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">id</span> <span style="color:#66d9ef">int64</span>) (<span style="color:#f92672">*</span><span style="color:#a6e22e">User</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">u</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">query</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">id</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>, <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;load user %d: %w&#34;</span>, <span style="color:#a6e22e">id</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">u</span>, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The second version produces messages that read like a stack trace written in English:</p>
<pre tabindex="0"><code>handle GET /users/42: load user 42: query users: sql: no rows in result set
</code></pre><h2 id="wrapping-with-w">Wrapping With %w</h2>
<p><code>fmt.Errorf</code> has a special verb, <code>%w</code>, that wraps the original error instead of flattening it to a string. The result behaves like a normal error, but the original stays reachable underneath.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Open</span>(<span style="color:#e6db74">&#34;/etc/app/config.yaml&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">wrapped</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;read config: %w&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">wrapped</span>)
</span></span><span style="display:flex;"><span><span style="color:#75715e">// read config: open /etc/app/config.yaml: no such file or directory</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">Is</span>(<span style="color:#a6e22e">wrapped</span>, <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">ErrNotExist</span>)) <span style="color:#75715e">// true</span>
</span></span></code></pre></div><p>That last line is the whole point. <code>%v</code> would have given you the same message, but <code>errors.Is</code> would have returned <code>false</code> because the chain was broken.</p>
<p>A few conventions that keep the output readable:</p>
<ul>
<li>Start the message with a lowercase verb phrase describing what <em>this</em> function was doing: <code>&quot;load user&quot;</code>, <code>&quot;encode response&quot;</code>, <code>&quot;dial redis&quot;</code>.</li>
<li>Do not end with punctuation, and do not include the word &ldquo;error&rdquo; or &ldquo;failed&rdquo; — the chain already reads as a failure.</li>
<li>Put the wrapped error last, after a colon and a space.</li>
</ul>
<p>Since Go 1.20 you can wrap more than one error in a single call, which is handy when you are cleaning up:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">doWork</span>()
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#a6e22e">cerr</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">f</span>.<span style="color:#a6e22e">Close</span>(); <span style="color:#a6e22e">cerr</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;work failed: %w; close failed: %w&#34;</span>, <span style="color:#a6e22e">err</span>, <span style="color:#a6e22e">cerr</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h2 id="sentinel-errors-for-conditions-callers-branch-on">Sentinel Errors: For Conditions Callers Branch On</h2>
<p>A sentinel is a package-level error value that callers are meant to recognise.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">store</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> <span style="color:#e6db74">&#34;errors&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> (
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">ErrNotFound</span>  = <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">New</span>(<span style="color:#e6db74">&#34;not found&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">ErrConflict</span>  = <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">New</span>(<span style="color:#e6db74">&#34;conflict&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">ErrForbidden</span> = <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">New</span>(<span style="color:#e6db74">&#34;forbidden&#34;</span>)
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><p>Return them wrapped, so the caller gets both the identity and the context:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">s</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Store</span>) <span style="color:#a6e22e">User</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">id</span> <span style="color:#66d9ef">int64</span>) (<span style="color:#f92672">*</span><span style="color:#a6e22e">User</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">u</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">s</span>.<span style="color:#a6e22e">query</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">id</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">Is</span>(<span style="color:#a6e22e">err</span>, <span style="color:#a6e22e">sql</span>.<span style="color:#a6e22e">ErrNoRows</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>, <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;load user %d: %w&#34;</span>, <span style="color:#a6e22e">id</span>, <span style="color:#a6e22e">ErrNotFound</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>, <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;load user %d: %w&#34;</span>, <span style="color:#a6e22e">id</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">u</span>, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>And check them with <code>errors.Is</code>, which walks the whole chain:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">u</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">store</span>.<span style="color:#a6e22e">User</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">id</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">switch</span> {
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">case</span> <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">Is</span>(<span style="color:#a6e22e">err</span>, <span style="color:#a6e22e">store</span>.<span style="color:#a6e22e">ErrNotFound</span>):
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#a6e22e">w</span>, <span style="color:#e6db74">&#34;user not found&#34;</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusNotFound</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">case</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#e6db74">&#34;load user&#34;</span>, <span style="color:#e6db74">&#34;err&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#a6e22e">w</span>, <span style="color:#e6db74">&#34;internal error&#34;</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusInternalServerError</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Never compare with <code>==</code> when the error might be wrapped, and never compare error strings. <code>errors.Is(err, ErrNotFound)</code> keeps working when someone adds another wrapping layer three months from now; <code>err == ErrNotFound</code> silently stops working.</p>
<p>Keep the list short. Sentinels are part of your package&rsquo;s public API — every one you export is a promise. If callers cannot meaningfully branch on it, it should not be a sentinel.</p>
<h2 id="custom-error-types-when-you-need-to-carry-data">Custom Error Types: When You Need to Carry Data</h2>
<p>A sentinel says <em>what went wrong</em>. A custom type also says <em>with what</em>. Reach for one when the caller needs a field, not just an identity.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// ValidationError reports a single field that failed validation.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">ValidationError</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Field</span>  <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Reason</span> <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">e</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">ValidationError</span>) <span style="color:#a6e22e">Error</span>() <span style="color:#66d9ef">string</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Sprintf</span>(<span style="color:#e6db74">&#34;field %q is invalid: %s&#34;</span>, <span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">Field</span>, <span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">Reason</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><code>errors.As</code> finds it anywhere in the chain and assigns it to your variable:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">vErr</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">ValidationError</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">As</span>(<span style="color:#a6e22e">err</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">vErr</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">WriteHeader</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusBadRequest</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">NewEncoder</span>(<span style="color:#a6e22e">w</span>).<span style="color:#a6e22e">Encode</span>(<span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">string</span>]<span style="color:#66d9ef">string</span>{
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;field&#34;</span>:  <span style="color:#a6e22e">vErr</span>.<span style="color:#a6e22e">Field</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;reason&#34;</span>: <span style="color:#a6e22e">vErr</span>.<span style="color:#a6e22e">Reason</span>,
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Two details that trip people up:</p>
<ol>
<li><strong>Pass a pointer to the target.</strong> <code>errors.As(err, &amp;vErr)</code> where <code>vErr</code> is already a <code>*ValidationError</code>. Passing <code>vErr</code> directly panics.</li>
<li><strong>Be consistent about pointer vs. value receivers.</strong> If <code>Error()</code> is defined on <code>*ValidationError</code>, then only <code>*ValidationError</code> implements <code>error</code>. Return <code>&amp;ValidationError{...}</code>, and match against <code>*ValidationError</code>.</li>
</ol>
<p>If your type wraps another error, give it an <code>Unwrap</code> method so <code>errors.Is</code> can keep walking:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">QueryError</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Query</span> <span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Err</span>   <span style="color:#66d9ef">error</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">e</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">QueryError</span>) <span style="color:#a6e22e">Error</span>() <span style="color:#66d9ef">string</span> { <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">Query</span> <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;: &#34;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">Err</span>.<span style="color:#a6e22e">Error</span>() }
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">e</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">QueryError</span>) <span style="color:#a6e22e">Unwrap</span>() <span style="color:#66d9ef">error</span> { <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">Err</span> }
</span></span></code></pre></div><p>Now <code>errors.Is(err, sql.ErrNoRows)</code> still works even with a <code>QueryError</code> in the middle.</p>
<h2 id="collecting-several-errors-with-errorsjoin">Collecting Several Errors With errors.Join</h2>
<p>When you validate a whole struct, failing on the first problem makes for a frustrating API. <code>errors.Join</code> (Go 1.20) combines errors into one value that <code>errors.Is</code> and <code>errors.As</code> can still search.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">r</span> <span style="color:#a6e22e">CreateUserRequest</span>) <span style="color:#a6e22e">Validate</span>() <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">errs</span> []<span style="color:#66d9ef">error</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">Email</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;&#34;</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">errs</span> = append(<span style="color:#a6e22e">errs</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">ValidationError</span>{<span style="color:#a6e22e">Field</span>: <span style="color:#e6db74">&#34;email&#34;</span>, <span style="color:#a6e22e">Reason</span>: <span style="color:#e6db74">&#34;required&#34;</span>})
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> len(<span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">Password</span>) &lt; <span style="color:#ae81ff">12</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">errs</span> = append(<span style="color:#a6e22e">errs</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">ValidationError</span>{<span style="color:#a6e22e">Field</span>: <span style="color:#e6db74">&#34;password&#34;</span>, <span style="color:#a6e22e">Reason</span>: <span style="color:#e6db74">&#34;too short&#34;</span>})
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Join returns nil when every element is nil, so this is safe as-is.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">Join</span>(<span style="color:#a6e22e">errs</span><span style="color:#f92672">...</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The joined error prints one message per line, and <code>errors.As</code> will find the first <code>*ValidationError</code> inside it.</p>
<h2 id="which-tool-for-which-job">Which Tool for Which Job</h2>
<table>
	<thead>
			<tr>
					<th>Situation</th>
					<th>Reach for</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Adding context on the way up</td>
					<td><code>fmt.Errorf(&quot;...: %w&quot;, err)</code></td>
			</tr>
			<tr>
					<td>Caller branches on a known condition</td>
					<td>Sentinel + <code>errors.Is</code></td>
			</tr>
			<tr>
					<td>Caller needs data about the failure</td>
					<td>Custom type + <code>errors.As</code></td>
			</tr>
			<tr>
					<td>Several independent failures at once</td>
					<td><code>errors.Join</code></td>
			</tr>
			<tr>
					<td>Failure is expected and unremarkable</td>
					<td>Return a plain value, not an error</td>
			</tr>
	</tbody>
</table>
<p>That last row matters more than it looks. A cache miss is not an error. An empty search result is not an error. Reserve errors for situations where the caller genuinely cannot continue as planned.</p>
<h2 id="errors-at-the-http-boundary">Errors at the HTTP Boundary</h2>
<p>The boundary is where errors finally get handled, and it is worth doing that in exactly one place. Middleware is a natural home for it — the same pattern as the <a href="/posts/go-middleware-example/">Go middleware example</a>, just applied to errors instead of responses.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// handlerFunc is like http.HandlerFunc but may return an error.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">handlerFunc</span> <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) <span style="color:#66d9ef">error</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// wrap turns a handlerFunc into a plain http.Handler, translating</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// errors into status codes in one place.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">wrap</span>(<span style="color:#a6e22e">h</span> <span style="color:#a6e22e">handlerFunc</span>) <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Handler</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">HandlerFunc</span>(<span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">h</span>(<span style="color:#a6e22e">w</span>, <span style="color:#a6e22e">r</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">==</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">vErr</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">ValidationError</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">switch</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">case</span> <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">Is</span>(<span style="color:#a6e22e">err</span>, <span style="color:#a6e22e">store</span>.<span style="color:#a6e22e">ErrNotFound</span>):
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#a6e22e">w</span>, <span style="color:#e6db74">&#34;not found&#34;</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusNotFound</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">case</span> <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">Is</span>(<span style="color:#a6e22e">err</span>, <span style="color:#a6e22e">store</span>.<span style="color:#a6e22e">ErrForbidden</span>):
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#a6e22e">w</span>, <span style="color:#e6db74">&#34;forbidden&#34;</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusForbidden</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">case</span> <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">As</span>(<span style="color:#a6e22e">err</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">vErr</span>):
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#a6e22e">w</span>, <span style="color:#a6e22e">vErr</span>.<span style="color:#a6e22e">Error</span>(), <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusBadRequest</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">case</span> <span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">Is</span>(<span style="color:#a6e22e">err</span>, <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Canceled</span>):
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">// The client hung up. Nothing to report.</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">default</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">// Log the full chain, tell the client nothing.</span>
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">slog</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#e6db74">&#34;request failed&#34;</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;method&#34;</span>, <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">Method</span>, <span style="color:#e6db74">&#34;path&#34;</span>, <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">URL</span>.<span style="color:#a6e22e">Path</span>, <span style="color:#e6db74">&#34;err&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Error</span>(<span style="color:#a6e22e">w</span>, <span style="color:#e6db74">&#34;internal error&#34;</span>, <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">StatusInternalServerError</span>)
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Handlers become quiet and linear:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">getUser</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">id</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">strconv</span>.<span style="color:#a6e22e">ParseInt</span>(<span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">PathValue</span>(<span style="color:#e6db74">&#34;id&#34;</span>), <span style="color:#ae81ff">10</span>, <span style="color:#ae81ff">64</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">ValidationError</span>{<span style="color:#a6e22e">Field</span>: <span style="color:#e6db74">&#34;id&#34;</span>, <span style="color:#a6e22e">Reason</span>: <span style="color:#e6db74">&#34;must be an integer&#34;</span>}
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">u</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">store</span>.<span style="color:#a6e22e">User</span>(<span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">Context</span>(), <span style="color:#a6e22e">id</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;get user: %w&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">NewEncoder</span>(<span style="color:#a6e22e">w</span>).<span style="color:#a6e22e">Encode</span>(<span style="color:#a6e22e">u</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The <code>context.Canceled</code> case is easy to forget and shows up constantly in production: a user closes the tab, the request context is cancelled, and your dashboard fills with 500s that nothing went wrong for. If you have not met that machinery yet, <a href="/posts/understanding-golang-context/">understanding Golang context</a> covers where those cancellations come from.</p>
<h2 id="common-pitfalls">Common Pitfalls</h2>
<table>
	<thead>
			<tr>
					<th>Pitfall</th>
					<th>What to do instead</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>if err.Error() == &quot;not found&quot;</code></td>
					<td><code>errors.Is(err, ErrNotFound)</code></td>
			</tr>
			<tr>
					<td><code>%v</code> when the caller needs the chain</td>
					<td><code>%w</code></td>
			</tr>
			<tr>
					<td>Logging <em>and</em> returning the same error</td>
					<td>Return it; log once at the boundary</td>
			</tr>
			<tr>
					<td><code>errors.As(err, vErr)</code></td>
					<td><code>errors.As(err, &amp;vErr)</code> — pass a pointer</td>
			</tr>
			<tr>
					<td>Wrapping with the caller&rsquo;s own function name</td>
					<td>Describe the <em>operation</em>, not the function</td>
			</tr>
			<tr>
					<td>Exporting a sentinel nobody branches on</td>
					<td>Keep it unexported, or drop it</td>
			</tr>
	</tbody>
</table>
<p>One more, subtle enough to deserve its own note: <strong>a non-nil interface holding a nil pointer is not nil.</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">find</span>() <span style="color:#f92672">*</span><span style="color:#a6e22e">ValidationError</span> { <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span> }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">check</span>() <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">find</span>() <span style="color:#75715e">// returns a non-nil error holding a nil *ValidationError</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#a6e22e">check</span>() <span style="color:#f92672">==</span> <span style="color:#66d9ef">nil</span>) <span style="color:#75715e">// false — almost certainly not what you wanted</span>
</span></span></code></pre></div><p>Declare the return type as <code>error</code> and return a literal <code>nil</code>, or check the concrete value before returning it.</p>
<h2 id="testing-error-paths">Testing Error Paths</h2>
<p>Assert on identity and type, never on the message text. Messages are for humans and will change.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">TestUserNotFound</span>(<span style="color:#a6e22e">t</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">T</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">store</span>.<span style="color:#a6e22e">User</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>(), <span style="color:#ae81ff">999</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">Is</span>(<span style="color:#a6e22e">err</span>, <span style="color:#a6e22e">store</span>.<span style="color:#a6e22e">ErrNotFound</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Fatalf</span>(<span style="color:#e6db74">&#34;got %v, want ErrNotFound&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">TestValidation</span>(<span style="color:#a6e22e">t</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">T</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">CreateUserRequest</span>{}.<span style="color:#a6e22e">Validate</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">vErr</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">ValidationError</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">As</span>(<span style="color:#a6e22e">err</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">vErr</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Fatalf</span>(<span style="color:#e6db74">&#34;got %v, want *ValidationError&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">vErr</span>.<span style="color:#a6e22e">Field</span> <span style="color:#f92672">!=</span> <span style="color:#e6db74">&#34;email&#34;</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;got field %q, want email&#34;</span>, <span style="color:#a6e22e">vErr</span>.<span style="color:#a6e22e">Field</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>This pairs nicely with mocking the database layer, which I covered in <a href="/posts/how-to-test-database-interactions-go/">how to test database interactions in Golang applications</a> — you can force <code>sql.ErrNoRows</code> and check that your store translates it into <code>ErrNotFound</code>.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Go&rsquo;s error handling is verbose, but it is also unusually honest: every failure is a value you can inspect, wrap and route. Wrap with <code>%w</code> on the way up, export a small set of sentinels for the conditions callers care about, use custom types when they need the details, and handle everything exactly once at the boundary. Do that and the <code>if err != nil</code> blocks stop feeling like noise and start reading like documentation.</p>
<p>One case that breaks the usual rules and deserves a look: an LLM call can return HTTP 200 while declining to answer, so <code>err</code> is nil and there is nothing in the content. <a href="/posts/calling-claude-from-go/">Calling an LLM from Go</a> covers that branch.</p>]]></content:encoded>
      <category>Go Programming</category>
      <category>Backend Development</category>
      <category>Testing</category>
    </item>
    <item>
      <title>An Easy Way to Load Test Your Web Apps</title>
      <link>https://webdevstation.com/posts/an-easy-way-to-loadtest-your-web-apps/</link>
      <pubDate>Fri, 12 Feb 2021 18:56:46 +0000</pubDate>
      <author>Alex</author>
      <guid isPermaLink="true">https://webdevstation.com/posts/an-easy-way-to-loadtest-your-web-apps/</guid>
      <description>Learn how to implement effective load testing for your web applications using the k6 tool and automate performance testing in your GitLab CI/CD pipeline.</description>
      <content:encoded><![CDATA[<p>This time, I want to share my positive experience of load testing of one of our web services, by using <a href="https://k6.io/">K6</a> tool.
Moreover, we will see how easily we can integrate this into the GitLab CI pipeline.</p>
<p>When you develop web applications, it&rsquo;s crucial to have a testing strategy. Nobody argues about the importance of unit,
functional, and integration testing. Nevertheless, very often developers forget to test how their application works under high load.
Even, when we have a &ldquo;green light&rdquo; from all our testing stages, including manual testing, better to not release it to production, until you load test it.
Otherwise, nobody can guarantee, that application will work properly when 50 users will use it simultaneously.</p>
<p>In this article I&rsquo;m going to load test our web application from the <a href="https://webdevstation.com/posts/how-to-show-flash-messages-in-go-echo/">previous article</a>.</p>
<p>For that, we are going to use a K6 tool, written in Go, and uses JavaScript for scripting.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>k6 is a developer-centric, free and open-source load testing tool built for making performance testing a productive and enjoyable experience.
</span></span></code></pre></div><p>Let&rsquo;s install this tool:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>brew install k6
</span></span></code></pre></div><p>If you have different from the macOS operating system, please read about others ways to install <a href="https://k6.io/docs/getting-started/installation">here</a>.</p>
<p>Next, we create a <code>loadtests</code> folder in the root of our project and inside we add a <code>test.js</code> file, where we are going to write our load tests scenarios:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">sleep</span> } <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#39;k6&#39;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">http</span> <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#39;k6/http&#39;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">check</span> } <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#39;k6&#39;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">Rate</span> } <span style="color:#a6e22e">from</span> <span style="color:#e6db74">&#39;k6/metrics&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">errorRate</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Rate</span>(<span style="color:#e6db74">&#39;errors&#39;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">options</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Here we define our scenarios.
</span></span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">scenarios</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">sign_in_page_test</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">executor</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;constant-vus&#39;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">duration</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;1m&#39;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">vus</span><span style="color:#f92672">:</span> <span style="color:#ae81ff">100</span>, <span style="color:#75715e">// amount of the virtual users
</span></span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">tags</span><span style="color:#f92672">:</span> { <span style="color:#a6e22e">test_type</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;signInPage&#39;</span> },
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">exec</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;signInPage&#39;</span>,
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// List of thresholds.
</span></span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">thresholds</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">http_req_duration</span><span style="color:#f92672">:</span> [<span style="color:#e6db74">&#39;avg&lt;500&#39;</span>], <span style="color:#75715e">// avg response times must be below 0.5s
</span></span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">errors</span><span style="color:#f92672">:</span> [<span style="color:#e6db74">&#39;rate&lt;0.1&#39;</span>], <span style="color:#75715e">// &lt;10% errors
</span></span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>};
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">signInPage</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">res</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">get</span>(<span style="color:#a6e22e">getDomain</span>() <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39;/user/signin&#39;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Here we check the response status.
</span></span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">result</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">check</span>(<span style="color:#a6e22e">res</span>, {
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#39;status is 200&#39;</span><span style="color:#f92672">:</span> (<span style="color:#a6e22e">r</span>) =&gt; <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">status</span> <span style="color:#f92672">==</span> <span style="color:#ae81ff">200</span>,
</span></span><span style="display:flex;"><span>    });
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// If it&#39;s different from 200, add info to the errorRate.
</span></span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">errorRate</span>.<span style="color:#a6e22e">add</span>(<span style="color:#f92672">!</span><span style="color:#a6e22e">result</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">sleep</span>(<span style="color:#ae81ff">3</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Gets the domain from the environment variables.
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">getDomain</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">__ENV</span>.<span style="color:#a6e22e">DOMAIN</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Above, we have declared a scenario to load test <code>/user/signin</code> page with 100 virtual users who continuously accessing our page in parallel.</p>
<p>To run this load test, we need to execute this command in the terminal:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>k6 run --env DOMAIN<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;http://localhost:8777&#34;</span> ./loadtests/test.js
</span></span></code></pre></div><p>After some time we will see this results output:
<img src="/images/0221/k6.png" alt="k6 terminal output listing checks, request duration percentiles and the passing thresholds for the load test" title="k6 load test results in the terminal"></p>
<p>As you could notice, all our defined thresholds were satisfied. So far so good!</p>
<p>Now, let&rsquo;s see how we can integrate load testing to the Gitlab CI pipeline. Fortunately, it&rsquo;s easy to do :)</p>
<p>Inside <code>.gitlab-ci.yml</code> we need to add this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">stages</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">loadtest</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">loadtesting</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">stage</span>: <span style="color:#ae81ff">loadtest</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">image</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">name</span>: <span style="color:#ae81ff">loadimpact/k6:latest</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">entrypoint</span>: [ <span style="color:#e6db74">&#39;&#39;</span> ]
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">variables</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">DOMAIN</span>: <span style="color:#e6db74">&#39;[your-testing-domain-here]&#39;</span>  
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">script</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#ae81ff">echo &#34;executing K6 load tests in k6 container...&#34;</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#ae81ff">k6 run --env DOMAIN=${DOMAIN} ./loadtests/test.js</span>
</span></span></code></pre></div><p>That was it! I&rsquo;ve described just an idea how you can easily integrate the load testing in your development routine.
In the real-world situations you might create more complex load testing scenarios, which will help you find weak points of your application and prevent unexpected downtimes.</p>
<p>I wish you happy coding and no pagerduty calls during the night!😉</p>
<p>Once you can measure, you have something to optimise against. Two places I usually look first: <a href="/posts/early-hints-in-go-or-the-way-of-how-to-improve-perforamnce-of-web-page/">103 Early Hints in Go</a> for front-end latency, and <a href="/posts/ristretto-the-most-performant-concurrent-cache-library-for-go/">Ristretto caching</a> for the expensive calls behind it. It is also the right tool to prove your <a href="/posts/graceful-shutdown-in-go-web-services/">graceful shutdown</a> really does keep errors at zero during a deploy.</p>]]></content:encoded>
      <category>DevOps</category>
      <category>Testing</category>
    </item>
    <item>
      <title>How to test database interactions in golang applications</title>
      <link>https://webdevstation.com/posts/how-to-test-database-interactions-go/</link>
      <pubDate>Tue, 22 Dec 2020 20:12:51 +0000</pubDate>
      <author>Alex</author>
      <guid isPermaLink="true">https://webdevstation.com/posts/how-to-test-database-interactions-go/</guid>
      <description>Testing of functions with database interactions always was challenging. Recently, I found a wonderful library, which will simplify writing tests and mocking database…</description>
      <content:encoded><![CDATA[<p>Testing of functions with database interactions always was challenging. Recently, I found a wonderful library <a href="https://github.com/DATA-DOG/go-sqlmock">go-sqlmock</a> which will simplify writing tests and mocking database queries in golang applications a lot.</p>
<p>And I want to share a short example of how to work with it.</p>
<p>First, we have to install it</p>
<p><code>go get github.com/DATA-DOG/go-sqlmock</code></p>
<p>We have this function with SQL query:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">MenuByNameAndLanguage</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">db</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">sql</span>.<span style="color:#a6e22e">DB</span>, <span style="color:#a6e22e">name</span> <span style="color:#66d9ef">string</span>, <span style="color:#a6e22e">langcode</span> <span style="color:#66d9ef">string</span>) (<span style="color:#f92672">*</span><span style="color:#a6e22e">models</span>.<span style="color:#a6e22e">Menu</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">result</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">Query</span>(<span style="color:#e6db74">&#34;SELECT id, langcode, title, link__uri, view_sidemenu FROM menu_link_content_data WHERE menu_name=? AND langcode=?&#34;</span>,
</span></span><span style="display:flex;"><span>         <span style="color:#a6e22e">name</span>,
</span></span><span style="display:flex;"><span>         <span style="color:#a6e22e">langcode</span>,
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">result</span>.<span style="color:#a6e22e">Close</span>()
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">menuLinks</span> []<span style="color:#a6e22e">models</span>.<span style="color:#a6e22e">MenuLink</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> <span style="color:#a6e22e">result</span>.<span style="color:#a6e22e">Next</span>() {
</span></span><span style="display:flex;"><span>         <span style="color:#a6e22e">menuLink</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">models</span>.<span style="color:#a6e22e">MenuLink</span>{}
</span></span><span style="display:flex;"><span>         <span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">result</span>.<span style="color:#a6e22e">Scan</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">menuLink</span>.<span style="color:#a6e22e">ID</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">menuLink</span>.<span style="color:#a6e22e">Langcode</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">menuLink</span>.<span style="color:#a6e22e">Title</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">menuLink</span>.<span style="color:#a6e22e">URL</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">menuLink</span>.<span style="color:#a6e22e">SideMenu</span>)
</span></span><span style="display:flex;"><span>         <span style="color:#a6e22e">menuLinks</span> = append(<span style="color:#a6e22e">menuLinks</span>, <span style="color:#a6e22e">menuLink</span>)    
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">menu</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">models</span>.<span style="color:#a6e22e">Menu</span>{
</span></span><span style="display:flex;"><span>         <span style="color:#a6e22e">Name</span>: <span style="color:#a6e22e">name</span>,
</span></span><span style="display:flex;"><span>         <span style="color:#a6e22e">Links</span>: <span style="color:#a6e22e">menuLinks</span>,
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">menu</span>, <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>This function just getting menu links by menu name and language.</p>
<p>And now let&rsquo;s test it.</p>
<p>We are going to test that MenuByNameAndLanguage function will return correct Menu structure.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">menu</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;context&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;testing&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;github.com/DATA-DOG/go-sqlmock&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;github.com/stretchr/testify/assert&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;gitlab.mfb.io/user/graphql_server/models&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">TestShouldReturnCorrectMenu</span>(<span style="color:#a6e22e">t</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">T</span>) {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Creates sqlmock database connection and a mock to manage expectations.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">db</span>, <span style="color:#a6e22e">mock</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">sqlmock</span>.<span style="color:#a6e22e">New</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Fatalf</span>(<span style="color:#e6db74">&#34;an error &#39;%s&#39; was not expected when opening a stub database connection&#34;</span>, <span style="color:#a6e22e">err</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Closes the database and prevents new queries from starting.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">Close</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Here we are creating rows in our mocked database.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">rows</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">sqlmock</span>.<span style="color:#a6e22e">NewRows</span>([]<span style="color:#66d9ef">string</span>{<span style="color:#e6db74">&#34;id&#34;</span>, <span style="color:#e6db74">&#34;langcode&#34;</span>, <span style="color:#e6db74">&#34;title&#34;</span>, <span style="color:#e6db74">&#34;link__uri&#34;</span>, <span style="color:#e6db74">&#34;view_sidemenu&#34;</span>}).
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">AddRow</span>(<span style="color:#ae81ff">1</span>, <span style="color:#e6db74">&#34;en&#34;</span>, <span style="color:#e6db74">&#34;enTitle&#34;</span>, <span style="color:#e6db74">&#34;/en-link&#34;</span>, <span style="color:#e6db74">&#34;0&#34;</span>).
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">AddRow</span>(<span style="color:#ae81ff">2</span>, <span style="color:#e6db74">&#34;en&#34;</span>, <span style="color:#e6db74">&#34;enTitle2&#34;</span>, <span style="color:#e6db74">&#34;/en-link2&#34;</span>, <span style="color:#e6db74">&#34;0&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// This is most important part in our test. Here, literally, we are altering SQL query from MenuByNameAndLanguage</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">﻿</span>    <span style="color:#75715e">// function and replacing result with our expected result. </span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">mock</span>.<span style="color:#a6e22e">ExpectQuery</span>(<span style="color:#e6db74">&#34;^SELECT (.+) FROM menu_link_content_data*&#34;</span>).
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">WithArgs</span>(<span style="color:#e6db74">&#34;main&#34;</span>, <span style="color:#e6db74">&#34;en&#34;</span>).
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">WillReturnRows</span>(<span style="color:#a6e22e">rows</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">ctx</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">TODO</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Calls MenuByNameAndLanguage with mocked database connection in arguments list. </span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">menu</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">MenuByNameAndLanguage</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">db</span>, <span style="color:#e6db74">&#34;main&#34;</span>, <span style="color:#e6db74">&#34;en&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Here we just construction our expecting result.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">menuLinks</span> []<span style="color:#a6e22e">models</span>.<span style="color:#a6e22e">MenuLink</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">menuLink1</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">models</span>.<span style="color:#a6e22e">MenuLink</span>{
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">ID</span>:       <span style="color:#ae81ff">1</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">Title</span>:    <span style="color:#e6db74">&#34;enTitle&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">Langcode</span>: <span style="color:#e6db74">&#34;en&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">URL</span>:      <span style="color:#e6db74">&#34;/en-link&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">SideMenu</span>: <span style="color:#e6db74">&#34;0&#34;</span>,
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">menuLinks</span> = append(<span style="color:#a6e22e">menuLinks</span>, <span style="color:#a6e22e">menuLink1</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">menuLink2</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">models</span>.<span style="color:#a6e22e">MenuLink</span>{
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">ID</span>:       <span style="color:#ae81ff">2</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">Title</span>:    <span style="color:#e6db74">&#34;enTitle2&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">Langcode</span>: <span style="color:#e6db74">&#34;en&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">URL</span>:      <span style="color:#e6db74">&#34;/en-link2&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">SideMenu</span>: <span style="color:#e6db74">&#34;0&#34;</span>,
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">menuLinks</span> = append(<span style="color:#a6e22e">menuLinks</span>, <span style="color:#a6e22e">menuLink2</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">expectedMenu</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">models</span>.<span style="color:#a6e22e">Menu</span>{
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">Name</span>:  <span style="color:#e6db74">&#34;main&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">Links</span>: <span style="color:#a6e22e">menuLinks</span>,
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// And, finally, let&#39;s check if result from MenuByNameAndLanguage equal with expected result.// Here I used Testify library (https://github.com/stretchr/testify).</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">assert</span>.<span style="color:#a6e22e">Equal</span>(<span style="color:#a6e22e">t</span>, <span style="color:#a6e22e">expectedMenu</span>, <span style="color:#a6e22e">menu</span>)
</span></span><span style="display:flex;"><span>} 
</span></span></code></pre></div><p>As you see everything in this example was pretty straightforward.</p>
<p>For mo details, you can refer to <a href="https://godoc.org/github.com/DATA-DOG/go-sqlmock">GoDocs</a>.</p>
<p>Mocking the database is also the easiest way to test your error paths: force <code>sql.ErrNoRows</code> and assert that your store turns it into a sentinel your handler can branch on. I covered how to build those sentinels in <a href="/posts/error-handling-in-go/">error handling in Go</a>. The same fake-the-boundary trick works for code that calls a language model, which people usually assume is untestable — <a href="/posts/testing-go-code-that-calls-an-llm/">how to test Go code that calls an LLM</a>.</p>]]></content:encoded>
      <category>Go Programming</category>
      <category>Backend Development</category>
      <category>Testing</category>
    </item>
  </channel>
</rss>
