<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Rafal Zajac</title>
    <description>The latest articles on DEV Community by Rafal Zajac (@rzajac).</description>
    <link>https://dev.to/rzajac</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2945881%2F6d65566e-610a-41ba-8c36-1b299ba6c9f0.jpeg</url>
      <title>DEV Community: Rafal Zajac</title>
      <link>https://dev.to/rzajac</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rzajac"/>
    <language>en</language>
    <item>
      <title>Crafting Go Testing Module: Step 4 - Goldy and Must</title>
      <dc:creator>Rafal Zajac</dc:creator>
      <pubDate>Fri, 16 May 2025 20:18:57 +0000</pubDate>
      <link>https://dev.to/rzajac/crafting-go-testing-module-step-4-goldy-and-must-eno</link>
      <guid>https://dev.to/rzajac/crafting-go-testing-module-step-4-goldy-and-must-eno</guid>
      <description>&lt;p&gt;This is the fourth chapter in my blog series about building a Go Testing Module from scratch. If you’re new here, I recommend checking out the &lt;a href="https://dev.to/rzajac/crafting-testing-module-step-3-testing-the-testers-en6/edit"&gt;previous post&lt;/a&gt; for some context. In this post, I’m diving into two new packages I’ve created — &lt;code&gt;goldy&lt;/code&gt; and &lt;code&gt;must&lt;/code&gt;. These tools tackle problems you’ve probably faced in writing tests, making your test code cleaner, more readable, and a lot more enjoyable to work with. Let me walk you through how I built these packages to solve those familiar testing headaches.&lt;/p&gt;

&lt;h2&gt;
  
  
  The &lt;code&gt;goldy&lt;/code&gt; Package
&lt;/h2&gt;

&lt;p&gt;You’ve likely faced the frustration of testing a function that generates a complex, multi-line string — like a JSON response or a formatted report. Hardcoding that string in your test code turns it into a cluttered mess that’s tough to read and even harder to maintain. I’ve been there, staring at a test file bloated with string literals. That’s why I created the &lt;code&gt;goldy&lt;/code&gt; package, a lightweight tool that uses &lt;code&gt;golden files&lt;/code&gt; to store expected outputs, keeping test code tidy and manageable.&lt;/p&gt;

&lt;h3&gt;
  
  
  What’s a Golden File?
&lt;/h3&gt;

&lt;p&gt;A &lt;em&gt;golden file&lt;/em&gt; is like a reference sheet for tests. It stores the expected output in a separate file, making it easy to compare against actual results. In &lt;code&gt;goldy&lt;/code&gt;, I designed golden files to be clear and contextual. They include comments to explain their purpose, followed by the expected content. Here’s an example of a golden file, typically saved with a &lt;code&gt;.gld&lt;/code&gt; extension:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This comment explains the golden file's purpose.
It can span multiple lines for clarity.
---
Content line #1.
Content line #2.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The format is simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Optional comment lines at the top to describe the file’s intent.&lt;/li&gt;
&lt;li&gt;A mandatory &lt;code&gt;---&lt;/code&gt; separator line to mark the start of the content.&lt;/li&gt;
&lt;li&gt;The expected content (the "golden" output).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This structure keeps things organized and makes golden files easy to read and update.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using &lt;code&gt;goldy&lt;/code&gt; in Tests
&lt;/h3&gt;

&lt;p&gt;Here’s how you use &lt;code&gt;goldy&lt;/code&gt; to test a function that generates a multi-line string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestSomething&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// --- Given ---&lt;/span&gt;
    &lt;span class="n"&gt;gld&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;goldy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"testdata/test_case123.gld"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 

    &lt;span class="c"&gt;// --- When ---&lt;/span&gt;
    &lt;span class="n"&gt;have&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;Something&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;123&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// --- Then ---&lt;/span&gt;
    &lt;span class="n"&gt;affirm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gld&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;have&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// Use content as string.&lt;/span&gt;
    &lt;span class="n"&gt;affirm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gld&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;have&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// Use content as bytes.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;goldy.Open&lt;/code&gt; function loads the golden file and returns a &lt;code&gt;Goldy&lt;/code&gt; struct with fields I find useful during testing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Goldy represents golden file.&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Goldy&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Path&lt;/span&gt;    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="c"&gt;// Path to the golden file.&lt;/span&gt;
    &lt;span class="n"&gt;Comment&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="c"&gt;// Comments from the file.&lt;/span&gt;
    &lt;span class="n"&gt;Content&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt; &lt;span class="c"&gt;// Content after the --- marker.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the expected output changes, I can update the golden file by calling &lt;code&gt;gld.Save()&lt;/code&gt;. This makes it simple to keep golden files in sync as the code evolves.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why &lt;code&gt;goldy&lt;/code&gt; Matters
&lt;/h3&gt;

&lt;p&gt;Building &lt;code&gt;goldy&lt;/code&gt; was my answer to the chaos of managing large outputs in tests. It’s not just about cleaner code — it’s about making tests easier to maintain and understand. When a test fails, I can quickly check the golden file’s comments to grasp the context of the expected output. Storing large outputs in separate files also keeps test files focused on logic, not data.&lt;/p&gt;

&lt;h2&gt;
  
  
  The &lt;code&gt;must&lt;/code&gt; Package
&lt;/h2&gt;

&lt;p&gt;You’ve probably dealt with the annoyance of repetitive error handling in a test setup section. I know I have — writing a setup section full of &lt;code&gt;if err != nil&lt;/code&gt; checks that clutter the code and distract from the test’s purpose. It’s functional, but it feels like wading through noise. The &lt;code&gt;must&lt;/code&gt; package is my solution: a set of helper functions that panic on errors, letting me focus on the test logic instead of boilerplate or setup code.&lt;/p&gt;

&lt;p&gt;Here is an example of a test setup I used to write before &lt;code&gt;must&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestSomething&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// --- Given ---&lt;/span&gt;
    &lt;span class="n"&gt;wd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getwd&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;fil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/data"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// --- When ---&lt;/span&gt;
    &lt;span class="n"&gt;have&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;Something&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;123&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// --- Then ---&lt;/span&gt;
    &lt;span class="n"&gt;affirm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;have&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is of course very simple example yet the &lt;code&gt;Given&lt;/code&gt; section is noisy, with error checks drowning out the test’s purpose. It works, but it’s far from elegant, in my opinion. Here’s the same test using &lt;code&gt;must&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestSomething&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// --- Given ---&lt;/span&gt;
    &lt;span class="n"&gt;wd&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;must&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getwd&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; 
    &lt;span class="n"&gt;fil&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;must&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/data"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="c"&gt;// --- When ---&lt;/span&gt;
    &lt;span class="n"&gt;have&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;Something&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;123&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// --- Then ---&lt;/span&gt;
    &lt;span class="n"&gt;affirm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;have&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is so much cleaner! The setup is concise, readable, and keeps the focus on preparing the test state. The &lt;code&gt;must&lt;/code&gt; functions handle errors by panicking if something goes wrong, which Go’s testing framework catches and reports as a test failure. I find this approach perfect for test setup, where I want to fail fast if preconditions aren’t met.&lt;/p&gt;

&lt;h3&gt;
  
  
  Exploring &lt;code&gt;must&lt;/code&gt; Functions
&lt;/h3&gt;

