assert

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Aug 15, 2025 License: MIT Imports: 9 Imported by: 0

README

Assert

PkgGoDev

An assertion package with a minimal API, yet powerful enough to cover most use cases.

The assert package is designed to provide a more configurable and type-safe alternative to the popular testify/assert package.

Motivation

Have you ever struggled with comparing complex structs in your tests? One that use decimal.Decimal, time.Time, or uuid.UUID types? Or perhaps you wanted to ignore automatically generated fields, such as ID or CreatedAt, in your struct comparisons?

assert allows for more flexible assertions, such as ignoring unexported fields or skipping empty fields in struct comparisons.

Usage

package main

import (
    "errors"
    "fmt"
    "io"
    "io/fs"
    "testing"

    "github.com/krhubert/assert"
)

func TestXYZ(t *testing.T) {
    // assert checks if value implements Equal() bool method
    // and uses it to compare values. This makes it possible
    // to compare e.g. time.Time values.
    now := time.Now()
    assert.Equal(t, now, now)

    // assert is type safe, so this will not compile
    // assert.Equal(t, 1, "1")

    // assert do not compare pointers, but values they point to
    a, b := 1, 1
    assert.Equal(t, &a, &b)

    // assert checks for errors
    assert.Error(t, errors.New("error"))
    assert.NoError(t, nil)

    // assert checks if error contains a target value
    // like string, error or struct
    err := fmt.Errorf(
        "closed socket: %w %w",
        io.EOF,
        &fs.PathError{Op: "read", Path: "socket", Err: io.ErrClosedPipe},
    )
    assert.ErrorContains(t, err, "closed socket")
    assert.ErrorContains(t, err, io.EOF)
    var pathError *fs.PathError
    assert.ErrorContains(atb, err, &pathError)

    // assert checks if function panics or not
    assert.Panic(t, func() { panic(0) })
    assert.NotPanic(t, func() { })

    // assert can be used to check errors in defer functions
    defer assert.Defer(t, func() error { return file.Close() })()

    // assert can be used to check if a value is of a specific type
    gs := TypeAssert[fmt.GoStringer](t, time.Time{})
}

Suite

package main

import (
    "errors"
    "fmt"
    "io"
    "io/fs"
    "testing"

 _ "github.com/mattn/go-sqlite3"
 "go.uber.org/mock/gomock"
    "github.com/krhubert/assert"
)

type ExampleTestSuite struct {
    assert.Suite

    i    int
    ctrl *gomock.Controller
    db  *sql.DB
}

func (s *ExampleTestSuite) Setup(t *testing.T) {
    s.i = 4
    s.ctrl = gomock.NewController(t)
    db, err := sql.Open("sqlite3", ":memory:")
    assert.NoError(t, err)
    s.db = db
}

func (s *ExampleTestSuite) Teardown(t *testing.T) {
    s.i = 0
    assert.NoError(t, s.db.Close())
}

func TestExample(t *testing.T) {
     ts := assert.Setup[ExampleTestSuite](t)
     row := ts.db.QueryRow("select date()")
     var date string
     assert.NoError(ts, row.Scan(&date))
     assert.NoError(ts, row.Err())
}

Assert vs testify

package assert

import (
 "testing"
 "time"

 "github.com/google/uuid"
 "github.com/shopspring/decimal"
 "github.com/stretchr/testify/require"
)

func TestExampleEqual(t *testing.T) {
  type User struct {
   Id        uuid.UUID
   Email     string
   CreatedAt time.Time
   Balance   decimal.Decimal

   active bool
  }

  loc, _ := time.LoadLocation("Europe/Warsaw")

  // db.CreateUser("test@example.com")
  createdAt := time.Now()
  user := User{
   Id:        uuid.New(),
   Email:     "test@example.com",
   CreatedAt: createdAt,
   Balance:   decimal.NewFromFloat(1),

   active: true,
  }

  want := User{
    Email:     "test@example.com",
    CreatedAt: createdAt.In(loc),
    Balance:   decimal.RequireFromString("1"),
  }
}

Running go test on:

