Browse lessons

Testing Uploads (Direct to Server)

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"}>&lt; 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.