Phoenix live routes - define PATCH and DELETE manually

I generated a live resource using a mix task mix phx.gen.live. Compared to Rails, no routes were added to the router.ex file. So I had to add some manually like this:

scope "/", XClarityWeb do
    pipe_through :browser

    get "/", PageController, :home
    live "/clients", ClientLive.Index, :index
    live "/clients/new", ClientLive.Index, :new
    live "/clients/:id/edit", ClientLive.Index, :edit
  end
...

When starting the app and playing around with it, - everything seems to work as needed (CRUD work fine).
But when running the tests, I have 2 of them failing saying:

1) test Index saves new client (XClarityWeb.ClientLiveTest)
     test/xclarity_web/live/client_live_test.exs:26
     ** (ArgumentError) expected XClarityWeb.ClientLive.Index to patch to "/clients", but got none
     code: assert_patch(index_live, ~p"/clients")
     stacktrace:
       (phoenix_live_view 0.18.18) lib/phoenix_live_view/test/live_view_test.ex:1314: Phoenix.LiveViewTest.assert_navigation/4
       (phoenix_live_view 0.18.18) lib/phoenix_live_view/test/live_view_test.ex:1214: Phoenix.LiveViewTest.assert_patch/3
       test/xclarity_web/live/client_live_test.exs:42: (test)


  2) test Index updates client in listing (XClarityWeb.ClientLiveTest)
     test/xclarity_web/live/client_live_test.exs:49
     ** (ArgumentError) expected XClarityWeb.ClientLive.Index to patch to "/clients", but got none
     code: assert_patch(index_live, ~p"/clients")
     stacktrace:
       (phoenix_live_view 0.18.18) lib/phoenix_live_view/test/live_view_test.ex:1314: Phoenix.LiveViewTest.assert_navigation/4
       (phoenix_live_view 0.18.18) lib/phoenix_live_view/test/live_view_test.ex:1214: Phoenix.LiveViewTest.assert_patch/3
       test/xclarity_web/live/client_live_test.exs:65: (test)

The content of the Index module is as follows:

defmodule XClarityWeb.ClientLive.Index do
  use XClarityWeb, :live_view

  alias XClarity.Clients
  alias XClarity.Clients.Client

  @impl true
  def mount(_params, _session, socket) do
    {:ok, stream(socket, :clients, Clients.list_clients())}
  end

  @impl true
  def handle_params(params, _url, socket) do
    {:noreply, apply_action(socket, socket.assigns.live_action, params)}
  end

  defp apply_action(socket, :edit, %{"id" => id}) do
    socket
    |> assign(:page_title, "Edit Client")
    |> assign(:client, Clients.get_client!(id))
  end

  defp apply_action(socket, :new, _params) do
    socket
    |> assign(:page_title, "New Client")
    |> assign(:client, %Client{})
  end

  defp apply_action(socket, :index, _params) do
    socket
    |> assign(:page_title, "Listing Clients")
    |> assign(:client, nil)
  end

  @impl true
  def handle_info({XClarityWeb.ClientLive.FormComponent, {:saved, client}}, socket) do
    {:noreply, stream_insert(socket, :clients, client)}
  end

  @impl true
  def handle_event("delete", %{"id" => id}, socket) do
    client = Clients.get_client!(id)
    {:ok, _} = Clients.delete_client(client)

    {:noreply, stream_delete(socket, :clients, client)}
  end
end

I can’t figure out:

  • what the reason of the failure is
  • should I declare manually PATCH, POST and DELETE routes?
{:phoenix, "~> 1.7.2"},
{:phoenix_html, "~> 3.3"},
{:phoenix_live_reload, "~> 1.2", only: :dev},
{:phoenix_live_view, "~> 0.18.16"},
...

Thank you.

After checking the documentation related to the mix task, it clearly tells to add the following routes after running the task:

Add the live routes to your browser scope in lib/app_web/router.ex:

  live "/users", UserLive.Index, :index
  live "/users/new", UserLive.Index, :new
  live "/users/:id/edit", UserLive.Index, :edit

  live "/users/:id", UserLive.Show, :show
  live "/users/:id/show/edit", UserLive.Show, :edit