&lt;p&gt;I built the &lt;code&gt;must&lt;/code&gt; package using generics to make it flexible and type-safe. Here are the key functions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;func Value[T any](val T, err error) T&lt;/code&gt;- Returns the value or panics on error.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;func Values[T, TT any](val0 T, val1 TT, err error) (T, TT)&lt;/code&gt; - Returns two values or panics.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;func Nil(err error)&lt;/code&gt; - Panics if the error is non-nil.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;func First[T any](s []T, err error) T&lt;/code&gt; - Returns the first element of a slice or panics.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;func Single[T any](s []T, err error) T&lt;/code&gt; - Returns a single element or panics.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These functions work with any type, so I can use them in all sorts of testing scenarios. They’re especially useful in the &lt;code&gt;Given&lt;/code&gt; section, where I’m setting up dependencies and want to avoid error-handling clutter.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Word of Caution
&lt;/h3&gt;

&lt;p&gt;While &lt;code&gt;must&lt;/code&gt; is great for test setup, I use it carefully. Panicking is a strong choice, so I save &lt;code&gt;must&lt;/code&gt; for cases where an error means the test can’t go on. For assertions or checks in the &lt;code&gt;Then&lt;/code&gt; section, I use &lt;code&gt;affirm&lt;/code&gt; package to give clear failure messages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;Building &lt;code&gt;goldy&lt;/code&gt; and &lt;code&gt;must&lt;/code&gt; has been a solid step in making my Go Testing Module more practical and user-friendly. These packages come from my own struggles with test code, addressing the clutter and complexity you’ve probably faced too. They help me write tests that are not just functional but also clear and easy to maintain, letting me focus on catching bugs and ensuring code works as expected.&lt;/p&gt;

&lt;p&gt;It’s hard to believe this is already the fourth post in the series, and I haven’t even started on the &lt;code&gt;assert&lt;/code&gt; package yet. It’s a reminder of how many pieces need to come together before diving into assertions. Tools like &lt;code&gt;goldy&lt;/code&gt; and &lt;code&gt;must&lt;/code&gt; lay the groundwork, handling setup and comparison tasks so the &lt;code&gt;assert&lt;/code&gt; package can focus on clear, expressive checks. Building a testing module is like assembling a puzzle — each piece, has to fit just right before the assertion package can take shape.&lt;/p&gt;

&lt;p&gt;I’d love for you to check out the code for &lt;code&gt;goldy&lt;/code&gt; and &lt;code&gt;must&lt;/code&gt; in the &lt;a href="https://github.com/ctx42/testing" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;. Give them a try, poke around, and let me know what you think. If you have feedback or ideas, open an issue or drop a note on &lt;a href="https://x.com/context42" rel="noopener noreferrer"&gt;X&lt;/a&gt;. Your thoughts help improve this project, and I’m looking forward to the next steps.&lt;/p&gt;

&lt;p&gt;Thanks for reading. Until the next post, happy testing!&lt;/p&gt;

</description>
      <category>go</category>
      <category>testing</category>
    </item>
    <item>
      <title>Crafting Go Testing Module: Step 3 - Testing the Test Helpers</title>
      <dc:creator>Rafal Zajac</dc:creator>
      <pubDate>Mon, 14 Apr 2025 14:01:05 +0000</pubDate>
      <link>https://dev.to/rzajac/crafting-testing-module-step-3-testing-the-testers-en6</link>
      <guid>https://dev.to/rzajac/crafting-testing-module-step-3-testing-the-testers-en6</guid>
      <description>&lt;p&gt;In my &lt;a href="https://dev.to/rzajac/crafting-go-testing-module-step-2-core-1a33"&gt;previous post&lt;/a&gt;, I introduced the &lt;code&gt;core&lt;/code&gt; and &lt;code&gt;affirm&lt;/code&gt; packages, laying the groundwork for a dependency-free Go testing module focused on readable, intuitive tests. Now, in this third installment, I’m diving into testing the &lt;code&gt;affirm&lt;/code&gt; package - specifically, how to verify its assertion helpers. I’ll share my thought process, the challenges I faced, and how I solved them. Let’s get started!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Follow Testing Module development on &lt;a href="https://github.com/ctx42/testing" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why Test Testing Tools?
&lt;/h2&gt;

&lt;p&gt;You might ask: why go through the trouble of testing tools meant for testing? It’s a valid question, and the answer boils down to trust. If an assertion like does not work properly or misreports a crash, every test relying on it risks silent failures or false positives. That undermines reliability. By thoroughly testing, I ensure packages I create are dependable - it's like calibrating a ruler, you just created, before measuring with it. Plus, these tests serve as living examples, showing how the pieces fit together as the module grows.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Challenge of Testing Affirmations
&lt;/h2&gt;

&lt;p&gt;Testing the &lt;code&gt;core&lt;/code&gt; package was relatively straightforward, as its utilities (&lt;code&gt;IsNil&lt;/code&gt;, &lt;code&gt;WillPanic&lt;/code&gt;, &lt;code&gt;Same&lt;/code&gt;) don’t interact directly with &lt;code&gt;*testing.T&lt;/code&gt;. The &lt;code&gt;affirm&lt;/code&gt; package, on the other hand, poses a challenge. Its helpers - such as &lt;code&gt;Equal&lt;/code&gt;, &lt;code&gt;Nil&lt;/code&gt;, and &lt;code&gt;Panic&lt;/code&gt; - rely on &lt;code&gt;*testing.T&lt;/code&gt; to log errors and failures, among other tasks, making them harder to test without affecting the actual test runner.&lt;/p&gt;

&lt;p&gt;Consider the &lt;code&gt;Equal&lt;/code&gt; helper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;comparable&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;want&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;have&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Helper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;want&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;have&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;format&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"expected %T to be equal:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;  want: %#v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;  have: %#v"&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;want&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;want&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;have&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Testing the success case is simple enough. We can use the test’s own &lt;code&gt;*testing.T&lt;/code&gt; instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Test_Equal_success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// --- When ---&lt;/span&gt;
    &lt;span class="n"&gt;have&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// --- Then ---&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;have&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"expected true"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works because when &lt;code&gt;want&lt;/code&gt; equals &lt;code&gt;have&lt;/code&gt;, &lt;code&gt;Equal&lt;/code&gt; doesn’t call &lt;code&gt;t.Errorf&lt;/code&gt;, and the test passes quietly. But testing the failure case is problematic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Test_Equal_failure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// --- When ---&lt;/span&gt;
    &lt;span class="n"&gt;have&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;44&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// --- Then ---&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;have&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"expected false"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here’s the catch: when &lt;code&gt;want&lt;/code&gt; doesn't equal &lt;code&gt;have&lt;/code&gt;, &lt;code&gt;Equal&lt;/code&gt; calls &lt;code&gt;t.Errorf&lt;/code&gt;, which marks the test as failed in the real test runner. Even though &lt;code&gt;Equal&lt;/code&gt; behaved correctly (logging the error and returning false), the test itself fails, masking the fact that we’re verifying the right behavior. This is a classic testing conundrum: how do we test a function that triggers test failures without failing the test?&lt;/p&gt;

&lt;h2&gt;
  
  
  A Solution
&lt;/h2&gt;

