assert

package module
v0.26.5 Latest Latest
Warning

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

Go to latest
Published: Sep 6, 2026 License: MIT Imports: 6 Imported by: 0

README

Assert Go Reference Coverage Status

Package assert is the minimalist testing helper for Go.

Inspired by Mat Ryer and Alex Edwards.

Features

  • Simple and readable test assertions using generics, chainable off an assert.TB
  • Built-in helpers for common cases like TB.OK and TB.Equal
  • Choose Fail or FailNow semantics depending on how you construct your assert.TB (assert.Continues(t) or assert.FailsNow(t))
  • Helpers for testing against golden files with the testfile subpackage
  • No sub-dependencies: just uses standard library

Example usage

Test for simple equality using generics:

// First create a testing helper
be := assert.Continues(t)

// Test two unequal strings
be.Equal("hello", "world")    // bad
// t.Fail(); t.Log("want: world; got: hello")
// Test two equal strings
be.Equal("goodbye", "goodbye") // good
// Test equal integers, etc.
be.Equal(resp.StatusCode, 200)
be.Equal(gotPtr, tc.wantPtr)

// Test for inequality
be.NotEqual("hello", "world")    // good
be.NotEqual("goodbye", "goodbye") // bad
// t.Fail(); t.Log("got: goodbye")

Chain related tests:

be.
	Equal(x, 1).
	Equal(y, 2)

Test for equality of slices:

s := []int{1, 2, 3}
be.SlicesEqual([]int{1, 2, 3}, s) // good
be.SlicesEqual([]int{3, 2, 1}, s) // bad
// t.Fail(); t.Log("got: [3 2 1]; want: [1 2 3]")

Handle errors:

f := be.OK(os.Open("nosuchfile")) // bad, and also returns nil *os.File
be.Falsey(f)                      // good

var err error
be.Falsey(err)                     // good
be.ErrorIs(nil, err)              // good
be.Truthy(err)                    // bad
be.ErrorIs(err, os.ErrPermission) // bad

err = errors.New("(O_o)")
be.ErrorAsType[*os.PathError](err) // bad
be.Truthy(err)                     // good

Check for regexp matching:

be.Match(mystring, `world`)               // good
be.Match(mystring, `World`)               // bad
// t.Fail(); t.Log(`missing match: /World/ !~ "hello, world"`)
be.Match([]byte("\a\b\x00\r\t"), `^\W*$`)    // good
be.NotMatch([]byte("\a\b\x00\r\t"), `^\W*$`) // bad

Check how long something rangeable is:

seq := strings.FieldsSeq("1 2 3 4")
be.EqualLength(seq, 4)     // good
be.EqualLength(seq, 1)     // bad
be.AtLeastLength(seq, 1)   // good
be.AtLeastLength(seq, 5)   // bad
be.AtLeastLength("123", 3) // good
be.AtLeastLength("123", 4) // bad

Test anything else:

be.True(o.IsValid())

Test using goldenfiles:

// Start a sub-test for each .txt file
testfile.Run(t, "testdata/*.txt", func(t assert.TB, path string) {
	// Read the file
	input := testfile.Read(t, path)

	// Do some conversion on it
	type myStruct struct{ Whatever string }
	got := myStruct{strings.ToUpper(input)}

	// See if the struct is equivalent to a .json file
	wantFile := testfile.Ext(path, ".json")
	testfile.EqualJSON(t, wantFile, got)

	// If it's not equivalent,
	// the got struct will be dumped
	// to a file named testdata/-failed-test-name.json
})

Philosophy

Tests usually should not fail. When they do fail, the failure should be repeatable. Therefore, it doesn't make sense to spend a lot of time writing good test messages. (This is unlike error messages, which should happen fairly often, and in production, irrepeatably.) Package assert is designed to simply fail a test quickly and quietly if a condition is not met with a reference to the line number of the failing test. If the reason for having the test is not immediately clear from context, you can write a comment, just like in normal code. If you do need more extensive reporting to figure out why a test is failing, use testing.TB.Log to capture more information.

The assertions in package assert are methods of the assert.TB type, which captures a testing.TB and can either call testing.TB.Fail or testing.TB.FailNow on failure depending on how you want the assertions to work.

