Go Testing 2026: Unit Tests, Mocks en Technische Sollicitatievragen

Een uitgebreide gids over Go-testpraktijken in 2026, inclusief unit tests met het testing-pakket, mocking met gomock en veelgestelde sollicitatievragen over Go-testen.

Go Testing 2026: Unit Tests, Mocks en Sollicitatievragen

Go testing is gebaseerd op het standaard testing-pakket, dat wordt meegeleverd met elke Go-installatie en geen externe afhankelijkheden vereist. In tegenstelling tot frameworks in andere talen geeft Go de voorkeur aan eenvoud: testbestanden bevinden zich naast de productiecode, testfuncties volgen een naamgevingsconventie, en het go test-commando regelt de detectie en uitvoering.

Waar interviewers op letten

Kandidaten die table-driven tests schrijven, interfaces gebruiken voor dependency injection en begrijpen wanneer mocking waarde toevoegt versus wanneer het alleen maar ruis veroorzaakt.

Unit Tests schrijven met het testing-pakket

Elk testbestand eindigt met _test.go en bevindt zich in hetzelfde package als de te testen code. Testfuncties beginnen met Test gevolgd door een naam met een hoofdletter. De *testing.T-parameter biedt methoden voor het rapporteren van fouten.

calculator_test.gogo
package calculator

import "testing"

func TestAdd(t *testing.T) {
    result := Add(2, 3)
    if result != 5 {
        t.Errorf("Add(2, 3) = %d; want 5", result)
    }
}

Het uitvoeren van go test ./... voert alle tests in de huidige module uit. De -v-vlag toont individuele testnamen en hun geslaagd/gefaald-status.

Table-Driven Tests: De Go-Standaard

Table-driven tests verminderen duplicatie en maken het toevoegen van nieuwe cases triviaal. Elke rij in de tabel vertegenwoordigt een scenario met inputs en verwachte outputs. Dit patroon komt voor in de Go standaardbibliotheek en in productiecodebases in de hele industrie.

validator_test.gogo
package validator

import "testing"

func TestValidateEmail(t *testing.T) {
    tests := []struct {
        name    string
        email   string
        wantErr bool
    }{
        {"valid email", "user@example.com", false},
        {"missing at sign", "userexample.com", true},
        {"empty string", "", true},
        {"unicode local part", "user@例え.jp", false},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            err := ValidateEmail(tt.email)
            if (err != nil) != tt.wantErr {
                t.Errorf("ValidateEmail(%q) error = %v, wantErr %v",
                    tt.email, err, tt.wantErr)
            }
        })
    }
}

De t.Run-methode maakt subtests die onafhankelijk van elkaar draaien. Gefaalde subtests rapporteren welke specifieke case is gefaald zonder andere cases te stoppen.

Dependency Injection via Interfaces

Go interfaces maken het mogelijk om code te testen die afhankelijk is van externe systemen: databases, HTTP-clients, bestandssystemen. Het definiëren van een minimale interface op het punt van gebruik maakt het mogelijk om echte implementaties te vervangen door test doubles.

service.gogo
package order

// Repository definieert de data access methoden die deze service nodig heeft
type Repository interface {
    FindByID(id string) (*Order, error)
    Save(order *Order) error
}

// Service behandelt de order business logic
type Service struct {
    repo Repository
}

// NewService maakt een service met de gegeven repository
func NewService(repo Repository) *Service {
    return &Service{repo: repo}
}

// Process valideert en slaat een order op
func (s *Service) Process(order *Order) error {
    if order.Total <= 0 {
        return ErrInvalidTotal
    }
    return s.repo.Save(order)
}

De Service accepteert elk type dat voldoet aan Repository. Productiecode geeft een database-backed implementatie door. Tests geven een mock of stub door.

Mocking met gomock en mockgen

Het gomock-pakket genereert mock-implementaties vanuit interface-definities. Het mockgen-commando leest bronbestanden en produceert mock-structs die aanroepen registreren en geconfigureerde waarden retourneren.