So, I restored show.ex and show.html.heex files I removed previously, and re-ran the tests.
It failed again with the same error. What am I missing?

Hmm, that’s odd… those two failing assertions are checking for the successful patch navigation events from the two push_patch/2 function calls in your UserLive.FormComponent. Are there any changes to that module/file?

Here is the content of the form_component.ex module:

defmodule XClarityWeb.ClientLive.FormComponent do
  use XClarityWeb, :live_component

  alias XClarity.Clients

  @impl true
  def render(assigns) do
    ~H"""
    <div>
      <.header>
        <%= @title %>
        <:subtitle>Use this form to manage client records in your database.</:subtitle>
      </.header>

      <.simple_form
        for={@form}
        id="client-form"
        phx-target={@myself}
        phx-change="validate"
        phx-submit="save"
      >
        <.input field={@form[:name]} type="text" label="Name" />
        <.input field={@form[:vat]} type="text" label="Vat" />
        <:actions>
          <.button phx-disable-with="Saving...">Save Client</.button>
        </:actions>
      </.simple_form>
    </div>
    """
  end

  @impl true
  def update(%{client: client} = assigns, socket) do
    changeset = Clients.change_client(client)

    {:ok,
     socket
     |> assign(assigns)
     |> assign_form(changeset)}
  end

  @impl true
  def handle_event("validate", %{"client" => client_params}, socket) do
    changeset =
      socket.assigns.client
      |> Clients.change_client(client_params)
      |> Map.put(:action, :validate)

    {:noreply, assign_form(socket, changeset)}
  end

  def handle_event("save", %{"client" => client_params}, socket) do
    save_client(socket, socket.assigns.action, client_params)
  end

  defp save_client(socket, :edit, client_params) do
    case Clients.update_client(socket.assigns.client, client_params) do
      {:ok, client} ->
        notify_parent({:saved, client})

        {:noreply,
         socket
         |> put_flash(:info, "Client updated successfully")
         |> push_patch(to: socket.assigns.patch)}

      {:error, %Ecto.Changeset{} = changeset} ->
        {:noreply, assign_form(socket, changeset)}
    end
  end

  defp save_client(socket, :new, client_params) do
    case Clients.create_client(client_params) do
      {:ok, client} ->
        notify_parent({:saved, client})

        {:noreply,
         socket
         |> put_flash(:info, "Client created successfully")
         |> push_patch(to: socket.assigns.patch)}

      {:error, %Ecto.Changeset{} = changeset} ->
        {:noreply, assign_form(socket, changeset)}
    end
  end

  defp assign_form(socket, %Ecto.Changeset{} = changeset) do
    assign(socket, :form, to_form(changeset))
  end

  defp notify_parent(msg), do: send(self(), {__MODULE__, msg})
end

I can’t see any diffs compared to another draft app I created later just to check if the same mix task generates something different and if the tests pass.

Hmm, that form component looks fine to me…
How’s it being called from the HEEx template?

<.live_component
  module={XClarityWeb.ClientLive.FormComponent}
  id={@client.id || :new}
  title={@page_title}
  action={@live_action}
  client={@client}
  patch={~p"/clients"} # does it have this bit in particular?
/>

I’m also having a similar problem (Also for my ClientLive, funnily enough).

After running mix phx.gen.live any generated tests with assert_patch(SOME_LIVE, ~p"/INDEXVIEW") fail.

I confirmed that the patch line is in the HEEx template. The app behaves correctly, it seems like the test does not.

I got the same error messages (… patch to “/the_route”, but got none …) when bootstrapping LiveViews by leaving internal implementations of context and schema using the --no-context and --no-schema flags (e.g. mix phx.gen.live Apples Apple apples --no-context --no-schema).

The bootstrapped LiveView test file was created with empty maps for CRUD-Tests on top of the test file: @create_attrs %{}, @update_attrs %{} and @invalide_attrs %{}. Therefore no record was created or updated at testing and the “save” and “update” tests failed using assert_patch because there were no records to show.

Maybe you also have empty attrs in your test files?