Digging Deeper
Testing
Combine focused package tests with full application HTTP integration tests.
Test at the ownership boundary
Keep focused tests beside the package they exercise:
- Framework renderer, dispatch, application, and routing tests live in
golazy. - Service tests live beside application services.
- Adapter tests live beside
libpackages. - Executable helper tests live in
cmd/app.
Complete application behavior belongs in sample_app/test.
Construct the real handler
Integration tests should use the same composition path as main:
func application() http.Handler {
return appinit.App()
}
This catches broken dependency wiring, route registration, template rendering, dispatch, and public-file setup together.
Exercise requests
Use the standard library:
request := httptest.NewRequest(http.MethodGet, "/posts", nil)
response := httptest.NewRecorder()
application().ServeHTTP(response, request)
if response.Code != http.StatusOK {
t.Fatalf("status = %d", response.Code)
}
Useful assertions include:
- Status code.
Content-Type.ETagandIf-None-Matchbehavior.Cache-Controlfor logical and permanent asset URLs.Allowfor method errors.- Escaped or rendered body content.
404behavior for missing records and files.- Embedded public-file responses and asset permalinks.
Test asset permalinks
The rendered page should link content-hashed asset URLs through asset_path.
An integration test can extract the stylesheet URL and request it directly:
body := response.Body.String()
match := regexp.MustCompile(`href="([^"]*styles-[^"]+\.css)"`).FindStringSubmatch(body)
if match == nil {
t.Fatal("stylesheet permalink not found")
}
asset := httptest.NewRecorder()
application().ServeHTTP(
asset,
httptest.NewRequest(http.MethodGet, match[1], nil),
)
if got := asset.Header().Get("Cache-Control"); !strings.Contains(got, "immutable") {
t.Fatalf("Cache-Control = %q, want immutable", got)
}
if asset.Header().Get("ETag") == "" {
t.Fatal("asset ETag is empty")
}
Also test a matching If-None-Match request returns 304 Not Modified.
Logical asset paths should keep a revalidation-friendly cache policy, while
permanent asset paths should be safe to cache indefinitely.
Verify request-local controllers
Controller state is mutable, so integration tests should exercise concurrent requests:
var wait sync.WaitGroup
for range 20 {
wait.Add(1)
go func() {
defer wait.Done()
response := httptest.NewRecorder()
handler.ServeHTTP(
response,
httptest.NewRequest(http.MethodGet, "/posts", nil),
)
if response.Code != http.StatusOK {
t.Errorf("status = %d", response.Code)
}
}()
}
wait.Wait()
Run this under the race detector.
Test rendering boundaries
Framework renderer tests should verify:
- Ordinary values are HTML-escaped.
- View output is composed into the layout.
- Missing views and layouts return contextual errors.
- A renderer cannot be initialized without the default layout.
Application tests should verify only the behavior the application owns.
Release verification
Regenerate JavaScript library assets before Go verification when JavaScript manifest or package files changed:
lazy js
For the framework:
go test ./...
go test -race ./...
go vet ./...
For the sample application:
go test ./...
go test -race ./...
go vet ./...
go build -o /tmp/sample-app ./cmd/app
Use a temporary GOCACHE and CC=/usr/bin/gcc when required by the managed
development environment.