bash
# Genereer mocks voor de Repository interface
mockgen -source=service.go -destination=mocks/repository_mock.go -package=mocks

Gebruik van de gegenereerde mock in tests:

service_test.gogo
package order

import (
    "testing"

    "github.com/stretchr/testify/assert"
    "go.uber.org/mock/gomock"

    "yourmodule/order/mocks"
)

func TestService_Process_SavesValidOrder(t *testing.T) {
    ctrl := gomock.NewController(t)
    mockRepo := mocks.NewMockRepository(ctrl)

    order := &Order{ID: "123", Total: 99.99}

    // Verwacht dat Save eenmaal wordt aangeroepen met deze order, retourneer nil
    mockRepo.EXPECT().
        Save(order).
        Return(nil).
        Times(1)

    svc := NewService(mockRepo)
    err := svc.Process(order)

    assert.NoError(t, err)
}

func TestService_Process_RejectsZeroTotal(t *testing.T) {
    ctrl := gomock.NewController(t)
    mockRepo := mocks.NewMockRepository(ctrl)

    // Geen aanroepen naar repo verwacht omdat validatie eerst faalt

    svc := NewService(mockRepo)
    err := svc.Process(&Order{ID: "456", Total: 0})

    assert.ErrorIs(t, err, ErrInvalidTotal)
}

De mock verifieert dat Save precies eenmaal is aangeroepen met het verwachte argument. Als de te testen code Save nooit aanroept, of het aanroept met andere argumenten, faalt de test.

Testify gebruiken voor Assertions en Suites

testify biedt assertion-functies die duidelijkere foutmeldingen produceren dan handmatige if-checks. Het assert-pakket gaat door met uitvoering na fouten. Het require-pakket stopt de test onmiddellijk.

user_test.gogo
package user

import (
    "testing"

    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"
)

func TestParseUser(t *testing.T) {
    input := `{"id": "u1", "name": "Alice"}`

    user, err := ParseUser(input)

    require.NoError(t, err, "parsing should not fail")
    assert.Equal(t, "u1", user.ID)
    assert.Equal(t, "Alice", user.Name)
    assert.Empty(t, user.Email, "email should default to empty")
}

De require.NoError-aanroep stopt de test als parsing faalt, waardoor nil pointer panics bij volgende assertions worden voorkomen.

Klaar om je Go gesprekken te halen?

Oefen met onze interactieve simulatoren, flashcards en technische tests.

HTTP Handlers testen met httptest

Het net/http/httptest-pakket biedt ResponseRecorder voor het vastleggen van handler-output en Server voor het draaien van een lokale testserver. De meeste unit tests gebruiken ResponseRecorder om network overhead te vermijden.

handler_test.gogo
package api

import (
    "net/http"
    "net/http/httptest"
    "testing"

    "github.com/stretchr/testify/assert"
)

func TestHealthHandler(t *testing.T) {
    req := httptest.NewRequest(http.MethodGet, "/health", nil)
    rec := httptest.NewRecorder()

    HealthHandler(rec, req)

    assert.Equal(t, http.StatusOK, rec.Code)
    assert.JSONEq(t, `{"status": "ok"}`, rec.Body.String())
}

Voor handlers die externe services aanroepen, wordt een mock-client geïnjecteerd of wordt httptest.NewServer gebruikt om een fake backend te maken.

Veelgestelde Sollicitatievragen over Go Testing

Interviewers beoordelen testkennis door vragen die ervaring met echte codebases onthullen. Hier zijn patronen die voorbereide kandidaten onderscheiden:

"Hoe zou je een functie testen die een externe API aanroept?"

Definieer een interface voor de HTTP-client, injecteer deze in de functie of struct, en voorzie een mock die vooraf bepaalde responses retourneert. Dit vermijdt netwerkaanroepen en maakt tests deterministisch.

