Update testing docs for async helper

This commit is contained in:
Claudio Ortolina
2026-05-16 22:35:06 +01:00
parent 90ac4fa23a
commit 1a5313cff0
2 changed files with 30 additions and 16 deletions
+29 -15
View File
@@ -75,15 +75,23 @@ conn
### ConnCase Auto-Imports ### ConnCase Auto-Imports
`MusicLibraryWeb.ConnCase` imports `MusicLibraryWeb.LiveTestHelpers`, `PhoenixTest`
in full, and selected `Phoenix.LiveViewTest` functions.
`LiveTestHelpers` provides:
- `escape/1`
- `render_async/1`, `render_async/2`
- `trigger_hook/3`, `trigger_hook/4`
`MusicLibraryWeb.ConnCase` auto-imports the following LiveViewTest functions (with `only:`): `MusicLibraryWeb.ConnCase` auto-imports the following LiveViewTest functions (with `only:`):
- `render_async/1`, `render_change/1`, `render_click/3` - `render_change/1`, `render_click/3`
- `render_hook/2`, `render_hook/3` - `render_hook/2`, `render_hook/3`
- `element/2`, `element/3` - `element/2`, `element/3`
- `form/3` - `form/3`
It also imports `PhoenixTest` in full. When a test needs LiveViewTest functions NOT in When a test needs LiveViewTest functions NOT in the auto-import list (e.g.
the auto-import list (e.g. `render_click/1`, `render_submit/1`), add an explicit `render_click/1`, `render_submit/1`), add an explicit `only:` import to the test
`only:` import to the test file rather than importing the whole module. file rather than importing the whole module.
### The `unwrap/2` Escape Hatch ### The `unwrap/2` Escape Hatch
@@ -215,29 +223,32 @@ When converting a test file from `Phoenix.LiveViewTest.live/2` to `PhoenixTest.v
2. **Replace `live(conn, path)`** with `visit(conn, path)`. The session returned by 2. **Replace `live(conn, path)`** with `visit(conn, path)`. The session returned by
`visit` pipes into all PhoenixTest helpers. `visit` pipes into all PhoenixTest helpers.
3. **Replace `form/3` + `render_submit/1`** with `fill_in/3` + `click_button/2` (for 3. **After `visit/2`, replace `unwrap(&render_async/1)`** with `render_async()`.
For custom timeouts, use `render_async(timeout)`.
4. **Replace `form/3` + `render_submit/1`** with `fill_in/3` + `click_button/2` (for
standard inputs with labels) or `unwrap` with `form/3` + `render_submit/1` (for standard inputs with labels) or `unwrap` with `form/3` + `render_submit/1` (for
forms inside LiveComponents without labeled inputs). forms inside LiveComponents without labeled inputs).
4. **Replace `form/3` + `render_change/1`** with `fill_in/3` (triggers `phx-change` 5. **Replace `form/3` + `render_change/1`** with `fill_in/3` (triggers `phx-change`
automatically) or `unwrap` with `form/3` + `render_change/1` (for Fluxon custom automatically) or `unwrap` with `form/3` + `render_change/1` (for Fluxon custom
components or label-less inputs). components or label-less inputs).
5. **Replace `element/2` + `render_click/1`** with `click_button/2` (for `<button>`) 6. **Replace `element/2` + `render_click/1`** with `click_button/2` (for `<button>`)
or `click_link/2` (for `<a>`), or `unwrap` with `element/2` + `render_click/1` or `click_link/2` (for `<a>`), or `unwrap` with `element/2` + `render_click/1`
(for `<li>`, `<span>`, or other non-standard clickable elements). (for `<li>`, `<span>`, or other non-standard clickable elements).
6. **Replace `render_hook/3`** with `unwrap(&render_hook(&1, event, value))`. 7. **Replace `render_hook/3`** with `unwrap(&render_hook(&1, event, value))`.
7. **Replace `assert_redirect(view, path)`** with `assert_path(session, path)`. 8. **Replace `assert_redirect(view, path)`** with `assert_path(session, path)`.
8. **Replace `has_element?(view, selector)`** with `assert_has(session, selector)` or 9. **Replace `has_element?(view, selector)`** with `assert_has(session, selector)` or
`refute_has(session, selector)`. `refute_has(session, selector)`.
9. **Replace `render(view)` with substring checks** (`html =~ "text"`) with 10. **Replace `render(view)` with substring checks** (`html =~ "text"`) with
`assert_has(session, selector, text)` or `assert_has(session, selector, text: text)`. `assert_has(session, selector, text)` or `assert_has(session, selector, text: text)`.
10. **For position-based assertions** (e.g., verifying alphabetical order in HTML), 11. **For position-based assertions** (e.g., verifying alphabetical order in HTML),
access raw HTML via `Phoenix.LiveViewTest.render(session.view)`. access raw HTML via `Phoenix.LiveViewTest.render(session.view)`.
## Known Blockers for Full PhoenixTest Migration ## Known Blockers for Full PhoenixTest Migration
@@ -445,16 +456,19 @@ assert render_hook(view, "music_library:clipcopy", %{text: "copy me"}) =~ "copy
## Testing `handle_async` ## Testing `handle_async`
For async operations in LiveView tests, use `unwrap(&render_async/1)` which handles For async operations in PhoenixTest-backed LiveView tests, use `render_async()` from
the async resolution and returns a PhoenixTest session: `LiveTestHelpers`. It waits for async work and returns the PhoenixTest session:
```elixir ```elixir
conn conn
|> visit(~p"/maintenance") |> visit(~p"/maintenance")
|> unwrap(&render_async/1) |> render_async()
|> assert_has("span", "Connected as alice") |> assert_has("span", "Connected as alice")
``` ```
For raw `live/2` or `live_isolated/3` views, the same helper accepts the view or
element and delegates to `Phoenix.LiveViewTest.render_async/2`.
For unit testing `handle_async/3` callbacks, always handle all three cases: For unit testing `handle_async/3` callbacks, always handle all three cases:
```elixir ```elixir
+1 -1
View File
@@ -71,7 +71,7 @@ Key rules: `json_extract()` must match expression index text exactly, subquery m
→ See `.agents/skills/testing/SKILL.md` for full conventions, fixture modules, and SQLite/Swoosh/Worker test patterns. → See `.agents/skills/testing/SKILL.md` for full conventions, fixture modules, and SQLite/Swoosh/Worker test patterns.
Key rules: feature setup stays in test modules that need it, fixture modules use `System.unique_integer([:positive])`, assert specific values not just shapes, error assertions match specific error types, worker tests must `assert_enqueued`, no boilerplate-only tests. Key rules: feature setup stays in test modules that need it, fixture modules use `System.unique_integer([:positive])`, LiveView async page tests use `render_async()` from `LiveTestHelpers` rather than `unwrap(&render_async/1)`, assert specific values not just shapes, error assertions match specific error types, worker tests must `assert_enqueued`, no boilerplate-only tests.
## Tech Debt / Hygiene ## Tech Debt / Hygiene