0
0
GoHow-ToBeginner · 3 min read

How to Test Goroutines in Go: Simple Guide with Examples

To test goroutines in Go, use synchronization tools like sync.WaitGroup or channels to wait for goroutines to finish before asserting results. This ensures your test waits for concurrent code to complete and captures its output correctly.
📐

Syntax

Testing goroutines requires waiting for them to finish before checking results. The common pattern uses sync.WaitGroup to track active goroutines and Done() to signal completion. You start goroutines with go keyword and call Wait() to block until all are done.

go
var wg sync.WaitGroup
wg.Add(1) // Tell WaitGroup to wait for 1 goroutine

// Start goroutine
go func() {
    defer wg.Done() // Signal goroutine done
    // goroutine work here
}()

wg.Wait() // Wait for all goroutines to finish
💻

Example

This example shows how to test a function that runs a goroutine to double a number and send the result on a channel. The test waits for the goroutine to finish and checks the output.

go
package main

import (
    "sync"
    "testing"
)

func doubleAsync(n int, wg *sync.WaitGroup, result chan<- int) {
    defer wg.Done()
    result <- n * 2
}

func TestDoubleAsync(t *testing.T) {
    var wg sync.WaitGroup
    result := make(chan int, 1)

    wg.Add(1)
    go doubleAsync(5, &wg, result)

    wg.Wait()
    close(result)

    val := <-result
    if val != 10 {
        t.Errorf("Expected 10, got %d", val)
    }
}
⚠️

Common Pitfalls

  • Not waiting for goroutines to finish causes tests to pass or fail unpredictably.
  • Forgetting to close channels can cause deadlocks.
  • Using unbuffered channels without proper synchronization can block goroutines indefinitely.
  • Relying on time.Sleep to wait is unreliable and slows tests.

Always use sync.WaitGroup or channels to coordinate goroutines in tests.

go
package main

import (
    "testing"
    "time"
)

// Wrong: Using sleep to wait for goroutine
func TestWrong(t *testing.T) {
    result := 0
    go func() {
        result = 5
    }()
    time.Sleep(10 * time.Millisecond) // Unreliable wait
    if result != 5 {
        t.Errorf("Expected 5, got %d", result)
    }
}

// Right: Using WaitGroup

import (
    "sync"
    "testing"
)

func TestRight(t *testing.T) {
    var wg sync.WaitGroup
    result := 0

    wg.Add(1)
    go func() {
        defer wg.Done()
        result = 5
    }()

    wg.Wait()
    if result != 5 {
        t.Errorf("Expected 5, got %d", result)
    }
}
📊

Quick Reference

  • Use sync.WaitGroup to wait for goroutines to finish.
  • Use buffered channels to avoid blocking when sending results.
  • Close channels after sending to signal completion.
  • Avoid time.Sleep for synchronization in tests.
  • Check results only after all goroutines complete.

Key Takeaways

Always synchronize goroutines in tests using sync.WaitGroup or channels.
Never rely on time.Sleep to wait for goroutines; it is unreliable.
Close channels properly to avoid deadlocks in tests.
Wait for all goroutines to finish before asserting test results.
Use buffered channels when sending data from goroutines to prevent blocking.