Skip to content

Testing

Unit Test Example

go
package main

import (
    "net/http"
    "net/http/httptest"
    "testing"
    
    "butterfly.orx.me/core/app"
    "github.com/gin-gonic/gin"
    "github.com/stretchr/testify/assert"
)

func TestPingRoute(t *testing.T) {
    gin.SetMode(gin.TestMode)
    
    config := &app.Config{
        Service: "test-service",
        Router: func(r *gin.Engine) {
            r.GET("/ping", func(c *gin.Context) {
                c.JSON(200, gin.H{"message": "pong"})
            })
        },
    }
    
    router := gin.New()
    config.Router(router)
    
    w := httptest.NewRecorder()
    req, _ := http.NewRequest("GET", "/ping", nil)
    
    router.ServeHTTP(w, req)
    
    assert.Equal(t, 200, w.Code)
    assert.Contains(t, w.Body.String(), "pong")
}

Mock Logger (testsuite)

The framework provides a mock logger for capturing and asserting log output in unit tests:

go
import (
    "testing"
    "log/slog"
    
    "butterfly.orx.me/core/testsuite"
)

func TestUserCreation(t *testing.T) {
    // Create mock logger and capture helper
    logger, mockLog := testsuite.NewMockLog()
    
    // Pass logger directly to code under test
    service := NewUserService(logger)
    service.CreateUser("[email protected]")
    
    // Assert log output
    if !mockLog.ContainsMessage("user created") {
        t.Error("expected 'user created' log message")
    }
    
    // Check specific log levels
    if mockLog.CountLevel(slog.LevelError) > 0 {
        t.Error("unexpected error logs")
    }
    
    // Get all messages
    messages := mockLog.Messages()
    t.Logf("logged messages: %v", messages)
    
    // Get full entries with attributes
    entries := mockLog.Entries()
    for _, entry := range entries {
        t.Logf("level=%s msg=%s attrs=%v", entry.Level, entry.Message, entry.Attrs)
    }
    
    // Reset between test cases
    mockLog.Reset()
}

func TestWithDefaultLogger(t *testing.T) {
    _, mockLog := testsuite.NewMockLog()
    
    // Set as the default slog logger (returns restore function)
    restore := mockLog.SetAsDefault()
    defer restore()
    
    // Any code using slog.Default() will now be captured
    slog.Info("this will be captured")
    
    if !mockLog.ContainsMessage("this will be captured") {
        t.Error("message not captured")
    }
}

Testing with Wire

Wire makes testing easier by allowing you to swap implementations:

go
// test/wire_test.go
//go:build wireinject
// +build wireinject

package test

import (
    "github.com/google/wire"
    "your-app/internal/test/mocks"
)

var TestSet = wire.NewSet(
    mocks.ProvideMockDatabase,
    mocks.ProvideMockRedis,
    mocks.ProvideMockLogger,
    wire.ProvideUserRepository,
    wire.ProvideUserService,
    wire.ProvideUserHandler,
)

func InitializeTestApp() (*UserHandler, error) {
    wire.Build(TestSet)
    return &UserHandler{}, nil
}