Testify Assertions and Require in Go Tests Explained
Learn testify assert and require in Go: install, Equal, NoError, ErrorIs, Contains, ElementsMatch, JSONEq and Panics, with the real failure message of each.

The standard testing package gives you t.Errorf and t.Fatalf, and a Go test is a series of if got != want blocks. Testify packages those blocks as functions: assert.Equal(t, want, got) replaces four lines, and failure messages come with a diff. It is the most widely used third-party testing module in Go, and it deserves a plain explanation of what it does and where it is awkward.
Tested with Go 1.25.6, testify v1.11.1, macOS on Apple Silicon (arm64)
This tutorial covers assert and require on Go 1.25.6 with testify v1.11.1. Later articles cover suites, mocks and HTTP helpers. If you are weighing whether to use it at all, read the comparison of assertion libraries first.
Install and import
go get github.com/stretchr/[email protected]Testify has several packages. assert and require hold the assertions, mock and suite the heavier tools, and http a small helper. In a test file you normally import two:
import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)The two families
Every assertion exists in both packages with the same name and arguments. They differ in what happens on failure.
assert.Equal(t, want, got)records the failure witht.Errorfand returns false; the test goes on.require.Equal(t, want, got)records it witht.FailNowand stops the test.
The distinction is the difference between t.Error and t.Fatal, and the rule for choosing is the same. Use require for steps that later lines depend on, such as require.NoError(t, err) before using the value the call returned. Use assert for independent checks, so one run reports every mismatch. The dedicated assert versus require article goes through the cases.
A first test
package main
import (
"encoding/json"
"errors"
"fmt"
)
var ErrDivZero = errors.New("division by zero")
func Div(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("div %v/%v: %w", a, b, ErrDivZero)
}
return a / b, nil
}
type Item struct {
SKU string `json:"sku"`
Qty int `json:"qty"`
Tags []string `json:"tags"`
}
func Encode(i Item) string { b, _ := json.Marshal(i); return string(b) }
func MustPositive(n int) int {
if n <= 0 {
panic("not positive")
}
return n
}
func main() {}package main
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestPassingAssertions(t *testing.T) {
got, err := Div(10, 4)
require.NoError(t, err) // stop here if Div failed, the next line would be meaningless
assert.Equal(t, 2.5, got)
assert.InDelta(t, 0.3, 0.1+0.2, 1e-9)
_, err = Div(1, 0)
assert.ErrorIs(t, err, ErrDivZero)
assert.EqualError(t, err, "div 1/0: division by zero")
items := []string{"b", "a", "c"}
assert.Len(t, items, 3)
assert.Contains(t, items, "a")
assert.ElementsMatch(t, []string{"a", "b", "c"}, items)
assert.JSONEq(t, `{"qty":2,"sku":"x","tags":null}`, Encode(Item{SKU: "x", Qty: 2}))
assert.Panics(t, func() { MustPositive(0) })
assert.NotPanics(t, func() { MustPositive(1) })
assert.Nil(t, nil)
assert.Empty(t, []int{})
t.Log("all passing assertions ran")
}
func TestFailingMessages(t *testing.T) {
assert.Equal(t, 3, 4, "totals differ for order %d", 7)
assert.Contains(t, []string{"a", "b"}, "z")
assert.Len(t, []int{1, 2}, 3)
assert.ErrorIs(t, nil, ErrDivZero)
}go test -count=1 -v . 2>&1 | grep -vE '^(=== RUN|=== PAUSE|=== CONT|=== NAME|PASS|ok|FAIL$|FAIL\s|exit status)' | sed -E 's/ \([0-9.]+s\)//; s#^ +[a-z_0-9]+\.go:[0-9]+: # #; s/\x1b\[[0-9;]*m//g; s#/[^[:space:]]*/([a-z_0-9]+\.go)#\1#g; s/[[:space:]]+$//' | grep -vE '^\s*$' all passing assertions ran
--- PASS: TestPassingAssertions
Error Trace: calc_test.go:34
Error: Not equal:
expected: 3
actual : 4
Test: TestFailingMessages
Messages: totals differ for order 7
Error Trace: calc_test.go:35
Error: []string{"a", "b"} does not contain "z"
Test: TestFailingMessages
Error Trace: calc_test.go:36
Error: "[1 2]" should have 3 item(s), but has 2
Test: TestFailingMessages
Error Trace: calc_test.go:37
Error: Expected error with "division by zero" in chain but got nil.
Test: TestFailingMessages
--- FAIL: TestFailingMessagesTestPassingAssertions exercises the assertions you will use most. Going through them:
Equality. assert.Equal(t, 2.5, got) compares with ObjectsAreEqual, which uses reflect.DeepEqual and handles byte slices specially. Values must have the same type: Equal(t, 1, int64(1)) fails, which surprises people, and EqualValues converts first when you want that. assert.NotEqual is the inverse. For floating point use InDelta or InEpsilon, as the test does for 0.1 + 0.2, because the sum is not exactly 0.3.
Errors. require.NoError(t, err) guards the call before its result is used. assert.ErrorIs(t, err, ErrDivZero) checks the chain with errors.Is, which is the right comparison for wrapped errors; assert.ErrorAs is the errors.As version. assert.EqualError(t, err, "div 1/0: division by zero") compares the message text exactly, which couples the test to wording; prefer ErrorIs when a sentinel exists and ErrorContains for a fragment.
Collections. assert.Len checks length, assert.Contains works on strings, slices and map keys, and assert.ElementsMatch compares two slices ignoring order, which is the right tool for results built from a map or from goroutines. Empty, NotEmpty, Subset and Equal on slices complete the set.
Structured text. assert.JSONEq compares two JSON strings as data, so key order and whitespace do not matter: the test compares {"qty":2,"sku":"x","tags":null} with the output of Encode. YAMLEq does the same for YAML.
Panics. assert.Panics and assert.NotPanics take a function, run it and check whether it panicked. PanicsWithValue and PanicsWithError check what was panicked with.
Nil and zero. Nil and NotNil treat typed nil pointers and nil slices, maps and channels as nil; Zero and NotZero compare with the zero value of the type; True and False take a bool.
The test logs all passing assertions ran, and passes.
Reading failures
The second test fails on purpose, four times, to show what testify prints. The format is consistent:
Error Trace: calc_test.go:34
Error: Not equal:
expected: 3
actual : 4
Test: TestFailingMessages
Messages: totals differ for order 7The Error Trace is the file and line of the assertion, which t.Helper handling inside testify makes point at your test and not at the library. Error is the explanation; for Equal it shows both values and, for structures, a diff (see reading diffs). Test names the test or subtest, and Messages is the optional trailing arguments you passed: a format string and values, assert.Equal(t, 3, 4, "totals differ for order %d", 7).
Other assertions describe themselves:
Containssays[]string{"a", "b"} does not contain "z".Lensays"[1 2]" should have 3 item(s), but has 2.ErrorIswith a nil error saysExpected error with "division by zero" in chain but got nil.
Because the output is long, a failing assertion in a loop is hard to place. Pass a message that names the case, or use subtests so the test name does. A message such as "case %q", c.name costs nothing and saves a debugging session.
The argument order trap
The signature is Equal(t, expected, actual). Go's standard library convention in the if got != want form is the opposite, and many people write assert.Equal(t, got, want) out of habit. The test still passes or fails correctly, but the failure message labels the values the wrong way round, and a reader chases the wrong side. Pick the order testify uses and keep to it: first what you expect, then what the code returned. A linter such as testifylint flags reversed arguments; it is described here and was not run.
Boolean checks and why to avoid them
assert.True(t, a == b) works and tells you nothing when it fails: Should be true. Prefer the specific assertion, assert.Equal(t, a, b), which prints both values. The same goes for assert.True(t, len(x) == 3) against assert.Len(t, x, 3), and for assert.True(t, err == nil) against assert.NoError(t, err). Reserve True and False for conditions that are naturally boolean, such as the result of a predicate function.
assert.New and require.New
Passing t to every call is repetitive in a long test. is := assert.New(t) returns an object whose methods drop the first argument: is.Equal(want, got). The suite package builds on the same idea. It is a style choice; the only trap is binding t once and then using it inside a subtest, where the subtest's own t is the right one. Create is inside t.Run.
Using it with the rest of the testing package
Testify does not replace testing. Subtests, t.Parallel, t.Cleanup and t.TempDir work as usual, and the assertions take whatever t you give them. Two rules apply because of how FailNow works. Call require assertions only from the test's own goroutine; from another goroutine they do not stop the test, and go vet flags the pattern. And in helpers that wrap assertions, call t.Helper() first so that the reported line is the caller's.
Equal, EqualValues, Same and friends
The family of equality assertions is where most confusion lives, so it is worth a short map.
| Assertion | Compares | Use it for |
|---|---|---|
Equal | Deep equality; same type required | Most values, structs, slices, maps |
EqualValues | Converts to the same type first | int against int64, named types against their base |
Exactly | Same type and value | When the type is part of the check |
Same / NotSame | Pointer identity | Singletons, caches, "the same object" |
ObjectsAreEqual | The function behind Equal | Writing your own helper |
assert.Equal(t, 1, int64(1)) fails with a message that shows both values as 1 and gives no hint that the types differ, which is a confusing moment. EqualValues is the fix when the difference is not meaningful. Do not reach for it by habit; a type mismatch is sometimes a real bug, such as a function returning int where a caller expects int64.
Equal on pointers compares what they point to, not the addresses, because ObjectsAreEqual uses reflect.DeepEqual. If you need identity, Same is the assertion. Two pointers to equal structs pass Equal and fail Same.
Errors in depth
Go errors are values, wrapped by fmt.Errorf("...: %w", err), and the assertions follow errors.Is and errors.As.
assert.ErrorIs(t, err, target)passes when any error in the chain equals the target. Use it with sentinel errors such asio.EOF,sql.ErrNoRowsand your own.assert.ErrorAs(t, err, &target)finds the first error in the chain that can be assigned to the target's type and sets it. Use it to read fields of a custom error type. The target must be a non-nil pointer to a type that implementserror, andgo vetchecks this at compile time for the directerrors.Ascall but not for the testify one, so a wrong pointer level fails at run time.assert.EqualError(t, err, "text")compareserr.Error()exactly. It is fine for a leaf error whose text is a deliberate part of the interface, and brittle otherwise.assert.ErrorContains(t, err, "fragment")is the middle ground.assert.Error(t, err)andassert.NoError(t, err)check presence only. Prefer a specific assertion when the kind of error matters, since a test that only checksErrorpasses for any failure, including the wrong one. That is the most common weak assertion in Go tests: the code returns an error, but not the error you meant.
The error testing article covers the same ground with the standard library.
Time, floats and tolerance
assert.WithinDuration(t, expected, actual, delta) compares times with a tolerance, which suits values stamped by time.Now(). A better fix is to inject the clock so the value is exact, as in determinism. assert.InDelta and assert.InEpsilon compare floats by absolute and relative error. Use InDelta with a delta that reflects the precision you need, not a number that happens to pass.
Writing your own helper
When the same three assertions appear in many tests, wrap them. A helper function takes t *testing.T, calls t.Helper(), and uses require or assert inside. The reported line is then the caller's. Name it for the domain, requireUserValid(t, u), and keep it to one idea. A helper that returns a value after asserting, such as mustParse(t, s), lets the test read as a story. The risk is that helpers hide what is being checked; if a reader must open the helper to know, it is too clever.
Messages that help
The optional trailing arguments are a format string and values. Use them to name the case in loops, assert.Equal(t, c.want, got, "case %q", c.name), and to add context that the assertion cannot know, "after retry %d". Do not repeat what the assertion already prints. A message that says "values should be equal" adds nothing to Not equal:.
Costs
The module adds a dependency and a vocabulary. Reflection-based comparison is slower than == for hot loops, which matters in benchmarks, not tests. The error output is verbose, which helps in the first minute and annoys in the thousandth. Some teams prefer go-cmp for structural diffs and the standard library for the rest, as argued in the stdlib article. Testify's assertions are also stringly typed in their messages and loosely typed in their arguments: Equal takes any, so a type mismatch is a run-time failure, not a compile error.
Mistakes beginners make
- Using
assertwhererequireis needed.assert.NoError(t, err)followed bygot.Namepanics with a nil dereference when the call failed, and the panic hides the original error. Userequirefor the guard. - Comparing floats with
Equal. It passes until it does not. UseInDelta. - Comparing errors by message. Use
ErrorIs; text changes, sentinels do not. - Asserting in goroutines.
requirein a goroutine does not stop the test. Collect results in the test goroutine. - Testing slices of structs with
Contains. It usesObjectsAreEqualfor each element, so it works, but the failure output is long; compare withEqualon a projection when only one field matters. - Ignoring the return value.
assertfunctions return a bool.if !assert.Equal(t, a, b) { return }lets a test stop early withoutrequire, when you want to keep the test going elsewhere.
Parallel tests and subtests
Assertions are plain function calls on *testing.T, so they work in subtests and parallel tests without any special handling. Inside t.Run, use the subtest's own t for every assertion, including those made through assert.New(t). A common bug is writing is := assert.New(t) before the loop and calling is.Equal inside t.Run: the failure is attached to the parent test, the subtest passes, and the report points at the wrong name. Create the bound assertion object inside the subtest function.
When tests are parallel, assertions are safe to call concurrently on different t values. The data they compare is your responsibility, and the race detector will tell you if two subtests share a variable. The patterns for tables are in the testify table tests article.
A starting set
If you adopt it, begin with six functions and add the rest as needed: require.NoError, assert.Equal, assert.ErrorIs, assert.Len, assert.Contains and assert.ElementsMatch. They cover most tests, and the failure messages are good enough to act on. The full list is in the testify documentation, and a compact table is in the cheat sheet.