&lt;p&gt;At this stage, my Go testing module doesn’t yet have a full-fledged mocking library (that’s coming later - stay tuned!). To test &lt;code&gt;affirm&lt;/code&gt; helpers, I needed a way to intercept &lt;code&gt;*testing.T&lt;/code&gt; calls without affecting the actual test runner. That’s where the core package’s &lt;code&gt;T&lt;/code&gt; interface and &lt;code&gt;Spy&lt;/code&gt; struct come in. I introduced the &lt;code&gt;T&lt;/code&gt; interface to capture a subset of &lt;code&gt;testing.TB&lt;/code&gt; methods used by affirm helpers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// errors or failures.&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;Failed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;
    &lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;Helper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Spy&lt;/code&gt; struct implements this interface, acting as a mock &lt;code&gt;*testing.T&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Spy&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;HelperCalled&lt;/span&gt;     &lt;span class="kt"&gt;bool&lt;/span&gt;          &lt;span class="c"&gt;// Tracks if Helper was called.&lt;/span&gt;
    &lt;span class="n"&gt;ReportedError&lt;/span&gt;    &lt;span class="kt"&gt;bool&lt;/span&gt;          &lt;span class="c"&gt;// Tracks if Error or Errorf was called.&lt;/span&gt;
    &lt;span class="n"&gt;TriggeredFailure&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;          &lt;span class="c"&gt;// Tracks if Fatal or Fatalf was called.&lt;/span&gt;
    &lt;span class="n"&gt;Messages&lt;/span&gt;         &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Buffer&lt;/span&gt; &lt;span class="c"&gt;// Log messages if set.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;Spy&lt;/code&gt;, I can track whether an assertion helper called &lt;code&gt;Error&lt;/code&gt;, &lt;code&gt;Errorf&lt;/code&gt;, or other methods, and inspect the logged messages when needed — all without failing the actual test.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adapting Affirm Helpers
&lt;/h3&gt;

