Testing
Testing in GoForj starts with normal Go tests and expands only when behavior crosses a runtime boundary, depends on generated wiring, or requires an infrastructure backend. The goal is confidence without making every test boot the whole App.
The JSON API Route is the runnable baseline: it creates a Users service and controller, includes a complete service test, and verifies the generated HTTP route. From that App root:
forj build
go test ./...
forj route:listExpected result: build and tests pass, and the route list contains /api/v1/users/:id.
Choose a Test Layer
Use the narrowest layer that proves the behavior:
| Behavior to prove | Start with | Add a broader test when |
|---|---|---|
| Services, repositories with fakes, and pure domain behavior | Unit Tests with go test ./... | Generated wiring or a real backend changes the result |
| Controllers, route groups, and generated HTTP behavior | HTTP Tests with httptest | Listener or full-App behavior is the point |
| Console commands | Command Tests | Generated registration or runtime composition matters |
| Job handlers and dispatch | Job and Queue Tests | Retries, worker lifecycle, shutdown, or backend behavior matters |
| Event publishing and subscribers | Event Tests | Delivery through an external backend matters |
| Cache and storage behavior | Cache and Storage Tests | Driver-specific semantics matter |
| Database, external backends, or App boundaries | Integration Tests with go test -tags=integration ./... | A new App must also be exercised |
| Template and generated-App compile confidence | Rendered App Smoke Tests | A targeted framework integration suite is required |
Most App teams should begin from their App root:
go test ./...Expected result: each package reports ok; a failure identifies the package and test name to investigate.
GoForj Apps include generated tests for enabled framework-owned surfaces such as lifecycle idempotency, runtime topology defaults, health and readiness, OpenAPI serving, metrics, events, database connections, and generated commands.
Keep Domain Behavior Direct
Application services, job handlers, event handlers, and scheduled work should remain testable without starting HTTP, workers, or the scheduler. Use constructor injection with fakes or local drivers, then test runtime integration separately only when that boundary is relevant.
For scheduler work, keep the registry declarative:
s.DailyAt("04:11").Name("cleanup:stale-sessions").Do(s.authService.Cleanup)Test authService.Cleanup directly. Add scheduler integration coverage only when registration, runtime behavior, or observability is the target.
Local infrastructure drivers can keep broader tests small:
- use
inprocfor event delivery - use
syncorworkerpoolfor queue dispatch - use
nullwhen no-op queue behavior is the contract
Maintainer Workflows
Framework and template contributors can render a temporary App and run its tests:
forj test:render -sUse the framework integration command for targeted heavier suites:
forj test:integration
forj test:integration rendered --target database --variant sqliteThese are maintainer workflows, not the default path for every application team. Depending on the selected suite, integration tests may require Docker or external backends.
Related Sections
- Getting Started introduces the first local application path.
- Operations explains runtime behavior that integration tests should verify.