Most tests just need simple equality testing, which is handled by TB.Equal (for comparable types), TB.SlicesEqual (for slices of comparable types), and TB.DeepEqual (which relies on reflect.DeepEqual), and simple err == nil checking, which is handled by TB.OK. Another common test is that a string or byte slice should contain or not some substring, which is handled by TB.Match and TB.NotMatch. Rather than package assert providing every possible test helper, you are encouraged to write your own advanced helpers for use with TB.True, while package assert takes away the drudgery of writing yet another simple func nilErr(t *testing.T, err) { ... }.

To make table based testing easier, assert.Run takes a map from name to testcase struct and runs a sub-test for each map entry.

The github.com/earthboundkid/assert/testfile subpackage has functions that make it easy to write file-based tests that ensure that the output of some transformation matches a golden file. Subtests can automatically be run for all files matching a glob pattern, such as testfile.Run(t, "testdata/*/input.txt", ...). If the test fails, the failure output will be written to a file, such as "testdata/basic-test/-failed-output.txt", and then the output can be examined via diff testing with standard tools. Set the environmental variable TESTFILE_UPDATE to update the golden file.

Documentation

Overview

Package assert is a minimalist test assertion helper library.

Philosophy

Tests usually should not fail. When they do fail, the failure should be repeatable. Therefore, it doesn't make sense to spend a lot of time writing good test messages. (This is unlike error messages, which should happen fairly often, and in production, irrepeatably.) Package assert is designed to simply fail a test quickly and quietly if a condition is not met with a reference to the line number of the failing test. If the reason for having the test is not immediately clear from context, you can write a comment, like normal code. If you do need more extensive reporting to figure out why a test is failing, use *testing.T.Log to capture more information.

The assertions in assert package are methods of the TB type, which wraps a testing.TB and can either call testing.TB.Fail or testing.TB.FailNow on failure depending on how you want the assertions to work.

Most tests just need simple equality testing, which is handled by TB.Equal (for comparable types), TB.SlicesEqual (for slices of comparable types), and TB.DeepEqual (which relies on reflect.DeepEqual). Another common test is that a string or byte slice should contain or not some substring, which is handled by TB.Match and TB.NotMatch. Rather than package assert providing every possible test helper, you are encouraged to write your own advanced helpers for use with TB.True, while package assert takes away the drudgery of writing yet another simple func nilErr(t *testing.T, err) { ... }.

To make table based testing easier, assert.Run takes a map from name to testcase struct and runs a sub-test for each map entry.

The github.com/earthboundkid/assert/testfile subpackage has functions that make it easy to write file-based tests that ensure that the output of some transformation matches a golden file. Subtests can automatically be run for all files matching a glob pattern, such as testfile.Run(t, "testdata/*/input.txt", ...). If the test fails, the failure output will be written to a file, such as "testdata/basic-test/-failed-output.txt", and then the output can be examined via diff testing with standard tools. Set the environmental variable TESTFILE_UPDATE to update the golden file.

Example
be := assert.Continues(&mockingT{})

be.
	Equal("hello", "world").    // bad
	Equal("goodbye", "goodbye") // good
be.
	NotEqual("hello", "world").    // good
	NotEqual("goodbye", "goodbye") // bad

s := []int{1, 2, 3}
be.
	SlicesEqual([]int{1, 2, 3}, s). // good
	SlicesEqual([]int{3, 2, 1}, s)  // bad

f := be.OK(os.Open("nosuchfile")) // bad
be.Falsey(f)                      // good

var err error
be.
	Falsey(err).                   // good
	ErrorIs(nil, err).             // good
	Truthy(err).                   // bad
	ErrorIs(err, os.ErrPermission) // bad

err = errors.New("(O_o)")
be.ErrorAsType[*os.PathError](err) // bad
be.Truthy(err)                     // good

type mytype string
var mystring mytype = "hello, world"
be.
	Match(mystring, `world`).                 // good
	Match(mystring, `World`).                 // bad
	Match([]byte("\a\b\x00\r\t"), `^\W*$`).   // good
	NotMatch([]byte("\a\b\x00\r\t"), `^\W*$`) // bad

seq := strings.FieldsSeq("1 2 3 4")
be.
	EqualLength(seq, 4).     // good
	EqualLength(seq, 1).     // bad
	AtLeastLength(seq, 1).   // good
	AtLeastLength(seq, 5).   // bad
	AtLeastLength("123", 3). // good
	AtLeastLength("123", 4)  // bad
