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 ¶
- func Catch(fn func()) (r any)
- func Run[Testcase any](t *testing.T, m map[string]Testcase, f func(be TB, tc Testcase))
- func Truthy[T any](v T) bool
- type TB
- func (be TB) AtLeastLength(seq any, want int) TB
- func (be TB) Continues() TB
- func (be TB) DeepEqual[T any](got, want T) TB
- func (be TB) Equal[T comparable](got, want T) TB
- func (be TB) EqualLength(seq any, want int) TB
- func (be TB) ErrorAsType[T error](got error) T
- func (be TB) ErrorIs(got, target error) TB
- func (be TB) FailsNow() TB
- func (be TB) False(value bool) TB
- func (be TB) Falsey[T any](value T) TB
- func (be TB) Match[byteseq ~string | ~[]byte](got byteseq, pattern string) TB
- func (be TB) NilError(err error) TB
- func (be TB) NotEqual[T comparable](got, bad T) TB
- func (be TB) NotMatch[byteseq ~string | ~[]byte](got byteseq, pattern string) TB
- func (be TB) NotOK[T any](value T, err error) error
- func (be TB) OK[T any](value T, err error) T
- func (be TB) OK2[T1, T2 any](v1 T1, v2 T2, err error) (T1, T2)
- func (be TB) Panicked(fn func()) TB
- func (be TB) SlicesEqual[T comparable](got, want []T) TB
- func (be TB) True(value bool) TB
- func (be TB) Truthy[T any](value T) TB
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
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)
})
}
}
Output:
func Truthy ¶
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
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
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
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
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
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
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
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
ErrorAsType asserts that errors.AsType can unwrap got as T.
func (TB) FailsNow ¶ added in v0.26.3
FailsNow returns a copy of the TB that will end the test after an assertion failure with testing.TB.FailNow.
func (TB) Match ¶ added in v0.26.3
Match asserts got matches the regexp pattern.
The pattern must compile.
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
NotMatch asserts got does not matches the regexp pattern.
The pattern must compile.
func (TB) NotOK ¶ added in v0.26.4
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
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
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
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).