Phoenix.LiveViewTest
Conveniences for testing Phoenix LiveViews.
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 LiveViews and their children.
LiveView Testing
The life-cycle of a LiveView 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:
import Plug.Conn import 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 LiveView 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/1
- sends a phx-click event and value, returning the rendered result of thehandle_event/3
callback.render_focus/2
- sends a phx-focus event and value, returning the rendered result of thehandle_event/3
callback.render_blur/1
- sends a phx-blur event and value, returning the rendered result of thehandle_event/3
callback.render_submit/1
- sends a form phx-submit event and value, returning the rendered result of thehandle_event/3
callback.render_change/1
- sends a form phx-change event and value, returning the rendered result of thehandle_event/3
callback.render_keydown/1
- sends a form phx-keydown event and value, returning the rendered result of thehandle_event/3
callback.render_keyup/1
- sends a form phx-keyup event and value, returning the rendered result of thehandle_event/3
callback.render_hook/3
- sends a hook event and value, returning 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
LiveViews 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 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 a DOM selector in a list:
{:ok, view, html} = live(conn, "/users") html = view |> element("#user-13 a", "Delete") |> render_click() refute html =~ "user-13" refute view |> element("#user-13") |> has_element?()
In the example above, LiveView will lookup for an element with ID=user-13 and retrieve its phx-target
. If phx-target
points to a component, that will be the component used, otherwise it will fallback to the view.
Summary
Functions
- assert_patch(view, to, timeout \\ 100)
Asserts a live patch will happen within
timeout
.- assert_patched(view, to)
Asserts a live patch was performed.
- assert_push_event(view, event, payload, timeout \\ 100)
Asserts an event will be pushed within
timeout
.- assert_redirect(view, to, timeout \\ 100)
Asserts a redirect will happen within
timeout
.- assert_redirected(view, to)
Asserts a redirect was performed.
- assert_reply(view, payload, timeout \\ 100)
Asserts a hook reply was returned from a
handle_event
callback.- element(view, selector, text_filter \\ nil)
Returns an element to scope a function to.
- find_live_child(parent, child_id)
Gets the nested LiveView child by
child_id
from theparent
LiveView.- follow_redirect(reason, conn, to \\ nil)
Follows the redirect from a
render_*
action.- form(view, selector, form_data \\ %{})
Returns a form element to scope a function to.
- has_element?(element)
Checks if the given element exists on the page.
- has_element?(view, selector, text_filter \\ nil)
Checks if the given
selector
withtext_filter
is onview
.- live(conn, path \\ nil)
Spawns a connected LiveView process.
- live_children(parent)
Returns the current list of LiveView children for the
parent
LiveView.- live_isolated(conn, live_view, opts \\ [])
Spawns a connected LiveView process mounted in isolation as the sole rendered element.
- page_title(view)
Returns the most recent title that was updated via a
page_title
assign.- put_connect_info(conn, params)
Puts connect info to be used on LiveView connections.
- put_connect_params(conn, params)
Puts connect params to be used on LiveView connections.
- render(view)
Returns the HTML string of the rendered view or element.
- render_blur(element, value \\ %{})
Sends a blur event given by
element
and returns the rendered result.- render_blur(view, event, value)
Sends a blur event to the view and returns the rendered result.
- render_change(element, value \\ %{})
Sends a form change event given by
element
and returns the rendered result.- render_change(view, event, value)
Sends a form change event to the view and returns the rendered result.
- render_click(element, value \\ %{})
Sends a click event given by
element
and returns the rendered result.- render_click(view, event, value)
Sends a click
event
to theview
withvalue
and returns the rendered result.- render_component(component, assigns, opts \\ [])
Mounts, updates and renders a component.
- render_focus(element, value \\ %{})
Sends a focus event given by
element
and returns the rendered result.- render_focus(view, event, value)
Sends a focus event to the view and returns the rendered result.
- render_hook(view_or_element, event, value \\ %{})
Sends a hook event to the view or an element and returns the rendered result.
- render_keydown(element, value \\ %{})
Sends a keydown event given by
element
and returns the rendered result.- render_keydown(view, event, value)
Sends a keydown event to the view and returns the rendered result.
- render_keyup(element, value \\ %{})
Sends a keyup event given by
element
and returns the rendered result.- render_keyup(view, event, value)
Sends a keyup event to the view and returns the rendered result.
- render_patch(view, path)
Simulates a
live_patch
to the givenpath
and returns the rendered result.- render_submit(element, value \\ %{})
Sends a form submit event given by
element
and returns the rendered result.- render_submit(view, event, value)
Sends a form submit event to the view and returns the rendered result.
Functions
assert_patch(view, to, timeout \\ 100)
Asserts a live patch will happen within timeout
.
It always returns :ok
. To assert on the flash message, you can assert on the result of the rendered LiveView.
Examples
render_click(view, :event_that_triggers_patch) assert_patch view, "/path"
assert_patched(view, to)
Asserts a live patch was performed.
It always returns :ok
. To assert on the flash message, you can assert on the result of the rendered LiveView.
Examples
render_click(view, :event_that_triggers_redirect) assert_patched view, "/path"
assert_push_event(view, event, payload, timeout \\ 100)
Asserts an event will be pushed within timeout
.
Examples
assert_push_event view, "scores", %{points: 100, user: "josé"}
assert_redirect(view, to, timeout \\ 100)
Asserts a redirect will happen within timeout
.
It returns the flash messages from said redirect, if any. Note the flash will contain string keys.
Examples
render_click(view, :event_that_triggers_redirect) flash = assert_redirect view, "/path" assert flash["info"] == "Welcome"
assert_redirected(view, to)
Asserts a redirect was performed.
It returns the flash messages from said redirect, if any. Note the flash will contain string keys.
Examples
render_click(view, :event_that_triggers_redirect) flash = assert_redirected view, "/path" assert flash["info"] == "Welcome"
assert_reply(view, payload, timeout \\ 100)
Asserts a hook reply was returned from a handle_event
callback.
Examples
assert_reply view, %{result: "ok", transaction_id: _}
element(view, selector, text_filter \\ nil)
Returns an element to scope a function to.
It expects the current LiveView, a query selector, and a text filter.
An optional text filter may be given to filter the results by the query selector. If the text filter is a string or a regex, it will match any element that contains the string or matches the regex. After the text filter is applied, only one element must remain, otherwise an error is raised.
If no text filter is given, then the query selector itself must return a single element.
assert view |> element("#term a:first-child()", "Increment") |> render() =~ "Increment</a>"
find_live_child(parent, child_id)
Gets the nested LiveView child by child_id
from the parent
LiveView.
Examples
{:ok, view, _html} = live(conn, "/thermo") assert clock_view = find_live_child(view, "clock") assert render_click(clock_view, :snooze) =~ "snoozing"
follow_redirect(reason, conn, to \\ nil)
Follows the redirect from a render_*
action.
Imagine you have a LiveView that redirects on a render_click
event. You can make it sure it immediately redirects after the render_click
action by calling follow_redirect/3
:
live_view |> render_click("redirect") |> follow_redirect(conn)
follow_redirect/3
expects a connection as second argument. This is the connection that will be used to perform the underlying request.
If the LiveView redirects with a live redirect, this macro returns {:ok, live_view, disconnected_html}
with the content of the new LiveView, the same as the live/3
macro. If the LiveView redirects with a regular redirect, this macro returns {:ok, conn}
with the rendered redirected page. In any other case, this macro raises.
Finally, note that you can optionally assert on the path you are being redirected to by passing a third argument:
live_view |> render_click("redirect") |> follow_redirect(conn, "/redirected/page")
form(view, selector, form_data \\ %{})
Returns a form element to scope a function to.
It expects the current LiveView, a query selector, and the form data. The query selector must return a single element.
The form data will be validated directly against the form markup and make sure the data you are changing/submitting actually exists, failing otherwise.
Examples
assert view |> form("#term", user: %{name: "hello"}) |> render_submit() =~ "Name updated"
This function is meant to mimic what the user can actually do, so you cannot set hidden input values. However, hidden values can be given when calling render_submit/2
or render_change/2
, see their docs for examples.
has_element?(element)
Checks if the given element exists on the page.
Examples
assert view |> element("#some-element") |> has_element?()
has_element?(view, selector, text_filter \\ nil)
Checks if the given selector
with text_filter
is on view
.
See element/3
for more information.
Examples
assert has_element?(view, "#some-element")
live(conn, path \\ nil)
Spawns a connected LiveView process.
If a path
is given, then a regular get(conn, path)
is done and the page is upgraded to a LiveView
. If no path is given, it assumes a previously rendered %Plug.Conn{}
is given, which will be converted to a LiveView
immediately.
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")
live_children(parent)
Returns the current list of LiveView children for the parent
LiveView.
Children are returned in the order they appear in the rendered HTML.
Examples
{:ok, view, _html} = live(conn, "/thermo") assert [clock_view] = live_children(view) assert render_click(clock_view, :snooze) =~ "snoozing"
live_isolated(conn, live_view, opts \\ [])
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 LiveView for rendering. Refer to Phoenix.LiveView.Helpers.live_render/3
for a list of supported render options.
Examples
{:ok, view, html} = live_isolated(conn, AppWeb.ClockLive, session: %{"tz" => "EST"})
page_title(view)
Returns the most recent title that was updated via a page_title
assign.
Examples
render_click(view, :event_that_triggers_page_title_update) assert page_title(view) =~ "my title"
put_connect_info(conn, params)
Puts connect info to be used on LiveView connections.
put_connect_params(conn, params)
Puts connect params to be used on LiveView connections.
render(view)
Returns the HTML string of the rendered view or element.
If a view is provided, the entire LiveView is rendered. If an element is provided, only that element is rendered.
Examples
{:ok, view, _html} = live(conn, "/thermo") assert render(view) =~ ~s|<button id="alarm">Snooze</div>| assert view |> element("#alarm") |> render() == "Snooze"
render_blur(element, value \\ %{})
Sends a blur event given by element
and returns the rendered result.
The element
is created with element/3
and must point to a single element on the page with a phx-blur
attribute in it. The event name given set on phx-blur
is then sent to the appropriate LiveView (or component if phx-target
is set accordingly). All phx-value-*
entries in the element are sent as values. Extra values can be given with the value
argument.
It returns the contents of the whole LiveView or an {:error, redirect}
tuple.
Examples
{:ok, view, html} = live(conn, "/thermo") assert view |> element("#inactive") |> render_blur() =~ "Tap to wake"
render_blur(view, event, value)
Sends a blur event to the view and returns the rendered result.
It returns the contents of the whole LiveView or an {:error, redirect}
tuple.
Examples
{:ok, view, html} = live(conn, "/thermo") assert html =~ "The temp is: 30℉" assert render_blur(view, :inactive) =~ "Tap to wake"
render_change(element, value \\ %{})
Sends a form change event given by element
and returns the rendered result.
The element
is created with element/3
and must point to a single element on the page with a phx-change
attribute in it. The event name given set on phx-change
is then sent to the appropriate LiveView (or component if phx-target
is set accordingly). All phx-value-*
entries in the element are sent as values.
If you need to pass any extra values or metadata, such as the "_target" parameter, you can do so by giving a map under the value
argument.
It returns the contents of the whole LiveView or an {:error, redirect}
tuple.
Examples
{:ok, view, html} = live(conn, "/thermo") assert view |> element("form") |> render_change(%{deg: 123}) =~ "123 exceeds limits" # Passing metadata {:ok, view, html} = live(conn, "/thermo") assert view |> element("form") |> render_change(%{_target: ["deg"], deg: 123}) =~ "123 exceeds limits"
As with render_submit/2
, hidden input field values can be provided like so:
refute view |> form("#term", user: %{name: "hello"}) |> render_change(%{user: %{"hidden_field" => "example"}}) =~ "can't be blank"
render_change(view, event, value)
Sends a form change event to the view and returns the rendered result.
It returns the contents of the whole LiveView or an {:error, redirect}
tuple.
Examples
{:ok, view, html} = live(conn, "/thermo") assert html =~ "The temp is: 30℉" assert render_change(view, :validate, %{deg: 123}) =~ "123 exceeds limits"
render_click(element, value \\ %{})
Sends a click event given by element
and returns the rendered result.
The element
is created with element/3
and must point to a single element on the page with a phx-click
attribute in it. The event name given set on phx-click
is then sent to the appropriate LiveView (or component if phx-target
is set accordingly). All phx-value-*
entries in the element are sent as values. Extra values can be given with the value
argument.
If the element is does not have a phx-click
attribute but it is a link (the <a>
tag), the link will be followed accordingly:
- if the link is a
live_patch
, the current view will be patched - if the link is a
live_redirect
, this function will return{:error, {:live_redirect, %{to: url}}}
, which can be followed withfollow_redirect/2
- if the link is a regular link, this function will return
{:error, {:redirect, %{to: url}}}
, which can be followed withfollow_redirect/2
It returns the contents of the whole LiveView or an {:error, redirect}
tuple.
Examples
{:ok, view, html} = live(conn, "/thermo") assert view |> element("buttons", "Increment") |> render_click() =~ "The temperature is: 30℉"
render_click(view, event, value)
Sends a click event
to the view
with value
and returns the rendered result.
It returns the contents of the whole LiveView or an {:error, redirect}
tuple.
Examples
{:ok, view, html} = live(conn, "/thermo") assert html =~ "The temperature is: 30℉" assert render_click(view, :inc) =~ "The temperature is: 31℉"
render_component(component, assigns, opts \\ [])
Mounts, updates and renders a component.
If the component uses the @myself
assigns, then an id
must be given to it is marked as stateful.
Examples
assert render_component(MyComponent, id: 123, user: %User{}) =~ "some markup in component" assert render_component(MyComponent, %{id: 123, user: %User{}}, router: SomeRouter) =~ "some markup in component"
render_focus(element, value \\ %{})
Sends a focus event given by element
and returns the rendered result.
The element
is created with element/3
and must point to a single element on the page with a phx-focus
attribute in it. The event name given set on phx-focus
is then sent to the appropriate LiveView (or component if phx-target
is set accordingly). All phx-value-*
entries in the element are sent as values. Extra values can be given with the value
argument.
It returns the contents of the whole LiveView or an {:error, redirect}
tuple.
Examples
{:ok, view, html} = live(conn, "/thermo") assert view |> element("#inactive") |> render_focus() =~ "Tap to wake"
render_focus(view, event, value)
Sends a focus event to the view and returns the rendered result.
It returns the contents of the whole LiveView or an {:error, redirect}
tuple.
Examples
{:ok, view, html} = live(conn, "/thermo") assert html =~ "The temp is: 30℉" assert render_focus(view, :inactive) =~ "Tap to wake"
render_hook(view_or_element, event, value \\ %{})
Sends a hook event to the view or an element and returns the rendered result.
It returns the contents of the whole LiveView or an {:error, redirect}
tuple.
Examples
{:ok, view, html} = live(conn, "/thermo") assert html =~ "The temp is: 30℉" assert render_hook(view, :refresh, %{deg: 32}) =~ "The temp is: 32℉"
If you are pushing events from a hook to a component, then you must pass an element
, created with element/3
, as first argument and it must point to a single element on the page with a phx-target
attribute in it:
{:ok, view, _html} = live(conn, "/thermo") assert view |> element("#thermo-component") |> render_hook(:refresh, %{deg: 32}) =~ "The temp is: 32℉"
render_keydown(element, value \\ %{})
Sends a keydown event given by element
and returns the rendered result.
The element
is created with element/3
and must point to a single element on the page with a phx-keydown
or phx-window-keydown
attribute in it. The event name given set on phx-keydown
is then sent to the appropriate LiveView (or component if phx-target
is set accordingly). All phx-value-*
entries in the element are sent as values. Extra values can be given with the value
argument.
It returns the contents of the whole LiveView or an {:error, redirect}
tuple.
Examples
{:ok, view, html} = live(conn, "/thermo") assert html =~ "The temp is: 30℉" assert view |> element("#inc") |> render_keydown() =~ "The temp is: 31℉"
render_keydown(view, event, value)
Sends a keydown event to the view and returns the rendered result.
It returns the contents of the whole LiveView or an {:error, redirect}
tuple.
Examples
{:ok, view, html} = live(conn, "/thermo") assert html =~ "The temp is: 30℉" assert render_keydown(view, :inc) =~ "The temp is: 31℉"
render_keyup(element, value \\ %{})
Sends a keyup event given by element
and returns the rendered result.
The element
is created with element/3
and must point to a single element on the page with a phx-keyup
or phx-window-keyup
attribute in it. The event name given set on phx-keyup
is then sent to the appropriate LiveView (or component if phx-target
is set accordingly). All phx-value-*
entries in the element are sent as values. Extra values can be given with the value
argument.
It returns the contents of the whole LiveView or an {:error, redirect}
tuple.
Examples
{:ok, view, html} = live(conn, "/thermo") assert html =~ "The temp is: 30℉" assert view |> element("#inc") |> render_keyup() =~ "The temp is: 31℉"
render_keyup(view, event, value)
Sends a keyup event to the view and returns the rendered result.
It returns the contents of the whole LiveView or an {:error, redirect}
tuple.
Examples
{:ok, view, html} = live(conn, "/thermo") assert html =~ "The temp is: 30℉" assert render_keyup(view, :inc) =~ "The temp is: 31℉"
render_patch(view, path)
Simulates a live_patch
to the given path
and returns the rendered result.
render_submit(element, value \\ %{})
Sends a form submit event given by element
and returns the rendered result.
The element
is created with element/3
and must point to a single element on the page with a phx-submit
attribute in it. The event name given set on phx-submit
is then sent to the appropriate LiveView (or component if phx-target
is set accordingly). All phx-value-*
entries in the element are sent as values. Extra values, including hidden input fields, can be given with the value
argument.
It returns the contents of the whole LiveView or an {:error, redirect}
tuple.
Examples
{:ok, view, html} = live(conn, "/thermo") assert view |> element("form") |> render_submit(%{deg: 123}) =~ "123 exceeds limits"
To submit a form along with some with hidden input values:
assert view |> form("#term", user: %{name: "hello"}) |> render_submit(%{user: %{"hidden_field" => "example"}}) =~ "Name updated"
render_submit(view, event, value)
Sends a form submit event to the view and returns the rendered result.
It returns the contents of the whole LiveView or an {:error, redirect}
tuple.
Examples
{:ok, view, html} = live(conn, "/thermo") assert html =~ "The temp is: 30℉" assert render_submit(view, :refresh, %{deg: 32}) =~ "The temp is: 32℉"
© 2018 Chris McCord
Licensed under the MIT License.
https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html