&lt;p&gt;To use &lt;code&gt;Spy&lt;/code&gt;, I updated affirm helpers to accept &lt;code&gt;core.T&lt;/code&gt; instead of &lt;code&gt;*testing.T&lt;/code&gt;. For example, here’s the revised &lt;code&gt;Nil&lt;/code&gt; helper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Nil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;have&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Helper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsNil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;have&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;have&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This change is subtle but powerful: by using the &lt;code&gt;core.T&lt;/code&gt; interface, helpers become testable with &lt;code&gt;Spy&lt;/code&gt; while still working with &lt;code&gt;*testing.T&lt;/code&gt; in real tests (since &lt;code&gt;*testing.T&lt;/code&gt; implements &lt;code&gt;core.T&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Writing Tests with Spy
&lt;/h3&gt;

&lt;p&gt;Now, testing both success and failure cases becomes straightforward. Here’s how I test the &lt;code&gt;Nil&lt;/code&gt; helper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Test_Nil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// --- Given ---&lt;/span&gt;
        &lt;span class="n"&gt;spy&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewSpy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c"&gt;// --- When ---&lt;/span&gt;
        &lt;span class="n"&gt;have&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;Nil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c"&gt;// --- Then ---&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;have&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;spy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Failed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"expected passed test"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// --- Given ---&lt;/span&gt;
        &lt;span class="n"&gt;spy&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewSpy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"m0"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c"&gt;// --- When ---&lt;/span&gt;
        &lt;span class="n"&gt;have&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;Nil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c"&gt;// --- Then ---&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;have&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;spy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Failed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"expected test error"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;spy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReportedError&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"expected test error"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The success case verifies that &lt;code&gt;Nil&lt;/code&gt; returns true for a nil input and doesn’t mark the test as failed. The failure case confirms &lt;code&gt;Nil&lt;/code&gt; returns false for a non-nil input, and uses &lt;code&gt;Errorf&lt;/code&gt; to mark the test as failed. The &lt;code&gt;Spy&lt;/code&gt; lets me inspect the helper’s behavior - return values, error states, and log output - without interfering with the real test runner.&lt;/p&gt;

&lt;p&gt;Another benefit is that helpers can call &lt;code&gt;t.Fatal&lt;/code&gt; or &lt;code&gt;t.Fatalf&lt;/code&gt; without causing the real test to panic or exit the goroutine, making &lt;code&gt;Spy&lt;/code&gt; a versatile tool for testing all kinds of assertions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trade-Offs and Reflections
&lt;/h3&gt;

&lt;p&gt;Switching &lt;code&gt;affirm&lt;/code&gt; helpers to use &lt;code&gt;core.T&lt;/code&gt; instead of &lt;code&gt;*testing.T&lt;/code&gt; is a pragmatic choice, but it’s not without trade-offs. On one hand, it makes testing possible without a full mocking framework. On the other, it slightly increases the package’s complexity by introducing an interface. I’m okay with this for now - it’s a small price for testability, and &lt;code&gt;T&lt;/code&gt; is narrowly scoped to internal packages only.&lt;br&gt;
Another consideration is &lt;code&gt;Spy&lt;/code&gt;’s design. It’s minimal, capturing only the essentials (errors, failures, messages), but it’s flexible enough to grow if I add more assertion helpers later. For example, if I introduce helpers that call &lt;code&gt;Fatal&lt;/code&gt; or &lt;code&gt;FailNow&lt;/code&gt;, Spy’s &lt;code&gt;TriggeredFailure&lt;/code&gt; field will already handle them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;Testing the &lt;code&gt;affirm&lt;/code&gt; package pushed me to think creatively about mocking &lt;code&gt;*testing.T&lt;/code&gt;, and I’m thrilled with how &lt;code&gt;Spy&lt;/code&gt; and &lt;code&gt;T&lt;/code&gt; turned out. They’re not just tools for testing &lt;code&gt;affirm&lt;/code&gt; — they’re building blocks for a module that’s as reliable as it is intuitive. This step reinforced my belief that trustworthy tests start with trustworthy tools.&lt;/p&gt;

&lt;p&gt;As always, I’d love for you to check out the progress on the &lt;a href="https://github.com/ctx42/testing" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;. The code and early docs are there - feel free to explore, test it out, or share feedback via an issue. You can also follow updates on &lt;a href="https://x.com/context42" rel="noopener noreferrer"&gt;X&lt;/a&gt;. Your thoughts help shape this journey, so don’t hold back!&lt;/p&gt;

</description>
      <category>go</category>
      <category>testing</category>
      <category>programming</category>
    </item>
    <item>
      <title>Crafting Go Testing Module: Step 2 - Core</title>
      <dc:creator>Rafal Zajac</dc:creator>
      <pubDate>Wed, 09 Apr 2025 20:43:44 +0000</pubDate>
      <link>https://dev.to/rzajac/crafting-go-testing-module-step-2-core-1a33</link>
      <guid>https://dev.to/rzajac/crafting-go-testing-module-step-2-core-1a33</guid>
      <description>&lt;p&gt;Welcome back to my series on building a Go Testing Module from scratch! In the &lt;a href="https://dev.to/rzajac/crafting-go-testing-module-step-1-requirements-4l37"&gt;first post&lt;/a&gt;, I shared my vision for this project: a dependency-free, developer-friendly toolkit designed to make writing tests in Go more readable and intuitive. I laid out the key requirements - like robust assertions, clear log messages, and mocking support - and explained why I’m tackling this, from my gripes with the standard library to my passion for great developer experience (DX). If you haven’t checked it out yet, I’d recommend giving it a read to see where this journey started. Now, in this second part, we’re rolling up our sleeves and diving into the actual work. I’ll walk you through where I’m starting the implementation, breaking down the first steps and the thinking behind them. Let’s get into it!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Follow Testing Module development on &lt;a href="https://github.com/ctx42/testing" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Chicken-and-Egg Problem
&lt;/h2&gt;

&lt;p&gt;In the last post, I argued that test cases riddled with conditional statements - like &lt;code&gt;if want != have {}&lt;/code&gt; - can be a readability headache in my opinion. But here’s the catch: how do we write clean, readable tests for a Testing Module when the assertion library we’re building doesn’t exist yet? It’s a classic chicken-and-egg dilemma, and like most challenges in software engineering, it calls for a compromise. My solution is to bootstrap the project with two very small internal packages: &lt;code&gt;core&lt;/code&gt; and &lt;code&gt;affirm&lt;/code&gt;. These will provide just enough functionality to write readable tests for the rest of the module, even if their own tests lean on the standard library for now.&lt;/p&gt;

&lt;p&gt;The trade-off? The internal packages will rely on those clunky &lt;code&gt;if&lt;/code&gt; statements in their tests. But since they’re small, self-contained, and tucked away as internal tools, I’m fine with that compromise. It’s a practical move to kick things off, freeing us to craft the broader, user-facing features - like the &lt;code&gt;assert&lt;/code&gt; package - with the readability I’m chasing. Think of it as laying a foundation below ground: it’s not pretty, and no one will see it, but it’s sturdy and reliable to support the real beautiful construction above.&lt;/p&gt;

&lt;h2&gt;
  
  
  The &lt;code&gt;core&lt;/code&gt; Package
&lt;/h2&gt;

&lt;p&gt;The core package is the bedrock of this module, a minimalist set of utilities that everything else will build on. It’s lean by design, with just couple of functions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;func IsNil(have any) bool&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;func WillPanic(fn func()) (val any, stack string)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;func Same(want, have any) bool&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s break these down and see why they’re essential.&lt;/p&gt;

&lt;h3&gt;
  
  
  IsNil
&lt;/h3&gt;

&lt;p&gt;Checking for nil in Go isn’t as simple as &lt;code&gt;want == nil&lt;/code&gt;. Sure, that works for basic types like pointers or interfaces, but Go’s type system throws curveballs. An interface with a nil value and a non-nil type isn’t considered nil by a direct comparison - it’s a subtle gotcha that trips up even seasoned developers. &lt;code&gt;IsNil&lt;/code&gt; handles this properly by inspecting the underlying value and type, giving us a reliable way to test for "nil-ness" across the board. In tests, this is critical: you need to know definitively whether something’s truly absent or just masquerading as nil. It will be a basis for all assertions that need to check for nil - like &lt;code&gt;assert.NoError&lt;/code&gt;. For a deeper dive into the quirks of nil checking in Go, check out this great article:&lt;br&gt;
    &lt;a href="https://codefibershq.com/blog/golang-why-nil-is-not-always-nil" rel="noopener noreferrer"&gt;Why Golang Nil Is Not Always Nil? Nil Explained&lt;/a&gt;. &lt;/p&gt;
&lt;h3&gt;
  
  
  WillPanic
&lt;/h3&gt;

&lt;p&gt;Panics are a big deal in testing - you often want to verify that a function does panic under certain conditions (like invalid input) or doesn’t when it shouldn’t. Writing this check by hand with &lt;code&gt;recover()&lt;/code&gt; is tedious and error-prone, so &lt;code&gt;WillPanic&lt;/code&gt; wraps it up neatly. It runs the provided function, catches any panic, and returns what value was panicked, and a stack trace for context. This makes it a breeze to assert panic behavior - like ensuring a some call blows up as expected - without cluttering test code with boilerplate. Plus, that stack trace? Gold for debugging when things go sideways.&lt;/p&gt;
&lt;h3&gt;
  
  
  Same
&lt;/h3&gt;

&lt;p&gt;While equality checks (&lt;code&gt;==&lt;/code&gt;) compare values, &lt;code&gt;Same&lt;/code&gt; answers a different question: do &lt;code&gt;want&lt;/code&gt; and &lt;code&gt;have&lt;/code&gt; point to the exact same memory address? This is handy for testing pointer-heavy code, like when you’re verifying that a function returns an existing object rather than a new copy. It’s a niche but powerful tool, particularly useful when you’re verifying pointer behavior or ensuring object identity in tests. By using Go’s &lt;code&gt;any&lt;/code&gt; type, it stays flexible enough to handle any pointer type we throw at it.&lt;/p&gt;

&lt;p&gt;These three functions may seem straightforward, but they play a crucial role in driving the testing module’s development forward.&lt;/p&gt;
&lt;h2&gt;
  
  
  The &lt;code&gt;affirm&lt;/code&gt; Package
&lt;/h2&gt;

&lt;p&gt;Next up is &lt;code&gt;affirm&lt;/code&gt;, a stepping stone to the full-blown &lt;code&gt;assert&lt;/code&gt; package I’ll build later. It’s deliberately minimalist, focusing on basic assertions with clean, readable log messages for a narrow set of common cases. Think of it as a starting point: it gives us usable assertions to work with while we build out the full version. Here’s what it includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;func True(t *testing.T, have bool) bool&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;func False(t *testing.T, have bool) bool&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;func Equal[T comparable](t *testing.T, want, have T) bool&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;func DeepEqual(t *testing.T, want, have any) bool&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;func Nil(t *testing.T, have any) bool&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;func NotNil(t *testing.T, have any) bool&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;func Panic(t *testing.T, fn func()) *string&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most of the functions return a bool to indicate success (allowing you to evaluate or act on results if needed) and logs a clear, formatted failure message using &lt;code&gt;t.Error&lt;/code&gt; when things go wrong. For instance, &lt;code&gt;Equal&lt;/code&gt; generates a log message when values don’t match, such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;expected values to be equal:
  want: 42
  have: 43
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps it simple but effective. Unlike the eventual &lt;code&gt;assert&lt;/code&gt; package, &lt;code&gt;affirm&lt;/code&gt; doesn’t handle fancy edge cases - like recursive types or &lt;code&gt;trails&lt;/code&gt; mentioned in the previous article - yet. It’s just enough to make tests for the rest of the module readable, leaning on &lt;code&gt;core&lt;/code&gt; for the heavy lifting (e.g., &lt;code&gt;IsNil&lt;/code&gt; or &lt;code&gt;WillPanic&lt;/code&gt;). The real &lt;code&gt;assert&lt;/code&gt; package will take this further, adding the robustness and polish I outlined in the first post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;This post marks the first real step into coding my Go Testing Module. Starting with &lt;code&gt;core&lt;/code&gt; and &lt;code&gt;affirm&lt;/code&gt; packages solves the chicken-and-egg problem, giving me a foothold to write readable tests for the bigger features ahead - like the &lt;code&gt;assert&lt;/code&gt; package, mocking, and golden files. It’s not glamorous, but it’s foundational, and I’m excited to see it take shape. In the next post, I’ll dive into how I plan to test the &lt;code&gt;affirm&lt;/code&gt; package, ensuring it’s a solid stepping stone before we expand it further.&lt;/p&gt;

&lt;p&gt;I’d love for you to check out the progress on Go Testing Module - head over to the project's &lt;a href="https://github.com/ctx42/testing" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; to see the code and documentation in action. Feedback is always appreciated, whether it’s a suggestion, a bug report, or just your thoughts on the approach. Open an issue on GitHub or drop a message on &lt;a href="https://x.com/context42" rel="noopener noreferrer"&gt;X&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>go</category>
      <category>testing</category>
      <category>programming</category>
    </item>
    <item>
      <title>Crafting Go Testing Module: Step 1 - Requirements</title>
      <dc:creator>Rafal Zajac</dc:creator>
      <pubDate>Mon, 07 Apr 2025 19:45:57 +0000</pubDate>
      <link>https://dev.to/rzajac/crafting-go-testing-module-step-1-requirements-4l37</link>
      <guid>https://dev.to/rzajac/crafting-go-testing-module-step-1-requirements-4l37</guid>
      <description>&lt;p&gt;This post marks the start of a series where I document my journey of developing a &lt;a href="https://github.com/ctx42/testing" rel="noopener noreferrer"&gt;Testing Module&lt;/a&gt; for Go from the ground up. My goal is to share my thought process, my approach to problem-solving, and how I design software that’s not only functional but also a joy to use. A big part of this is focusing on developer experience (DX) - making sure the module feels intuitive and seamless for anyone who picks it up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why?
&lt;/h2&gt;

&lt;p&gt;I’ve always admired Go for its simplicity and readability - at least in most cases. However, I hold an unpopular opinion: tests written solely with the standard library can be tough to follow. All those &lt;code&gt;if&lt;/code&gt; statements and manual checks clutter the code and make it harder to understand what’s being tested. For example, here’s a typical assertion using the standard library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;want&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;have&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"expected %v, but got %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;want&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;have&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works, but it’s verbose and repetitive. In my view, tests become much more readable when you use assertion functions. They cut through the noise and let the intent shine through.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;want&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;have&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That said, assertions are just one piece of the puzzle. A truly comprehensive testing module requires more than assertions. &lt;/p&gt;

&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;I began by asking myself: what should a robust, full-featured testing module contain? After some thought, I compiled a list of requirements. It’s an ambitious set of goals, but I’m certain I can create a module that delivers on every one. Let’s break down each requirement, digging into what it entails and my approach to making it happen.&lt;/p&gt;

&lt;h3&gt;
  
  
  No External Dependencies
&lt;/h3&gt;

&lt;p&gt;Without relying on someone else’s API or design choices, I can tailor the module exactly to my needs and preferences. This lets me craft a cohesive, intuitive interface that aligns with my vision, rather than adapting to the quirks of external tools. Another reason is simplicity. Dependency-free modules have fewer moving parts, making them easier to understand, debug, and maintain. External libraries often come with features you don’t use, adding unnecessary overhead. By writing a module from scratch, I can optimize it for my specific needs, keeping it lightweight and fast. It’s like crafting a custom tool instead of lugging around a bulky toolbox. Plus, it’s rewarding to build a tool tailored exactly to my vision, from features to docs. The catch is that it means writing a ton of code myself — code that itself will need rigorous testing to back it up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Well-Documented
&lt;/h3&gt;

&lt;p&gt;Good documentation is as vital as clean, well-crafted code - it’s the bridge linking the module to its users. To encourage developers to adopt this module, I want to provide a clear, welcoming entry point that makes diving in a breeze. That begins with a detailed README filled with practical, real-world examples they can follow, complemented by thoroughly commented code with docstrings that explain how things work. I’ll also leverage Go’s documentation strengths by including runnable examples - interactive snippets users can execute directly in the Go playground or locally - to showcase the module in action. My goal is to simplify onboarding, transforming a potentially intimidating task into a smooth, enjoyable experience. When developers can quickly see the module’s value, test it hands-on, and understand its purpose, they’re much more likely to adopt it for their own projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  Asserting Composite and Recursive Types
&lt;/h3&gt;

&lt;p&gt;Handling complex composite types-like &lt;code&gt;[]map[int]User&lt;/code&gt; or other nested structures - shouldn’t be a stumbling block for this module. Equality assertions need to work seamlessly, whether you’re comparing a simple integer or a deeply recursive type with slices, maps, and structs intertwined. Personally, I can’t stand those massive, unwieldy diffs that spill out when composite values don’t match-they’re a headache to sift through. That’s why, when a comparison fails, this module won’t just dump a wall of text; it’ll provide a concise &lt;code&gt;trail&lt;/code&gt; of differences that I find far more readable, pinpointing the exact spot where things went off track.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;expected values to be equal:  
  trail: User.Addresses[0].Appartment  
   want: 42  
   have: 4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To me, these trails are a game-changer, making debugging faster and less frustrating.&lt;/p&gt;

&lt;p&gt;The module will be opinionated by design, shaped by my own experiences and needs as a developer, so it’s built to tackle these pain points head-on. The result is a tool that doesn’t just work - it works the way I think it should, turning a messy process into something clear and manageable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Informative, Well-Formatted Log Messages
&lt;/h3&gt;

&lt;p&gt;When a test fails, the error message shouldn’t leave developers guessing - it needs to tell the full story right away. That means delivering a neatly formatted log that clearly contrasts the expected value (&lt;code&gt;want&lt;/code&gt;) with the actual value (&lt;code&gt;have&lt;/code&gt;), so the mismatch is immediately obvious. A vague or cluttered message is a time sink, forcing developers to dig through code or rerun tests just to figure out what went wrong. On the other hand, a crisp, informative log acts like a spotlight, pointing straight to the problem and saving precious debugging time. My goal is to craft messages that not only highlight the failure but also provide enough context - like variable names or data types - to make fixing it as painless as possible. After all, a well-designed error message isn’t just a report; it’s a tool to keep workflows smooth and frustration low.&lt;/p&gt;

&lt;p&gt;I'm thinking about messages like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// assert.Equal(42, byte(42))

expected values to be equal:  
      want: 42  
      have: 0x2a ('*')  
 want type: int  
 have type: uint8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Basic Assertions
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;assert&lt;/code&gt; package will be the backbone of this testing module, and it needs to cover at least 90% of the typical use cases developers run into day-to-day. In these early stages, I’m starting with the essentials - scenarios I encounter regularly in my own work, like equality checks (&lt;code&gt;assert.Equal&lt;/code&gt;), nil checks (&lt;code&gt;assert.Nil&lt;/code&gt;), and basic boolean assertions. These are the bread-and-butter tests that pop up everywhere, and getting them right sets a solid foundation. From there, I’ll build out the package iteratively, refining it based on real-world feedback and evolving needs. The goal isn’t just to check boxes but to make assertions so intuitive and reliable that developers can write tests quickly without second-guessing the tools. Over time, this focused approach will ensure the module grows into something that feels indispensable, handling the common cases effortlessly while leaving room for more advanced features down the line.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configurable
&lt;/h3&gt;

&lt;p&gt;Flexibility isn’t just a nice-to-have - it’s a cornerstone of this module. I want developers to feel like they can mold it to suit their unique needs, whether that’s skipping specific fields during comparisons, registering custom checkers for their own types, or fine-tuning how those handy data trails are generated. Configuration isn’t about overwhelming users with options; it’s about handing them the reins to adapt the tool to their workflows seamlessly. For instance, if a struct has fields that don’t matter for a test, they can opt to ignore them without cluttering the output. Or if they’re working with a quirky custom type, they can plug in a tailored checker to handle it just right. To me, this kind of control is empowering - it transforms the module from a rigid framework into a versatile ally that bends to fit the way developers already work, rather than forcing them to adjust. And that flexibility sets the stage for extensibility, where developers can take this adaptability even further by building their own custom features on top.&lt;/p&gt;

&lt;h3&gt;
  
  
  Extensible
&lt;/h3&gt;

&lt;p&gt;If configurability is about fine-tuning the module, extensibility is about opening the door wide for customization. I want this module to serve as an open invitation—a foundation that empowers developers to craft their own test helpers and tools, tailored to their unique ideas and needs. To make that happen, I’m including a set of building blocks: helper functions, assertion templates, and value dumpers that turn any type into a readable format. With these, anyone can craft their own test utilities, whether it’s a niche assertion for a specific use case or a custom log formatter that fits their style. ExtensibilitImage descriptiony isn’t just a feature - it’s a promise that the module can evolve alongside its users, growing more powerful as they add their own flair. My goal is to strike a balance: provide a solid foundation that’s ready to use out of the box, while leaving plenty of room for developers to extend it into something uniquely theirs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mocking
&lt;/h3&gt;

&lt;p&gt;A testing module isn’t truly complete without solid mocking support - it’s a non-negotiable for serious testing. Mocking lets developers isolate dependencies, swap out real implementations with controlled fakes, and dig into those tricky edge cases that are otherwise tough to replicate. I’ve been burned before by tests that flaked out because of tangled external systems, and I’m determined to spare others that frustration. That’s why I’m building a robust mocking library into this module, designed to make the process as painless as possible. Whether it’s stubbing out a database call or simulating a flaky network, the goal is to give developers a simple, reliable way to create mocks that just work. It’s about stripping away complexity so they can focus on testing their logic, not wrestling with setup - and that’s where a mock generator comes in to take the heavy lifting even further.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mock Generator
&lt;/h3&gt;

&lt;p&gt;Hand-writing mocks might be manageable for a small test suite, but it quickly turns into a tedious, error-prone slog as projects grow. I’ve spent too many hours tweaking mock implementations by hand, only to miss a method or typo my way into a subtle bug-there’s got to be a better way. That’s why my module will include a mock generator that automatically creates mocks from interfaces, saving time and cutting down on human error. It’s a tool designed to churn out reliable, flexible, and ready-to-use mocks with minimal effort, so developers can focus on writing tests instead of wrestling with boilerplate. For me, this is a must-have to scale test coverage efficiently - whether you’re mocking one dependency or a dozen, it keeps the process smooth and mistake-free, building on the mocking support to make testing even more streamlined.&lt;/p&gt;

&lt;h3&gt;
  
  
  Golden Files
&lt;/h3&gt;

&lt;p&gt;Comparing long strings, multiline outputs, or JSON blobs manually is a hassle without golden files. Basic support for them will let developers set expected outputs and check results easily, catching regressions without extra effort. For example, when testing a function that generates a big configuration file, you can save the correct version as a golden file and compare it to the output each time. Or if you’re working on an API that returns complex JSON, a golden file ensures the structure and data stay consistent. It’s a simple way to make testing these cases faster and more reliable in day-to-day work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test Kit
&lt;/h3&gt;

&lt;p&gt;To wrap things up, I’m adding a &lt;code&gt;tstkit&lt;/code&gt; package packed with utilities to make test writing faster and more readable. It’ll include handy helper functions to reduce repetitive code. The idea is to smooth out the rough edges of testing, making it less of a chore and more of a breeze. It’s all about giving developers practical shortcuts to keep their focus on the logic, not the busywork.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqs1cev8ufl9umjyyzzla.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqs1cev8ufl9umjyyzzla.png" alt="Go Testing Module" width="695" height="630"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is just the beginning of my journey to build a Go Testing Module from scratch, and I’m excited to see where it takes me. I’ve laid out the vision - readable tests, no dependencies, and a toolbox that grows with its users - and now it’s time to roll up my sleeves and make it real. Each step will bring its own challenges and trade-offs, and I’ll be sharing them all here as I go. Stick around for the ride! In the next post, we’ll dive into the first real hurdle: designing readable tests for the module itself when the assertion library we need doesn’t exist yet. How do we write clean assertions without the tools we’re building? It’s a classic chicken-and-egg puzzle, and I can’t wait to unpack how we’ll crack it.&lt;/p&gt;

&lt;p&gt;I’d love for you to check out the progress on Go Testing Module - head over to the &lt;a href="https://github.com/ctx42/testing" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; to see the code and documentation in action. Feedback is always appreciated, whether it’s a suggestion, a bug report, or just your thoughts on the approach. Open an issue on GitHub or drop a message on &lt;a href="https://x.com/context42" rel="noopener noreferrer"&gt;X&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>go</category>
      <category>testing</category>
      <category>programming</category>
    </item>
    <item>
      <title>Stringify with Style: a Configurable Go Package for Value Dumping</title>
      <dc:creator>Rafal Zajac</dc:creator>
      <pubDate>Tue, 18 Mar 2025 18:25:23 +0000</pubDate>
      <link>https://dev.to/rzajac/the-dump-package-for-go-521m</link>
      <guid>https://dev.to/rzajac/the-dump-package-for-go-521m</guid>
      <description>&lt;p&gt;As a Go developer, I’ve often faced the frustration of comparing complex data structures during testing or debugging sessions. Nested structs, maps with mixed types, or recursive types can turn a simple equality check into a headache. Go’s built-in &lt;code&gt;reflect.DeepEqual&lt;/code&gt; is a start, but when it fails, it leaves you guessing why two values differ. That’s where the &lt;code&gt;dump&lt;/code&gt; package steps in — a utility that transforms any Go value into a human-readable string, making differences easy to spot, especially with diff tools. After wrestling with these challenges myself, I created the &lt;code&gt;dump&lt;/code&gt; package to simplify the process, and I’m excited to share how it can help you.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;dump&lt;/code&gt; package is part of the Ctx42 Testing Module, an evolving project aimed at building a flexible, developer-friendly testing framework for Go. In this article, I’ll walk you through its features, show you how to use it effectively, and explain why it’s a game-changer for testing and debugging in Go.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the dump Package?
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;dump&lt;/code&gt; package, available at &lt;a href="https://github.com/ctx42/testing/tree/master/pkg/dump/README.md" rel="noopener noreferrer"&gt;github.com/ctx42/testing/tree/master/pkg/dump&lt;/a&gt;, provides a configurable way to serialize any Go value — whether it’s a simple integer, a nested struct, or a recursive data structure — into a human-readable string. This is invaluable in testing, where comparing complex values often demands more than a boolean result. By rendering values as strings, the dump package lets you use string comparison or diff tools to quickly pinpoint discrepancies, offering clarity where &lt;code&gt;reflect.DeepEqual&lt;/code&gt; falls short.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic Usage
&lt;/h2&gt;

&lt;p&gt;Getting started with the dump package is straightforward. Using its default configuration, you can dump a value with minimal setup. Here’s an example with a struct from the types package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"time"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/ctx42/testing/internal/types"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/ctx42/testing/pkg/dump"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TA&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Dur&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Int&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Loc&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WAW&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Str&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"abc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Tim&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTC&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;TAp&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;have&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;dump&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;have&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Int&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Str&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"abc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Tim&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"2000-01-02T03:04:05Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Dur&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"3ns"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Loc&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Europe/Warsaw"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;TAp&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The default configuration produces a nicely formatted, multi-line string. Fields are listed in the order they’re declared in the struct, ensuring consistent output for reliable comparisons.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuration Options
&lt;/h2&gt;

&lt;p&gt;One of the &lt;code&gt;dump&lt;/code&gt;'s strengths is its configurability. You can tweak how values are rendered to fit your needs. Here are the key options, each with practical examples:&lt;/p&gt;

&lt;h3&gt;
  
  
  Flat Output
&lt;/h3&gt;

&lt;p&gt;For a compact, single-line representation, use the &lt;code&gt;dump.Flat&lt;/code&gt; option — ideal for logs or quick comparisons:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;"int"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"loc"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WAW&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"nil"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;dump&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dump&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithFlat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;have&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;dump&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;have&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"int"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"loc"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Europe/Warsaw"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"nil"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When dumping maps, keys are sorted (when possible) to ensure consistent output.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom Time Formats
&lt;/h3&gt;

&lt;p&gt;Customize how time.Time values appear with the &lt;code&gt;dump.WithTimeFormat&lt;/code&gt; option. This is great for aligning with your preferred timestamp style:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;dump&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dump&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithFlat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dump&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithTimeFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Kitchen&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;have&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;dump&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;have&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"3:04AM"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;42&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pointer Addresses
&lt;/h3&gt;

&lt;p&gt;By default, pointer addresses are hidden, but you can reveal them with the &lt;code&gt;dump.WithPtrAddr&lt;/code&gt; option — useful for distinguishing between instances:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;"fn0"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="s"&gt;"fn1"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;dump&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dump&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithPtrAddr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;have&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;dump&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;have&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output (example addresses):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;map[string]any{
    "fn0": &amp;lt;func&amp;gt;(&amp;lt;0x533760&amp;gt;),
    "fn1": &amp;lt;func&amp;gt;(&amp;lt;0x533780&amp;gt;),
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Custom Dumpers
&lt;/h2&gt;

&lt;p&gt;For ultimate flexibility, define custom dumpers to control how specific types are serialized. A custom dumper is a function matching the &lt;code&gt;dump.Dumper&lt;/code&gt; signature:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Dumper&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dmp&lt;/span&gt; &lt;span class="n"&gt;Dump&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here’s an example that renders integers as hexadecimal values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="n"&gt;custom&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dmp&lt;/span&gt; &lt;span class="n"&gt;dump&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Dump&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lvl&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Kind&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;reflect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Int&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%X"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"unexpected kind"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;dump&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dump&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Flat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dump&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithCompact&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dump&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithDumper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;custom&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;have&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;dump&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;have&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

&lt;/div&gt;



&lt;p&gt;This feature lets you tailor the output to your specific use case, enhancing readability or compatibility with other tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling Complex and Recursive Types
&lt;/h2&gt;

&lt;p&gt;The dump package excels at managing complex and recursive data structures, with built-in cycle detection to prevent infinite loops. Here’s an example with a recursive struct:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Value&lt;/span&gt;    &lt;span class="kt"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;Children&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Children&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Children&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Children&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Children&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;}}},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;have&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;dump&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;have&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    Value: 1,
    Children: []*main.Node{
        {
            Value: 2,
            Children: nil,
        },
        {
            Value: 3,
            Children: []*main.Node{
                {
                    Value: 4,
                    Children: nil,
                },
            },
        },
    },
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This structured output makes nested data easy to visualize and compare.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using in Tests
&lt;/h2&gt;

