testifylint

repository·master·Indexed 41 days ago

https://github.com/antonboom/testifylint

A linter for Go projects designed to detect improper or ambiguous usage of the github.com/stretchr/testify testing framework. It helps maintain test consistency and readability by enforcing idiomatic API usage, correcting argument order, and preventing common bugs like using require in goroutines. It supports automatic fixing for many checkers and integrates with golangci-lint.

Tokens
7K
Snippets
28
Records
30
Agent score
1%

What's inside testifylint

  1. Avoid using require in goroutines with go-require

    master

    The go-require checker flags the use of require (or any function that calls t.FailNow) inside a goroutine.

    Why this is a problem: Functions that call t.FailNow (like require.NoError) terminate the current goroutine using runtime.GoExit. If called inside a goroutine, they will only terminate that specific goroutine and not the main test function, leading to resource leaks and tests that pass even when they should fail.

    Best Practices:

    • Perform assertions in the main test goroutine.
    • If you must check something in a goroutine, use assert and then signal the failure back to the main goroutine.
    • Avoid using require inside HTTP handlers (which run in separate service goroutines). You can ignore this specific check using --go-require.ignore-http-handlers.

    Autofix: Not supported.

    // ❌ Avoid (require in goroutine)
    go func() {
        conn, err = lis.Accept()
        require.NoError(t, err) 
    }()
    
    // ✅ Use assert and handle failure in main goroutine
  2. Use error-nil assertions correctly

    master

    The error-nil checker flags non-idiomatic ways of checking for nil or empty errors in testify. Instead of using generic equality or nil checks, use the specialized NoError or Error functions which provide clearer failure messages.

    Recommended patterns:

    • Use assert.NoError(t, err) instead of assert.Nil(t, err), assert.Empty(t, err), or assert.Equal(t, nil, err).
    • Use assert.Error(t, err) instead of assert.NotNil(t, err) or assert.NotEqual(t, nil, err).

    Autofix: Supported.

    // ❌ Avoid
    assert.Nil(t, err)
    assert.Equal(t, nil, err)
    
    // ✅ Use
    assert.NoError(t, err)
    assert.Error(t, err)
  3. Migrate to mockery Expecter Structs with mock-expect

    master

    The mock-expect checker helps migrate mocks generated by mockery from the string-based testify/mock API to the type-safe Expecter Structs. Using On with string method names prevents the Go compiler from verifying method existence or argument signatures. Using EXPECT() provides compile-time safety.

    Note: Calls chained with .Run() are reported but not automatically fixed because their callback signatures cannot always be safely converted.

    // ❌ String-based (no compile-time check)
    m.On("CreateUser", mock.Anything, User{}).Return(nil)
    
    // ✅ Expecter-based (compile-time checked)
    m.EXPECT().CreateUser(mock.Anything, User{}).Return(nil)
  4. Compare floating point numbers with epsilon or delta

    master

    The float-compare checker flags direct equality comparisons of floating-point numbers, which are prone to rounding errors. Instead, use InEpsilon or InDelta to allow for a small margin of error.

    Recommended patterns:

    • Use assert.InEpsilon(t, expected, actual, epsilon)
    • Use assert.InDelta(t, expected, actual, delta)

    Autofix: Not supported.

    // ❌ Avoid
    assert.Equal(t, 42.42, result)
    
    // ✅ Use
    assert.InEpsilon(t, 42.42, result, 0.0001)
  5. Testify v2 compatibility

    master
    The current version of testifylint is designed for testify v1. While testify v2 promises a more pleasant API that may render some current checkers irrelevant, support for v2 is not currently implemented. Users should monitor the testify project milestones for v2 progress.
  6. Use expected-actual argument order

    master

    The expected-actual checker ensures that testify assertions follow the standard (t, expected, actual) argument order. This prevents confusion during test failures by ensuring the 'expected' value is always the first comparison argument.

    Note: This is intended to align with the planned v2 of testify which aims to move to a more natural (actual, expected) order, but currently, the linter enforces the (expected, actual) pattern to improve failure clarity.

    Autofix: Supported.

    // ❌ Avoid (actual is first)
    assert.Equal(t, result, expected)
    
    // ✅ Use (expected is first)
    assert.Equal(t, expected, result)
  7. Identify meaningless testify assertions

    master

    The testifylint tool detects meaningless assertions in Go tests using the testify library. These are assertions that are always true or always false regardless of the input, which often indicate bugs, dead code, or poor test quality.

    Examples of problematic patterns include:

    • Comparing a value to itself: assert.Equal(t, tt.value, tt.value).
    • Using boolean or numeric literals that guarantee the result: assert.True(t, true) or assert.Negative(42).
    • Asserting against nil or empty literals: assert.Error(t, nil) or assert.Nil(t, nil).
    • Using comparison operators on values that will always satisfy the condition (e.g., assert.Less(len(x), 0) for a slice length).

    This checker is enabled by default to protect against bugs and dead code, but it does not support autofixing.

    // Meaningless assertions detected by the linter:
    assert.Contains(t, tt.value, tt.value)
    assert.True(t, true)
    assert.Zero(t, nil)
    assert.Negative(42)
    assert.Less(len(x), 0)
  8. Understanding the chain of warnings during code evolution

    master

    When refactoring code to satisfy testifylint, you may encounter a 'chain of warnings' where the linter suggests multiple sequential improvements. The linter does not automatically handle the evolution of these changes. For example, when improving error assertions, you might move through several stages before reaching the optimal state:

    1. assert.True(err == nil) -> Linter suggests assert.Equal.
    2. assert.Equal(t, err, nil) -> Linter suggests assert.NoError.
    3. assert.NoError(t, err) -> Linter suggests require.NoError (to use require for error assertions).

    Final state: require.NoError(t, err)

    assert.True(err == nil) // compares: use assert.Equal
    assert.Equal(t, err, nil) // error-nil: use assert.NoError
    assert.NoError(t, err) // require-error: for error assertions use require
    require.NoError(t, err)
  9. Avoid parallel tests in suites with suite-broken-parallel

    master

    The suite-broken-parallel checker flags calls to t.Parallel() inside testify suites (including SetupTest, TearDownTest, and individual test methods).

    testify v1 does not support parallel tests or subtests within a suite. Using t.Parallel() in these contexts can lead to data races, panics, or silent failures of suite hooks. The recommended practice is to avoid parallel tests inside a suite.

    func (s *MySuite) SetupTest() {
        s.T().Parallel() // ❌ Forbidden in suites
    }
    
    func (s *MySuite) TestSomething() {
        s.T().Parallel() // ❌ Forbidden in suites
    }
  10. Fix testifylint issues automatically

    master

    Use the --fix flag to automatically apply fixes for supported issues.

    Note:

    • Fixing via golangci-lint is currently unavailable.
    • After running the fix, you may have unused imports; run go fmt to clean up your code.
    $ testifylint --fix ./...
  11. Use require instead of assert for error handling with require-error

    master

    The require-error checker encourages using testify/require instead of testify/assert when checking for errors. This ensures that if an error is encountered, the test execution stops immediately, preventing subsequent panics and making debugging easier.

    Configuration: You can limit which error-related calls are checked using the --require-error.fn-pattern flag. For example, to only check Error, Errorf, NoError, and NoErrorf, use: --require-error.fn-pattern="^(Errorf?|NoErrorf?)$".

    Exclusions: To minimize false positives, this checker ignores assertions in if conditions, boolean expressions, explicit goroutines, and sequence of NoError assertions.

    // ❌ Using assert for errors (can lead to panics if error is not handled)
    assert.Error(t, err)
    
    // ✅ Using require for errors (stops execution on failure)
    require.Error(t, err)
  12. Use the zero checker for time.Time

    master

    The zero checker identifies cases where time.Time objects are being compared to their zero value using general equality assertions. Instead of using Equal, EqualValues, or IsZero(), you should use the more appropriate testify API for zero values to ensure clearer failure messages.

    Current Support: Only time.Time is currently supported.

    Rules:

    • Variables starting with the prefix zero are also treated as having zero values.
    • Autofix: Supported (true).
    • Enabled by default: true.

    Conversion Guide:

    Instead of...Use...
    assert.Equal(t, time.Time{}, ts)assert.Zero(t, ts)
    assert.True(t, ts.IsZero())assert.Zero(t, ts)
    assert.NotEqual(t, time.Time{}, ts)assert.NotZero(t, ts)
    assert.False(t, ts.Equal(time.Time{}))assert.NotZero(t, ts)
    // ❌ Incorrect/Verbose
    assert.Equal(t, time.Time{}, ts)
    assert.True(t, ts.IsZero())
    
    // ✅ Correct/Idiomatic
    assert.Zero(t, ts)
    assert.NotZero(t, ts)