Output:
want: world; got: hello
got: goodbye
got: [3 2 1]; want: [1 2 3]
err != nil: open nosuchfile: no such file or directory
got: <nil>
got errors.Is(<nil>, permission denied) == false
got errors.AsType[*fs.PathError]((O_o)) == false
missing match: /World/ !~ "hello, world"
unexpected match: /^\W*$/ =~ "\a\b\x00\r\t"
want len(seq) == 1; got at least 2
want len(seq) >= 5; got 4
want len(seq) >= 4; got 3

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Catch

func Catch(fn func()) (r any)

Catch runs the callback and returns the recovered panic, if any.

func Run added in v0.26.3

func Run[Testcase any](t *testing.T, m map[string]Testcase, f func(be TB, tc Testcase))

Run runs all the test cases in the map with *testing.Run using map keys as sub-test names. The TB associated with the sub-test is FailsNow by default.

Example
package main

import (
	"strings"
	"testing"

	"github.com/earthboundkid/assert"
)

func main() {
	// TestCapitalize
	_ = func(t *testing.T) {
		type testcase struct {
			in, want string
		}
		assert.Run(t, map[string]testcase{
			"blank":            {in: "", want: ""},
			"a":                {in: "a", want: "A"},
			"already upper":    {in: "A", want: "A"},
			"multi character":  {in: "Abc", want: "ABC"},
			"other characters": {in: " a,.c", want: " A,.C"},
		}, func(be assert.TB, tc testcase) {
			be.Equal(strings.ToUpper(tc.in), tc.want)
		})
	}
}

func Truthy

func Truthy[T any](v T) bool

Truthy returns

  • !v.IsZero(), for types with an IsZero() method.
  • len(v) != 0, for slices and maps.
  • v != the zero value of T, for all other types.
Example
package main

import (
	"fmt"

	"github.com/earthboundkid/assert"
)

func main() {
	fmt.Printf("%#v is %t-ish\n", 0, assert.Truthy(0))
	fmt.Printf("%#v is %t-ish\n", 1, assert.Truthy(1))
	fmt.Printf("%#v is %t-ish\n", "", assert.Truthy(""))
	fmt.Printf("%#v is %t-ish\n", "hi", assert.Truthy("hi"))
	fmt.Printf("%#v is %t-ish\n", error(nil), assert.Truthy[error](nil))
}
Output:
0 is false-ish
1 is true-ish
"" is false-ish
"hi" is true-ish
<nil> is false-ish

Types

type TB added in v0.26.3

type TB struct {
	testing.TB
	// contains filtered or unexported fields
}

TB is a type that wraps a *testing.T, *testing.B, or *testing.F and adds methods for doing assertion tests against that test manager.

The methods of testing.TB that TB uses for assertions are Helper, Logf, and FailNow (for FailsNow) or Fail (for Continues).

func Continues added in v0.26.3

func Continues(t testing.TB) TB

Continues returns a TB that will continue testing even after an assertion failure. It calls testing.TB.Fail.

func FailsNow added in v0.26.3

func FailsNow(t testing.TB) TB

FailsNow returns a TB that will end the test after an assertion failure with testing.TB.FailNow.

func (TB) AtLeastLength added in v0.26.3

func (be TB) AtLeastLength(seq any, want int) TB

AtLeastLength asserts that seq has a length that is at least want.

The type of seq must be array, array pointer, slice, map, string, channel, iter.Seq, or iter.Seq2. For channels and iterators, the values are consumed to get the sequence length.

func (TB) Continues added in v0.26.3

func (be TB) Continues() TB

Continues returns a copy of the TB that will continue testing even after an assertion failure. It calls testing.TB.Fail.

func (TB) DeepEqual added in v0.26.3

func (be TB) DeepEqual[T any](got, want T) TB

DeepEqual asserts got is reflect.DeepEqual to want.

Prefer to use TB.SlicesEqual if possible.

Example
be := assert.Continues(&mockingT{})

// good
m1 := map[int]bool{1: true, 2: false}
m2 := map[int]bool{1: true, 2: false}
be.DeepEqual(m1, m2)

// bad
var s1 []int
s2 := []int{}
be.DeepEqual(s1, s2) // DeepEqual is picky about nil vs. len 0
Output:
reflect.DeepEqual([]int(nil), []int{}) == false

func (TB) Equal added in v0.26.3

func (be TB) Equal[T comparable](got, want T) TB

