Testing
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-name>.
Complete generated-application behavior belongs in test/. For example, the
sample application keeps its full app checks in test/application_test.go.
Construct the real handler
Integration tests should use the same composition path as main and wrap it
with lazytest:
func TestApplication(t *testing.T) {
app := lazytest.New(t, appinit.App())
app.Cases(
lazytest.Case{
Name: "posts",
Method: http.MethodGet,
Path: "/posts",
Status: http.StatusOK,
Contains: []string{"Hello, GoLazy"},
ContentType: "text/html",
},
)
}
This catches broken dependency wiring, route registration, template rendering, dispatch, and public-file setup together.
Exercise requests
Use lazytest for most request assertions:
app := lazytest.New(t, appinit.App())
response := app.Get("/posts")
response.AssertStatus(http.StatusOK)
response.AssertContains("Hello, GoLazy")
response.AssertContentType("text/html")
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.
Read Route Tests, Controller Tests, and Integration Tests for focused examples.