Testing Framework & Strategy
Relevant source files
The following files were used as context for generating this wiki page:
The GoCasa testing framework prioritizes high-fidelity integration tests by building the real application in a controlled environment. The strategy centers on driving the application over HTTP, using a temporary SQLite database, a deterministic fake clock, and mocked upstream services. This approach ensures that tests exercise the full stack, from routing and logic to database persistence and server-rendered templates.
Sources: internal/app/app.go:1-5, internal/apptest/apptest.go:1-4, README.md:104-106
Test Architecture & The Harness
The core of the testing framework is the Harness struct, located in the apptest package. It encapsulates the application instance, an httptest.Server, a localized clock, and an HTTP client that maintains its own cookie jar to simulate a web browser.
Components of the Test Harness
- Temporary SQLite Database: Every test runner creates a fresh database in a temporary directory to maintain isolation.
- Fake Clock: A
Clockstruct allows tests to set specific times or advance time manually, which is critical for testing scheduled jobs and calendar occurrences. - Unroutable Upstreams: By default, third-party API URLs (Google, OpenMeteo) point to
http://127.0.0.1:1. This forces tests to explicitly provide fakes or fail rather than making actual network calls. - Device Simulation: The
Device()method allows a single test to simulate multiple "browsers" (Members) by providing clients with independent cookie jars.
Sources: internal/apptest/apptest.go:13-30, internal/apptest/apptest.go:42-65, internal/apptest/apptest.go:80-85
Test Initialization Flow
The following diagram illustrates how the apptest.New function constructs the environment for a test case:
The diagram shows the setup stages required to ensure test repeatability and isolation.
Sources: internal/apptest/apptest.go:42-76
Testing Scheduled Jobs
GoCasa uses a Scheduler to handle background tasks like backups and integration syncs. The testing strategy utilizes the RunDue() method to execute pending jobs immediately at the fake clock's current time.
- Deterministic Execution: Tests advance the
Clockand then callh.RunDue()to verify that jobs run only when they are supposed to. - Resilience Testing: Tests verify that if one job panics or returns an error, it does not prevent subsequent due jobs from executing.
Sources: internal/app/app_test.go:61-75, internal/app/app_test.go:77-90, internal/apptest/apptest.go:88-88
HTTP & Integration Testing
Tests interact with the application primarily through Get and Post methods provided by the Harness. These methods simulate a "same-origin" browser by setting appropriate headers like Sec-Fetch-Site: same-origin.
| Method | Description | Usage Example |
|---|---|---|
h.Get(t, path) | Fetches a path and returns the status code and body string. | Verifying page renders or 404s. |
h.Post(t, path, form) | Submits url.Values to a path using POST. | Creating events or changing settings. |
h.Device() | Returns a copy of the harness with a fresh cookie jar. | Testing multi-user scenarios. |
Sources: internal/apptest/apptest.go:91-125, internal/app/app_test.go:14-34
Sequence: Event Creation Test
This diagram demonstrates the full-stack flow exercised during a standard integration test.
Sources: internal/app/calendar_test.go:48-52, internal/app/app.go:119-122
Data Persistence & Migrations
The strategy includes verifying database behavior, specifically concerning schema versions and safety backups.
- Migration Safety: Before running migrations, the app calls a
preMigrationfunction that creates a backup. Tests verify this by checking the filesystem for.dbfiles named with the-pre-vN.dbsuffix. - Version Pinning: The application refuses to start if the SQLite
PRAGMA user_versionis higher than the version supported by the binary. A specific test case manually increments this version to ensure the application fails with the expected error message.
Sources: internal/app/app.go:59-71, internal/app/app_test.go:48-59
Configuration Validation
Configuration logic is tested in isolation within the config package. The strategy ensures that the application refuses to start under several conditions:
- Missing required variables (e.g.,
BASE_URL). - Malformed data (e.g., a
TZthat is not a valid IANA zone). - Insecure secrets (e.g., an
APP_ENCRYPTION_KEYthat is not 32 bytes when base64 decoded).
Sources: internal/config/config_test.go:34-52
Internal Logic Testing
While high-level integration tests are preferred, the project uses internal tests (package app instead of app_test) for complex logic such as calendar recurrence and event "splits."
- Agenda Verification: A helper function
agenda()translates database state into a sorted list of strings (e.g.,"03-15 09:00–10:00 Walk") to allow for concise assertions on complex recurrence rules. - Bus Subscriptions: Tests can subscribe to the internal
Busto verify that specific entities (e.g., "event", "calendar") publish "created" or "updated" messages following an action.
Sources: internal/app/events_internal_test.go:76-92, internal/app/events_internal_test.go:121-125
Summary
The GoCasa testing strategy provides high confidence by running the actual application logic against a real (though temporary) database. By controlling time via a fake clock and simulating browser behavior through a specialized harness, the framework allows developers to reproduce complex calendar behaviors and background scheduling logic reliably and deterministically.