test

GoDoc Go Report Card CI Tests License: MPL 2.0

test is a generics based testing assertions library for Go.

There are two packages, test and must.

  • test – assertions that mark the test for failure and allow the test case to continue
  • must – assertions that mark the test for failure and halt the test case immediately

Requirements

Only depends on github.com/google/go-cmp.

The minimum Go version is go1.18.

Install

Use go get to grab the latest version of test.

go get -u github.com/shoenig/[email protected]

Influence

This library was made after a ~decade of using testify, quite possibly the most used library in the whole Go ecosystem. All credit of inspiration belongs them.

Philosophy

Go has always lacked a strong definition of equivalency, and until recently lacked the language features necessary to make type-safe yet generic assertive statements based on the contents of values.

This test (and companion must) package aims to provide a test-case assertion library where the caller is in control of how types are compared, and to do so in a strongly typed way – avoiding erroneous comparisons in the first place.

Generally there are 4 ways of asserting equivalence between types.

the == operator

Functions like EqOp and ContainsOp work on types that are comparable, i.e. are compatible with Go’s built-in == and != operators.

a comparator function

Functions like EqFunc and ContainsFunc work on any type, as the caller passes in a function that takes two arguments of that type, returning a boolean indicating equivalence.

an .Equals method

Functions like Equals and ContainsEquals work on types implementing the EqualsFunc generic interface (i.e. implement an .Equals method). The .Equals method is called to determine equivalence.

the cmp.Equal or reflect.DeepEqual functions

Functions like Eq and Contains work on any type, using the cmp.Equal or reflect.DeepEqual functions to determine equivalence. Although this is the easiest / most compatible way to “just compare stuff”, it the least deterministic way of comparing instances of a type. Changes to the underlying types may cause unexpected changes in their equivalence (e.g. the addition of unexported fields, function field types, etc.).

output

When possible, a nice diff output is created to show why an equivalence has failed. This is done via the cmp.Diff function. For incompatible types, their GoString values are printed instead.

All output is directed through t.Log functions, and is visible only if test verbosity is turned on (e.g. go test -v).

fail fast vs. fail later

The test and must packages are identical, except for how test cases behave when encountering a failure. Sometimes it is helpful for a test case to continue running even though a failure has occurred (e.g. contains cleanup logic not captured via a t.Cleanup function). Other times it make sense to fail immediately and stop the test case execution.

test – functions allow test cases to continue execution

must – functions stop test case execution immediately

Examples (equality)

import "github.com/shoenig/test/must"

// ... 

e1 := Employee{ID: 100, Name: "Alice"}
e2 := Employee{ID: 101, Name: "Bob"}

// using cmp.Equal (like magic!)
must.Eq(t, e1, e2)

// using == operator
must.EqOp(t, e1, e2)

// using a custom comparator
must.EqFunc(t, e1, e2, func(a, b *Employee) bool {
    return a.ID == b.ID
})

// using .Equals method
must.Equals(t, e1, e2)

Examples (slices)

import "github.com/shoenig/test/must"

// ... 

a := []*Employee{
  {ID: 100, Name: "Alice"},
  {ID: 101, Name: "Bob"},
  {ID: 102, Name: "Carl"},
}
b := []*Employee{
  {ID: 100, Name: "Alice"},
  {ID: 101, Name: "Bob"},
  {ID: 103, Name: "Dian"},
}

must.EqSliceFunc(tc, a, b, func(a, b *Person) bool {
  return a.ID == b.ID && a.Name == b.Name
})

Examples (maps)

import "github.com/shoenig/test/must"

// ... 

a := map[int]Person{
  0: {ID: 100, Name: "Alice"},
  1: {ID: 101, Name: "Bob"},
}

b := map[int]Person{
  0: {ID: 100, Name: "Alice"},
  1: {ID: 101, Name: "Bob B."},
}

must.MapEqFunc(tc, a, b, func(p1, p2 Person) bool {
  return p1.ID == p2.ID && p1.Name == p2.Name
})

Output

The test and must package attempt to create useful, readable output when an assertions goes awry. Some random examples below.

test_test.go:779: expected different file permissions
↪ name: find
↪ exp: -rw-rwx-wx
↪ got: -rwxr-xr-x

tests_test.go:569: expected maps of same values via 'eq' function
↪ difference:
  map[int]test.Person{
  	0: {ID: 100, Name: "Alice"},
  	1: {
  		ID:   101,
- 		Name: "Bob",
+ 		Name: "Bob B.",
  	},
  }

test_test.go:520: expected slice[1].Less(slice[2])
↪ slice[1]: &{200 Bob}
↪ slice[2]: &{150 Carl}

test_test.go:688: expected maps of same values via .Equals method
↪ differential ↷
  map[int]*test.Person{
  	0: &{ID: 100, Name: "Alice"},
  	1: &{
- 		ID:   101,
+ 		ID:   200,
  		Name: "Bob",
  	},
  }

test_test.go:801: expected regexp match
↪ s: abcX
↪ re: abc\d

License

Open source under the MPL

GitHub

View Github