"Wanneer zou je een stub versus een mock gebruiken?"

Stubs retourneren standaard responses zonder te verifiëren hoe ze werden aangeroepen. Mocks verifiëren dat specifieke methoden werden aangeroepen met specifieke argumenten. Gebruik stubs wanneer de retourwaarde belangrijker is dan de interactie. Gebruik mocks wanneer het verifiëren van de interactie het doel van de test is.

"Wat zijn table-driven tests en waarom geeft Go er de voorkeur aan?"

Table-driven tests definiëren testcases als data in een slice van structs. Ze verminderen boilerplate, maken het toevoegen van cases triviaal en produceren leesbare testoutput met t.Run. Het patroon sluit aan bij Go's voorkeur voor expliciete, repetitieve code boven slimme abstracties.

"Hoe ga je om met test fixtures of setup die meerdere tests delen?"

Gebruik TestMain voor setup en teardown op package-niveau. Gebruik helperfuncties of test suites (van testify) voor gedeelde setup over gerelateerde tests. Vermijd globale state die koppeling tussen tests creëert.

Voorbereiden op een Go-sollicitatiegesprek? De Go Testing module behandelt deze patronen uitgebreid met oefenvragen.

Tests uitvoeren met Coverage en Race Detection

Het go test-commando accepteert vlaggen die ongeteste codepaden en concurrency-bugs onthullen.

bash
# Tests uitvoeren met coverage rapport
go test -cover ./...

# HTML coverage visualisatie genereren
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out -o coverage.html

# Race conditions detecteren (langzamer, maar vindt echte bugs)
go test -race ./...

De race detector instrumenteert geheugentoegangen en signaleert gelijktijdige reads en writes naar dezelfde variabele. Het uitvoeren van tests met -race in CI vangt bugs die zich alleen onder specifieke timing manifesteren.

Tests organiseren in Grote Codebases

Naarmate projecten groeien, beïnvloedt testorganisatie de onderhoudbaarheid:

  • Same-package tests (package foo) hebben toegang tot niet-geëxporteerde functies en velden. Gebruik voor unit tests die intern gedrag verifiëren.
  • External-package tests (package foo_test) importeren het package als externe code. Gebruik voor integratietests en om de publieke API te verifiëren.
  • Testdata directories slaan fixtures op zoals JSON-bestanden, golden outputs of seed databases. Go negeert deze tijdens builds.
  • Build tags scheiden unit tests van integratietests die externe systemen vereisen. Voer go test -tags=integration ./... uit om ze op te nemen.

Voor meer Go best practices buiten testing, behandelt het artikel over concurrency patronen goroutines en channels.

Wat te onthouden over Go Testing

  • Het testing-pakket wordt meegeleverd met Go en vereist geen externe afhankelijkheden
  • Table-driven tests verminderen duplicatie en zijn het verwachte patroon in sollicitatiegesprekken
  • Interfaces maken dependency injection mogelijk; definieer ze op het punt van gebruik, niet bij de implementatie
  • gomock genereert mocks vanuit interfaces; testify biedt schonere assertions
  • httptest.ResponseRecorder legt handler-output vast zonder network overhead
  • De -race-vlag detecteert concurrency-bugs die unit tests anders missen
  • Coverage-metrics sturen de aandacht maar garanderen geen correctheid

Begin met oefenen!

Test je kennis met onze gespreksimulatoren en technische tests.

Dagelijkse challenge

Zie jij de bug in Go?

Een echt codefragment, een verborgen bug, één poging per dag. Zonder account uit te proberen.

Anthony Fillion-Maillet

Geschreven door

Anthony Fillion-Maillet

Oprichter van SharpSkill

Al meer dan 10 jaar fullstack-ontwikkelaar. Hij leidt SharpSkill en staat in voor alles wat hier verschijnt.

Bijgewerkt op 24 augustus 2026

Tags

#go
#testing
#unit-tests
#mocking
#interview

Delen

Gerelateerde artikelen