Running live view tests that calls get_connect_params raise error

:phoenix, “~> 1.6.2”
:phoenix_live_view, “~> 0.17.5”

I am trying to follow this post [Phoenix LiveView] formatting date/time with local time zone - DEV Community to handle the proper local timezone for my Phoenix Live View app.

The implementation looks fine and working, but I am facing problems when it comes to testing.

Here is what I have in terms of code:

// app.js

const liveSocket = new LiveSocket('/live', Socket, {
  params: {
    _csrf_token: csrfToken,
    timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
  }
})
# my_file.ex

def mount(%{"state" => state, "user_id" => user_id}, session, socket) do
    {:ok, socket} = assign_defaults(session, socket)

    offices = Locations.list_offices_for_state(state)
    time_zone = get_connect_params(socket)["timezone"] || @default_time_zone

    socket =
      socket
      |> assign(:page_title, "Registration - Schedule | Mindful Care")
      |> assign(:state, state)
      |> assign(:user_id, user_id)
      |> assign(:offices, offices)
      |> assign(:time_zone, time_zone)
      |> reset_state()
      |> assign(:step, "select_office")

    {:ok, socket}
  end
# my_file_test.exs

test "loads offices with providers and sets state", %{
      socket: socket,
      user: user,
      office: office,
      provider: provider
    } do
      params = %{"user_id" => user.id, "state" => office.state_abbr}
      {:ok, socket} = Schedule.mount(params, nil, socket)
  
      assert socket.assigns.page_title == "Registration - Schedule | Mindful Care"
      assert socket.assigns.step == "select_office"
      assert [off] = socket.assigns.offices
      assert off.id == office.id
      assert [prov] = off.providers
      assert prov.id == provider.id
    end

user, office, provider are just factories

So I wanna test if calling this mount will return the data I am expecting. My mount function uses another function Phoenix.LiveView — Phoenix LiveView v0.17.7 to get timezone value, but the call to get_connect_params is raising an error that I can not fix to make my test work.

Error:

  1) test mount/3 loads offices with providers and sets state (MindfulWeb.OnboardingLive.ScheduleTest)
     ** (RuntimeError) attempted to read connect_params outside of MindfulWeb.OnboardingLive.Schedule.mount/3.
     
     connect_params only exists while mounting. If you require access to this information
     after mount, store the state in socket assigns.

Is there any way to bypass this, to preset a value, or even a better approach to test if the mount will set the values I am expecting?

Thanks ahead!

2 Likes

How are you creating the socket used in your test?

Usually, when I test a live view I don’t try to inspect the content of the socket assigns or test the single functions of the view. Instead I use the Phoenix.LiveViewTest — Phoenix LiveView v0.17.7 function to spawn the view and then I assert that the view correctly rendered its elements.

3 Likes

Socket is defined in the setup:

setup do
  socket = %Phoenix.LiveView.Socket{}
  {:ok, socket: socket}
end

But your suggestion makes sense, the other tests are being done the way you told, basically took from here: ElixirConf 2020 - German Velasco - Testing LiveView - YouTube

2 Likes

You are trying to write a unit test for your LiveView mount, which is not recommended because in your code you do not invoke the mount callback directly. Instead as German points out in the talk you want to test the lifecycle of your LiveView.

For instance, you can use put_connect_params/2 to simulate the client connection with params:

defmodule MyAppWeb.MyFileTest do
  use MyAppWeb.ConnCase

  test "connect params", %{conn: conn} do
    # Simulate client behaviour
    conn = put_connect_params(conn, %{"timezone" => "America/Chicago"})

    {:ok, view, _html} = live(conn, "/")

    # Assert something about the _rendered output_
    assert render(view) =~ "CDT"
  end
end

Note that you can’t trust client-side data without some kind of validation. In the above example I am asserting on a computed timezone value to imply that some validation took place in order to arrive at the rendered output.

Note also that such an assertion only works if you are rendering the value somewhere on the template. Similar to traditional Controller tests, we want to assert on the side effects of the controller request/response flow as opposed to the state of the things directly after invoking the action function.

3 Likes

Thanks, both of you @mcrumm and @trisolaran now I know better how to properly test live view code.

2 Likes