Every test package uses a TestMain function in main_test.go for setup/teardown:
func TestMain(m *testing.M) {
setUp()
retCode := m.Run()
tearDown()
os.Exit(retCode)
}Tests support two database backends:
When no TEST_DATABASE_URL environment variable is set, tests use ephemeral SQLite databases:
cfg.Test = true
dbName = fmt.Sprintf("%d-services.db", time.Now().UnixNano())
cfg.DbName = dbName
database.Init()Each test run creates a unique .db file (timestamped) and removes it in tearDown(). This ensures test isolation without external dependencies.
Set TEST_DATABASE_URL to run tests against PostgreSQL, enabling full coverage of fuzzy search (fuzzystrmatch/Levenshtein), advisory locks, and other PostgreSQL-specific features:
# Start local PostgreSQL
make infra
# Run tests with PostgreSQL
make test-pgOr set the variable directly:
TEST_DATABASE_URL="host=localhost user=quickstarts password=quickstarts dbname=quickstarts_test port=5432 sslmode=disable" go test -p 1 ./... -vImportant: Use -p 1 (or make test-pg) when running against PostgreSQL. By default go test ./... runs packages in parallel, which causes race conditions on the shared test database (concurrent TRUNCATE/INSERT across packages). The -p 1 flag serialises package execution.
When using PostgreSQL, CleanTestTables() truncates all tables at the start of each test run to ensure a clean state (SQLite achieves this by creating a fresh file).
Tests run AutoMigrate in setUp() to create tables:
database.DB.AutoMigrate(
&models.Quickstart{},
&models.QuickstartProgress{},
&models.HelpTopic{},
&models.FavoriteQuickstart{},
&models.Tag{},
)The database package tests also call SeedTags() after migration to test the seeding flow end-to-end.
| Package | What it tests | SQLite DB? | Seeds data? |
|---|---|---|---|
pkg/routes |
HTTP handlers, parameter parsing, API responses | Yes | No (inserts test data per test) |
pkg/database |
Seeding logic, content parsing, tag associations | Yes | Yes (SeedTags()) |
Use net/http/httptest to test handlers:
func TestSomeEndpoint(t *testing.T) {
// Insert test data
database.DB.Create(&models.Quickstart{...})
// Create request
req := httptest.NewRequest("GET", "/api/quickstarts/v1/quickstarts/", nil)
w := httptest.NewRecorder()
// Execute
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusOK, w.Code)
}Use github.com/stretchr/testify:
assert.Equal(t, expected, actual)assert.NoError(t, err)assert.Contains(t, string, substring)
Since each test package gets its own ephemeral database, there's no need to clean up between individual tests within the same package. However, if a test modifies shared state (like seeded content), be aware of ordering effects since tests in Go run sequentially within a package by default.
Some features are PostgreSQL-only and must be guarded in code:
if db.Dialector.Name() == "postgres" {
// PostgreSQL-specific code (advisory locks, fuzzystrmatch, etc.)
}These features have fallback behavior when running with SQLite:
- Advisory locks (
pg_advisory_xact_lock) — skipped on non-PostgreSQL - Fuzzy search (
fuzzystrmatchextension) — falls back toILIKE - SSL/TLS — not applicable in tests
When running tests with TEST_DATABASE_URL (PostgreSQL), all of these features are fully tested including fuzzystrmatch-based fuzzy search.
# All tests with coverage
make test
# Specific package with verbose output
go test ./pkg/routes -v
# Specific test function
go test ./pkg/database -run TestSeedTags -v
# Coverage report in browser
make coverageTests run as part of the Docker build (make test in Dockerfile) and in the Konflux CI pipeline (quickstarts-on-pull-request). By default, CI uses SQLite. To enable PostgreSQL testing in Konflux, add a PostgreSQL sidecar container to the Tekton pipeline and set the TEST_DATABASE_URL environment variable during the build step.