cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists 馃崈
charm leaflet readability golang
at main 268 lines 5.1 kB view raw view rendered
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