Skip to main content

Overview

MockLogger is a complete implementation of the Logger interface designed specifically for unit testing. It captures all log entries in memory, allowing you to inspect and assert on logging behavior without writing to actual outputs.

Creating a MockLogger

package myapp

import (
    "testing"
    "github.com/drossan/go_logs"
)

func TestMyFunction(t *testing.T) {
    // Create mock logger with default Info level
    mock := go_logs.NewMockLogger()
    
    // Use it like any other logger
    mock.Info("test message", go_logs.String("key", "value"))
    
    // Inspect captured entries
    if mock.Count() != 1 {
        t.Errorf("expected 1 log entry, got %d", mock.Count())
    }
}

API Reference

Creation

NewMockLogger()
*MockLogger
Creates a new MockLogger with:
  • Default level: InfoLevel
  • Empty entries list
  • Thread-safe mutex protection

Logging Methods

MockLogger implements all standard Logger methods:
mock.Log(level Level, msg string, fields ...Field)
mock.LogCtx(ctx context.Context, level Level, msg string, fields ...Field)

mock.Trace(msg string, fields ...Field)
mock.Debug(msg string, fields ...Field)
mock.Info(msg string, fields ...Field)
mock.Warn(msg string, fields ...Field)
mock.Error(msg string, fields ...Field)
mock.Fatal(msg string, fields ...Field)  // Does NOT exit in mock

mock.With(fields ...Field) Logger
mock.SetLevel(level Level)
mock.GetLevel() Level
mock.Sync() error  // No-op, returns nil
Fatal() does NOT call os.Exit(1) in MockLogger. It simply logs at Fatal level for testing purposes.

Inspection Methods

Count()

Returns the total number of captured log entries:
mock := go_logs.NewMockLogger()
mock.Info("message 1")
mock.Error("message 2")

if mock.Count() != 2 {
    t.Errorf("expected 2 entries, got %d", mock.Count())
}

Entries()

Returns all captured entries as a slice:
entries := mock.Entries()

for _, entry := range entries {
    fmt.Printf("Level: %v, Message: %s\n", entry.Level, entry.Message)
    
    // Inspect fields
    for _, field := range entry.Fields {
        fmt.Printf("  %s = %v\n", field.Key, field.Value)
    }
}
MockEntry
struct
Each entry contains:
  • Level - The log level (Trace, Debug, Info, etc.)
  • Message - The log message string
  • Fields - Slice of structured fields

LastEntry()

Returns the most recent log entry, or nil if no entries:
mock.Info("first")
mock.Error("second")

last := mock.LastEntry()
if last == nil {
    t.Fatal("expected an entry")
}

if last.Message != "second" {
    t.Errorf("expected 'second', got %s", last.Message)
}

if last.Level != go_logs.ErrorLevel {
    t.Error("expected Error level")
}

HasMessage()

Checks if any entry contains the exact message:
mock.Info("user logged in")
mock.Error("connection failed")

if !mock.HasMessage("user logged in") {
    t.Error("expected 'user logged in' message")
}

if mock.HasMessage("not logged") {
    t.Error("should not have partial match")
}
HasMessage() requires an exact match, not substring matching.

HasLevel()

Checks if any entry has the specified level:
mock.Info("info message")
mock.Error("error message")

if !mock.HasLevel(go_logs.ErrorLevel) {
    t.Error("expected at least one Error level log")
}

if mock.HasLevel(go_logs.FatalLevel) {
    t.Error("should not have Fatal level logs")
}

Reset()

Clears all captured entries:
mock.Info("message 1")
mock.Info("message 2")
assert.Equal(t, 2, mock.Count())

mock.Reset()

assert.Equal(t, 0, mock.Count())
assert.Nil(t, mock.LastEntry())

Level Filtering

MockLogger respects level filtering just like real loggers:
mock := go_logs.NewMockLogger()
mock.SetLevel(go_logs.WarnLevel) // Only Warn and above

mock.Debug("debug message")  // Filtered out
mock.Info("info message")    // Filtered out
mock.Warn("warn message")    // Captured
mock.Error("error message")  // Captured

assert.Equal(t, 2, mock.Count()) // Only Warn and Error

