Documentation Index Fetch the complete documentation index at: https://mintlify.com/aarock1234/go-template/llms.txt
Use this file to discover all available pages before exploring further.
The Go Template includes a comprehensive testing setup with race detection and best practices for unit and integration testing.
Running Tests
The project uses Go’s built-in testing framework with additional tooling for safety and quality.
Quick Start
Run all tests with race detection:
This executes go test -race ./... which:
Runs all tests in the project
Enables the race detector to catch concurrency bugs
Reports any data races found during test execution
Test Commands
Make Commands
Go Commands
# Run all tests with race detection
make test
# Format code before testing
make format && make test
# Lint and test
make lint && make test
# Run all tests
go test ./...
# Run with race detection
go test -race ./...
# Run with verbose output
go test -v ./...
# Run tests in a specific package
go test ./pkg/retry
# Run a specific test
go test -run TestExponentialBackoff ./pkg/retry
# Run with coverage
go test -cover ./...
# Generate coverage report
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out
Test Structure
Tests are colocated with the code they test, following Go conventions:
pkg/
├── retry/
│ ├── retry.go
│ └── retry_test.go
├── client/
│ ├── client.go
│ └── client_test.go
└── db/
├── db.go
└── db_test.go
Writing Tests
Unit Tests
Unit tests verify individual functions in isolation:
package retry
import (
" testing "
" time "
)
func TestExponentialBackoff ( t * testing . T ) {
tests := [] struct {
name string
attempt int
base time . Duration
max time . Duration
wantMin time . Duration
wantMax time . Duration
}{
{
name : "first attempt" ,
attempt : 0 ,
base : 100 * time . Millisecond ,
max : 5 * time . Second ,
wantMin : 100 * time . Millisecond ,
wantMax : 200 * time . Millisecond ,
},
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
duration := ExponentialBackoff ( tt . attempt , tt . base , tt . max )
if duration < tt . wantMin || duration > tt . wantMax {
t . Errorf ( "ExponentialBackoff() = %v , want between %v and %v " ,
duration , tt . wantMin , tt . wantMax )
}
})
}
}
Table-Driven Tests
Use table-driven tests for testing multiple scenarios:
func TestValidateEmail ( t * testing . T ) {
tests := [] struct {
name string
email string
wantErr bool
}{
{ "valid email" , "user@example.com" , false },
{ "missing @" , "userexample.com" , true },
{ "missing domain" , "user@" , true },
{ "empty string" , "" , true },
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
err := ValidateEmail ( tt . email )
if ( err != nil ) != tt . wantErr {
t . Errorf ( "ValidateEmail() error = %v , wantErr %v " , err , tt . wantErr )
}
})
}
}
Integration Tests
For tests that require external dependencies like databases:
package db_test
import (
" context "
" testing "
" os "
" github.com/you/myproject/pkg/db "
)
func TestDatabaseIntegration ( t * testing . T ) {
// Skip if DATABASE_URL is not set
databaseURL := os . Getenv ( "DATABASE_URL" )
if databaseURL == "" {
t . Skip ( "DATABASE_URL not set, skipping integration test" )
}
ctx := context . Background ()
// Create database connection
database , err := db . New ( ctx , databaseURL )
if err != nil {
t . Fatalf ( "failed to connect: %v " , err )
}
defer database . Close ()
// Run test
t . Run ( "create user" , func ( t * testing . T ) {
user , err := database . CreateUser ( ctx , "test@example.com" )
if err != nil {
t . Fatalf ( "CreateUser() failed: %v " , err )
}
if user . Email != "test@example.com" {
t . Errorf ( "got email %q , want %q " , user . Email , "test@example.com" )
}
})
}
Testing Best Practices
Always use the race detector
The race detector catches concurrency bugs: The template’s make test command includes -race by default.
Use t.Helper() for test utilities
Mark helper functions to improve error reporting: func assertNoError ( t * testing . T , err error ) {
t . Helper ()
if err != nil {
t . Fatalf ( "unexpected error: %v " , err )
}
}
Use subtests for organization
Group related tests with t.Run: func TestUserService ( t * testing . T ) {
t . Run ( "CreateUser" , func ( t * testing . T ) {
// Test user creation
})
t . Run ( "GetUser" , func ( t * testing . T ) {
// Test user retrieval
})
}
Always clean up test resources: func TestWithDatabase ( t * testing . T ) {
db := setupTestDB ( t )
defer db . Close ()
t . Cleanup ( func () {
cleanupTestData ( t , db )
})
// Run tests
}
Skip slow tests with short mode
Use -short flag for quick test runs: func TestSlowOperation ( t * testing . T ) {
if testing . Short () {
t . Skip ( "skipping slow test in short mode" )
}
// Run slow test
}
Run quick tests:
Testing Database Code
When testing code that uses the database:
Use test database
Create a separate test database or use transaction rollback: func TestWithTransaction ( t * testing . T ) {
db := setupTestDB ( t )
err := db . InTx ( context . Background (), func ( q sqlc . Querier ) error {
// Run tests inside transaction
// Transaction is rolled back after test
return nil
})
if err != nil {
t . Fatalf ( "transaction failed: %v " , err )
}
}
Use Docker for integration tests
Start a test database with Docker: # Start test database
docker run -d \
--name test-postgres \
-e POSTGRES_PASSWORD=test \
-e POSTGRES_DB=test \
-p 5433:5432 \
postgres:18-alpine
# Run tests
DATABASE_URL = postgres://postgres:test@localhost:5433/test go test ./...
# Cleanup
docker rm -f test-postgres
Run migrations in tests
Ensure schema is up-to-date: func setupTestDB ( t * testing . T ) * db . DB {
t . Helper ()
// Run migrations
cmd := exec . Command ( "goose" , "-dir" , "pkg/db/migrations" ,
"postgres" , databaseURL , "up" )
if err := cmd . Run (); err != nil {
t . Fatalf ( "migrations failed: %v " , err )
}
// Create connection
database , err := db . New ( context . Background (), databaseURL )
if err != nil {
t . Fatalf ( "connect failed: %v " , err )
}
return database
}
Testing HTTP Clients
The template includes a TLS-fingerprinted HTTP client. Test it with mocking:
package myservice_test
import (
" net/http "
" net/http/httptest "
" testing "
)
func TestHTTPRequest ( t * testing . T ) {
// Create test server
server := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
if r . URL . Path != "/api/data" {
t . Errorf ( "unexpected path: %s " , r . URL . Path )
}
w . WriteHeader ( http . StatusOK )
w . Write ([] byte ( `{"status":"ok"}` ))
}))
defer server . Close ()
// Test your service with server.URL
}
Continuous Integration
The project includes a GitHub Actions workflow for CI:
# .github/workflows/ci.yaml
name : CI
on : [ push , pull_request ]
jobs :
test :
runs-on : ubuntu-latest
steps :
- uses : actions/checkout@v4
- uses : actions/setup-go@v5
with :
go-version : '1.26'
- run : go test -race ./...
The CI badge is displayed in the README.
Beyond testing, use these tools to maintain code quality:
Linting
Run static analysis:
This runs go vet ./... which checks for common mistakes.
Ensure consistent code style:
This runs go fmt ./... to format all Go files.
Modernization
Update code to use newer APIs:
This runs go fix ./... which rewrites code to use current Go idioms.
Coverage Reports
Generate and view test coverage:
Generate coverage data
go test -coverprofile=coverage.out ./...
View coverage summary
go tool cover -func=coverage.out
View HTML coverage report
go tool cover -html=coverage.out
This opens an interactive HTML report in your browser.
Benchmarking
Write benchmarks for performance-critical code:
func BenchmarkExponentialBackoff ( b * testing . B ) {
for i := 0 ; i < b . N ; i ++ {
ExponentialBackoff ( 5 , 100 * time . Millisecond , 5 * time . Second )
}
}
Run benchmarks:
go test -bench=. ./pkg/retry
Next Steps
Local Setup Set up your development environment
Database Learn about testing with databases