In this lesson we’re going to look at how to test uploads that are saved directly to our server.
We’ll reuse AlbumLive from last lesson, but we’ll focus on the submission pieces.
AlbumLive.Index
As a recap, we have a form for uploads:
<.form
:let={f}
for={@changeset}
id="upload-form"
phx-change="validate"
phx-submit="save"
class="flex flex-col space-y-6"
>
<.input field={{f, :name}} label="Name" />
<.live_file_input upload={@uploads.photos} />
<button type="submit">Upload</button>
</.form>
The form has phx-submit="save", which hits:
def handle_event("save", %{"album" => params}, socket) do
uploaded_file_paths =
consume_uploaded_entries(socket, :photos, fn %{path: path}, entry ->
dest = upload_destination(entry)
File.cp!(path, dest)
{:ok, Path.join(Ranger.public_uploads_path(), Path.basename(dest))}
end)
params
|> Map.put("photo_urls", uploaded_file_paths)
|> Album.changeset()
|> Repo.insert()
|> case do
{:ok, album} ->
{:noreply, push_navigate(socket, to: ~p"/albums/#{album}")}
{:error, changeset} ->
{:noreply, assign(socket, :changeset, changeset)}
end
end
Some logic is normal form handling. The upload-specific part is consume_uploaded_entries/3, where we copy temp files to permanent server paths.
AlbumLive.Show
defmodule RangerWeb.AlbumLive.Show do
use RangerWeb, :live_view
alias Ranger.{Album, Repo}
def render(assigns) do
~H"""
<div class="max-w-lg mx-auto space-y-6">
<.link navigate={~p"/albums"}>< Back</.link>
<section class="space-y-6">
<h2 class="text-xl font-bold text-center"><%= @album.name %></h2>
<%= for photo_url <- @album.photo_urls do %>
<img data-role="image" src={photo_url} class="rounded-md" />
<% end %>
</section>
</div>
"""
end
end
We render a title and each uploaded image (data-role="image").
Testing submission
test "user can submit upload", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/albums")
{:ok, show_view, _show_html} =
view
|> upload("moria-door.png")
|> create_album("Moria adventures")
|> follow_redirect(conn)
assert has_element?(show_view, "h2", "Moria adventures")
assert has_element?(show_view, "[data-role='image']")
end
defp create_album(view, name) do
view
|> form("#upload-form", %{album: %{name: name}})
|> render_submit()
end
Breakdown:
- Normal setup with
live/2 - Upload file, then submit form via helper
- Follow redirect caused by
push_navigate - Assert title and image on show page
Cleaning upload data
Since tests write files, clean uploads after each test run:
setup do
on_exit(fn ->
File.rm_rf!(Ranger.uploads_dir())
File.mkdir!(Ranger.uploads_dir())
end)
end
This keeps test uploads isolated and avoids clutter.
Note: using a dedicated test uploads dir (for example from config/test.exs) prevents accidental deletion of development uploads.