Equal asserts that got == want.

func (TB) EqualLength added in v0.26.3

func (be TB) EqualLength(seq any, want int) TB

EqualLength asserts that seq has a length that is exactly want.

The type of seq must be array, array pointer, slice, map, string, channel, iter.Seq, or iter.Seq2. For channels and iterators, the values are consumed to get the sequence length.

func (TB) ErrorAsType added in v0.26.3

func (be TB) ErrorAsType[T error](got error) T

ErrorAsType asserts that errors.AsType can unwrap got as T.

func (TB) ErrorIs added in v0.26.3

func (be TB) ErrorIs(got, target error) TB

ErrorIs asserts that got errors.Is target.

func (TB) FailsNow added in v0.26.3

func (be TB) FailsNow() TB

FailsNow returns a copy of the TB that will end the test after an assertion failure with testing.TB.FailNow.

func (TB) False added in v0.26.3

func (be TB) False(value bool) TB

False asserts that value is false.

func (TB) Falsey added in v0.26.3

func (be TB) Falsey[T any](value T) TB

Falsey asserts value is not Truthy.

func (TB) Match added in v0.26.3

func (be TB) Match[byteseq ~string | ~[]byte](got byteseq, pattern string) TB

Match asserts got matches the regexp pattern.

The pattern must compile.

func (TB) NilError added in v0.26.5

func (be TB) NilError(err error) TB

NilError asserts err is nil.

func (TB) NotEqual added in v0.26.3

func (be TB) NotEqual[T comparable](got, bad T) TB

NotEqual asserts that got != want.

func (TB) NotMatch added in v0.26.3

func (be TB) NotMatch[byteseq ~string | ~[]byte](got byteseq, pattern string) TB

NotMatch asserts got does not matches the regexp pattern.

The pattern must compile.

func (TB) NotOK added in v0.26.4

func (be TB) NotOK[T any](value T, err error) error

NotOK asserts that error is not nil the return value is falsey. Typical use is like

err := be.NotOK(canFail())
failure := be.ErrorAsType[FailError](err)
be.Equal(failure.cause, "failed")

func (TB) OK added in v0.26.3

func (be TB) OK[T any](value T, err error) T

OK asserts that error is nil and returns value. Typical use is like

v := be.OK(canFail())
Example
be := assert.Continues(&mockingT{})

// be.OK asserts the error returned is nil
// and returns the value for subsequent testing.
f := be.OK(os.Open("nosuchfile"))
if f != nil {
	f.Close()
}
Output:
err != nil: open nosuchfile: no such file or directory

func (TB) OK2 added in v0.26.3

func (be TB) OK2[T1, T2 any](v1 T1, v2 T2, err error) (T1, T2)

OK asserts that error is nil and returns v1 and v2. Typical use is like

v1, v2 := be.OK2(canFail())

func (TB) Panicked added in v0.26.3

func (be TB) Panicked(fn func()) TB

Panicked asserts that fn panics when run.

Example
be := assert.Continues(&mockingT{})

divide := func(num, denom int) int {
	return num / denom
}

// Test that division by zero panics
be.Panicked(func() {
	divide(1, 0)
})

// Because a panic fails a test by default,
// testing that an operation does not panic is less necessary,
// but may be helpful in a table test.
for _, testcase := range []struct {
	num, denom, want int
	shouldPanic      bool
}{
	{0, 1, 0, false},
	{1, 1, 1, false},
	{1, 0, 0xbadc0ffee, true},
	{0, 0, 0xbadc0ffee, true},
} {
	got := 0xbadc0ffee
	panicVal := assert.Catch(func() {
		got = divide(testcase.num, testcase.denom)
	})
	be.Equal(got, testcase.want)
	be.Equal(panicVal != nil, testcase.shouldPanic)
}

func (TB) SlicesEqual added in v0.26.3

func (be TB) SlicesEqual[T comparable](got, want []T) TB

SlicesEqual asserts that slices.Equal(got, want).

func (TB) True added in v0.26.3

func (be TB) True(value bool) TB

True asserts that value is true.

func (TB) Truthy added in v0.26.3

func (be TB) Truthy[T any](value T) TB

Truthy asserts value is Truthy.

Directories

Path Synopsis
Package testfile has test helpers that work by comparing files.
Package testfile has test helpers that work by comparing files.

Jump to

Keyboard shortcuts

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