Foreword: At Google, the definition of an assertion library is a library that combines the validation and production of failure messages within a test. “testing” is also kind of an assertion library, but it is much more concerned with providing low-level primitives for marking tests failed, writing to test logs, and so on. It leaves it to you to combine those primitives with Go code into validation and failure messages. In this article, when I’m talking about “assertion libraries”, I’m talking about third-party assertion libraries that sit above “testing”.


This post is a follow-up to “Don’t use assertion libraries in Go” specifically targeted at the github.com/stretchr/testify library.

I recommend you don’t use testify: instead, use “testing”, ordinary Go, and occasionally github.com/google/go-cmp/cmp for complex comparisons. Here’s an example:

package foo_test

import "testing"

func TestFoo(t *testing.T) {
    if got, want := Foo(), 5; got != want {
        t.Errorf("got %d, want %d", got, want)
    }
}

Main gripes

testify is not idiomatic Go

Go’s stdlib has a testing package, which is the idiomatic and recommended way to write tests. testify is not idiomatic Go.

I’ll also note that it does not appear that popular, either. As of this writing, pkg.go.dev counts 16,016 imports for testify/assert whereas 110,931 for “testing”.

testify is a relic from 2012

When Go came on the scene in 2012, the first thing that people did is start porting conventions from languages they were coming from. testify started in 2012 as basically a port of JUnit4, and is one such relic:

assertEquals(expected, actual) // JUnit4
assert.Equal(t, expected, actual) // testify

It didn’t actually do anything new or add value that the “testing” package didn’t already provide, other than being more terse than conditionals. Otherwise it’s just added a wrapper around “testing”.

I’m fairly sure it just got popular out of other-language familiarity until people started getting comfortable with the “testing” package, Go’s more verbose / less magic aesthetic, and maybe some momentum from the BDD/Gherkin craze at the time.

You lose the type system with testify

It is completely reflect / interface{} based, which is slow, clunky, and you completely lose Go’s type system. For example:

// "testing" approach: you get an immediate compiler warning in your
// editor.
if 1 != "foo" {
    t.Errorf("...")
}

// testify approach: this compiles! It's only at runtime that you get told
// these types are not equivalent, and even then in a completely novel way
// that is totally unlike how the compiler would tell you.
assert.Equal(t, 1, "foo")

testify is a rat’s nest of assertions

(And if you don’t trust my word on it, the maintainers also allude to it)

I count 312 assertions in the library. 312. That. Is. Mad. It is literally trying to express in assertions every possible thing you could express with ordinary Go code. It is now not at all enough to know Go to read your colleague’s test, you now have to learn the ins and outs of this entire library.

And if you decide to diligently do so, and you’re reading the docs but they don’t exactly answer the question of what it’s doing, surely you could peek at the code and read for ourselves?

Ahhhhhh, it’s all deeply nested reflection logic, my eyes are burning!

So that’s mostly a nope.

And even then, you’ll reach points where the library can’t quite express what you want to do. What happens then is that you end up writing the Go code equivalent anyways:

// testify
assert.Condition(t, func() bool {
	return config.Timeout > 10 && config.MaxRetries == 3 && !config.Debug
}, "...")

// You literally could have just written Go code!
if config.Timeout > 10 && config.MaxRetries == 3 && !config.Debug {
    t.Errorf("...")
}

It is only implements a subset of Go testing

The project never fully implemented the set of things you can do with Go testing, but the project appears to have stalled circa ~2021-2023 (?) and has not kept up with new improvements to testing in Go:

You can’t run parallel tests (with suite).

There’s no support for generics.

There’s no support for synctest.

There’s no support for fuzz testing.

There’s no support for benchmarks.

The mock library completely misses the point about Go interfaces

Go has implicit interfaces. I don’t know if the original authors of the testify mocking library understood the implicitations of that.

There is zero need for a mocking library since every client can add small, targeted interfaces for any external dependency and provide fakes in their test.

Another issue I have with testify mocks is that they use string matching and once again you lose the type system:

// Renaming "MyMethod" with your editor won't update this magical string and
// your tests will blow up. Yay no type system!
Mock.On("MyMethod", 2, 2).Return(0)

Smaller gripes

  • It is a largely stalled project, not having had a release in ~1yr (as of writing)
  • Its diffing dep github.com/pmezard/go-difflib is WIP and unmaintained.
    • github.com/google/go-cmp by comparison is elegantly small, type safe, modern, maintained by the Go team, has simple/elegant custom comparison options, and can handle protos, unexported fields, generics, and various other funky types.
  • It uses a pretty printer github.com/davecgh/go-spew whose last commit was 8 years ago.
  • It has had a v2 schism; go-openapi.github.io/testify/index.html vs stretchr/testify/discussions/1560. Latter hasn’t seen updates in ~3y and readme says no v2 in sight.
  • It’s also pretty riddled with bugs, from a quick read through the issue tracker, with several fun panics, hangs, and incorrect assertion bugs outstanding.