LiveView asynchronous task patterns

Thanks for the good suggestion @vsoraki. We generally do use a similar pattern to that to mock out our api calls. The thing that makes this difficult to test is that the call (mocked or not) is being done in a process that is spawned from the view. So to test it, we need to know when that separate process has finished, sent a message back to the view process, and the view process has consumed that message.

The simplest way to do that is to repeatedly render the view and check for the effect that you expect, but that can be slow and fail in unexpected ways. What we’ve been doing is actually using the API mock (using Tesla) to capture the pid of the spawned process and relay it back to the test process, then have the test process monitor the spawned process and assert_receive that we get the :DOWN message for the spawned pid. This works ok most of the time but is still sometimes flakey (false negatives) and it requires a bit of overhead in terms of set up.

It looks something like this

test "fetches data from API and renders results", %{conn: conn} do
  test_pid  = self()   
  tag = :api_call # tag because we may have multiple APIs to call

  Tesla.Mock.mock(fn ->
    send(test_pid, {tag, self()})
    {:ok, "Hello from the API"}
  end)

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

  assert_receive {:api_call, spawned_pid}
  ref = Process.monitor(spawned_pid)
  assert_receive {:DOWN, ^ref, :process, _pid, _reason}, 1_000

  assert render(view) =~ "Hello from the API"
end