SkillAgentSearch skills...

Testifylint

The Golang linter that checks usage of github.com/stretchr/testify.

Install / Use

/learn @Antonboom/Testifylint
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

testifylint

Latest release CI Go Report Card Coverage Status MIT License PRs Welcome

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
View on GitHub
GitHub Stars165
CategoryDevelopment
Updated6d ago
Forks18

Languages

Go

Security Score

100/100

Audited on Mar 19, 2026

No findings