This is a post about a few functions to test that our app does what we think it does when a form is submitted. If you want to deploy your Phoenix LiveView app right now, then check out how to get started. You could be up and running in minutes.
In previous posts we’ve used forms for different purposes, but we’ve never talked about how to test that our app does the right thing when a form is submitted. In this post we’ll take a walk around some functions of the LiveViewTest module that come in handy for testing forms.
Testing the phx-submit result
We can test the behavior of our LiveView when the event specified with the phx-submit
option is handled. The render_submit/2 function sends a form submit event and returns the rendered result. It is useful if we want to test the contents of our LiveView right after handling the submit event.
For example, this form checks what happens if we submit a password-update form with a too-short password and a non-matching password confirmation:
Verified routes can be used in tests too, Phoenix 1.7 will bring really cool stuff!
test "Update password: renders errors with invalid data", %{conn: conn} do
{:ok, lv, _html} = live(conn, ~p"/users/settings")
result =
lv
|> form("#password_update_form", %{
"current_password" => "invalid",
"user" => %{
"password" => "too short",
"password_confirmation" => "does not match"
}
})
|> render_submit()
assert result =~ "<h1>Settings</h1>"
assert result =~ "should be at least 12 character(s)"
assert result =~ "does not match password"
assert result =~ "is not valid"
end
We use the render_submit
function to try to update the user’s password. After handling the submit event, we can verify that the LiveView’s content contains the expected error messages by asserting the resulting content.
Testing a form submission with a redirect
The render_submit
function has two return types. We already saw the first one —the rendered content of the LiveView— but we haven’t mentioned the second one; it returns a tuple of type {:error, reason}
when the LiveView redirects after handling the phx-submit
event.
We can use the follow_redirect/3 function to verify that the LiveView redirects as expected. When there is a redirect it returns a {:ok, conn}
or {:ok, live_view, html}
tuple depending on the redirect type we use: a regular redirect or a live redirect respectively. If there is no redirect or if it redirects to the wrong place, it raises an error.
We can also use the resulting conn
to verify the new rendered page content. For example, we have a LiveView to reset the user’s password after receiving a reset link. If the password is successfully reset, an :info
message is added and the user is redirected to the login page as follows:
{:noreply,
socket
|> put_flash(:info, "Password reset successfully.")
|> redirect(to: ~p"/users/log_in")}
We can verify that the LiveView redirects to the correct place and it’s setting the expected :info
message:
test "resets password once", %{conn: conn, token: token, user: user} do
{:ok, lv, _html} = live(conn, ~p"/users/reset_password/#{token}")
{:ok, conn} =
lv
|> form("#reset_password_form",
user: %{
"password" => "new valid password",
"password_confirmation" => "new valid password"
}
)
|> render_submit() # {:error, {:redirect, %{to: "/users/log_in", flash: "SFMyNTY..."}}}
|> follow_redirect(conn, ~p"/users/log_in")
refute get_session(conn, :user_token)
assert get_flash(conn, :info) =~ "Password reset successfully"
assert Accounts.get_user_by_email_and_password(user.email, "new valid password")
end
The render_submit
function is called and it returns an {:error, redirect}
tuple. We pass the redirect tuple and the conn
to follow_redirect/3
to perform the underlying request, and the route ~p"/users/log_in"
to verify that it redirects to the correct place.
Finally we use the resulting conn
to verify that:
1) The user wasn’t logged in.
2) The expected flash message was added.
3) The user’s password was reset.
Testing an HTTP form submission
In a previous post, we used a form’s :action
attribute to execute a controller action from a LiveView.
For example, we have a form to send a username and password to the ~p/users/log_in
route and save the user’s data in session if the authentication is successful:
<.form
id="login_form"
:let={f}
for={:user}
action={~p"/users/log_in"}
as={:user}
>
<%= label f, :email %>
<%= email_input f, :email, required: true, value: @email %>
<%= label f, :password %>
<%= password_input f, :password, required: true %>
<div>
<%= submit "Log in" %>
</div>
</.form>
In this case, we don’t have the phx-submit
option since we don’t do any validation/process inside the LiveView, but we delegate the whole login process to the ~p"/users/log_in"
controller action.
In LiveView 0.18, the function submit_form/2 was added to test the results of sending an HTTP request through the plug pipeline. Let’s use it to test a form submission with valid login params:
test "Login user with valid credentials", %{conn: conn} do
password = "valid_password"
user = user_fixture(%{password: password})
{:ok, lv, _html} = live(conn, ~p"/users/log_in")
form =
form(lv, "#login_form", user: %{
email: user.email,
password: password,
remember_me: true})
conn = submit_form(form, conn)
assert redirected_to(conn) == ~p"/"
assert get_session(conn, :user_token)
assert get_flash(conn, :info) =~ "Welcome back!"
end
The submit_form/2
function receives a form and executes the HTTP request specified in the form’s :action
attribute. Then we use the resulting conn
to check that:
1) This results in the right redirect.
2) The logged user’s token was added to the session.
3) The expected flash message was returned.
Triggering the submit form action and testing the results
In the same post, we used the phx-trigger-action
option to trigger a form :action
only once a condition had been met. Now we’ll cover how to test that the phx-trigger-action
attribute has been set to true
, and we’ll follow the underlying HTTP request to test the action call results.
In this test we submit the same password-update form we used at the beginning of the post, but this time we send valid params. We expect the form :action
— in charge of updating the user’s token in session — is triggered after validating that the user’s password was successfully updated and setting the phx-trigger-action
to the form:
test "updates the user password", %{conn: conn, user: user} do
new_password = valid_user_password()
{:ok, lv, _html} = live(conn, ~p"/users/settings")
form =
form(lv, "#password_update_form", %{
"current_password" => user.password,
"user" => %{
"email" => user.email,
"password" => new_password,
"password_confirmation" => new_password
}
})
assert render_submit(form) =~ ~r/phx-trigger-action/
new_password_conn = follow_trigger_action(form, conn)
assert get_session(new_password_conn, :user_token) !=
get_session(conn, :user_token)
assert get_flash(new_password_conn, :info) =~
"Password updated successfully"
assert Accounts.get_user_by_email_and_password(user.email, new_password)
end
We build a form and pass it to the render_submit/1
function, which returns the new content of our LiveView after handling the phx-submit
event so that we can assert that the phx-trigger-action
option is added to the form.
Then, we execute the controller action specified in the :action
attribute using the follow_trigger_action/2 function, and we use new_password_conn
to check that:
1) The user’s token was renewed with the new password.
2) The expected flash message was added to the connection.
3) The user’s password was updated.
Wrap-up
In this post we used a few functions of the LiveViewTest
module to test form submission results when we are using LiveView.
We can use render_submit
to test that our LiveView executes the correct logic and renders the expected results after handling the submit event that we specify with the phx_submit
attribute. Additionally, we can use follow_redirect
together with it for those times when we redirect to another LiveView after handling the submit event.
We can also test the results of executing an HTTP form submission using the :action
attribute. We use submit_form
to test the results of sending an HTTP request through the plug pipeline, and follow_trigger_action
to test the resulting content of the action that is executed once the phx-trigger-action
attribute is set to true.