&lt;p&gt;The primary use case for the &lt;code&gt;dump&lt;/code&gt; package in testing is to visualize not equal values by dumping them as part of error message or as an input to a diff tool to highlight differences.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extensibility
&lt;/h2&gt;

&lt;p&gt;Built with extensibility in mind, the dump package lets you define custom dumpers for your own types. This adaptability ensures it can evolve with your project, integrating seamlessly with your specific needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Comparing complex data structures in Go doesn’t have to be a struggle. The dump package simplifies the process by turning any value into a human-readable string, ready for comparison or diffing. Its configurability — flat output, custom time formats, pointer addresses, and custom dumpers—makes it versatile, while its handling of recursive types and testing support make it indispensable.&lt;/p&gt;

&lt;p&gt;I encourage you to try the &lt;a href="https://github.com/ctx42/testing/tree/master/pkg/dump/README.md" rel="noopener noreferrer"&gt;dump&lt;/a&gt; package in your next project and explore the Ctx42 Testing Modules. It’s an evolving toolset, and your feedback or contributions could help shape its future. Let’s make testing in Go simpler, more reliable, and—yes—even enjoyable!&lt;/p&gt;

</description>
      <category>go</category>
      <category>programming</category>
    </item>
    <item>
      <title>Testing Go Test Helpers</title>
      <dc:creator>Rafal Zajac</dc:creator>
      <pubDate>Sat, 15 Mar 2025 17:31:43 +0000</pubDate>
      <link>https://dev.to/rzajac/testing-go-test-helpers-4k3g</link>
      <guid>https://dev.to/rzajac/testing-go-test-helpers-4k3g</guid>
      <description>&lt;p&gt;If you've spent any time writing Go tests, you've probably encountered the joy of &lt;code&gt;*testing.T&lt;/code&gt;. It's the backbone of Go's testing framework  -  powerful, flexible, and ubiquitous. But as your test suite grows, you might find yourself repeating the same chunks of test logic across multiple test cases. To streamline your tests, improve readability, and reduce complexity you decide to move those chunks to reusable functions called &lt;em&gt;test helpers&lt;/em&gt;. Various assert libraries are prime examples of test helpers, turning verbose checks into concise assertions.&lt;/p&gt;