AssertTestify
assert.Equal(t, user, want, IgnoreUnexported(), SkipEmptyFields())
PASS: TestExampleEqual (0.00s)
require.Equal(t, user, want)
--- FAIL: TestExampleEqual (0.00s)
    test_test.go:35:
                Error Trace:
                Error:          Not equal:
                                expected: assert.User{Id:uuid.UUID{0x66, 0x43, 0x33, 0x3b, 0xad, 0xf6, 0x48, 0xec, 0x9a, 0x7d, 0xff, 0x53, 0xc0, 0x90, 0x6e, 0xf1}, Email:"test@example.com", CreatedAt:time.Date(2025, time.July, 17, 13, 12, 17, 81207156, time.Local), Balance:decimal.Decimal{value:(*big.Int)(0xc0000b5b00), exp:0}, active:true}
                                actual  : assert.User{Id:uuid.UUID{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, Email:"test@example.com", CreatedAt:time.Date(2025, time.July, 17, 13, 12, 17, 81207156, time.Location("Europe/Warsaw")), Balance:decimal.Decimal{value:(*big.Int)(0xc0000b5b20), exp:0}, active:false}

                                Diff:
                                --- Expected
                                +++ Actual
                                @@ -2,3 +2,3 @@
                                  Id: (uuid.UUID) (len=16) {
                                -  00000000  66 43 33 3b ad f6 48 ec  9a 7d ff 53 c0 90 6e f1  |fC3;..H..}.S..n.|
                                +  00000000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
                                  },
                                @@ -6,6 +6,6 @@
                                  CreatedAt: (time.Time) {
                                -  wall: (uint64) 13985458619913019252,
                                -  ext: (int64) 1236572,
                                +  wall: (uint64) 81207156,
                                +  ext: (int64) 63888347537,
                                   loc: (*time.Location)({
                                -   name: (string) (len=5) "Local",
                                +   name: (string) (len=13) "Europe/Warsaw",
                                    zone: ([]time.zone) (len=11) {
                                @@ -1078,3 +1078,3 @@
                                  },
                                - active: (bool) true
                                + active: (bool) false
                                 }
                Test:           TestExampleEqual

Documentation

Overview

Copyright 2017, The Go Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Defer

func Defer(t testing.TB, fn func() error) func()

Defer returns a function that will call fn and check if an error is returned.

func Empty

func Empty(t testing.TB, got any)

Empty checks if got is empty.

func Equal

func Equal[V any](t testing.TB, got V, want V, opts ...EqualOption)

Equal checks if two values are equal with the given options.

This functions uses [go-cmp](https://pkg.go.dev/github.com/google/go-cmp) to determine equality.

func Error

func Error(t testing.TB, err error)

Error checks if an error is not nil.

func ErrorContains

func ErrorContains(t testing.TB, err error, target any)

ErrorContains checks if an error is not nil and contains the target.

Target can be:

1. string

The string is compiled as a regexp, and the error is matched against it. If it is not a valid regexp, it is used as a string to check if the error contains it.

2. error

The error is checked if it is equal to the target using errors.Is.

3. type

The error is checked if it can be converted to the target type using errors.As.

func ErrorWant

func ErrorWant(t testing.TB, want bool, err error)

ErrorWant checks if an error is expected for the test. A common usage in tests is:

type tests struct {
	name    string
	// other fields
	wantErr bool
}

for _, tt := range tests {
	t.Run(tt.name, func(t *testing.T) {
		err := fn()
		assert.ErrorWant(t, tt.wantErr, err)
	})
}

func False

func False(t testing.TB, got bool)

False checks if got is false.

func Len

func Len[V any](t testing.TB, got V, want int)

Len checks if the length of got is l. got can be any go type accepted by builtin len function.

func Must

func Must[P1 any](p1 P1, err error) P1

Must is a helper function to handle a single return value from a function.

func Must2

func Must2[P1 any, P2 any](p1 P1, p2 P2, err error) (P1, P2)

Must2 is a helper function to handle two return values from a function.

func Must3

func Must3[P1 any, P2 any, P3 any](p1 P1, p2 P2, p3 P3, err error) (P1, P2, P3)

Must3 is a helper function to handle three return values from a function.

func Nil

func Nil(t testing.TB, got any)

Nil checks if got is nil.

func NoError

func NoError(t testing.TB, err error)

NoError checks if an error is nil.

func NotEmpty

func NotEmpty(t testing.TB, got any)

NotEmpty checks if got is not empty.

func NotEqual

func NotEqual[V any](t testing.TB, got V, want V, opts ...EqualOption)

NotEqual checks if two values are not equal. See Equal for rules used to determine equality.

func NotNil

func NotNil(t testing.TB, got any)

NotNil checks if got is not nil.

func NotPanic

func NotPanic(t testing.TB, f func())

NotPanic checks if f does not panic.

func NotZero

func NotZero[T any](t testing.TB, got T)

NotZero checks if got is not zero value. If value implements IsZero() bool method, it will be used to determine if the value is zero.

func Panic

func Panic(t testing.TB, f func())

Panic checks if f panics.

func Setup

func Setup[V any, S interface {
	*V
	Suiter
}](t *testing.T) S

Setup allocates, initializes, and returns a test suite of type S.

It performs the following steps:

  1. Allocates a new instance of the suite (S)
  2. Calls the suite's Setup(t *testing.T) method.
  3. Registers the suite's Teardown method using t.Cleanup.

This ensures that test lifecycle hooks are consistently applied and automatically cleaned up, even on test failures.

func True

func True(t testing.TB, got bool)

True checks if got is true.

func TypeAssert

func TypeAssert[V any](t testing.TB, got any) V

TypeAssert checks if got is of type V and returns it.

func Zero

func Zero[T any](t testing.TB, got T)

Zero checks if got is zero value. If value implements IsZero() bool method, it will be used to determine if the value is zero.

Types

type EqualOption

type EqualOption func(o *equaler)

EqualOption configures the equality check behavior.

func IgnoreUnexported

func IgnoreUnexported() EqualOption

IgnoreUnexported returns an EqualOption that ignores unexported fields of structs.

func SkipEmptyFields

func SkipEmptyFields() EqualOption

SkipEmptyFields returns an EqualOption that ignores struct fields that are empty. see Empty for details on how empty is determined.

func SkipFieldNames

func SkipFieldNames(names ...string) EqualOption

SkipFieldNames returns an EqualOption that ignores a specific field names in the struct.

The name may be a dot-delimited string (e.g., "Foo.Bar") to ignore a specific sub-field that is embedded or nested within the parent struct.

This option can be only used for structs, otherwise it will panic.

func SkipZeroFields

func SkipZeroFields() EqualOption

SkipZeroFields returns an EqualOption that ignores struct fields that are zero. see Zero for details on how zero is determined.

type Suite

type Suite struct{}

Suite is a noop implementation of a test suite.

It provides Setup and Teardown methods that can be overridden by specific test suites. This struct is designed to be embedded in custom test suite types, allowing you to inherit the basic suite behavior and override only the methods you need.

Suite can be used if you don't want to define both Setup and Teardown methods in your test suite - you can embed Suite and override only the methods you actually need.

Example:

type DatabaseTestSuite struct {
	assert.Suite  // Embed to inherit noop implementations
	db *sql.DB
}

// Only override Setup, Teardown inherited as noop
func (s *DatabaseTestSuite) Setup(t *testing.T) {
	s.db = setupTestDB(t)
}

func (Suite) Setup

func (s Suite) Setup(t *testing.T)

Setup is a no-op method provided for embedding. Override this method in your custom suite to perform test setup logic.

func (Suite) Teardown

func (s Suite) Teardown(t *testing.T)

Teardown is a no-op method provided for embedding. Override this method in your custom suite to perform cleanup logic.

type Suiter

type Suiter interface {
	// Setup initializes the test suite before a test runs.
	// It receives a *testing.T which can be used for logging, assertions,
	// or controlling test execution (e.g., t.Skip(), t.Fatal()).
	Setup(t *testing.T)

	// Teardown cleans up the test suite after a test completes.
	// It receives a *testing.T which can be used for logging cleanup
	// operations or reporting cleanup failures.
	Teardown(t *testing.T)
}

Suiter defines the interface that all test suites must implement.

This interface establishes the contract for test suite lifecycle management, providing hooks for initialization and cleanup operations. Any type that implements these two methods can be used as a test suite with the Setup function.

Jump to

Keyboard shortcuts

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