Testifylint
The Golang linter that checks usage of github.com/stretchr/testify.
Install / Use
/learn @Antonboom/TestifylintREADME
testifylint
Checks usage of github.com/stretchr/testify.
Table of Contents
Problem statement
Tests are also program code and the requirements for them should not differ much from the requirements for the code under tests 🙂
We should try to maintain the consistency of tests, increase their readability, reduce the chance of bugs and speed up the search for a problem.
testify is the most popular Golang testing framework* in recent years. But it has a terrible ambiguous API in places, and the purpose of this linter is to protect you from annoying mistakes.
* JetBrains "The State of Go Ecosystem" reports 2021 and 2022.
Installation & usage
$ go install github.com/Antonboom/testifylint@latest
$ testifylint -h
$ testifylint ./...
Fixing
$ testifylint --fix ./...
Fixing with golangci-lint is currently unavailable due to
golangci/golangci-lint#1779.
Be aware that there may be unused imports after the fix, run go fmt.
Configuring
CLI
# Enable all checkers.
$ testifylint --enable-all ./...
# Enable specific checkers only.
$ testifylint --disable-all --enable=empty,error-is-as ./...
# Disable specific checkers only.
$ testifylint --enable-all --disable=empty,error-is-as ./...
# Checkers configuration.
$ testifylint --bool-compare.ignore-custom-types ./...
$ testifylint --expected-actual.pattern=^wanted$ ./...
$ testifylint --formatter.check-format-string --formatter.require-f-funcs --formatter.require-string-msg ./...
$ testifylint --go-require.ignore-http-handlers ./...
$ testifylint --require-error.fn-pattern="^(Errorf?|NoErrorf?)$" ./...
$ testifylint --suite-extra-assert-call.mode=require ./...
golangci-lint
https://golangci-lint.run/docs/linters/configuration/#testifylint
Checkers
- ✅ – yes
- ❌ – no
- 🤏 – partially
| Name | Enabled By Default | Autofix | |-----------------------------------------------------|--------------------|---------| | blank-import | ✅ | ❌ | | bool-compare | ✅ | ✅ | | compares | ✅ | ✅ | | contains | ✅ | 🤏 | | empty | ✅ | ✅ | | encoded-compare | ✅ | ✅ | | equal-values | ✅ | ✅ | | error-is-as | ✅ | 🤏 | | error-nil | ✅ | ✅ | | expected-actual | ✅ | ✅ | | float-compare | ✅ | ❌ | | formatter | ✅ | 🤏 | | go-require | ✅ | ❌ | | len | ✅ | ✅ | | negative-positive | ✅ | ✅ | | nil-compare | ✅ | ✅ | | regexp | ✅ | ✅ | | require-error | ✅ | ❌ | | suite-broken-parallel | ✅ | ✅ | | suite-dont-use-pkg | ✅ | ✅ | | suite-extra-assert-call | ✅ | ✅ | | suite-method-signature | ✅ | ❌ | | suite-subtest-run | ✅ | ❌ | | suite-thelper | ❌ | ✅ | | useless-assert | ✅ | ❌ |
⚠️ Also look at open for contribution checkers
blank-import
❌
import (
"testing"
_ "github.com/stretchr/testify"
_ "github.com/stretchr/testify/assert"
_ "github.com/stretchr/testify/http"
_ "github.com/stretchr/testify/mock"
_ "github.com/stretchr/testify/require"
_ "github.com/stretchr/testify/suite"
)
✅
import (
"testing"
)
Autofix: false. <br>
Enabled by default: true. <br>
Reason: testify doesn't do any init() magic, so these imports as _ do nothing and considered useless.
bool-compare
❌
assert.Equal(t, false, result)
assert.EqualValues(t, false, result)
assert.Exactly(t, false, result)
assert.NotEqual(t, true, result)
assert.NotEqualValues(t, true, result)
assert.False(t, !result)
assert.True(t, result == true)
// And other variations...
✅
assert.True(t, result)
assert.False(t, result)
Autofix: true. <br> Enabled by default: true. <br> Reason: Code simplification.
Also bool-compare supports user defined types like
type Bool bool
And fixes assertions via casting variable to builtin bool:
var predicate Bool
❌ assert.Equal(t, false, predicate)
✅ assert.False(t, bool(predicate))
To turn off this behavior use the --bool-compare.ignore-custom-types flag.
compares
❌
assert.True(t, a == b)
assert.True(t, a != b)
assert.True(t, a > b)
assert.True(t, a >= b)
assert.True(t, a < b)
assert.True(t, a <= b)
assert.False(t, a == b)
// And so on...
✅
assert.Equal(t, a, b)
assert.NotEqual(t, a, b)
assert.Greater(t, a, b)
assert.GreaterOrEqual(t, a, b)
assert.Less(t, a, b)
assert.LessOrEqual(t, a, b)
Autofix: true. <br>
Enabled by default: true. <br>
Reason: More appropriate testify API with clearer failure message.
If a and b are pointers then assert.Same/NotSame is required instead,
due to the inappropriate recursive nature of assert.Equal (based on
reflect.DeepEqual).
contains
❌
assert.True(t, strings.Contains(a, "abc123"))
assert.False(t, !strings.Contains(a, "abc123"))
assert.False(t, strings.Contains(a, "abc123"))
assert.True(t, !strings.Contains(a, "abc123"))
assert.Contains(t, arr, 1, 2)
assert.NotContains(t, arr, 1, 2)
✅
assert.Contains(t, a, "abc123")
assert.NotContains(t, a, "abc123")
assert.Subset(t, arr, 1, 2)
assert.NotSubset(t, arr, 1, 2)
Autofix: partially. <br>
Enabled by default: true. <br>
Reason: Protection from bugs, code simplification and more appropriate testify API with clearer failure message.
An example of bug in Kubernetes tests
<details> <summary>Click to expand...</summary>❌
assert.Contains(t, cadvisorInfoToUserDefinedMetrics(&cInfo),
statsapi.UserDefinedMetric{
UserDefinedMetricDescriptor: statsapi.UserDefinedMetricDescriptor{
Name: "qos",
Type: statsapi.MetricGauge,
Units: "per second",
},
Time: metav1.NewTime(timestamp2),
Value: 100,
},
statsapi.UserDefinedMetric{
UserDefinedMetricDescriptor: statsapi.UserDefinedMetricDescriptor{
Name: "cpuLoad",
Type: statsapi.MetricCumulative,
Units: "count",
},
Time: metav1.NewTime(timestamp2),
Value: 2.1,
})
✅
assert.Subset(t, cadvisorInfoToUserDefinedMetrics(&cInfo), []statsapi.UserDefinedMetric{
{
UserDefinedMetricDescriptor: statsapi.UserDefinedMetricDescriptor{
Name: "qos",
Type: statsapi.MetricGauge,
Units: "per second",
},
Time: metav1.NewTime(timestamp2),
Value: 100,
},
{
UserDefinedMetricDescriptor: statsapi.UserDefinedMetricDescriptor{
Name: "cpuLoad",
Type: statsapi.MetricCumulative,
Units: "count",
},
Time: metav1.NewTime(timestamp2),
Value: 2.1,
}})
</details>
An example of bug in Prometheus tests
<details> <summary>Click to expand...</summary>❌
logLines := getLogLines(t, ql1File)
/*
{
"time": "2025-09-14T07:49:51.652292+03:00",
"params": { "query": "test statement" },
"error": "failure",
"spanID": "0000000000000000",
"foo": "bar"
}
*/
require.Contains(t, logLines[0], "error
