Testing LiveViews that rely on PubSub for updates

I just completed my first LiveView that makes use of PubSub topic. While this is easy to monkey test (and build!), I am finding automated testing to be… not so much difficult, but indeterminate.

Certain things happen when messages arrive. While these appear near instantaneous to a human at a browser, inside of an automated test, execution can outpace message processing. Is there some way to test this besides using timers and retries? I mean, it works… but I guess I just assume there has to be a better way. I’m wondering if there is a way to synchronize messages or something.

I can have the current test subscribe to the topic to ensure messages are fired and use assert_receive, but sometimes the test gets the message first and sometimes the LiveView does.

2 Likes

If nothing else, you could at least add a small delay (like 50ms) between the broadcast and assertion.

I ended up going with a solution similar to this:

It seems to be working well. It looks like the reality is simply that you gotta wait for things.

1 Like

wait_until helpers are a common way of solving it and I think readable. But just in case you want to know another way, you could use tracing for it (but assert_receive will use something similar to a wait_until logic).

assert {:ok, live_view, html} = live(conn, Routes.some_path(conn, :index))
pid = live_view.pid
:erlang.trace(pid, true, [:receive])

assert_receive {:trace, ^pid, :receive, {:the_pub_sub_message, :in_its_format}}

In test you usually don’t care to turn the tracing off afterwards but otherwise you should. If you want to dig deeper you can check erlang:trace/3

(I haven’t tested this particular snippet but it should work or be very close to working)

A slightly simpler option is is:

assert {:ok, live_view, html} = live(conn, Routes.some_path(conn, :index))
_ = :sys.get_state(live_view.pid)

# now do an assert render

Messages by the live view process are handled in order, and :sys.get_state is a GenServer.call. By the time it returns you know the live view has processed any other messages (like ones from a pubsub broadcast).

7 Likes

Yeap, I think if you can be sure the messages have been dispatched before :sys.get_state then it’s the best - or if you know they’ll eventually change the state, a wait_until helper (that it’s what I usually use) along with :sys.get_state are very readable and don’t require special handles on the process - you can wait until the state is what you expected and then call render on the lv. Here I don’t know if OP can be sure the message has been broadcast by the end of the live + get_state - but since it’s in a test it probably can be relied upon - if you were to add a delay on the pubsub emitter probably it would fail (and so would assert_receive without increasing the waiting time allowed, so, same same)

trace is nice when your process starts others and then those do asynchronous messaging through their own lifecycle - before editing the previous post I wrote that you can use set_on_spawn option on the trace but the example didn’t make total sense and I didn’t have patience to write a better one so just removed it. It’s useful to know too though (specially when in anger.)

2 Likes

This is super helpful. This is more along the lines of what I was looking for. I’ll likely update my tests to at least try this out.

I also have a clearer idea of when to use either and that wait style assertions are actually acceptable as well.

Also @amnu3387 I did see some other topics related to trace. Seems like it was a lot to digest at this stage, but it seems like it’s definitely info to keep in my head for a later date

Thanks for your input. This has been very helpful.

2 Likes

Since this question is almost 2 years old and LiveView has evolved a lot since then, is this still “the solution”? When I look into the Phoenix.LiveViewTest.render/1 function, it’s doing a GenServer.call under the hood, so that means that if there’s a message from PubSub in the view process mailbox, it will be processed before the render is called.
The problem I can see is when the desired message is not in the mailbox of the view process yet. Am I missing something?