&lt;p&gt;But here's the catch: how do you test the test helpers themselves? After all, these are the tools you rely on to ensure your code works as expected. If they fail, your tests might silently lie to you. This is where the tester package comes to the rescue. In this article, we'll explore why testing test helpers matters and how the &lt;code&gt;tester&lt;/code&gt; package makes it possible - and even enjoyable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test Helpers Are Code Too
&lt;/h2&gt;

&lt;p&gt;Let's start with a simple example. Imagine you've written a test helper to check if a number is odd:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// IsOdd asserts "have" is odd number. Returns true if it is,&lt;/span&gt;
&lt;span class="c"&gt;// otherwise marks the test as failed, writes error message to &lt;/span&gt;
&lt;span class="c"&gt;// the test log and returns false.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;IsOdd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;have&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Helper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;have&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"expected %d to be odd"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;have&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It takes a &lt;code&gt;*testing.T&lt;/code&gt; instance (aka &lt;em&gt;test manager&lt;/em&gt;, or &lt;em&gt;test context&lt;/em&gt;), marks itself as a helper with call to &lt;code&gt;t.Helper&lt;/code&gt; method, and either returns &lt;em&gt;true&lt;/em&gt; on success or logs an error or returns &lt;em&gt;false&lt;/em&gt; based on the input. It's simple, reusable, and makes your tests cleaner. You might use it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Test_Something&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;IsOdd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"additional log message or more assertions"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great, right? But what if there's a bug in &lt;code&gt;IsOdd&lt;/code&gt;? Maybe the modulo check is backwards, or the error message is misleading. How do you ensure this test helper behaves as expected? You can't just trust it  -  you need to test it. This is tricky because &lt;code&gt;*testing.T&lt;/code&gt; is deeply tied to the Go test runner, making it impossible to mock or inspect its behavior programmatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  The &lt;code&gt;tester&lt;/code&gt; Package
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;tester&lt;/code&gt; package solves this problem by introducing two key components: the &lt;code&gt;T&lt;/code&gt; interface and the &lt;code&gt;Spy&lt;/code&gt; struct. Together, they let you test your test helpers with precision and confidence.&lt;br&gt;
The &lt;code&gt;tester.T&lt;/code&gt; interface is a curated subset of &lt;code&gt;*testing.T&lt;/code&gt;'s methods - things like &lt;code&gt;Errorf&lt;/code&gt;, &lt;code&gt;Fatal&lt;/code&gt;, &lt;code&gt;Log&lt;/code&gt;, &lt;code&gt;Helper&lt;/code&gt;, &lt;code&gt;Cleanup&lt;/code&gt; and others to allow mocking.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// T is a subset of [testing.TB] interface.&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="n"&gt;Cleanup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
   &lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;FailNow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
   &lt;span class="n"&gt;Failed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;
   &lt;span class="n"&gt;Helper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
   &lt;span class="n"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;Logf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
   &lt;span class="n"&gt;Setenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;Skip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;TempDir&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
   &lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any test helper that works with &lt;code&gt;*testing.T&lt;/code&gt; can work with &lt;code&gt;tester.T&lt;/code&gt;, as long as it sticks to methods supported by &lt;code&gt;tester.T&lt;/code&gt;. This means you can test &lt;code&gt;IsOdd&lt;/code&gt; helper by first refactoring it to use &lt;code&gt;tester.T&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// IsOdd asserts "have" is odd number. Returns true if it is,&lt;/span&gt;
