<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Unit Testing in Go]]></title><description><![CDATA[Unit Testing in Go]]></description><link>https://gounittesting.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Wed, 23 Sep 2026 12:12:54 GMT</lastBuildDate><atom:link href="https://gounittesting.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[🧪 Mastering Unit Testing in Golang — From Basics to Advanced (with Mocks & Stubs)]]></title><description><![CDATA[“Testing is not just debugging — it’s designing software that never breaks.”
Unit testing is one of the most underrated skills in Go.The language was designed with testing in mind — simple, fast, tool-built, and clean.
In this post, you will learn:
✅...]]></description><link>https://gounittesting.hashnode.dev/mastering-unit-testing-in-golang-from-basics-to-advanced-with-mocks-and-stubs</link><guid isPermaLink="true">https://gounittesting.hashnode.dev/mastering-unit-testing-in-golang-from-basics-to-advanced-with-mocks-and-stubs</guid><category><![CDATA[golang]]></category><category><![CDATA[Testing]]></category><category><![CDATA[unit testing]]></category><category><![CDATA[backend]]></category><category><![CDATA[Software Engineering]]></category><dc:creator><![CDATA[NEELAM SAI KUMAR]]></dc:creator><pubDate>Thu, 30 Oct 2025 05:52:30 GMT</pubDate><content:encoded><![CDATA[<p><strong><em>“Testing is not just debugging — it’s designing software that never breaks.”</em></strong></p>
<p>Unit testing is one of the most underrated skills in Go.<br />The language was designed with <strong>testing in mind</strong> — simple, fast, tool-built, and clean.</p>
<p>In this post, you will learn:</p>
<p>✅ Basic Go testing<br />✅ Table-driven tests<br />✅ Setup &amp; teardown<br />✅ Parallel tests &amp; benchmarks<br />✅ Mocks — when &amp; how<br />✅ Stubs — when &amp; how<br />✅ Full production-style example (Repository + Email service)<br />✅ Best practices + folder structure</p>
<p>Let’s start 👇</p>
<hr />
<h2 id="heading-what-is-unit-testing">🧱 What Is Unit Testing?</h2>
<p>Unit tests validate the smallest piece of code (a function).<br />They give confidence when:</p>
<ul>
<li><p>Refactoring</p>
</li>
<li><p>Releasing to production</p>
</li>
<li><p>Fixing bugs</p>
</li>
<li><p>Working in large codebases</p>
</li>
</ul>
<p>In Go, testing needs <strong>no extra framework</strong> — just:</p>
<pre><code class="lang-plaintext">go test ./...
</code></pre>
<hr />
<h2 id="heading-basic-test-in-go">✨ Basic Test in Go</h2>
<p>mathutil.go</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> mathutil
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Add</span><span class="hljs-params">(a, b <span class="hljs-keyword">int</span>)</span> <span class="hljs-title">int</span></span> { <span class="hljs-keyword">return</span> a + b }
</code></pre>
<p>mathutil_test.go</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> mathutil
<span class="hljs-keyword">import</span> <span class="hljs-string">"testing"</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">TestAdd</span><span class="hljs-params">(t *testing.T)</span></span> {
    <span class="hljs-keyword">if</span> Add(<span class="hljs-number">2</span>, <span class="hljs-number">3</span>) != <span class="hljs-number">5</span> {
        t.Error(<span class="hljs-string">"expected 5"</span>)
    }
}
</code></pre>
<p>Run:</p>
<pre><code class="lang-plaintext">go test
</code></pre>
<hr />
<h2 id="heading-table-driven-testing-go-idiomatic">✅ Table-Driven Testing (Go-idiomatic)</h2>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">TestAddTable</span><span class="hljs-params">(t *testing.T)</span></span> {
    tests := []<span class="hljs-keyword">struct</span> {
        name <span class="hljs-keyword">string</span>
        a, b <span class="hljs-keyword">int</span>
        want <span class="hljs-keyword">int</span>
    }{
        {<span class="hljs-string">"positive"</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">5</span>},
        {<span class="hljs-string">"with zero"</span>, <span class="hljs-number">10</span>, <span class="hljs-number">0</span>, <span class="hljs-number">10</span>},
        {<span class="hljs-string">"negative"</span>, <span class="hljs-number">-2</span>, <span class="hljs-number">-3</span>, <span class="hljs-number">-5</span>},
    }

    <span class="hljs-keyword">for</span> _, tc := <span class="hljs-keyword">range</span> tests {
        t.Run(tc.name, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(t *testing.T)</span></span> {
            <span class="hljs-keyword">if</span> Add(tc.a, tc.b) != tc.want {
                t.Errorf(<span class="hljs-string">"expected %d"</span>, tc.want)
            }
        })
    }
}
</code></pre>
<hr />
<h2 id="heading-setup-amp-teardown-example">🔧 Setup &amp; Teardown Example</h2>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">createTestFile</span><span class="hljs-params">()</span> <span class="hljs-title">string</span></span> {
    f := <span class="hljs-string">"/tmp/data.txt"</span>
    os.WriteFile(f, []<span class="hljs-keyword">byte</span>(<span class="hljs-string">"hello"</span>), <span class="hljs-number">0644</span>)
    <span class="hljs-keyword">return</span> f
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">TestFile</span><span class="hljs-params">(t *testing.T)</span></span> {
    f := createTestFile()
    <span class="hljs-keyword">defer</span> os.Remove(f)

    b, _ := os.ReadFile(f)
    <span class="hljs-keyword">if</span> <span class="hljs-keyword">string</span>(b) != <span class="hljs-string">"hello"</span> {
        t.Fatal(<span class="hljs-string">"file content mismatch"</span>)
    }
}
</code></pre>
<hr />
<h2 id="heading-parallel-tests-amp-benchmarks">🚀 Parallel Tests &amp; Benchmarks</h2>
<p>Run tests in parallel:</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">TestParallel</span><span class="hljs-params">(t *testing.T)</span></span> {
    values := []<span class="hljs-keyword">int</span>{<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>}
    <span class="hljs-keyword">for</span> _, v := <span class="hljs-keyword">range</span> values {
        v := v
        t.Run(fmt.Sprint(v), <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(t *testing.T)</span></span> {
            t.Parallel()
        })
    }
}
</code></pre>
<p>Benchmark example:</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">BenchmarkAdd</span><span class="hljs-params">(b *testing.B)</span></span> {
    <span class="hljs-keyword">for</span> i := <span class="hljs-number">0</span>; i &lt; b.N; i++ {
        Add(<span class="hljs-number">2</span>, <span class="hljs-number">3</span>)
    }
}
</code></pre>
<hr />
<h2 id="heading-what-are-mocks-amp-stubs">🧪 What Are Mocks &amp; Stubs?</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Concept</td><td>Purpose</td><td>Use case</td></tr>
</thead>
<tbody>
<tr>
<td><strong>Stub</strong></td><td>Returns fixed data</td><td>Testing logic dependent on responses</td></tr>
<tr>
<td><strong>Mock</strong></td><td>Verifies interaction (calls, args)</td><td>Ensuring service calls / side-effects happen</td></tr>
</tbody>
</table>
</div><hr />
<h2 id="heading-stub-example-simple-dependency">🧩 Stub Example (simple dependency)</h2>
<p>payment.go</p>
<pre><code class="lang-go"><span class="hljs-keyword">type</span> Gateway <span class="hljs-keyword">interface</span> { 
    Pay(amount <span class="hljs-keyword">float64</span>) <span class="hljs-keyword">string</span> 
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Charge</span><span class="hljs-params">(g Gateway, amt <span class="hljs-keyword">float64</span>)</span> <span class="hljs-title">string</span></span> {
    <span class="hljs-keyword">return</span> <span class="hljs-string">"result:"</span> + g.Pay(amt)
}
</code></pre>
<p>payment_test.go</p>
<pre><code class="lang-go"><span class="hljs-keyword">type</span> stubGateway <span class="hljs-keyword">struct</span>{}
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(s stubGateway)</span> <span class="hljs-title">Pay</span><span class="hljs-params">(a <span class="hljs-keyword">float64</span>)</span> <span class="hljs-title">string</span></span> { <span class="hljs-keyword">return</span> <span class="hljs-string">"OK"</span> }

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">TestCharge</span><span class="hljs-params">(t *testing.T)</span></span> {
    res := Charge(stubGateway{}, <span class="hljs-number">10</span>)
    <span class="hljs-keyword">if</span> res != <span class="hljs-string">"result:OK"</span> { t.Errorf(<span class="hljs-string">"unexpected %s"</span>, res) }
}
</code></pre>
<hr />
<h2 id="heading-full-real-world-example-service-repository-email">🎯 Full Real-World Example: Service + Repository + Email</h2>
<h3 id="heading-code-under-test">✅ Code Under Test</h3>
<p>registration_service.go</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> registration

<span class="hljs-keyword">import</span> <span class="hljs-string">"errors"</span>

<span class="hljs-keyword">type</span> Repo <span class="hljs-keyword">interface</span> { 
    Save(email <span class="hljs-keyword">string</span>) error 
}

<span class="hljs-keyword">type</span> Mailer <span class="hljs-keyword">interface</span> { 
    Send(email <span class="hljs-keyword">string</span>) error 
}

<span class="hljs-keyword">type</span> Service <span class="hljs-keyword">struct</span> {
    repo Repo
    mail Mailer
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">New</span><span class="hljs-params">(r Repo, m Mailer)</span> *<span class="hljs-title">Service</span></span> { <span class="hljs-keyword">return</span> &amp;Service{r, m} }

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(s *Service)</span> <span class="hljs-title">Register</span><span class="hljs-params">(email <span class="hljs-keyword">string</span>)</span> <span class="hljs-title">error</span></span> {
    <span class="hljs-keyword">if</span> email == <span class="hljs-string">""</span> {
        <span class="hljs-keyword">return</span> errors.New(<span class="hljs-string">"invalid email"</span>)
    }

    <span class="hljs-keyword">if</span> err := s.repo.Save(email); err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> err
    }
    <span class="hljs-keyword">return</span> s.mail.Send(email)
}
</code></pre>
<hr />
<h3 id="heading-test-with-mocks">✅ Test with Mocks</h3>
<p>registration_service_test.go</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> registration

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"errors"</span>
    <span class="hljs-string">"testing"</span>
)

<span class="hljs-keyword">type</span> mockRepo <span class="hljs-keyword">struct</span> {
    saved     <span class="hljs-keyword">string</span>
    shouldErr <span class="hljs-keyword">bool</span>
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(m *mockRepo)</span> <span class="hljs-title">Save</span><span class="hljs-params">(e <span class="hljs-keyword">string</span>)</span> <span class="hljs-title">error</span></span> {
    <span class="hljs-keyword">if</span> m.shouldErr { <span class="hljs-keyword">return</span> errors.New(<span class="hljs-string">"db error"</span>) }
    m.saved = e
    <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
}

<span class="hljs-keyword">type</span> mockMailer <span class="hljs-keyword">struct</span> {
    sent <span class="hljs-keyword">string</span>
    err  <span class="hljs-keyword">bool</span>
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(m *mockMailer)</span> <span class="hljs-title">Send</span><span class="hljs-params">(e <span class="hljs-keyword">string</span>)</span> <span class="hljs-title">error</span></span> {
    <span class="hljs-keyword">if</span> m.err { <span class="hljs-keyword">return</span> errors.New(<span class="hljs-string">"smtp error"</span>) }
    m.sent = e
    <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">TestRegisterSuccess</span><span class="hljs-params">(t *testing.T)</span></span> {
    r := &amp;mockRepo{}
    m := &amp;mockMailer{}
    svc := New(r, m)

    <span class="hljs-keyword">if</span> err := svc.Register(<span class="hljs-string">"a@b.com"</span>); err != <span class="hljs-literal">nil</span> {
        t.Fatal(err)
    }

    <span class="hljs-keyword">if</span> r.saved != <span class="hljs-string">"a@b.com"</span> { t.Error(<span class="hljs-string">"not saved"</span>) }
    <span class="hljs-keyword">if</span> m.sent != <span class="hljs-string">"a@b.com"</span> { t.Error(<span class="hljs-string">"email not sent"</span>) }
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">TestRegisterRepoFail</span><span class="hljs-params">(t *testing.T)</span></span> {
    r := &amp;mockRepo{shouldErr: <span class="hljs-literal">true</span>}
    m := &amp;mockMailer{}
    svc := New(r, m)

    <span class="hljs-keyword">if</span> err := svc.Register(<span class="hljs-string">"a@b.com"</span>); err == <span class="hljs-literal">nil</span> {
        t.Error(<span class="hljs-string">"expected error"</span>)
    }
}
</code></pre>
<hr />
<h2 id="heading-code-coverage">📊 Code Coverage</h2>
<pre><code class="lang-plaintext">go test -cover
go test -coverprofile=cover.out
go tool cover -html=cover.out
</code></pre>
<hr />
<h2 id="heading-recommended-project-layout">📂 Recommended Project Layout</h2>
<pre><code class="lang-plaintext">/internal
    /mathutil
    /registration
/mocks (generated mocks)
go.mod
</code></pre>
<hr />
<h2 id="heading-best-practices">🧠 Best Practices</h2>
<p>✅ Table-driven tests<br />✅ Test positive + negative cases + edge cases<br />✅ Mock external systems (DB, MQ, API)<br />✅ Fail fast on unexpected errors<br />✅ Keep test functions short<br />✅ Run tests in CI pipeline</p>
<hr />
<h2 id="heading-conclusion">🎉 Conclusion</h2>
<p>Testing in Go is:</p>
<ul>
<li><p>Simple</p>
</li>
<li><p>Fast</p>
</li>
<li><p>Powerful</p>
</li>
<li><p>Real-world friendly</p>
</li>
</ul>
<p>When you combine <strong>table tests + mocks + coverage</strong>, you gain:</p>
<p>🚀 Refactoring freedom<br />🛡️ Safety in production<br />🧠 Clearer architecture</p>
<blockquote>
<p>Real engineers don’t trust “it works on my machine.”<br />They write tests.</p>
</blockquote>
<hr />
]]></content:encoded></item></channel></rss>