close

assert

package module
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 12, 2025 License: Apache-2.0 Imports: 6 Imported by: 0

Image README

 

assert-go

Tiny (~100 LoC) Go assertion library focused on crystal-clear failure messages and thoughtful source context.

 

CI go.dev reference Go Report Card

 

About

  • 🔍 Crystal-clear failure messages with contextual values
  • 📚 Rich source context showing the exact failure location
  • 🛠 Tiny and free of dependencies (~100 lines of Go)
  • 💡 Elegant, idiomatic Go API
  • 🎯 Two-tier assertion system with build tag support
  • ⚙️ Configurable source context behavior
  • ⚡ Zero-allocation hot path (~3ns per assertion)

Inspired by Tiger Style.

Installation

go get github.com/nikoksr/assert-go

Usage

Basic Usage

import "github.com/nikoksr/assert-go"

func PaymentProcessing() {
    payment := processPayment(PaymentRequest{
        Amount:      99.99,
        CustomerID: "cust_123",
        Currency:   "USD",
    })
    
    // Assert payment was processed successfully
    assert.Assert(payment.Status == "completed", "payment should be completed",
        // Optionally, add context to the panic
        "payment_id", payment.ID,
        "status", payment.Status,
        "amount", payment.Amount,
        "error", payment.Error,
        "timestamp", payment.ProcessedAt,
    )
}

On failure, you get:

Assertion failed at payment_test.go:43
Message: payment should be completed

Relevant values:
  [payment_id]: "pmt_789"
  [status]: "failed"
  [amount]: 99.99
  [error]: "insufficient_funds"
  [timestamp]: "2025-12-12T15:04:05Z"

Source context:
   37 |     payment := processPayment(PaymentRequest{
   38 |         Amount:      99.99,
   39 |         CustomerID: "cust_123",
   40 |         Currency:   "USD",
   41 |     })
   42 |
→  43 |     assert.Assert(payment.Status == "completed", "payment should be completed",
   44 |         "payment_id", payment.ID,
   45 |         "status", payment.Status,
   46 |         "amount", payment.Amount,
   47 |         "error", payment.Error,
   48 |         "timestamp", payment.ProcessedAt,
   49 |     )

goroutine 1 [running]:
github.com/nikoksr/assert-go.PaymentProcessing(0xc00011c000)
    /app/payment.go:43 +0x1b4
# ... regular Go stacktrace continues

Two-Tier Assertion System

The library provides two types of assertions:

  1. Assert() - Always active, meant for critical checks that should run in all environments
  2. Debug() - Development-time assertions that can be disabled in production
Using Debug Assertions

Debug assertions are disabled by default. To enable them, use the assertdebug build tag:

go test -tags assertdebug ./...
go run -tags assertdebug main.go

Example usage:

// This will only be evaluated when built with -tags assertdebug
assert.Debug(len(items) < 1000, "items list too large",
    "current_length", len(items),
    "max_allowed", 1000,
)

// This will always be evaluated regardless of build tags
assert.Assert(response != nil, "HTTP response cannot be nil",
    "status_code", response.StatusCode,
)

Configuration

You can configure the assertion behavior:

// Configure assertion behavior (call during initialization)
assert.SetConfig(assert.Config{
    // Enable/disable source context in error messages
    IncludeSource: true,
    // Number of context lines to show before and after the failing line
    ContextLines:  5,
})

Note: SetConfig should be called during program initialization before any assertions are made. It is not thread-safe and should not be called concurrently with assertions.

Performance

Assertions are designed to have minimal performance impact:

BenchmarkAssert_Success              ~3.0 ns/op    0 B/op    0 allocs/op
BenchmarkAssert_SuccessWithValues    ~6.1 ns/op    0 B/op    0 allocs/op
BenchmarkDebug_Success (disabled)    ~0.3 ns/op    0 B/op    0 allocs/op

Key Takeaways:

  • Successful assertions are extremely cheap (~3 nanoseconds)
  • Zero allocations on the hot path
  • Debug assertions when disabled are essentially free (compiler optimizes them away)
  • Even with contextual values, overhead remains minimal

Run benchmarks yourself: go test -bench=. -benchmem

A Personal Perspective on Assertions in Go

Like many Go developers, I initially dismissed assertions as incompatible with Go's philosophy of explicit error handling. That changed when I read TigerStyle's take on assertions and decided to experiment.

