How to initialize a `current_user` for live tests to run properly?

Hi folks,

I have a vanilla Phoenix project where I have used the built in generator to add authentication:

mix phx.gen.auth Accounts User users

I then added Articles using the live generator:

mix phx.gen.live Articles Article articles title:text content:text author_id:references:users

I then proceeded to make sure listing articles shows only those created by the current_user:

git diff 0bc9c43185047c2de8116895bbf60dd0afcb6eb7...6e70b9b54c8bd98500b9ee7d5284a0dd0e254f44
diff --git a/lib/bmvp/articles.ex b/lib/bmvp/articles.ex
index f03c63a..3d3b901 100644
--- a/lib/bmvp/articles.ex
+++ b/lib/bmvp/articles.ex
@@ -21,6 +21,10 @@ defmodule Bmvp.Articles do
     Repo.all(Article)
   end

+  def list_articles_by_author_id(author_id) do
+    Repo.all(from(a in Article, where: a.author_id == ^author_id))
+  end
+
   @doc """
   Gets a single article.

diff --git a/lib/bmvp_web/live/article_live/index.ex b/lib/bmvp_web/live/article_live/index.ex
index 901d1bf..3208909 100644
--- a/lib/bmvp_web/live/article_live/index.ex
+++ b/lib/bmvp_web/live/article_live/index.ex
@@ -4,9 +4,12 @@ defmodule BmvpWeb.ArticleLive.Index do
   alias Bmvp.Articles
   alias Bmvp.Articles.Article

+  on_mount({BmvpWeb.UserAuth, :mount_current_user})
+
   @impl true
   def mount(_params, _session, socket) do
-    {:ok, stream(socket, :articles, Articles.list_articles())}
+    author_id = socket.assigns.current_user.id
+    {:ok, stream(socket, :articles, Articles.list_articles_by_author_id(author_id))}
   end

   @impl true

At this point, mix test started to fail with:

  1) test Index saves new article (BmvpWeb.ArticleLiveTest)
     test/bmvp_web/live/article_live_test.exs:26
     ** (KeyError) key :id not found in: nil

     If you are using the dot syntax, such as map.field, make sure the left-hand side of the dot is a map
     code: {:ok, index_live, _html} = live(conn, ~p"/articles")
     stacktrace:
       (bmvp 0.1.0) lib/bmvp_web/live/article_live/index.ex:11: BmvpWeb.ArticleLive.Index.mount/3
       (phoenix_live_view 0.20.1) lib/phoenix_live_view/utils.ex:394: anonymous fn/6 in Phoenix.LiveView.Utils.maybe_call_live_view_mount!/5
       (telemetry 1.2.1) /Users/petros/Projects/petros/bmvp/deps/telemetry/src/telemetry.erl:321: :telemetry.span/3
       (phoenix_live_view 0.20.1) lib/phoenix_live_view/static.ex:277: Phoenix.LiveView.Static.call_mount_and_handle_params!/5
       (phoenix_live_view 0.20.1) lib/phoenix_live_view/static.ex:118: Phoenix.LiveView.Static.render/3
       (phoenix_live_view 0.20.1) lib/phoenix_live_view/controller.ex:39: Phoenix.LiveView.Controller.live_render/3
       (phoenix 1.7.10) lib/phoenix/router.ex:432: Phoenix.Router.__call__/5
       (bmvp 0.1.0) lib/bmvp_web/endpoint.ex:1: BmvpWeb.Endpoint.plug_builder_call/2
       (bmvp 0.1.0) lib/bmvp_web/endpoint.ex:1: BmvpWeb.Endpoint.call/2
       (phoenix 1.7.10) lib/phoenix/test/conn_test.ex:225: Phoenix.ConnTest.dispatch/5
       test/bmvp_web/live/article_live_test.exs:27: (test)

My assumption here is that I also need to have an authenticated current_user for my tests. But I am not sure where is the best place to initialize this and how to do it. I would love your insight!

Cheers,
Petros

That’s right, unless you also want your view to support unauthenticated users?

To implement authenticated tests you could do something like this in a helper module:

  def fake_user(attrs \\ %{}, opts \\ []),
    do: MyApp.Users.create_fake_user(attrs, opts)

  def conn(), do: conn(session_conn(), [])
  def conn(%Plug.Conn{} = conn), do: conn(conn, [])
  def conn(filters) when is_list(filters), do: conn(session_conn(), filters)

  def conn(conn, filters) when is_list(filters),
    do: Enum.reduce(filters, conn, &conn(&2, &1))

  def conn(conn, {:user, %User{id: id}}),
    do: put_session(conn, :current_user_id, id)

  def conn(conn, {:user, user_id}) when is_binary(user_id),
    do: put_session(conn, :current_user_id, user_id)

  defp session_conn(conn \\ build_conn()),
    do: Plug.Test.init_test_session(conn, %{})

And in your tests:

    {:ok, test_user} = fake_user()

    conn = conn(user: test_user)

Phx.gen.auth got you covered!

Take a look at how the MyAppWeb.ConnCase was adapted by the phx.gen.auth generator. It added a register_and_log_in_user/1 function that can be used as a setup step in your ExUnit testcase (assuming your test uses the MyAppWeb.ConnCase, if not, you have to add that to your test).

You should only add one line to have a current_user assign available in your test:


defmodule MyAppWeb.ArticleLiveTest do
  use MyAppWeb.ConnCase

  # ...
  setup :register_and_log_in_user

  test "some test", %{conn: conn, user: user} do
    # ...
  end
end

What @mayel suggested could work, but it requires adding a function in production code solely for testing, which I don’t like. The generated code uses the test fixtures for a fake user, which is more suitable for this. There is also no session token set this way, so I’m not entirely sure how that would work.

1 Like