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
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.