Timing issue when testing LiveView with GenServer

Hi all,

I am working through the Pragmatic Programmer LiveView course, and in lesson 22 Testing Messages, we are testing a Sales Dashboard that receives sales data from another module. The test as given in the lesson worked fine, but in order to practice using GenServer and PubSub I created a SalesProducer module that periodically broadcasts sales data over PubSub, but to make the manual Refresh button work, I also implemented a GenServer callback and this is where I am getting a strange timing issue.

Here is the GenServer part of the SalesProducer module:

def make_stats() do
    %{
      new_orders: Enum.random(1..40),
      sales_amount: Enum.random(1..200),
      satisfaction: Enum.random(1..100)
    }
  end

  def handle_call(:get_stats, _from,  state) do
    {:reply, make_stats(), state}
  end

Back in the LiveView, the Refresh button is implemented as:

 <button phx-click="refresh">
        <img src="images/refresh.svg">
        Refresh
      </button>

The button event is handled like:

def handle_event("refresh", _, socket) do
    socket = assign_stats(socket)
    {:noreply, socket}
  end

  defp assign_stats(socket) do
    stats = GenServer.call(LiveViewStudio.SalesProducer, :get_stats)
    IO.puts "assign_stats got "
    IO.inspect stats
    assign(
      socket,
      new_orders: stats.new_orders,
      sales_amount: stats.sales_amount,
      satisfaction: stats.satisfaction
    )
  end

This is where my code differs from the lesson in that I am making a GenServer call instead of one to a regular module, and this is where my test is failing. Here is the test code, and note the debug statement at the top:

test "refreshes when refresh button is pressed", %{conn: conn} do
    IO.puts "refreshes when refresh button is pressed"
    {:ok, view, _html} = live(conn, "/sales-dashboard")

    before_refresh = render(view)

    after_refresh =
      view
      |> element("button", "Refresh")
      |> render_click()

    refute before_refresh =~ after_refresh
  end

The test was failing because the before_refresh and after_refresh rendered views were exactly the same, so I started putting debug statements everywhere, and as soon as I put the one inside the test code that reprints the test description, the test passed:

15:37:39.813 [info]  Migrations already up
LiveViewStudio.SalesProducer
Sales producer starting
..assign_stats got
%{new_orders: 4, sales_amount: 104, satisfaction: 43}
assign_stats got
%{new_orders: 38, sales_amount: 200, satisfaction: 73}
.refreshes when refresh button is pressed
assign_stats got
%{new_orders: 32, sales_amount: 66, satisfaction: 55}
assign_stats got
%{new_orders: 1, sales_amount: 2, satisfaction: 29}
assign_stats got
%{new_orders: 14, sales_amount: 180, satisfaction: 26}
....................................................

Finished in 0.9 seconds (0.6s async, 0.2s sync)
55 tests, 0 failures

If all I do is comment out that debug statement at the top of the test, it fails:

15:51:49.736 [info]  Migrations already up
LiveViewStudio.SalesProducer
Sales producer starting
..assign_stats got
%{new_orders: 39, sales_amount: 127, satisfaction: 48}
assign_stats got
%{new_orders: 31, sales_amount: 130, satisfaction: 35}
assign_stats got
%{new_orders: 38, sales_amount: 143, satisfaction: 13}
assign_stats got
%{new_orders: 40, sales_amount: 190, satisfaction: 74}
assign_stats got
%{new_orders: 16, sales_amount: 143, satisfaction: 99}


  1) test refreshes after tick (LiveViewStudioWeb.SalesDashboardLiveTest)
     test/live_view_studio_web/live/sales_dashboard_live_test.exs:20
     Refute with =~ failed, both sides are exactly equal
     code: refute before_refresh =~ after_refresh
     left: "<div data-phx-main=\"true\" data-phx-session=\"SFMyNTY.g2gDaAJhBXQAAAAIZAACaWRtAAAAFHBoeC1Gc0VNS1R3cGtpakJHQVBCZAAMbGl2ZV9zZXNzaW9uaAJkAAdkZWZhdWx0bggAZ0LfhYiAvxZkAApwYXJlbnRfcGlkZAADbmlsZAAIcm9vdF9waWRkAANuaWxkAAlyb290X3ZpZXdkACtFbGl4aXIuTGl2ZVZpZXdTdHVkaW9XZWIuU2FsZXNEYXNoYm9hcmRMaXZlZAAGcm91dGVyZAAfRWxpeGlyLkxpdmVWaWV3U3R1ZGlvV2ViLlJvdXRlcmQAB3Nlc3Npb250AAAAAGQABHZpZXdkACtFbGl4aXIuTGl2ZVZpZXdTdHVkaW9XZWIuU2FsZXNEYXNoYm9hcmRMaXZlbgYASIcUwH0BYgABUYA.zy7wSfRN_3QmYukIYTrLXqjKY5v52VpK481Fo8Jo9F4\" data-phx-static=\"SFMyNTY.g2gDaAJhBXQAAAADZAAKYXNzaWduX25ld2pkAAVmbGFzaHQAAAAAZAACaWRtAAAAFHBoeC1Gc0VNS1R3cGtpakJHQVBCbgYAXIcUwH0BYgABUYA.s6AZpJXNjBmu1GqHZwrGK7GlsY2NkcEbZMzAv1FQEgs\" id=\"phx-FsEMKTwpkijBGAPB\"><main role=\"main\" class=\"container\"><p class=\"alert alert-info\" role=\"alert\" phx-click=\"lv:clear-flash\" phx-value-key=\"info\"></p><p class=\"alert alert-danger\" role=\"alert\" phx-click=\"lv:clear-flash\" phx-value-key=\"error\"></p><h1>Sales Dashboard</h1><div id=\"dashboard\"><div class=\"stats\"><div class=\"stat\"><span class=\"value\">\n        31\n      </span><span class=\"name\">\n        New Orders\n      </span></div><div class=\"stat\"><span class=\"value\">\n        $130\n      </span><span class=\"name\">\n        Sales Amount\n      </span></div><div class=\"stat\"><span class=\"value\">\n        35%\n      </span><span class=\"name\">\n        Satisfaction\n      </span></div></div><button phx-click=\"refresh\"><img src=\"images/refresh.svg\"/>\n    Refresh\n  </button></div></main></div>"
     stacktrace:
       test/live_view_studio_web/live/sales_dashboard_live_test.exs:26: (test)

....................................................

Finished in 1.1 seconds (0.7s async, 0.4s sync)
55 tests, 1 failure

I get that tests run concurrently and all that, but I thought a GenServer.call was blocking, vs a cast, so how could an IO action before the call change things to the point where the test fails?

Thanks for any pointers