&lt;span class="c"&gt;// otherwise marks the test as failed, writes error message to &lt;/span&gt;
&lt;span class="c"&gt;// the test log and returns false.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;IsOdd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;tester&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;have&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Helper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;have&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"expected %d to be odd"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;have&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then writing tests for it using &lt;code&gt;Spy&lt;/code&gt; instance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Spy Your Test Helpers
&lt;/h2&gt;

&lt;p&gt;The real magic happens with &lt;code&gt;tester.Spy&lt;/code&gt;. This struct implements &lt;code&gt;tester.T&lt;/code&gt; and acts as a spy  -  it tracks every interaction your test helper has with the test manager. Want to know if &lt;code&gt;t.Helper&lt;/code&gt; was called? If an error was logged? If a cleanup function was registered? &lt;code&gt;Spy&lt;/code&gt; has you covered.&lt;/p&gt;

&lt;p&gt;Here's how you might test IsOdd using Spy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Test_IsOdd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"error is not odd number"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// --- Given ---&lt;/span&gt;

    &lt;span class="c"&gt;// Set up the spy with expectations&lt;/span&gt;
    &lt;span class="n"&gt;tspy&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;tester&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tspy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExpectError&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                              &lt;span class="c"&gt;// Expect an error.&lt;/span&gt;
    &lt;span class="n"&gt;tspy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExpectLogEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"expected %d to be odd"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// Expect log.&lt;/span&gt;
    &lt;span class="n"&gt;tspy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                                    &lt;span class="c"&gt;// No more expectations.&lt;/span&gt;

    &lt;span class="c"&gt;// --- When ---&lt;/span&gt;
    &lt;span class="n"&gt;success&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;IsOdd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tspy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// Run the helper.&lt;/span&gt;

    &lt;span class="c"&gt;// --- Then ---&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;success&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c"&gt;// Verify the outcome.&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"expected success to be false"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;tspy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AssertExpectations&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// Ensure all expectations were met.&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"success is odd number"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Given&lt;/span&gt;
    &lt;span class="n"&gt;tspy&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;tester&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tspy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// When&lt;/span&gt;
    &lt;span class="n"&gt;success&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;IsOdd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tspy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Then&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;success&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"expected success to be true"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// The `tspy.AssertExpectations()` is called automatically.&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, &lt;code&gt;Spy&lt;/code&gt; lets you: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Define expectations (e.g., "a &lt;code&gt;t.Error()&lt;/code&gt; should be called", "a message should be logged").&lt;/li&gt;
