cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists 馃崈
charm
leaflet
readability
golang
1---
2title: Testing
3sidebar_label: Testing
4sidebar_position: 2
5description: Running tests and understanding test patterns.
6---
7
8# Testing
9
10Noteleaf maintains comprehensive test coverage using Go's built-in testing framework with consistent patterns across the codebase.
11
12## Running Tests
13
14### All Tests
15
16```sh
17task test
18# or
19go test ./...
20```
21
22### Coverage Report
23
24Generate HTML coverage report:
25
26```sh
27task coverage
28```
29
30Output: `coverage.html` (opens in browser)
31
32### Terminal Coverage
33
34View coverage in terminal:
35
36```sh
37task cov
38```
39
40Shows function-level coverage percentages.
41
42### Package-Specific Tests
43
44Test specific package:
45
46```sh
47go test ./internal/repo
48go test ./internal/handlers
49go test ./cmd
50```
51
52### Verbose Output
53
54```sh
55go test -v ./...
56```
57
58## Test Organization
59
60Tests follow a hierarchical 3-level structure:
61
62```go
63func TestRepositoryName(t *testing.T) {
64 // Setup once
65 db := CreateTestDB(t)
66 repos := SetupTestData(t, db)
67
68 t.Run("Feature", func(t *testing.T) {
69 t.Run("scenario description", func(t *testing.T) {
70 // Test logic
71 })
72 })
73}
74```
75
76Levels:
77
781. Package (top function)
792. Feature (first t.Run)
803. Scenario (nested t.Run)
81
82## Test Patterns
83
84### Repository Tests
85
86Repository tests use scaffolding from `internal/repo/test_utilities.go`:
87
88```go
89func TestTaskRepository(t *testing.T) {
90 db := CreateTestDB(t)
91 repos := SetupTestData(t, db)
92 ctx := context.Background()
93
94 t.Run("Create", func(t *testing.T) {
95 t.Run("creates task successfully", func(t *testing.T) {
96 task := NewTaskBuilder().
97 WithDescription("Test task").
98 Build()
99
100 created, err := repos.Tasks.Create(ctx, task)
101 AssertNoError(t, err, "create should succeed")
102 AssertEqual(t, "Test task", created.Description, "description should match")
103 })
104 })
105}
106```
107
108### Handler Tests
109
110Handler tests use `internal/handlers/handler_test_suite.go`:
111
112```go
113func TestHandlerName(t *testing.T) {
114 suite := NewHandlerTestSuite(t)
115 defer suite.cleanup()
116 handler := CreateHandler(t, NewHandlerFunc)
117
118 t.Run("Feature", func(t *testing.T) {
119 t.Run("scenario", func(t *testing.T) {
120 AssertNoError(t, handler.Method(), "operation should succeed")
121 })
122 })
123}
124```
125
126## Test Utilities
127
128### Assertion Helpers
129
130Located in `internal/repo/test_utilities.go` and `internal/handlers/test_utilities.go`:
131
132```go
133// Error checking
134AssertNoError(t, err, "operation should succeed")
135AssertError(t, err, "operation should fail")
136
137// Value comparison
138AssertEqual(t, expected, actual, "values should match")
139AssertTrue(t, condition, "should be true")
140AssertFalse(t, condition, "should be false")
141
142// Nil checking
143AssertNil(t, value, "should be nil")
144AssertNotNil(t, value, "should not be nil")
145
146// String operations
147AssertContains(t, str, substr, "should contain substring")
148```
149
150### Test Data Builders
151
152Create test data with builders:
153
154```go
155task := NewTaskBuilder().
156 WithDescription("Test task").
157 WithStatus("pending").
158 WithPriority("high").
159 WithProject("test-project").
160 Build()
161
162book := NewBookBuilder().
163 WithTitle("Test Book").
164 WithAuthor("Test Author").
165 Build()
166
167note := NewNoteBuilder().
168 WithTitle("Test Note").
169 WithContent("Test content").
170 Build()
171```
172
173### Test Database
174
175In-memory SQLite for isolated tests:
176
177```go
178db := CreateTestDB(t) // Automatic cleanup via t.Cleanup()
179```
180
181### Sample Data
182
183Pre-populated test data:
184
185```go
186repos := SetupTestData(t, db)
187// Creates tasks, notes, books, movies, TV shows
188```
189
190## Test Naming
191
192Use direct descriptions without "should":
193
194```go
195t.Run("creates task successfully", func(t *testing.T) { }) // Good
196t.Run("should create task", func(t *testing.T) { }) // Bad
197t.Run("returns error for invalid input", func(t *testing.T) { }) // Good
198```
199
200## Test Independence
201
202Each test must be independent:
203
204- Use `CreateTestDB(t)` for isolated database
205- Don't rely on test execution order
206- Clean up resources with `t.Cleanup()`
207- Avoid package-level state
208
209## Coverage Targets
210
211Maintain high coverage for:
212
213- Repository layer (data access)
214- Handler layer (business logic)
215- Services (external integrations)
216- Models (data validation)
217
218Current coverage visible via:
219
220```sh
221task cov
222```
223
224## Continuous Integration
225
226Tests run automatically on:
227
228- Pull requests
229- Main branch commits
230- Release builds
231
232CI configuration validates:
233
234- All tests pass
235- No race conditions
236- Coverage thresholds met
237
238## Debugging Tests
239
240### Run Single Test
241
242```sh
243go test -run TestTaskRepository ./internal/repo
244go test -run TestTaskRepository/Create ./internal/repo
245```
246
247### Race Detector
248
249```sh
250go test -race ./...
251```
252
253### Verbose with Stack Traces
254
255```sh
256go test -v -race ./internal/repo 2>&1 | grep -A 10 "FAIL"
257```
258
259## Best Practices
260
2611. Write tests for all public APIs
2622. Use builders for complex test data
2633. Apply semantic assertion helpers
2644. Keep tests focused and readable
2655. Test both success and error paths
2666. Avoid brittle time-based tests
2677. Mock external dependencies
2688. Use table-driven tests for variations