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 the handle_event/3 callback.

  • render_submit/3 - sends a form phx-submit event and value and returns the rendered result of the handle_event/3 callback.

  • render_change/3 - sends a form phx-change event and value and returns the rendered result of the handle_event/3 callback.

  • render_keydown/3 - sends a form phx-keydown event and value and returns the rendered result of the handle_event/3 callback.

  • render_keyup/3 - sends a form phx-keyup event and value and returns the rendered result of the handle_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

Link to this macro

assert_redirect(view, to, flash \\ nil)

View Source (macro)

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.

Link to this macro

assert_remove(view, reason, timeout \\ 100)

View Source (macro)

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"
Link to this function

find_child(parent, child_id)

View Source

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"
Link to this macro

live(conn, path_or_opts \\ [])

View Source (macro)

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

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()
Link to this macro

live(conn, path, opts)

View Source (macro)

See live/2.

Link to this macro

live_isolated(conn, live_view, opts \\ [])

View Source (macro)

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

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"
Link to this function

render_blur(view, event, value \\ %{})

View Source

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"
Link to this function

render_change(view, event, value \\ %{})

View Source

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"
Link to this function

render_click(view, event, value \\ %{})

View Source

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"
Link to this macro

render_component(component, assigns)

View Source (macro)

Mounts, updates and renders a component.

Examples

assert render_component(MyComponent, id: 123, user: %User{}) =~
         "some markup in component"
Link to this function

render_focus(view, event, value \\ %{})

View Source

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..."
Link to this function

render_hook(view, event, value \\ %{})

View Source

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℉"
Link to this function

render_keydown(view, event, key_code)

View Source

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℉"
Link to this function

render_keyup(view, event, key_code)

View Source

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℉"
Link to this function

render_patch(view, path)

View Source

Simulates a live_patch to the given path and returns the rendered result.

Link to this function

render_submit(view, event, value \\ %{})

View Source

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}