How to test LiveView state/html received via PubSub

I have a LiveView that starts a GenServer on mount. This GenServer loads data and at some point broadcasts a message to a topic that the LiveView is subscribed to.

How to write tests for this?

  test "displays company name", %{conn: conn} do
    {:ok, view, html} =
      live_isolated(conn, MyLiveView, session: %{"customer_id" => "5013"})

    assert html =~ "Company Name"
  end

Company Name is rendered after the PubSub message is received. But the live_isolated in the test gives me the html right after mount. So how can I get the new html in the test?

Ok, I think I got this by myself. There is also Phoenix.LiveViewTest.render/1

wait_until(fn ->
  assert render(view) =~ "Company Name"
end)
2 Likes

I ran into this same problem and wanted to try your solution, but the funciton wait_until does not seem to be a liveview function… I get an undefined function error.

He’s the implementation. You will need to import it in your test

defmodule TimeHelper do
  def wait_until(fun), do: wait_until(1_000, fun)

  def wait_until(0, fun), do: fun.()

  def wait_until(timeout, fun) do
    try do
      fun.()
    rescue
      ExUnit.AssertionError ->
        :timer.sleep(10)
        wait_until(max(0, timeout - 10), fun)
    end
  end
end
1 Like

Thanks for this :slight_smile: for others who come across this thread, here’s a helper I wrote which uses this function specifically for rendering LiveViews:

@doc """
Wait for a LiveView html-based assertion to pass.

Accepts a LiveView from Phoenix.LiveViewTest.live/2, and a function
of arity 1, which gets passed html from the rendered LiveView.
Returns the rendered html which passed the assertion.

## Examples

    iex> {:ok, lv, _html} = live(conn, ~p"/rooms/4")
    iex> html = render_until(lv, fn html -> assert html =~ "Loading finished!" end)
"""
def render_until(lv, fun) do
  wrapped_fun = fn ->
    html = Phoenix.LiveViewTest.render(lv)
    fun.(html)
    html
  end

  MyApp.TimeHelper.wait_until(wrapped_fun)
end

A better way to test this is to (if you’re using PubSub) subscribe your test process to the topic and then assert_receive on the message from PubSub in your test pid.

Then you can render your LiveView and the html will be there and you don’t need busy waiting loops in test

Funnily I actually came to this thread because that exact solution to my problem didn’t work. There’s still a race condition because the test process can receive the message before the LiveView process does. My tests tend to fail very rarely on local, but very commonly on CI (via GitHub Actions, not sure exactly what the deal with that is). There are some different solutions proposed in this thread: Testing LiveViews that rely on PubSub for updates, I haven’t tried the :sys.get_state/1 solution but maybe I’ll try writing a different helper with that and see if it’s reliable.

2 Likes