@@ -87,6 +87,87 @@ args = ["run", "--rm", "-e", "GITHUB_PERSONAL_ACCESS_TOKEN", "-i", "ghcr.io/gith
8787- Godoc comments for exports
8888- Mock external dependencies (Docker, network)
8989
90+ ## Testing with Testify
91+
92+ ** ALWAYS use testify for test assertions** - The project uses [ stretchr/testify] ( https://github.com/stretchr/testify ) for all test assertions.
93+
94+ ### Assert vs Require
95+
96+ - ** ` require ` ** : Use for critical checks - test stops on failure
97+ - ** ` assert ` ** : Use for non-critical checks - test continues on failure
98+
99+ ``` go
100+ import (
101+ " github.com/stretchr/testify/assert"
102+ " github.com/stretchr/testify/require"
103+ )
104+
105+ func TestExample (t *testing .T ) {
106+ result , err := DoSomething ()
107+ require.NoError (t, err) // Stop if error - can't continue
108+ assert.Equal (t, " expected" , result.Field ) // Continue even if fails
109+ }
110+ ```
111+
112+ ### Bound Asserters
113+
114+ For tests with multiple assertions, use bound asserters to reduce repetition:
115+
116+ ``` go
117+ func TestMultipleAssertions (t *testing .T ) {
118+ assert := assert.New (t)
119+ require := require.New (t)
120+
121+ result := GetResult ()
122+ require.NotNil (result) // Stop if nil
123+
124+ // Cleaner - no need to pass 't' repeatedly
125+ assert.Equal (" value1" , result.Field1 )
126+ assert.Equal (" value2" , result.Field2 )
127+ assert.True (result.Active )
128+ }
129+ ```
130+
131+ ### Specific Assertion Methods
132+
133+ Use specific assertion methods instead of generic ones for better error messages:
134+
135+ ``` go
136+ // ❌ Avoid generic assertions
137+ assert.True (t, len (items) == 0 )
138+ assert.True (t, err == nil )
139+ assert.True (t, strings.Contains (msg, " error" ))
140+
141+ // ✅ Use specific assertions
142+ assert.Empty (t, items)
143+ assert.NoError (t, err)
144+ assert.Contains (t, msg, " error" )
145+ ```
146+
147+ ### Common Patterns
148+
149+ ``` go
150+ // Length checking
151+ assert.Len (t, items, 5 , " Expected 5 items" )
152+
153+ // Unordered slice comparison
154+ assert.ElementsMatch (t, expected, actual, " Slices should contain same elements" )
155+
156+ // Nil checking
157+ assert.NotNil (t, obj, " Object should not be nil" )
158+ assert.Nil (t, err, " Error should be nil" )
159+
160+ // Error checking (prefer NoError over Nil for errors)
161+ assert.NoError (t, err, " Operation should succeed" )
162+ assert.Error (t, err, " Operation should fail" )
163+
164+ // HTTP status codes
165+ assert.Equal (t, http.StatusOK , response.StatusCode , " Should return 200 OK" )
166+
167+ // JSON comparison (ignores formatting)
168+ assert.JSONEq (t, expectedJSON, actualJSON)
169+ ```
170+
90171## Linting
91172
92173** golangci-lint** is integrated and runs as part of ` make lint ` :
@@ -96,9 +177,17 @@ args = ["run", "--rm", "-e", "GITHUB_PERSONAL_ACCESS_TOKEN", "-i", "ghcr.io/gith
96177- Install: ` make install ` (installs golangci-lint v2.8.0)
97178- Run manually: ` golangci-lint run --timeout=5m `
98179
99- ** Note** : Some linters (gosec, testifylint) are disabled to minimize noise. Enable them for stricter checks:
180+ ** testifylint** : Available but disabled due to requiring extensive test refactoring across the codebase.
181+ - Automatically catches common testify mistakes:
182+ - Suggests ` assert.Empty(t, x) ` instead of ` assert.True(t, len(x) == 0) `
183+ - Suggests ` assert.True(t, x) ` instead of ` assert.Equal(t, true, x) `
184+ - Suggests ` assert.NoError(t, err) ` instead of ` assert.Nil(t, err) `
185+ - To run on specific files: ` golangci-lint run --enable=testifylint --timeout=5m <files> `
186+ - To run on entire codebase: ` golangci-lint run --enable=testifylint --timeout=5m `
187+
188+ ** Note** : Some linters (gosec, testifylint, errcheck) are disabled to minimize noise. Enable them for stricter checks:
100189``` bash
101- golangci-lint run --enable=gosec,testifylint --timeout=5m
190+ golangci-lint run --enable=gosec,testifylint,errcheck --timeout=5m
102191```
103192
104193## Test Structure
0 commit comments