Testing Examples

Example 1: Verify Error Logging

func TestDatabaseConnection(t *testing.T) {
    mock := go_logs.NewMockLogger()
    db := NewDatabase(mock)
    
    err := db.Connect("invalid-host:3306")
    
    // Verify error was logged
    if !mock.HasLevel(go_logs.ErrorLevel) {
        t.Error("connection failure should log error")
    }
    
    last := mock.LastEntry()
    if !strings.Contains(last.Message, "connection") {
        t.Error("error message should mention connection")
    }
}

Example 2: Verify Fields Are Logged

func TestUserActivity(t *testing.T) {
    mock := go_logs.NewMockLogger()
    tracker := NewActivityTracker(mock)
    
    tracker.RecordLogin("[email protected]", "192.168.1.100")
    
    entries := mock.Entries()
    if len(entries) != 1 {
        t.Fatalf("expected 1 entry, got %d", len(entries))
    }
    
    entry := entries[0]
    
    // Verify fields
    hasEmail := false
    hasIP := false
    
    for _, field := range entry.Fields {
        if field.Key == "email" && field.Value == "[email protected]" {
            hasEmail = true
        }
        if field.Key == "ip" && field.Value == "192.168.1.100" {
            hasIP = true
        }
    }
    
    if !hasEmail {
        t.Error("expected email field")
    }
    if !hasIP {
        t.Error("expected ip field")
    }
}

Example 3: Test Structured Logging

func TestOrderProcessing(t *testing.T) {
    mock := go_logs.NewMockLogger()
    mock.SetLevel(go_logs.DebugLevel) // Capture everything
    
    processor := NewOrderProcessor(mock)
    order := &Order{ID: 12345, Total: 99.99}
    
    processor.Process(order)
    
    // Verify logging sequence
    entries := mock.Entries()
    
    if len(entries) < 2 {
        t.Fatal("expected multiple log entries")
    }
    
    // First log should be Debug level
    if entries[0].Level != go_logs.DebugLevel {
        t.Error("first log should be Debug")
    }
    
    // Last log should confirm processing
    if !mock.HasMessage("order processed") {
        t.Error("expected 'order processed' message")
    }
}

Example 4: Test Child Logger

func TestChildLogger(t *testing.T) {
    parent := go_logs.NewMockLogger()
    
    // Create child logger (returns same mock for simplicity)
    child := parent.With(
        go_logs.String("request_id", "abc-123"),
    )
    
    child.Info("processing request")
    
    // Verify log was captured
    if parent.Count() != 1 {
        t.Error("child logs should be captured by parent mock")
    }
}
MockLogger’s With() method returns the same mock instance for simplicity. If you need to track child logger calls separately, consider using multiple mock instances.

Example 5: Verify No Logs

func TestCaching(t *testing.T) {
    mock := go_logs.NewMockLogger()
    cache := NewCache(mock)
    
    // Cache hit should not log anything
    value := cache.Get("existing-key")
    
    if mock.Count() != 0 {
        t.Error("cache hit should not log")
    }
    
    // Cache miss should log
    value = cache.Get("missing-key")
    
    if mock.Count() != 1 {
        t.Error("cache miss should log once")
    }
}

Thread Safety

MockLogger is fully thread-safe and can be used in concurrent tests:
func TestConcurrentLogging(t *testing.T) {
    mock := go_logs.NewMockLogger()
    
    var wg sync.WaitGroup
    goroutines := 10
    logsPerGoroutine := 100
    
    for i := 0; i < goroutines; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            for j := 0; j < logsPerGoroutine; j++ {
                mock.Info("concurrent log",
                    go_logs.Int("goroutine", id),
                    go_logs.Int("iteration", j),
                )
            }
        }(i)
    }
    
    wg.Wait()
    
    expected := goroutines * logsPerGoroutine
    if mock.Count() != expected {
        t.Errorf("expected %d logs, got %d", expected, mock.Count())
    }
}

Source Code Reference

MockLogger implementation: testing.go:136-291

View Full Implementation

See the complete MockLogger source code

CaptureBuffer

For capturing raw log output

Testing Overview

Testing best practices

Build docs developers (and LLMs) love