Here's the problem I'd been living with: I've always felt the urge to validate internal invariants—checking that a logger I just initialized isn't nil, verifying state I just set up is correct, confirming assumptions about data flow within my own code. These aren't checks for user input or network failures. They're checks for my mistakes.

But the traditional Go approach felt wrong. Returning an error means telling my users: "Hey, handle this case where I might have screwed up." It pollutes APIs with impossible error cases, forcing callers to handle conditions that can only occur if my code is broken. I'd write these defensive checks anyway, feeling uncomfortable the whole time, knowing I was treating my own bugs the same as legitimate operational failures.

Assertions solved this. They let me validate what must be true without burdening my API consumers. When an invariant is violated, there's no graceful recovery—continuing would only mask the bug and spread corrupted state. Better to fail immediately with rich context pointing directly at the problem.

I use assertions in application code where I control the full context and can make strong guarantees about internal state. I don't use them in libraries (where I can't control callers) or for validating external input (that's proper error handling territory). But for checking preconditions, postconditions, and invariants I own? They're essential.

This approach has also opened doors to patterns like negative-space testing, where assertions help verify not just what should happen, but what shouldn't. Worth exploring if you go down this path.

What started as a skeptical experiment has become fundamental to how I write Go. Assertions make my code more reliable and bugs dramatically easier to catch and diagnose.

More Projects

If you find this library useful, you might also be interested in:

  • notify - Dead simple Go library for sending notifications to various messaging services (3,500+ ⭐)
  • typeid-zig - Complete Zig implementation of the TypeID specification, recognized as an official community implementation

Built with ❤️ by @nikoksr

Image Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Assert

func Assert(condition bool, msg string, values ...any)

Assert panics if the condition is false. Globally configurable via SetConfig.

Assert is intended for critical checks that should always be active, regardless of the build configuration. Use assert.Debug for non-critical checks that should only be active during development.

WARN: This assertion is live!

Example

ExampleAssert demonstrates basic usage of the Assert function. This will panic if the condition is false.

// This assertion passes - no panic
assert.Assert(true, "this should not panic")

// In real code, you might check invariants like:
// assert.Assert(user != nil, "user must be initialized before use",
// 	"user_id", userID,
// )

fmt.Println("Assertion passed")
Output:
Assertion passed
Example (WithContext)

ExampleAssert_withContext demonstrates using Assert with contextual values. The key-value pairs provide additional debugging information when assertions fail.

userID := "user_123"
balance := 100.50
status := "active"

// This passes - demonstrating the API
assert.Assert(status == "active", "user must be active",
	"user_id", userID,
	"balance", balance,
	"status", status,
)

fmt.Println("User validation passed")
Output:
User validation passed

func Debug added in v0.4.0

func Debug(_ bool, _ string, _ ...any)

Debug is a no-op. The `assertdebug` build tag is not set.

To learn more about build tags, see https://pkg.go.dev/go/build#hdr-Build_Constraints.

Example

ExampleDebug demonstrates the Debug assertion that can be enabled with build tags. Debug assertions are disabled by default and only active when built with -tags assertdebug.

// This assertion is only evaluated when built with: go test -tags assertdebug
assert.Debug(true, "this check only runs in debug builds")

// Use Debug for non-critical checks during development:
// assert.Debug(len(cache) < 10000, "cache getting large",
// 	"size", len(cache),
// )

fmt.Println("Debug assertion evaluated")
Output:
Debug assertion evaluated

func SetConfig

func SetConfig(config Config)

SetConfig sets the configuration for the assertion library.

Example

ExampleSetConfig demonstrates configuring assertion behavior.

// Configure assertion behavior
assert.SetConfig(assert.Config{
	// Enable source context in error messages
	IncludeSource: true,
	// Show 3 lines of context before and after the failing line
	ContextLines: 3,
})

// All subsequent assertions will use this configuration
assert.Assert(true, "this uses the new configuration")

fmt.Println("Configuration applied")
Output:
Configuration applied

Types

type AssertionError

type AssertionError struct {
	Message       string
	File          string
	SourceContext string
	Line          int
}

AssertionError is the error type returned when an assertion fails.

func (AssertionError) Error

func (e AssertionError) Error() string

Error returns the error message.

type Config

type Config struct {
	// IncludeSource determines if source context is included in errors.
	//
	// Default: true
	IncludeSource bool

	// ContextLines determines how many lines of context to show.
	//
	// Default: 5
	ContextLines int
}

Config is used to configure the behavior of the assertion library.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL