💾 Archived View for log.pfad.fr › 2024 › reinventing-the-assertion-wheel captured on 2024-06-16 at 12:01:23. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2024-05-26)
-=-=-=-=-=-=-
Like many developers, I like to reinvent the wheel. Like many Go developers, I created my own assertion package...
For a couple of months it lived as an internal package, but prompted by a discussion on lobste.rs, I finally decided to make it a dedicated package.
Why I don't use a third-party assertion library in Go unit tests | Lobsters
Here is my experience using other assertion libraries
This is probably one of the first that I used. It is quite complete, but also quite complex:
This is (pun intended) very clever, but a bit too clever for my taste (looking up the comment of the assertion line to display it along the error message). It also predates type parameters, so interface{} everywhere.
I have been using this package until now. The API surface is reasonable but it also predates type parameters and does not play nicely with auto-completion.
The first package that leverages type parameters, however, I find it quite cumbersome to:
This largely builds upon the "be" package above, with a twist: it returns a Failed struct to allow additional behavior on failure.
To mark a failure as fatal, call Fatal on the returned Failed struct (mainly interesting for error checking, to prevent a nil pointer later on):
obj, err := newObj() check.Equal(t, nil, err).Fatal() // will stop the test on failure (by calling testing.FailNow) check.Equal(t, obj, expected) // the test continue, even on failure
To log additional context, call Log (or Logf) on the returned Failed struct:
check.Equal(t, a, b). Logf("context: %#v", c)
Both the Fatal and Log operations above are no-ops if the check succeeded. And you can call Fatal after a Log (but not the other way around since it wouldn't make sense to try to log something after the test got interrupted).
The most important part (for me, at least) is that this API plays nicely with auto-completion. With testify and gotest.tools the log arguments are parts of the main Equal method, meaning that my editor will automatically add placeholders to them (even if I don't use them 90% of the time). Here is what I get after letting my editor complete `assert.Equa`.
assert.Equal(t assert.TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{})
Note that the signature is different from Printf, while behaving the same way for msgAndArgs (to make them all optional, however the first string will be interpreted as a formatting string).
On the other end, with my package, I get (in both cases, typing [tab] allows me to cycle through the arguments):
check.Equal(t testing.TB, want T, got T)
And in the 10% of cases where I need some context, I can call Log(f) afterward.
The API consists of only 3 top-level methods:
And 3 type-methods:
In case you want to compare complex structs, there is check.EqualDeep. And if you want a nice diff, you can import github.com/google/go-cmp/cmp:
check.EqualDeep(t, expectedObj, obj).Log(cmp.Diff(expectedObj, obj))
For other examples, consult the documentation:
code.pfad.fr/check very minimal Go assertion package
If you don't want to add it as a dependency, feel free to copy/paste the check.go file into your project.
📅 2024-05-21