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.
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))
_ = :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).
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.)
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.
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?