{"id":29115891,"url":"https://github.com/Antonboom/testifylint","last_synced_at":"2025-06-29T11:13:39.585Z","repository":{"id":193293399,"uuid":"501929647","full_name":"Antonboom/testifylint","owner":"Antonboom","description":"The Golang linter that checks usage of github.com/stretchr/testify.","archived":false,"fork":false,"pushed_at":"2025-06-01T09:44:07.000Z","size":7077,"stargazers_count":139,"open_issues_count":35,"forks_count":10,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-06-22T19:57:30.601Z","etag":null,"topics":["go","golang","linter","static-analysis","testify"],"latest_commit_sha":null,"homepage":"https://github.com/stretchr/testify","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Antonboom.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-06-10T06:41:27.000Z","updated_at":"2025-06-17T15:49:32.000Z","dependencies_parsed_at":"2023-10-05T01:23:58.850Z","dependency_job_id":"a4fc961e-e723-488d-b18d-4afde67f22da","html_url":"https://github.com/Antonboom/testifylint","commit_stats":null,"previous_names":["antonboom/testifylint"],"tags_count":24,"template":false,"template_full_name":null,"purl":"pkg:github/Antonboom/testifylint","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Antonboom%2Ftestifylint","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Antonboom%2Ftestifylint/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Antonboom%2Ftestifylint/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Antonboom%2Ftestifylint/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Antonboom","download_url":"https://codeload.github.com/Antonboom/testifylint/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Antonboom%2Ftestifylint/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262581513,"owners_count":23331925,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["go","golang","linter","static-analysis","testify"],"created_at":"2025-06-29T11:13:16.000Z","updated_at":"2025-06-29T11:13:39.553Z","avatar_url":"https://github.com/Antonboom.png","language":"Go","funding_links":[],"categories":["Code Analysis","代码分析"],"sub_categories":["Routers","路由器"],"readme":"# testifylint\n\n![Latest release](https://img.shields.io/github/v/release/Antonboom/testifylint)\n[![CI](https://github.com/Antonboom/testifylint/actions/workflows/ci.yml/badge.svg)](https://github.com/Antonboom/testifylint/actions/workflows/ci.yml)\n[![Go Report Card](https://goreportcard.com/badge/github.com/Antonboom/testifylint)](https://goreportcard.com/report/github.com/Antonboom/testifylint?dummy=unused)\n[![Coverage Status](https://coveralls.io/repos/github/Antonboom/testifylint/badge.svg?branch=master)](https://coveralls.io/github/Antonboom/testifylint?branch=master\u0026dummy=unused)\n[![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE)\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/Antonboom/testifylint/blob/master/CONTRIBUTING.md#Open-for-contribution)\n\nChecks usage of [github.com/stretchr/testify](https://github.com/stretchr/testify).\n\n## Table of Contents\n\n* [Problem statement](#problem-statement)\n* [Installation \u0026 usage](#installation--usage)\n* [Configuring](#configuring)\n* [Checkers](#checkers)\n* [Chain of warnings](#chain-of-warnings)\n* [testify v2](#testify-v2)\n\n## Problem statement\n\nTests are also program code and the requirements for them should not differ much from the requirements\nfor the code under tests 🙂\n\nWe should try to maintain the consistency of tests, increase their readability, reduce the chance of bugs\nand speed up the search for a problem.\n\n[testify](https://github.com/stretchr/testify) is the most popular Golang testing framework* in recent years.\nBut it has a terrible ambiguous API in places, and **the purpose of this linter is to protect you from annoying\nmistakes**.\n\nMost checkers are stylistic, but checkers like [error-is-as](#error-is-as), [require-error](#require-error),\n[expected-actual](#expected-actual), [float-compare](#float-compare) are really helpful.\n\n_* JetBrains \"The State of Go Ecosystem\" reports\n[2021](https://www.jetbrains.com/lp/devecosystem-2021/go/#Go_which-testing-frameworks-do-you-use-regularly-if-any)\nand [2022](https://www.jetbrains.com/lp/devecosystem-2022/go/#which-testing-frameworks-do-you-use-regularly-if-any-)._\n\n## Installation \u0026 usage\n\n```\n$ go install github.com/Antonboom/testifylint@latest\n$ testifylint -h\n$ testifylint ./...\n```\n\n### Fixing\n\n```\n$ testifylint --fix ./...\n```\n\nFixing with `golangci-lint` is currently **unavailable** due to\n[golangci/golangci-lint#1779](https://github.com/golangci/golangci-lint/issues/1779).\n\nBe aware that there may be unused imports after the fix, run `go fmt`.\n\n## Configuring\n\n### CLI\n\n```bash\n# Enable all checkers.\n$ testifylint --enable-all ./...\n\n# Enable specific checkers only.\n$ testifylint --disable-all --enable=empty,error-is-as ./...\n\n# Disable specific checkers only.\n$ testifylint --enable-all --disable=empty,error-is-as ./...\n\n# Checkers configuration.\n$ testifylint --bool-compare.ignore-custom-types ./...\n$ testifylint --expected-actual.pattern=^wanted$ ./...\n$ testifylint --formatter.check-format-string --formatter.require-f-funcs --formatter.require-string-msg ./...\n$ testifylint --go-require.ignore-http-handlers ./...\n$ testifylint --require-error.fn-pattern=\"^(Errorf?|NoErrorf?)$\" ./...\n$ testifylint --suite-extra-assert-call.mode=require ./...\n```\n\n### golangci-lint\n\nhttps://golangci-lint.run/usage/linters/#testifylint\n\n## Checkers\n\n- ✅ – yes\n- ❌ – no\n- 🤏 – partially\n\n| Name                                                | Enabled By Default | Autofix |\n|-----------------------------------------------------|--------------------|---------|\n| [blank-import](#blank-import)                       | ✅                  | ❌       |\n| [bool-compare](#bool-compare)                       | ✅                  | ✅       |\n| [compares](#compares)                               | ✅                  | ✅       |\n| [contains](#contains)                               | ✅                  | ✅       |\n| [empty](#empty)                                     | ✅                  | ✅       |\n| [encoded-compare](#encoded-compare)                 | ✅                  | ✅       |\n| [equal-values](#equal-values)                       | ✅                  | ✅       |\n| [error-is-as](#error-is-as)                         | ✅                  | 🤏      |\n| [error-nil](#error-nil)                             | ✅                  | ✅       |\n| [expected-actual](#expected-actual)                 | ✅                  | ✅       |\n| [float-compare](#float-compare)                     | ✅                  | ❌       |\n| [formatter](#formatter)                             | ✅                  | 🤏      |\n| [go-require](#go-require)                           | ✅                  | ❌       |\n| [len](#len)                                         | ✅                  | ✅       |\n| [negative-positive](#negative-positive)             | ✅                  | ✅       |\n| [nil-compare](#nil-compare)                         | ✅                  | ✅       |\n| [regexp](#regexp)                                   | ✅                  | ✅       |\n| [require-error](#require-error)                     | ✅                  | ❌       |\n| [suite-broken-parallel](#suite-broken-parallel)     | ✅                  | ✅       |\n| [suite-dont-use-pkg](#suite-dont-use-pkg)           | ✅                  | ✅       |\n| [suite-extra-assert-call](#suite-extra-assert-call) | ✅                  | ✅       |\n| [suite-method-signature](#suite-method-signature)   | ✅                  | ❌       |\n| [suite-subtest-run](#suite-subtest-run)             | ✅                  | ❌       |\n| [suite-thelper](#suite-thelper)                     | ❌                  | ✅       |\n| [useless-assert](#useless-assert)                   | ✅                  | ❌       |\n\n\u003e ⚠️ Also look at open for contribution [checkers](CONTRIBUTING.md#open-for-contribution)\n\n---\n\n### blank-import\n\n```go\n❌\nimport (\n    \"testing\"\n\n    _ \"github.com/stretchr/testify\"\n    _ \"github.com/stretchr/testify/assert\"\n    _ \"github.com/stretchr/testify/http\"\n    _ \"github.com/stretchr/testify/mock\"\n    _ \"github.com/stretchr/testify/require\"\n    _ \"github.com/stretchr/testify/suite\"\n)\n\n✅\nimport (\n    \"testing\"\n)\n```\n\n**Autofix**: false. \u003cbr\u003e\n**Enabled by default**: true. \u003cbr\u003e\n**Reason**: `testify` doesn't do any `init()` magic, so these imports as `_` do nothing and considered useless.\n\n---\n\n### bool-compare\n\n```go\n❌\nassert.Equal(t, false, result)\nassert.EqualValues(t, false, result)\nassert.Exactly(t, false, result)\nassert.NotEqual(t, true, result)\nassert.NotEqualValues(t, true, result)\nassert.False(t, !result)\nassert.True(t, result == true)\n// And other variations...\n\n✅\nassert.True(t, result)\nassert.False(t, result)\n```\n\n**Autofix**: true. \u003cbr\u003e\n**Enabled by default**: true. \u003cbr\u003e\n**Reason**: Code simplification.\n\nAlso `bool-compare` supports user defined types like\n\n```go\ntype Bool bool\n```\n\nAnd fixes assertions via casting variable to builtin `bool`:\n\n```go\nvar predicate Bool\n❌ assert.Equal(t, false, predicate)\n✅ assert.False(t, bool(predicate))\n```\n\nTo turn off this behavior use the `--bool-compare.ignore-custom-types` flag.\n\n---\n\n### compares\n\n```go\n❌\nassert.True(t, a == b)\nassert.True(t, a != b)\nassert.True(t, a \u003e b)\nassert.True(t, a \u003e= b)\nassert.True(t, a \u003c b)\nassert.True(t, a \u003c= b)\nassert.False(t, a == b)\n// And so on...\n\n✅\nassert.Equal(t, a, b)\nassert.NotEqual(t, a, b)\nassert.Greater(t, a, b)\nassert.GreaterOrEqual(t, a, b)\nassert.Less(t, a, b)\nassert.LessOrEqual(t, a, b)\n```\n\n**Autofix**: true. \u003cbr\u003e\n**Enabled by default**: true. \u003cbr\u003e\n**Reason**: More appropriate `testify` API with clearer failure message.\n\nIf `a` and `b` are pointers then `assert.Same`/`NotSame` is required instead,\ndue to the inappropriate recursive nature of `assert.Equal` (based on\n[reflect.DeepEqual](https://pkg.go.dev/reflect#DeepEqual)).\n\n---\n\n### contains\n\n```go\n❌\nassert.True(t, strings.Contains(a, \"abc123\"))\nassert.False(t, !strings.Contains(a, \"abc123\"))\n\nassert.False(t, strings.Contains(a, \"abc123\"))\nassert.True(t, !strings.Contains(a, \"abc123\"))\n\n✅\nassert.Contains(t, a, \"abc123\")\nassert.NotContains(t, a, \"abc123\")\n```\n\n**Autofix**: true. \u003cbr\u003e\n**Enabled by default**: true. \u003cbr\u003e\n**Reason**: Code simplification and more appropriate `testify` API with clearer failure message.\n\n---\n\n### empty\n\n```go\n❌\nassert.Len(t, arr, 0)\nassert.Zero(t, str)\nassert.Zero(t, len(arr))\nassert.Equal(t, 0, len(arr))\nassert.EqualValues(t, 0, len(arr))\nassert.Exactly(t, 0, len(arr))\nassert.LessOrEqual(t, len(arr), 0)\nassert.GreaterOrEqual(t, 0, len(arr))\nassert.Less(t, len(arr), 1)\nassert.Greater(t, 1, len(arr))\nassert.Equal(t, \"\", str)\nassert.EqualValues(t, \"\", str)\nassert.Exactly(t, \"\", str)\nassert.Equal(t, ``, str)\nassert.EqualValues(t, ``, str)\nassert.Exactly(t, ``, str)\n\nassert.Positive(t, len(arr))\nassert.NotZero(t, str)\nassert.NotZero(t, len(arr))\nassert.NotEqual(t, 0, len(arr))\nassert.NotEqualValues(t, 0, len(arr))\nassert.Greater(t, len(arr), 0)\nassert.Less(t, 0, len(arr))\nassert.NotEqual(t, \"\", str)\nassert.NotEqualValues(t, \"\", str)\nassert.NotEqual(t, ``, str)\nassert.NotEqualValues(t, ``, str)\n\n✅\nassert.Empty(t, arr)\nassert.NotEmpty(t, arr)\n```\n\n**Autofix**: true. \u003cbr\u003e\n**Enabled by default**: true. \u003cbr\u003e\n**Reason**: More appropriate `testify` API with clearer failure message.\n\nAlso `empty` removes extra `len` call in `*Emtpy` assertions:\n\n```go\n❌\nassert.Empty(t, len(arr))\nassert.NotEmpty(t, len(arr))\n\n✅\nassert.Empty(t, arr)\nassert.NotEmpty(t, arr)\n```\n\nP.S. `empty` does not remove the string conversion and keeps as is:\n\n- `string(bytes)` – to keep assert failure message readability;\n- `string(str)` – in favor of [unconvert](https://golangci-lint.run/usage/linters/#unconvert).\n\n---\n\n### encoded-compare\n\n```go\n❌\nassert.Equal(t, `{\"foo\": \"bar\"}`, body)\nassert.EqualValues(t, `{\"foo\": \"bar\"}`, body)\nassert.Exactly(t, `{\"foo\": \"bar\"}`, body)\nassert.Equal(t, expectedJSON, resultJSON)\nassert.Equal(t, expBodyConst, w.Body.String())\nassert.Equal(t, fmt.Sprintf(`{\"value\":\"%s\"}`, hexString), result)\nassert.Equal(t, \"{}\", json.RawMessage(resp))\nassert.Equal(t, expJSON, strings.Trim(string(resultJSONBytes), \"\\n\")) // + Replace, ReplaceAll, TrimSpace\n\nassert.Equal(t, expectedYML, conf)\n\n✅\nassert.JSONEq(t, `{\"foo\": \"bar\"}`, body)\nassert.YAMLEq(t, expectedYML, conf)\n```\n\n**Autofix**: true. \u003cbr\u003e\n**Enabled by default**: true. \u003cbr\u003e\n**Reason**: Protection from bugs and more appropriate `testify` API with clearer failure message.\n\n`encoded-compare` detects JSON-style string constants (usable in `fmt.Sprintf` also) and JSON-style/YAML-style named\nvariables. If variable is converted to `json.RawMessage`, then it is considered JSON unconditionally.\n\nWhen fixing, `encoded-compare` removes unnecessary conversions to `[]byte`, `string`, `json.RawMessage` and calls of\n`strings.Replace`, `strings.ReplaceAll`, `strings.Trim`, `strings.TrimSpace`, and adds a conversion to `string` when\nneeded.\n\n---\n\n### equal-values\n\n```go\n❌\nassert.EqualValues(t, 42, result.IntField)\nassert.NotEqualValues(t, 42, result.IntField)\n// And other variations with similar types (strings, numerics, structs, etc.)...\n\n✅\nassert.Equal(t, 42, result.IntField)\nassert.NotEqual(t, 42, result.IntField)\n```\n\n**Autofix**: true. \u003cbr\u003e\n**Enabled by default**: true. \u003cbr\u003e\n**Reason**: Using more appropriate and more error-proof `testify` API.\n\nIn additional:\n\n1. Overflow-underflow issues are [covered by testify itself](https://github.com/stretchr/testify/pull/1531).\n2. Nil comparisons are covered by [nil-compare](#nil-compare).\n\n---\n\n### error-is-as\n\n```go\n❌\nassert.Error(t, err, errSentinel) // Typo, errSentinel hits `msgAndArgs`.\nassert.NoError(t, err, errSentinel)\nassert.True(t, errors.Is(err, errSentinel))\nassert.False(t, errors.Is(err, errSentinel))\nassert.True(t, errors.As(err, \u0026target))\nassert.False(t, errors.As(err, \u0026target))\n\n✅\nassert.ErrorIs(t, err, errSentinel)\nassert.NotErrorIs(t, err, errSentinel)\nassert.ErrorAs(t, err, \u0026target)\nassert.NotErrorAs(t, err, \u0026target)\n```\n\n**Autofix**: partially. \u003cbr\u003e\n**Enabled by default**: true. \u003cbr\u003e\n**Reason**: In the first two cases, a common mistake that leads to hiding the incorrect wrapping of sentinel errors.\nIn the rest cases – more appropriate `testify` API with clearer failure message.\n\nAlso `error-is-as` repeats `go vet`'s\n[errorsas check](https://cs.opensource.google/go/x/tools/+/master:go/analysis/passes/errorsas/errorsas.go)\nlogic, but without autofix.\n\n---\n\n### error-nil\n\n```go\n❌\nassert.Nil(t, err)\nassert.Empty(t, err)\nassert.Zero(t, err)\nassert.Equal(t, nil, err)\nassert.EqualValues(t, nil, err)\nassert.Exactly(t, nil, err)\nassert.ErrorIs(t, err, nil)\n\nassert.NotNil(t, err)\nassert.NotEmpty(t, err)\nassert.NotZero(t, err)\nassert.NotEqual(t, nil, err)\nassert.NotEqualValues(t, nil, err)\nassert.NotErrorIs(t, err, nil)\n\n✅\nassert.NoError(t, err)\nassert.Error(t, err)\n```\n\n**Autofix**: true. \u003cbr\u003e\n**Enabled by default**: true. \u003cbr\u003e\n**Reason**: More appropriate `testify` API with clearer failure message.\n\n---\n\n### expected-actual\n\n```go\n❌\nassert.Equal(t, result, expected)\nassert.Equal(t, result, len(expected))\nassert.Equal(t, len(resultFields), len(expectedFields))\nassert.EqualExportedValues(t, resultObj, User{Name: \"Rob\"})\nassert.EqualValues(t, result, 42)\nassert.Exactly(t, result, int64(42))\nassert.JSONEq(t, result, `{\"version\": 3}`)\nassert.InDelta(t, result, 42.42, 1.0)\nassert.InDeltaMapValues(t, result, map[string]float64{\"score\": 0.99}, 1.0)\nassert.InDeltaSlice(t, result, []float64{0.98, 0.99}, 1.0)\nassert.InEpsilon(t, result, 42.42, 0.0001)\nassert.InEpsilonSlice(t, result, []float64{0.9801, 0.9902}, 0.0001)\nassert.IsType(t, result, (*User)(nil))\nassert.NotEqual(t, result, \"expected\")\nassert.NotEqualValues(t, result, \"expected\")\nassert.NotSame(t, resultPtr, \u0026value)\nassert.Same(t, resultPtr, \u0026value)\nassert.WithinDuration(t, resultTime, time.Date(2023, 01, 12, 11, 46, 33, 0, nil), time.Second)\nassert.YAMLEq(t, result, \"version: '3'\")\n\n✅\nassert.Equal(t, expected, result)\nassert.Equal(t, len(expected), result)\nassert.Equal(t, len(expectedFields), len(resultFields))\nassert.EqualExportedValues(t, User{Name: \"Rob\"}, resultObj)\nassert.EqualValues(t, 42, result)\n// And so on...\n```\n\n**Autofix**: true. \u003cbr\u003e\n**Enabled by default**: true. \u003cbr\u003e\n**Reason**: A common mistake that makes it harder to understand the reason of failed test.\n\nThe checker considers the expected value to be a basic literal, constant, or variable whose name matches the pattern\n(`--expected-actual.pattern` flag).\n\nIt is\nplanned [to change the order of assertion arguments](https://github.com/stretchr/testify/issues/1089#Argument_order) to\nmore natural (actual, expected) in `v2` of `testify`.\n\n---\n\n### float-compare\n\n```go\n❌\nassert.Equal(t, 42.42, result)\nassert.EqualValues(t, 42.42, result)\nassert.Exactly(t, 42.42, result)\nassert.True(t, result == 42.42)\nassert.False(t, result != 42.42)\n\n✅\nassert.InEpsilon(t, 42.42, result, 0.0001) // Or assert.InDelta\n```\n\n**Autofix**: false. \u003cbr\u003e\n**Enabled by default**: true. \u003cbr\u003e\n**Reason**: Do not forget about [floating point rounding issues](https://floating-point-gui.de/errors/comparison/).\n\nThis checker is similar to the [floatcompare](https://github.com/golangci/golangci-lint/pull/2608) linter.\n\n---\n\n### formatter\n\n```go\n❌\nassert.ElementsMatch(t, certConfig.Org, csr.Subject.Org, \"organizations not equal\")\nassert.Error(t, err, fmt.Sprintf(\"Profile %s should not be valid\", test.profile))\nassert.Errorf(t, err, fmt.Sprintf(\"test %s\", test.testName))\nassert.Truef(t, targetTs.Equal(ts), \"the timestamp should be as expected (%s) but was %s\", targetTs)\n// And other go vet's printf checks...\n\n✅\nassert.ElementsMatchf(t, certConfig.Org, csr.Subject.Org, \"organizations not equal\")\nassert.Errorf(t, err, \"Profile %s should not be valid\", test.profile)\nassert.Errorf(t, err, \"test %s\", test.testName)\nassert.Truef(t, targetTs.Equal(ts), \"the timestamp should be as expected (%s) but was %s\", targetTs, ts)\n```\n\n**Autofix**: partially. \u003cbr\u003e\n**Enabled by default**: true. \u003cbr\u003e\n**Reason**: Code simplification, protection from bugs, following the \"Go Way\".\n\nThe `formatter` checker has the following features:\n\n#### 1)\n\nDetecting unnecessary `fmt.Sprintf` in assertions. Somewhat reminiscent of the\n[staticcheck's S1028 check](https://staticcheck.dev/docs/checks/#S1028).\n\n#### 2)\n\nValidating consistency of assertions format strings and corresponding arguments, using a patched fork of `go vet`'s\n[printf](https://cs.opensource.google/go/x/tools/+/master:go/analysis/passes/printf/printf.go) analyzer. To\ndisable this feature, use `--formatter.check-format-string=false` flag.\n\n#### 3)\n\nRequirement of the f-assertions (e.g. assert.Equal**f**) if format string is used. Disabled by default, use\n`--formatter.require-f-funcs` flag to enable. \u003cbr\u003e\n\nThis helps follow Go's implicit convention _\"Printf-like functions must end with `f`\"_ and sets the stage for moving to\n`v2` of `testify`. In this way the checker resembles\nthe [goprintffuncname](https://github.com/jirfag/go-printf-func-name)\nlinter (included in [golangci-lint](https://golangci-lint.run/usage/linters/)). \u003cbr\u003e\n\nAlso, verbs in the format string of f-assertions are highlighted by an IDE, e.g. GoLand:\n\n\u003cimg width=\"600\" alt=\"F-assertion IDE highlighting\" src=\"https://github.com/Antonboom/testifylint/assets/17127404/9bdab802-d6eb-477d-a411-6cba043d33a5\"\u003e\n\n\u003cbr\u003e\n\n\u003e [!CAUTION]\n\u003e `--formatter.require-f-funcs` requires f-assertions, **even if there are no variable-length variables**, i.e. it\n\u003e requires `require.NoErrorf` for both these cases:\n\u003e ```\n\u003e require.NoErrorf(t, err, \"unexpected error\")\n\u003e require.NoErrorf(t, err, \"unexpected error for sid: %v\", sid)\n\u003e ```\n\u003e To understand this behavior, please read the reference below.\n\n#### Historical reference of formatter\n\n\u003cdetails\u003e\n\n\u003csummary\u003eClick to expand...\u003c/summary\u003e\n\n\u003cbr\u003e\nThose who are new to `testify` may be discouraged by the duplicative API:\n\n```go\nfunc Equal(t TestingT, expected, actual any, msgAndArgs ...any) bool\nfunc Equalf(t TestingT, expected, actual any, msg string, args ...any) bool\n```\n\nThe f-functions (Equal**f**, Error**f**, etc.) were introduced a long time ago (2017) to close\n[uber-go/zap's issue](https://github.com/stretchr/testify/issues/339): `go1.7 vet` complained\nabout the following logger\n[test](https://github.com/uber-go/zap/blame/8f5ee80ab2dbc713823341ce30334cd9c03a98e5/flag_test.go#L60):\n\n```go\nif tc.wantErr {\n    // flag_test.go:61: possible formatting directive in Error call\n    assert.Error(t, err, \"Parse(%v) should fail.\", tc.args)\n    return\n}\n```\n\nBut! It was the incorrect logic inside `go vet`'s **printf** analyzer, and not `testify`'s issue.\n\nFact is that in Go 1.7 **printf** only\n[checked the name of the function](https://github.com/golang/go/blob/2b7a7b710f096b1b7e6f2ab5e9e3ec003ad7cd12/src/cmd/vet/print.go#L69),\nbut did not take into account its package, thereby reacting to everything that is possible:\n\n```go\n// isPrint records the unformatted-print functions.\nvar isPrint = map[string]bool{\n    \"error\": true,\n    \"fatal\": true,\n    // ...\n}\n```\n\nThis behaviour\nwas [fixed](https://github.com/golang/go/blob/ad7c32dc3b6d5edc3dd72b3e15c80dc4f4c27064/src/cmd/vet/print.go#L62)\nin Go 1.10 after a related [issue](https://github.com/golang/go/issues/22936):\n\n```go\n// isPrint records the print functions.\nvar isPrint = map[string]bool{\n    \"fmt.Errorf\": true,\n    \"fmt.Fprint\": true,\n    // ...\n}\n```\n\nNow **printf** only checked Golang standard library functions (unless configured otherwise) and had nothing against\n`testify`'s assertions signatures.\n\nDespite this, f-functions have already been released, giving rise to ambiguous API.\n\nBut surely the maintainers had no choice to change the signatures in accordance with Go convention, because it would\nbreak backwards compatibility:\n\n```go\nfunc Equal(t TestingT, expected, actual any) bool\nfunc Equalf(t TestingT, expected, actual any, msg string, args ...any) bool\n```\n\nBut I hope it will be [introduced](https://github.com/stretchr/testify/issues/1089#issuecomment-1695059265)\nin `v2` of `testify`.\n\n\u003c/details\u003e\n\n#### 4)\n\nValidating the first argument of `msgAndArgs` has `string` type (based on `testify`'s\nmaintainer's [feedback](https://github.com/stretchr/testify/issues/1679#issuecomment-2480629257)) and this string is\nnot empty:\n\n```go\n❌\nassert.Equal(t, 1, strings.Count(b.String(), \"hello\"), tc)\nassert.Equalf(t, want, got, \"\")\n\n✅\nassert.Equal(t, 1, strings.Count(b.String(), \"hello\"), \"%+v\", tc)\nassert.Equal(t, want, got)\n```\n\nYou can disable this behaviour with the `--formatter.require-string-msg=false` flag.\n\n#### 5)\n\nValidating there are no arguments in `msgAndArgs` if message is not a string:\n\n```go\n❌\nassert.True(t, cco.IsCardNumber(valid), i, valid) // Causes panic.\n```\n\nSee [testify's issue](https://github.com/stretchr/testify/issues/1679) for details.\n\n#### 6) \n\nFinally, it checks that failure message in `Fail` and `FailNow` is not used as a format string (which won't work):\n\n```go\n❌\nassert.Fail(t, \"test case [%d] failed. %+v != %+v\", idx, tc.expected, actual) // Causes panic.\n\n✅\nassert.Fail(t, \"good luck!\", \"test case [%d] failed. %+v != %+v\", idx, tc.expected, actual)\n```\n\n---\n\n### go-require\n\n```go\ngo func() {\n    conn, err = lis.Accept()\n    require.NoError(t, err) ❌\n\n    if assert.Error(err) {     ✅\n        assert.FailNow(t, msg) ❌\n    }\n}()\n```\n\n**Autofix**: false. \u003cbr\u003e\n**Enabled by default**: true. \u003cbr\u003e\n**Reason**: Incorrect use of functions.\n\nThis checker is a radically improved analogue of `go vet`'s\n[testinggoroutine](https://cs.opensource.google/go/x/tools/+/master:go/analysis/passes/testinggoroutine/doc.go) check.\n\nThe point of the check is that, according to the [documentation](https://pkg.go.dev/testing#T),\nfunctions leading to `t.FailNow` (essentially to `runtime.GoExit`) must only be used in the goroutine that runs the\ntest.\nOtherwise, they will not work as declared, namely, finish the test function.\n\nYou can disable the `go-require` checker and continue to use `require` as the current goroutine finisher, but this could\nlead\n\n1. to possible resource leaks in tests;\n2. to increasing of confusion, because functions will be not used as intended.\n\nTypically, any assertions inside goroutines are a marker of poor test architecture.\nTry to execute them in the main goroutine and distribute the data necessary for this into it\n([example](https://github.com/ipfs/kubo/issues/2043#issuecomment-164136026)).\n\nAlso a bad solution would be to simply replace all `require` in goroutines with `assert`\n(like\n[here](https://github.com/gravitational/teleport/pull/22567/files#diff-9f5fd20913c5fe80c85263153fa9a0b28dbd1407e53da4ab5d09e13d2774c5dbR7377))\n– this will only mask the problem.\n\nThe checker is enabled by default, because `testinggoroutine` is enabled by default in `go vet`.\n\nIn addition, the checker warns about `require` in HTTP handlers (functions and methods whose signature matches\n[http.HandlerFunc](https://pkg.go.dev/net/http#HandlerFunc)), because handlers run in a separate\n[service goroutine](https://cs.opensource.google/go/go/+/refs/tags/go1.22.3:src/net/http/server.go;l=2782;drc=1d45a7ef560a76318ed59dfdb178cecd58caf948)\nthat services the HTTP connection. Terminating these goroutines can lead to undefined behaviour and difficulty debugging\ntests. You can turn off the check using the `--go-require.ignore-http-handlers` flag.\n\nP.S. Look at [testify's issue](https://github.com/stretchr/testify/issues/772), related to assertions in the goroutines.\n\n---\n\n### len\n\n```go\n❌\nassert.Equal(t, 42, len(arr))\nassert.Equal(t, len(arr), 42)\nassert.EqualValues(t, 42, len(arr))\nassert.EqualValues(t, len(arr), 42)\nassert.Exactly(t, 42, len(arr))\nassert.Exactly(t, len(arr), 42)\nassert.True(t, 42 == len(arr))\nassert.True(t, len(arr) == 42)\n\nassert.Equal(t, value, len(arr))\nassert.EqualValues(t, value, len(arr))\nassert.Exactly(t, value, len(arr))\nassert.True(t, len(arr) == value)\n\nassert.Equal(t, len(expArr), len(arr))\nassert.EqualValues(t, len(expArr), len(arr))\nassert.Exactly(t, len(expArr), len(arr))\nassert.True(t, len(arr) == len(expArr))\n\n✅\nassert.Len(t, arr, 42)\nassert.Len(t, arr, value)\nassert.Len(t, arr, len(expArr))\n```\n\n**Autofix**: true. \u003cbr\u003e\n**Enabled by default**: true. \u003cbr\u003e\n**Reason**: More appropriate `testify` API with clearer failure message.\n\n\u003e [!CAUTION]\n\u003e The checker ignores assertions in which length checking is not a priority, e.g.\n\u003e ```go\n\u003e assert.Equal(t, len(arr), value)\n\u003e ```\n\n---\n\n### negative-positive\n\n```go\n❌\nassert.Less(t, a, 0)\nassert.Greater(t, 0, a)\nassert.True(t, a \u003c 0)\nassert.True(t, 0 \u003e a)\nassert.False(t, a \u003e= 0)\nassert.False(t, 0 \u003c= a)\n\nassert.Greater(t, a, 0)\nassert.Less(t, 0, a)\nassert.True(t, a \u003e 0)\nassert.True(t, 0 \u003c a)\nassert.False(t, a \u003c= 0)\nassert.False(t, 0 \u003e= a)\n\n✅\nassert.Negative(t, a)\nassert.Positive(t, a)\n```\n\n**Autofix**: true. \u003cbr\u003e\n**Enabled by default**: true \u003cbr\u003e\n**Reason**: More appropriate `testify` API with clearer failure message.\n\nTyped zeros (like `int8(0)`, ..., `uint64(0)`) are also supported.\n\n---\n\n### nil-compare\n\n```go\n❌\nassert.Equal(t, nil, value)\nassert.EqualValues(t, nil, value)\nassert.Exactly(t, nil, value)\n\nassert.NotEqual(t, nil, value)\nassert.NotEqualValues(t, nil, value)\n\n✅\nassert.Nil(t, value)\nassert.NotNil(t, value)\n```\n\n**Autofix**: true. \u003cbr\u003e\n**Enabled by default**: true. \u003cbr\u003e\n**Reason**: Protection from bugs and more appropriate `testify` API with clearer failure message.\n\nUsing untyped `nil` in the functions above along with a non-interface type does not make sense:\n\n```go\nassert.Equal(t, nil, eventsChan)    // Always fail.\nassert.NotEqual(t, nil, eventsChan) // Always pass.\n```\n\nThe right way:\n\n```go\nassert.Equal(t, (chan Event)(nil), eventsChan)\nassert.NotEqual(t, (chan Event)(nil), eventsChan)\n```\n\nBut in the case of `Equal`, `NotEqual` and `Exactly` type conversion approach still doesn't work for the function type.\n\nThe best option here is to just use `Nil` / `NotNil` (see [details](https://github.com/stretchr/testify/issues/1524)).\n\n---\n\n### regexp\n\n```go\n❌\nassert.Regexp(t, regexp.MustCompile(`\\[.*\\] DEBUG \\(.*TestNew.*\\): message`), out)\nassert.NotRegexp(t, regexp.MustCompile(`\\[.*\\] TRACE message`), out)\n\n✅\nassert.Regexp(t, `\\[.*\\] DEBUG \\(.*TestNew.*\\): message`, out)\nassert.NotRegexp(t, `\\[.*\\] TRACE message`, out)\n```\n\n**Autofix**: true. \u003cbr\u003e\n**Enabled by default**: true. \u003cbr\u003e\n**Reason**: Code simplification.\n\n---\n\n### require-error\n\n```go\n❌\nassert.Error(t, err) // s.Error(err), s.Assert().Error(err)\nassert.ErrorIs(t, err, io.EOF)\nassert.ErrorAs(t, err, \u0026target)\nassert.EqualError(t, err, \"end of file\")\nassert.ErrorContains(t, err, \"end of file\")\nassert.NoError(t, err)\nassert.NotErrorIs(t, err, io.EOF)\n\n✅\nrequire.Error(t, err) // s.Require().Error(err), s.Require().Error(err)\nrequire.ErrorIs(t, err, io.EOF)\nrequire.ErrorAs(t, err, \u0026target)\n// And so on...\n```\n\n**Autofix**: false. \u003cbr\u003e\n**Enabled by default**: true. \u003cbr\u003e\n**Reason**: Such \"ignoring\" of errors leads to further panics, making the test harder to debug.\n\n[testify/require](https://pkg.go.dev/github.com/stretchr/testify@master/require#hdr-Assertions) allows\nto stop test execution when a test fails.\n\nBy default `require-error` only checks the `*Error*` assertions, presented above. \u003cbr\u003e\n\nYou can set `--require-error.fn-pattern` flag to limit the checking to certain calls (but still from the list above).\nFor example, `--require-error.fn-pattern=\"^(Errorf?|NoErrorf?)$\"` will only check `Error`, `Errorf`, `NoError`,\nand `NoErrorf`.\n\nAlso, to minimize false positives, `require-error` ignores:\n\n- assertions in the `if` condition;\n- assertions in the bool expression;\n- the entire `if-else[-if]` block, if there is an assertion in any `if` condition;\n- the last assertion in the block, if there are no methods/functions calls after it;\n- assertions in an explicit goroutine (including `http.Handler`);\n- assertions in an explicit testing cleanup function or suite teardown methods;\n- sequence of `NoError` assertions.\n\n---\n\n### suite-broken-parallel\n\n```go\nfunc (s *MySuite) SetupTest() {\n    s.T().Parallel() ❌\n}\n\n// And other hooks...\n\nfunc (s *MySuite) TestSomething() {\n    s.T().Parallel() ❌\n    \n    for _, tt := range cases {\n        s.Run(tt.name, func() {\n            s.T().Parallel() ❌\n        })\n        \n        s.T().Run(tt.name, func(t *testing.T) {\n            t.Parallel() ❌\n        })\n    }\n}\n```\n\n**Autofix**: true. \u003cbr\u003e\n**Enabled by default**: true. \u003cbr\u003e\n**Reason**: Protection from undefined behaviour.\n\n`v1` of `testify` doesn't support suite's parallel tests and subtests.\n\nDepending on [case](./analyzer/testdata/src/debug/suite_broken_parallel_test.go) using of `t.Parallel()` leads to\n\n- data race\n- panic\n- non-working suite hooks\n- silent ignoring of this directive\n\nSo, `testify`'s maintainers recommend discourage parallel tests inside suite.\n\n\u003cdetails\u003e\n\u003csummary\u003eRelated issues...\u003c/summary\u003e\n\n- https://github.com/stretchr/testify/issues/187\n- https://github.com/stretchr/testify/issues/466\n- https://github.com/stretchr/testify/issues/934\n- https://github.com/stretchr/testify/issues/1139\n- https://github.com/stretchr/testify/issues/1253\n\n\u003c/details\u003e\n\n---\n\n### suite-dont-use-pkg\n\n```go\nfunc (s *MySuite) TestSomething() {\n    ❌ assert.Equal(s.T(), 42, value)\n    ✅ s.Equal(42, value)\n}\n```\n\n**Autofix**: true. \u003cbr\u003e\n**Enabled by default**: true. \u003cbr\u003e\n**Reason**: More simple and uniform code.\n\n---\n\n### suite-extra-assert-call\n\nBy default, the checker wants you to remove unnecessary `Assert()` calls:\n\n```go\nfunc (s *MySuite) TestSomething() {\n    ❌ s.Assert().Equal(42, value)\n    ✅ s.Equal(42, value)\n}\n```\n\nBut sometimes, on the contrary, people want consistency with `s.Assert()` and `s.Require()`:\n\n```go\nfunc (s *MySuite) TestSomething() {\n    // ...\n\n    ❌\n    s.Require().NoError(err)\n    s.Equal(42, value)\n\n    ✅\n    s.Require().NoError(err)\n    s.Assert().Equal(42, value)\n}\n```\n\nYou can enable such behavior through `--suite-extra-assert-call.mode=require`.\n\n**Autofix**: true. \u003cbr\u003e\n**Enabled by default**: true, in the `remove` mode. \u003cbr\u003e\n**Reason**: More simple or uniform code.\n\n---\n\n### suite-method-signature\n\n```go\n❌\nfunc (s *MySuite) SetupTest(i int) { /* ... */ }\nfunc (s *MySuite) TestAlice(t *testing.T) { s.SetupTest(rand()) /* ... */ }\nfunc (s *MySuite) TestBob() { s.SetupTest(rand()) /* ... */ }\n\n✅\nfunc (s *MySuite) SetupTest() { s.setupTest(rand()) } // Use inversion-of-control's methods from suite interfaces.  \nfunc (s *MySuite) TestAlice() { /* ... */  }          // Keep test methods clean from args and returning values.\nfunc (s *MySuite) TestBob() { /* ... */  }\nfunc (s *MySuite) setupTest(i int) { /* ... */ }\n```\n\n**Autofix**: false. \u003cbr\u003e\n**Enabled by default**: true. \u003cbr\u003e\n**Reason**: Protection from panics and bugs.\n\nAll suite functionality methods are presented [here](https://github.com/stretchr/testify/blob/master/suite/interfaces.go).\n\n---\n\n### suite-subtest-run\n\n```go\nfunc (s *MySuite) TestSomething() {\n    ❌\n    s.T().Run(\"subtest\", func(t *testing.T) {\n        assert.Equal(t, 42, result)\n    })\n     \n    ✅\n    s.Run(\"subtest\", func() {\n        s.Equal(42, result)\n    }) \n}\n```\n\n**Autofix**: false. \u003cbr\u003e\n**Enabled by default**: true. \u003cbr\u003e\n**Reason**: Protection from undefined behavior.\n\nAccording to `testify` [documentation](https://pkg.go.dev/github.com/stretchr/testify/suite#Suite.Run), `s.Run` should\nbe used for running subtests. This call (among other things) initializes the suite with a fresh instance of `t` and\nprotects tests from undefined behavior (such as data races).\n\nAutofix is disabled because in the most cases it requires rewriting the assertions in the subtest and can leads to dead\ncode.\n\nThe checker is especially useful in combination with [suite-dont-use-pkg](#suite-dont-use-pkg).\n\n---\n\n### suite-thelper\n\n```go\n❌\nfunc (s *RoomSuite) assertRoomRound(roundID RoundID) {\n    s.Equal(roundID, s.getRoom().CurrentRound.ID)\n}\n\n✅\nfunc (s *RoomSuite) assertRoomRound(roundID RoundID) {\n    s.T().Helper()\n    s.Equal(roundID, s.getRoom().CurrentRound.ID)\n}\n```\n\n**Autofix**: true. \u003cbr\u003e\n**Enabled by default**: false. \u003cbr\u003e\n**Reason**: Consistency with non-suite test helpers. Explicit markup of helper methods.\n\n`s.T().Helper()` call is not important actually because `testify` prints full `Error Trace`\n[anyway](https://github.com/stretchr/testify/blob/882382d845cd9780bd93c1acc8e1fa2ffe266ca1/assert/assertions.go#L317).\n\nThe checker rather acts as an example of\na [checkers.AdvancedChecker](https://github.com/Antonboom/testifylint/blob/676324836555445fded4e9afc004101ec6f597fe/internal/checkers/checker.go#L56).\n\n---\n\n### useless-assert\n\nThe checker guards against assertion of the same variable:\n\n```go\nassert.Contains(t, tt.value, tt.value)\nassert.ElementsMatch(t, tt.value, tt.value)\nassert.Equal(t, tt.value, tt.value)\nassert.EqualExportedValues(t, tt.value, tt.value)\n// And other assert functions...\n\nassert.True(t, num \u003e num)\nassert.True(t, num \u003c num)\nassert.True(t, num \u003e= num)\nassert.True(t, num \u003c= num)\nassert.True(t, num == num)\nassert.True(t, num != num)\n\nassert.False(t, num \u003e num)\nassert.False(t, num \u003c num)\nassert.False(t, num \u003e= num)\nassert.False(t, num \u003c= num)\nassert.False(t, num == num)\nassert.False(t, num != num)\n```\n\nAnd against these meaningless assertions:\n\n```go\nassert.Empty(t, \"value\") // Any string literal.\nassert.Error(t, nil)\nassert.False(t, false) // Any bool literal.\nassert.Implements(t, (*any)(nil), new(Conn))\nassert.Negative(t, 42) // Any int literal.\nassert.Nil(t, nil)\nassert.NoError(t, nil)\nassert.NotEmpty(t, \"value\") // Any string literal.\nassert.NotImplements(t, (*any)(nil), new(Conn))\nassert.NotNil(t, nil)\nassert.NotZero(t, 42)      // Any int literal.\nassert.NotZero(t, \"value\") // Any string literal.\nassert.NotZero(t, nil)\nassert.NotZero(t, false) // Any bool literal.\nassert.Positive(t, 42)   // Any int literal.\nassert.True(t, true)     // Any bool literal.\nassert.Zero(t, 42)       // Any int literal.\nassert.Zero(t, \"value\")  // Any string literal.\nassert.Zero(t, nil)\nassert.Zero(t, false) // Any bool literal.\n\nassert.Negative(len(x))\nassert.Less(len(x), 0)\nassert.Greater(0, len(x))\nassert.GreaterOrEqual(len(x), 0)\nassert.LessOrEqual(0, len(x))\n\nassert.Negative(uintVal)\nassert.Less(uintVal, 0)\nassert.Greater(0, uintVal)\nassert.GreaterOrEqual(uintVal, 0)\nassert.LessOrEqual(0, uintVal)\n```\n\n**Autofix**: false. \u003cbr\u003e\n**Enabled by default**: true. \u003cbr\u003e\n**Reason**: Protection from bugs and dead code.\n\n---\n\n## Chain of warnings\n\nLinter does not automatically handle the \"evolution\" of changes.\nAnd in some cases may be dissatisfied with your code several times, for example:\n\n```go\nassert.True(err == nil) // compares: use assert.Equal\nassert.Equal(t, err, nil) // error-nil: use assert.NoError\nassert.NoError(t, err) // require-error: for error assertions use require\nrequire.NoError(t, err)\n```\n\nPlease [contribute](./CONTRIBUTING.md) if you have ideas on how to make this better.\n\n## testify v2\n\nThe second version of `testify` [promises](https://github.com/stretchr/testify/issues/1089) more \"pleasant\" API and\nmakes some above checkers irrelevant.\n\nIn this case, the possibility of supporting `v2` in the linter is not excluded.\n\nBut at the moment it looks like we\nare [extremely far](https://github.com/stretchr/testify/issues/1089#issuecomment-1812734472)\nfrom `v2`. Related milestone [here](https://github.com/stretchr/testify/milestone/4).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FAntonboom%2Ftestifylint","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FAntonboom%2Ftestifylint","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FAntonboom%2Ftestifylint/lists"}