About Phoenix LiveView, LiveView Life-Cycle and Testing

So I was taking a look at documentation and other people code as well to understand a bit more about LiveView and specially, how to test it.

I’m still a bit confuse about some things, so those are probably very basic questions but I would appreciate any possible help, tip or advice.

Let’s take this example.

import Plug.Conn
import Phoenix.ConnTest
import Phoenix.LiveViewTest
@endpoint MyEndpoint

test "disconnected and connected mount", %{conn: conn} do
  conn = get(conn, "/my-path")
  assert html_response(conn, 200) =~ "<h1>My Disconnected View</h1>"

  {:ok, view, html} = live(conn)
end

We have this test for “disconnected and connected mount”.
I have a couple of questions about this.
The first one is what the endpoint tag purpose, I still didn’t found about it in the docs (I’m a bit in a hurry so I’m not reading it sequentially, maybe that’s why)

Now, I wonder why would I test “disconnected” and then “connected” mount in the same test case.
But before that, what really does a “disconnected mount” mean? I’ve been searching for this both on Google and on docs, but didn’t found any clarification about it.

As I understand the LiveView Life-Cycle so far is like this:

You receive a regular HTTP Request, it triggers mount/3, then handle_params/3, then render/1, and return a stateless, regular Web Page as HTTP Response.
Then, a WebSocket connection will start triggering mount/3 → handle_params/3 → render/1 again.

What a disconnected mount mean in this cycle?


Just like in this code example above, I’ve found people testing “disconnected mount” in their code as well.

assert html_response(conn, 200) =~ "<h1>My Disconnected View</h1>"

Like this code suggest, I saw people testing it like this: If I render a table, I would test the table empty instead.

My question is how?
If I use mount/3 or handle_params/3 to load the contents of my page, like executing a query to populate a table, how do so I could test for an “empty table” if I’m receiving a page after the complete life-cycle?

Right now I’m prototyping an application to learn and practice LiveView, I have the table as LiveComponents, do I could test successfully passing one valid list to the live component with render_component/2 and asserting the result. This is because I can control the params received in a live component, so I can test it and assert that only one <td> was created if I only passed one element in the list as params.

But if I use the mount/3 function to fetch data testing the liveview page instead of an isolated component, I’ll not be able to ever see this table empty.
Isn’t that what I’m supposed to do? Isn’t mount/3 where I fetch data to prepare the page for the response?

So, what a “disconnected mount” means, and what is the difference to a connected mount?
Can I receive the HTTP response after mount/3 but before handle_params/3?
Can I have a mount/3 for the HTTP response and another mount/3 that will execute only after the WebSocket being stablished?
(Like not fetching data from database in the HTTP request but fetching after a Live Socket start?)

Additionally how can I, in a test, force a failed connection to the database?

Sorry, those are a lot of questions, but I’m still a bit lost about how to test things both on Phoenix as a whole but specially about LiveView. I was wondering for a good bit why people wasn’t using controllers but doing everything on LiveView for example, so is the LiveView supposed to be both Controller, View and Template?
(and possible all three in the same file since you can render a sigil/literal instead of creating a separate template…)

I’m having a lot of fun learning LiveView but I’m not sure on how to do it “right”, I mean, in an organised and well designed way.
Should I call my (context/model) functions directly from LiveView? Or is it a better design to create a Controller, call the Controller from the LiveView, and then this Controller will interact with the Context?
This is not how I’m seeing people use it but I thought that LiveView would only be another type of “View”, I was surprised about it acting as a Controller in a lot of code I’m consuming to learn from.

Thank you for taking your time to read this until the end.

First mount is disconnected which creates the websocket connection, rest of it happens over websocket (until you redirect etc). You can call connected?(socket) and it will be false for the first mount. You can setup a page with loading placeholders for things here if you like, which will populate either on the connected mount or you could send(self(), {:load_some_thing, ...}) in the mount so you can load the page first or you can do it async and send a message back to the liveview to assign it later.

Should I call my context/module from the LiveView?

Yep. You don’t need much in the way of controllers if your site is all live views. I don’t think of my project in terms of Models, Controllers and Views. I just build a UI in LiveView and call functions in modules :slight_smile:.

To learn about the lifecycle, you might like to watch this talk

This course might be of use to you. They go through the lifecycle and more.

2 Likes

Thank you for taking your time to reply cmo!

Your explanation was very helpful, honestly I understood what a disconnect mount is for your reply only.
The docs was not very clear for me, and the talk… I had watched it before and just watch it a second time right now and although it’s entertaining, for someone who is already confused about it showing non-working code it’s not really helpful. I liked the talk and I learned some things but not really about the life cycle itself… :sweat_smile:

I will take a look at the course, thank you!