&lt;li&gt;Run the helper with a controlled test manager.&lt;/li&gt;
&lt;li&gt;Verify both the helper's return value and its interactions with the test manager.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If &lt;code&gt;IsOdd&lt;/code&gt; doesn't behave as expected  -  say, it forgets to call &lt;code&gt;t.Helper()&lt;/code&gt; or logs the wrong message  -  &lt;code&gt;tspy.AssertExpectations()&lt;/code&gt; will fail the test, pinpointing the issue.&lt;/p&gt;

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

&lt;p&gt;You might be thinking: "Do I really need to test my test helpers? They're simple!" But simplicity doesn't guarantee correctness. Test helpers are code, and code can have bugs. A subtle mistake in a helper could cascade across dozens of tests, leading to &lt;em&gt;false positives&lt;/em&gt; (passing tests that should fail) or &lt;em&gt;false negatives&lt;/em&gt; (failing tests that should pass). By testing your helpers, you build a stronger foundation for your entire test suite.&lt;/p&gt;

&lt;p&gt;The tester package makes this process practical:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Flexibility:&lt;/strong&gt; The &lt;code&gt;T&lt;/code&gt; interface ensures your helpers are portable and testable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Precision:&lt;/strong&gt; &lt;code&gt;Spy&lt;/code&gt; lets you set fine-grained expectations, from method calls through log messages to cleanup calls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confidence:&lt;/strong&gt; You can trust your helpers because you've verified their behavior.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Beyond the Basics
&lt;/h2&gt;

&lt;p&gt;The tester package doesn't stop at simple error checks. With &lt;code&gt;Spy&lt;/code&gt;, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verify cleanup functions are registered and executed (&lt;code&gt;ExpectCleanups&lt;/code&gt;, &lt;code&gt;Finish&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Match log messages with exact strings, substrings, or regex patterns (&lt;code&gt;ExpectLogEqual&lt;/code&gt;, &lt;code&gt;ExpectLogContain&lt;/code&gt;, &lt;code&gt;ExpectLogNotContain&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Inspect temporary directories created by &lt;code&gt;TempDir&lt;/code&gt; (&lt;code&gt;GetTempDir&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Even ignore logs when they're irrelevant (&lt;code&gt;Spy.IgnoreLogs&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;And many more.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This depth makes it invaluable for testing complex helpers  -  think assertion libraries or setup/teardown utilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test Smarter, Not Harder
&lt;/h2&gt;

&lt;p&gt;Testing test helpers might sound like overkill, but it's a small investment with a big payoff. The tester package empowers you to treat your helpers as first-class citizens in your codebase, ensuring they're as reliable as the code they test. Next time you write a helper, don't just use it  -  test it with tester. Your future self (and your test suite) will thank you.&lt;/p&gt;

&lt;p&gt;Want to dive deeper? Check out the &lt;a href="https://github.com/ctx42/testing/tree/master/pkg/tester/README.md" rel="noopener noreferrer"&gt;full documentation&lt;/a&gt; for the tester package and start spying on your test helpers today!&lt;/p&gt;

</description>
      <category>go</category>
      <category>testing</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
