Skip to content

feat: inject template arguments into context map for handler testability#1416

Open
bgurmendi wants to merge 6 commits into
a-h:mainfrom
bgurmendi:feat/template-args-testability
Open

feat: inject template arguments into context map for handler testability#1416
bgurmendi wants to merge 6 commits into
a-h:mainfrom
bgurmendi:feat/template-args-testability

Conversation

@bgurmendi

Copy link
Copy Markdown

Problem

Testing an HTTP handler that renders a templ component requires parsing
the rendered HTML to verify which data was passed to the template. This
is fragile (depends on HTML structure) and couples the handler test to
the template output.

Solution

Add an opt-in mechanism: if the request context contains a
map[string]any under the key "_templ_args_map", the generated code
stores each component argument in that map at the start of execution.
This lets handler tests inspect the arguments directly without touching
the HTML.

argsMap := make(map[string]any)
ctx := context.WithValue(r.Context(), "_templ_args_map", argsMap)

mux.ServeHTTP(w, r.WithContext(ctx))

u := argsMap["u"].(User) // the argument passed to the templ component

The map is only populated when the key is present in the context, so
production requests are unaffected.

Changes

  • runtime: new SetTemplArg(m, key, value) helper that writes
    into the map only if the key is not already set.
  • generator: emits templruntime.SetTemplArg(...) calls at the
    start of each generated component function, for every parameter and
    for the receiver when present (templ (this T) Render(...)).
  • generator/test-args-map: new test case covering the mechanism.
  • examples/testing-args: runnable end-to-end example with handler
    • test.
  • docs: new subsection in the testing guide explaining the
    pattern, including nested component behaviour.

Nested components

When components are nested and share a parameter name, the outermost
component's value is kept (first write wins). This is intentional:
handler tests call the top-level component, so those are the arguments
worth asserting on.

Test plan

  • go test ./runtime/...
  • go test ./generator/...
  • go test -C examples/testing-args ./...

Beñat Gurmendi Culla added 6 commits June 18, 2026 14:31
Writes a key/value pair into the map only if the key is not already
present. This ensures that when components are nested, the outermost
component's argument takes precedence over any inner component that
happens to share the same parameter name.
…bility

If the request context contains a map[string]any under the key
"_templ_args_map", the generated code now calls
templruntime.SetTemplArg() to store each parameter (and the receiver,
if any) in that map at the start of execution.

This allows tests to verify which arguments a handler passed to a
template without parsing the rendered HTML. The map is only populated
when the key is present, so production requests are not affected.

Three helper functions are added to extract names from the raw
expression string stored in HTMLTemplate.Expression.Value:
- extractReceiverName: returns the receiver variable name if present
- extractParamList: returns the parameter list from the last (...) group
- extractParamNames: splits the list into individual variable names,
  handling nested types (func, map, slice, variadic)
All template_templ.go files in the generator test directories have been
regenerated to reflect the new SetTemplArg calls. Templates with no
parameters are unchanged.
…d code

New test directory covering the _templ_args_map mechanism:
- args_test.go: verifies that arguments are captured when the map key
  is present in the context, and that nothing is written when it isn't.
Runnable example showing the full pattern:
- handler.go: a standard http.HandlerFunc that queries a dependency and
  renders a templ component, with the dependency as a replaceable
  package-level variable.
- template.templ / template_templ.go: minimal templ component.
- handler_test.go: injects "_templ_args_map" into the request context,
  calls the handler through a real mux, and asserts that the component
  received the correct User argument.
Adds a new subsection "Testing template arguments from a handler" to
the testing guide explaining:
- The opt-in mechanism: inject a map[string]any under the key
  "_templ_args_map" into the request context before calling the handler.
- A worked example (handler + test) matching examples/testing-args/.
- Nested component behaviour: when two components share a parameter
  name, the outermost component's value is kept (first write wins).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant