testutil

package
v0.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 12, 2026 License: MIT Imports: 9 Imported by: 0

README

Test Utilities

Location: internal/testutil/ - This package is in the internal/ directory, which means it's only importable by code within this project. External users of the Twine library cannot import these test utilities, as they are for internal testing infrastructure only.

This package provides test utilities and helpers for the Twine project. It includes helpers for:

  • Creating temporary directories and files
  • Setting up test databases (SQLite in-memory by default)
  • HTTP response assertions
  • Test fixture management
  • Environment variable management

Usage

Core Test Utilities
import "github.com/cstone-io/twine/internal/testutil"

func TestMyFeature(t *testing.T) {
    // Create temporary directory
    dir := testutil.TempDir(t)
    // Automatically cleaned up after test

    // Create files
    testutil.CreateFile(t, filepath.Join(dir, "test.txt"), "content")

    // Set environment variables
    testutil.SetupTestEnv(t, map[string]string{
        "TEST_VAR": "test_value",
    })
    // Automatically restored after test
}
Database Testing
import "github.com/cstone-io/twine/internal/testutil"

func TestDatabaseOperations(t *testing.T) {
    // Setup in-memory SQLite database
    db := testutil.SetupTestDB(t)
    // Automatically closed after test

    // Run migrations
    testutil.AutoMigrate(t, db, &User{}, &Post{})

    // Seed test data
    users := []User{
        {Email: "user1@example.com"},
        {Email: "user2@example.com"},
    }
    testutil.SeedTestData(t, db, &users)

    // Assert records exist
    testutil.AssertRecordExists(t, db, &User{}, "email = ?", "user1@example.com")
    testutil.AssertRecordCount(t, db, &User{}, 2, "1 = ?", 1)
}
Using PostgreSQL for Tests

By default, tests use SQLite in-memory databases for speed and simplicity. To test against PostgreSQL:

# Set environment variable
export POSTGRES_TEST_DSN="host=localhost user=test password=test dbname=test_db port=5432 sslmode=disable"

# Run tests
make test
Transaction Rollback Pattern
func TestWithRollback(t *testing.T) {
    db := testutil.SetupTestDB(t)
    testutil.AutoMigrate(t, db, &User{})

    // Changes are rolled back after function completes
    testutil.RunInTransaction(t, db, func(tx *gorm.DB) {
        user := User{Email: "test@example.com"}
        err := tx.Create(&user).Error
        require.NoError(t, err)
        // Do your tests here
    })

    // Database is clean again
}
HTTP Assertions
import (
    httpAssert "github.com/cstone-io/twine/internal/testutil/assert"
    "net/http/httptest"
)

func TestHandler(t *testing.T) {
    w := httptest.NewRecorder()
    r := httptest.NewRequest("GET", "/api/users", nil)

    handler(w, r)

    // Assert JSON response
    httpAssert.AssertJSONResponse(t, w, 200, `{"users": []}`)

    // Assert HTML response contains text
    httpAssert.AssertHTMLResponse(t, w, 200, "Welcome")

    // Assert headers
    httpAssert.AssertHeader(t, w, "Content-Type", "application/json")

    // Assert Ajax partial response (no DOCTYPE, no <html>)
    httpAssert.AssertAjaxResponse(t, w, 200)

    // Assert redirect
    httpAssert.AssertRedirect(t, w, "/login")
}
Test Fixtures
// Load raw fixture data
data := testutil.LoadFixture(t, "users.json")

// Load and unmarshal JSON fixture
var users []User
testutil.LoadJSONFixture(t, "users.json", &users)

// Write fixture for test
testutil.WriteFixture(t, tempDir, "config.json", []byte(`{"key": "value"}`))

Directory Structure

internal/testutil/      # Internal test utilities (not importable by external projects)
  testutil.go           # Core test helpers
  database.go           # Database test helpers
  assert/
    http.go            # HTTP assertion helpers
  fixtures/            # Test data fixtures
    routes/            # File-based routing fixtures
    templates/         # Template test files
    config/            # Test .env files

Running Tests

# Run all tests
make test

# Run only fast unit tests (skip integration)
make test-unit

# Run tests with coverage report
make test-coverage

# Check if coverage meets 90% threshold
make test-coverage-check

# Run specific package tests
make test-pkg
# Enter: pkg/router

# Watch tests (requires entr)
make test-watch

Coverage Goals

  • Phase 1 (Current): Test infrastructure setup - testutil package has 90%+ coverage ✓
  • Phase 2: Package-by-package testing - target 90%+ coverage per package
  • Overall target: 90%+ coverage across the entire codebase

Writing New Tests

Test File Naming
  • <package>_test.go - Unit tests for primary functionality
  • <feature>_test.go - Unit tests for specific feature
  • <package>_integration_test.go - Integration tests
Test Function Naming
// Pattern: Test<FunctionName>_<Scenario>_<ExpectedResult>
func TestNewRouter_EmptyPrefix_CreatesRouter(t *testing.T)
func TestRouter_Sub_InheritsMiddleware(t *testing.T)
func TestParseToken_ExpiredToken_ReturnsError(t *testing.T)
Table-Driven Tests
func TestRouter_PathMatching(t *testing.T) {
    tests := []struct {
        name        string
        pattern     string
        path        string
        shouldMatch bool
    }{
        {
            name:        "exact match",
            pattern:     "/users",
            path:        "/users",
            shouldMatch: true,
        },
        // ... more test cases
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            // Test implementation
        })
    }
}

Best Practices

  1. Always use helpers: Use testutil.TempDir(), testutil.SetupTestDB(), etc. instead of manual setup
  2. Automatic cleanup: All helpers automatically clean up after tests
  3. Isolated tests: Each test should be independent and not rely on other tests
  4. Fast by default: Use in-memory SQLite for speed, PostgreSQL only when needed
  5. Descriptive names: Test names should clearly describe what they test
  6. Table-driven: Use table-driven tests for multiple similar scenarios
  7. Assert vs Require: Use assert for non-fatal checks, require for fatal checks

Future Enhancements

Potential additions for future phases:

  • Mock filesystem utilities (afero integration)
  • HTTP mock server helpers
  • Snapshot testing utilities
  • Performance benchmarking helpers
  • Parallel test execution helpers

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AssertRecordCount

func AssertRecordCount(t *testing.T, db *gorm.DB, model interface{}, expectedCount int64, where string, args ...interface{})

AssertRecordCount asserts that the database contains the expected number of records matching the given conditions.

Example usage:

testutil.AssertRecordCount(t, db, &User{}, 5, "active = ?", true)

func AssertRecordExists

func AssertRecordExists(t *testing.T, db *gorm.DB, model interface{}, where string, args ...interface{})

AssertRecordExists asserts that a record exists in the database with the given conditions.

Example usage:

testutil.AssertRecordExists(t, db, &User{}, "email = ?", "test@example.com")

func AssertRecordNotExists

func AssertRecordNotExists(t *testing.T, db *gorm.DB, model interface{}, where string, args ...interface{})

AssertRecordNotExists asserts that no record exists in the database with the given conditions.

Example usage:

testutil.AssertRecordNotExists(t, db, &User{}, "email = ?", "deleted@example.com")

func AutoMigrate

func AutoMigrate(t *testing.T, db *gorm.DB, models ...interface{})

AutoMigrate runs database migrations for the provided models. This is a convenience wrapper around GORM's AutoMigrate.

Example usage:

testutil.AutoMigrate(t, db, &User{}, &Post{})

func CreateFile

func CreateFile(t *testing.T, path string, content string)

CreateFile creates a file with the given content in the specified path. Parent directories are created automatically.

func DirExists

func DirExists(t *testing.T, path string) bool

DirExists checks if a directory exists at the given path.

func FileExists

func FileExists(t *testing.T, path string) bool

FileExists checks if a file exists at the given path.

func LoadFixture

func LoadFixture(t *testing.T, path string) []byte

LoadFixture loads a test fixture file from the internal/testutil/fixtures directory. Returns the raw bytes of the fixture file.

func LoadJSONFixture

func LoadJSONFixture(t *testing.T, path string, v interface{})

LoadJSONFixture loads a JSON fixture file and unmarshals it into the provided value.

func MkdirAll

func MkdirAll(t *testing.T, basePath string, dirs ...string)

MkdirAll creates a directory structure in the given base path. Useful for creating complex test directory structures.

func RunInTransaction

func RunInTransaction(t *testing.T, db *gorm.DB, fn func(tx *gorm.DB))

RunInTransaction runs the provided function within a database transaction that is automatically rolled back after the function completes. This is useful for testing database operations without persisting changes.

Example usage:

testutil.RunInTransaction(t, db, func(tx *gorm.DB) {
    user := User{Email: "test@example.com"}
    err := tx.Create(&user).Error
    require.NoError(t, err)
    // Changes are rolled back after this function returns
})

func SeedTestData

func SeedTestData(t *testing.T, db *gorm.DB, data interface{})

SeedTestData seeds the test database with the provided data. The data parameter should be a pointer to a struct or slice of structs.

Example usage:

users := []User{
    {Email: "user1@example.com"},
    {Email: "user2@example.com"},
}
testutil.SeedTestData(t, db, &users)

func SetupTestDB

func SetupTestDB(t *testing.T) *gorm.DB

SetupTestDB creates a test database. By default, it creates an in-memory SQLite database for fast, isolated testing. If POSTGRES_TEST_DSN environment variable is set, it will use a PostgreSQL database instead.

The database is automatically closed when the test completes.

Example usage:

db := testutil.SetupTestDB(t)
err := db.AutoMigrate(&User{})
require.NoError(t, err)

func SetupTestEnv

func SetupTestEnv(t *testing.T, env map[string]string)

SetupTestEnv sets test environment variables and automatically unsets them when the test completes.

func TempDir

func TempDir(t *testing.T) string

TempDir creates a temporary directory for tests that is automatically cleaned up when the test completes.

func TruncateTable

func TruncateTable(t *testing.T, db *gorm.DB, tableName string)

TruncateTable truncates the specified table in the database. Useful for cleaning up between test cases.

Example usage:

testutil.TruncateTable(t, db, "users")

func WriteFixture

func WriteFixture(t *testing.T, dir, filename string, data []byte) string

WriteFixture writes data to a fixture file in a temporary directory. Useful for creating test fixtures on the fly.

Types

This section is empty.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL