phoenix_live_view v0.8.1 Phoenix.LiveViewTest View Source
Conveniences for testing Phoenix live views.
In LiveView tests, we interact with views via process communication in substitution of a browser. Like a browser, our test process receives messages about the rendered updates from the view which can be asserted against to test the life-cycle and behavior of live views and their children.
LiveView Testing
The life-cycle of a live view as outlined in the Phoenix.LiveView
docs details how a view starts as a stateless HTML render in a disconnected
socket state. Once the browser receives the HTML, it connects to the
server and a new LiveView process is started, remounted in a connected
socket state, and the view continues statefully. The LiveView test functions
support testing both disconnected and connected mounts separately, for example:
use Phoenix.ConnTest
import Phoenix.LiveViewTest
@endpoint MyEndpoint
test "disconnected and connected mount", %{conn: conn} do
conn = get(conn, "/my-path")
assert html_response(conn, 200) =~ "<h1>My Disconnected View</h1>"
{:ok, view, html} = live(conn)
end
test "redirected mount", %{conn: conn} do
assert {:error, %{redirect: %{to: "/somewhere"}}} = live(conn, "my-path")
end
Here, we start by using the familiar Phoenix.ConnTest
function, get/2
to
test the regular HTTP GET request which invokes mount with a disconnected socket.
Next, live/1
is called with our sent connection to mount the view in a connected
state, which starts our stateful LiveView process.
In general, it's often more convenient to test the mounting of a view
in a single step, provided you don't need the result of the stateless HTTP
render. This is done with a single call to live/2
, which performs the
get
step for us:
test "connected mount", %{conn: conn} do
{:ok, _view, html} = live(conn, "/my-path")
assert html =~ "<h1>My Connected View</h1>"
end
Testing Events
The browser can send a variety of events to a live view via phx-
bindings,
which are sent to the handle_event/3
callback. To test events sent by the
browser and assert on the rendered side effect of the event, use the
render_*
functions:
render_click/3
- sends a phx-click event and value and returns the rendered result of thehandle_event/3
callback.render_submit/3
- sends a form phx-submit event and value and returns the rendered result of thehandle_event/3
callback.render_change/3
- sends a form phx-change event and value and returns the rendered result of thehandle_event/3
callback.render_keydown/3
- sends a form phx-keydown event and value and returns the rendered result of thehandle_event/3
callback.render_keyup/3
- sends a form phx-keyup event and value and returns the rendered result of thehandle_event/3
callback.
For example:
{:ok, view, _html} = live(conn, "/thermo")
assert render_click(view, :inc) =~ "The temperature is: 31℉"
assert render_click(view, :set_temp, 35) =~ "The temperature is: 35℉"
assert render_submit(view, :save, %{deg: 30}) =~ "The temperature is: 30℉"
assert render_change(view, :validate, %{deg: -30}) =~ "invalid temperature"
assert render_keydown(view, :key, :ArrowUp) =~ "The temperature is: 31℉"
assert render_keydown(view, :key, :ArrowDown) =~ "The temperature is: 30℉"
Testing regular messages
Live views are GenServer
's under the hood, and can send and receive messages
just like any other server. To test the side effects of sending or receiving
messages, simply message the view and use the render
function to test the
result:
send(view.pid, {:set_temp: 50})
assert render(view) =~ "The temperature is: 50℉"
Testing shutdowns and stopping views
Like all processes, views can shutdown normally or abnormally, and this
can be tested with assert_remove/3
. For example:
send(view.pid, :boom)
assert_remove view, {:shutdown, %RuntimeError{}}
stop(view)
assert_remove view, {:shutdown, :stop}
Nested views can be removed by a parent at any time based on conditional rendering. In these cases, the removal of the view is detected by the browser, or our test client, and the child is shutdown gracefully. This can be tested in the same way as above:
assert render(parent) =~ "some content in child"
assert child = find_child(parent, "child-dom-id")
send(parent.pid, :msg_that_removes_child)
assert_remove child, _
refute render(parent) =~ "some content in child"
Testing components
There are two main mechanisms for testing components. To test stateless
components or just a regular rendering of a component, one can use
render_component/2
:
assert render_component(MyComponent, id: 123, user: %User{}) =~
"some markup in component"
If you want to test how components are mounted by a LiveView and
interact with DOM events, you can use the regular live/2
macro
to build the LiveView with the component and then scope events by
passing the view and the component DOM ID in a list:
{:ok, view, html} = live(conn, "/users")
html = render_click([view, "user-13"], "delete", %{})
refute html =~ "user-13"
assert_remove_component(view, "user-13")
Link to this section Summary
Functions
Asserts a redirect was peformed with optional flash.
Asserts a view was removed by a parent or shutdown itself.
Returns the current list of children of the parent live view.
Finds the nested LiveView child given a parent.
Spawns a connected LiveView process.
Spawns a connected LiveView process mounted in isolation as the sole rendered element.
Returns the string of HTML of the rendered view or component.
Sends a blur event to the view and returns the rendered result.
Sends a form change event to the view and returns the rendered result.
Sends a click event to the view and returns the rendered result.
Mounts, updates and renders a component.
Sends a focus event to the view and returns the rendered result.
Sends a hook event to the view and returns the rendered result.
Sends a keydown event to the view and returns the rendered result.
Sends a keyup event to the view and returns the rendered result.
Simulates a live_patch
to the given path
and returns the rendered result.
Sends a form submit event to the view and returns the rendered result.
Stops a LiveView process.
Link to this section Functions
Asserts a redirect was peformed with optional flash.
Examples
render_click(view, :event_that_triggers_redirect)
assert_redirect view, "/path"
The flash may also be provided to match against:
assert_redirect view, "/path", %{"info" => "it worked!"}
Note: the flash will contain string keys.
Asserts a view was removed by a parent or shutdown itself.
Examples
[child1, child2] = children(parent_view)
send(parent_view.pid, :msg_that_removes_children)
assert_remove child1, _
assert_remove child2, {:shutdown, :removed}
Returns the current list of children of the parent live view.
Children are returned in the order they appear in the rendered HTML.
Examples
{:ok, view, _html} = live(conn, "/thermo")
assert [clock_view] = children(view)
assert render_click(clock_view, :snooze) =~ "snoozing"
Finds the nested LiveView child given a parent.
Examples
{:ok, view, _html} = live(conn, "/thermo")
assert clock_view = find_child(view, "clock")
assert render_click(clock_view, :snooze) =~ "snoozing"
Spawns a connected LiveView process.
Accepts either a previously rendered %Plug.Conn{}
or
an unsent %Plug.Conn{}
. The latter case is a convenience
to perform the get/2
and connected mount in a single
step.
Options
:connect_params
- the map of params available in the socket-connected mount. SeePhoenix.LiveView.get_connect_params/1
for more information.
Examples
{:ok, view, html} = live(conn, "/path")
assert view.module = MyLive
assert html =~ "the count is 3"
assert {:error, %{redirect: %{to: "/somewhere"}}} = live(conn, "/path")
{:ok, view, html} =
conn
|> get("/path")
|> live()
See live/2
.
Spawns a connected LiveView process mounted in isolation as the sole rendered element.
Useful for testing LiveViews that are not directly routable, such as those built as small components to be re-used in multiple parents. Testing routable LiveViews is still recommended whenever possible since features such as live navigation require routable LiveViews.
Options
:connect_params
- the map of params available in connected mount. SeePhoenix.LiveView.get_connect_params/1
for more information.:session
- the session to be given to the LiveView
All other options are forwarded to the live view for rendering. Refer to
Phoenix.LiveView.live_render/3
for a list of supported render options.
Examples
{:ok, view, html} =
live_isolated(conn, AppWeb.ClockLive, session: %{"tz" => "EST"})
Returns the string of HTML of the rendered view or component.
If a view is provided, the entire LiveView is rendered. A list may be passed containing view and the component DOM ID's:
Examples
{:ok, view, _html} = live(conn, "/thermo")
assert render(view) =~ "cooling"
assert render([view, "clock"]) =~ "12:00 PM"
assert render([view, "clock", "alarm"]) =~ "Snooze"
Sends a blur event to the view and returns the rendered result.
Examples
{:ok, view, html} = live(conn, "/thermo")
assert html =~ "The temp is: 30℉"
assert render_blur(view, :inactive) =~ "Tap to wake"
assert render_blur([view, "child-id"], :inactive) =~ "Tap to wake"
Sends a form change event to the view and returns the rendered result.
Examples
{:ok, view, html} = live(conn, "/thermo")
assert html =~ "The temp is: 30℉"
assert render_change(view, :validate, %{deg: 123}) =~ "123 exceeds limits"
Sends a click event to the view and returns the rendered result.
Examples
{:ok, view, html} = live(conn, "/thermo")
assert html =~ "The temperature is: 30℉"
assert render_click(view, :inc) =~ "The temperature is: 31℉"
assert render_click([view, "clock"], :set, %{time: "1:00"}) =~ "time: 1:00 PM"
Mounts, updates and renders a component.
Examples
assert render_component(MyComponent, id: 123, user: %User{}) =~
"some markup in component"
Sends a focus event to the view and returns the rendered result.
Examples
{:ok, view, html} = live(conn, "/thermo")
assert html =~ "The temp is: 30℉"
assert render_blur(view, :inactive) =~ "Tap to wake"
assert render_focus(view, :active) =~ "Waking up..."
assert render_focus([view, "child-id"], :active) =~ "Waking up..."
Sends a hook event to the view and returns the rendered result.
Examples
{:ok, view, html} = live(conn, "/thermo")
assert html =~ "The temp is: 30℉"
assert render_hook(view, :refresh, %{deg: 32}) =~ "The temp is: 32℉"
Sends a keydown event to the view and returns the rendered result.
Examples
{:ok, view, html} = live(conn, "/thermo")
assert html =~ "The temp is: 30℉"
assert render_keyup(view, :inc, :ArrowUp) =~ "The temp is: 32℉"
assert render_keyup([view, "child-id"], :inc, :ArrowDown) =~ "The temp is: 31℉"
Sends a keyup event to the view and returns the rendered result.
Examples
{:ok, view, html} = live(conn, "/thermo")
assert html =~ "The temp is: 30℉"
assert render_keyup(view, :inc, :ArrowUp) =~ "The temp is: 32℉"
assert render_keyup([view, "child-id"], :inc, :ArrowDown) =~ "The temp is: 31℉"
Simulates a live_patch
to the given path
and returns the rendered result.
Sends a form submit event to the view and returns the rendered result.
Examples
{:ok, view, html} = live(conn, "/thermo")
assert html =~ "The temp is: 30℉"
assert render_submit(view, :refresh, %{deg: 32}) =~ "The temp is: 32℉"
Stops a LiveView process.
Examples
stop(view)
assert_remove view, {:shutdown, :stop}