Start with unit tests on LiveView modules
Tags: elixir, liveview, phoenix, testing
Published: 2023-10-11 02:32:33

---
title: Start with unit tests on LiveView modules
published: true
description: Basic steps to test live view modules
tags: elixir, liveview, phoenix, testing
# cover_image: https://direct_url_to_image.jpg
# Use a ratio of 100:42 for best results.
# published_at: 2023-10-10 19:36 +0000
---

I have been working with Phoenix and LiveView in a full-time position for around 18 months now, and most recently LiveView started to become something that I do often.

At the beginning I was unsure about my coding and whether I was doing it correctly on the LiveView modules. Should I keep logic there? Should I create utility functions on the LiveView module? Should I make an external module to handle that?

After these months what use as a rule of thumb is: to deal with the LiveView module the same way I deal with a controller. That means I try to keep the module as clean as possible and only with the needed actions, such as mounting, handling callbacks, and assigning data to the socket.

I try to avoid adding business logic related to product on these modules cause this logics usually require more testing and testing it using LiveView can be tricky and not consider all scenarios.

This will make not only the code simple but also the effort to test tend to be very straightforward.

When checking the LiveView module test, your tests should be readable in a way that basically tells you how the user interacts with the page flow.

Let me start with a very simple and basic example:

```elixir
defmodule UrlShortenerWeb.ExampleLive.Index do
  use UrlShortenerWeb, :live_view

  def mount(_params, _session, socket) do
    {:ok, socket}
  end
end
```

In this live view module, there is nothing but the mount function and in the project structure, there is the `example_live/index.ex` and the `example_live/index.html.heex` so this way I will not need to use the `render` function.

The HTML has only a message 

```html
<p>Hello Live View</p>
```

Which will return the following when running the project

![Page rendered](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hdhcl10p220co3wfdhfc.png)

So, a simple way to test this basic render would be doing a test like this:

```elixir
defmodule UrlShortenerWeb.ExampleLive.IndexTest do
  use UrlShortenerWeb.ConnCase, async: true
  import Phoenix.LiveViewTest

  test "must access page and see the initial message", %{conn: conn} do
    {:ok, _view, html} =
      conn
      |> get(Routes.example_index_path(conn, :index))
      |> live()

    assert html =~ "Hello Live View"
  end
end
```

Here I have nothing to do with the `view` which is why I ignore it, in this case, I do not wanna test the connected view that has the socket and trigger some page behaviors but only the raw HTML generated.

By running it I got:

![First test running](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fqj6um4uv4w7gltwepqm.png)

Now I will add a button on the page that, once clicked will hide the previous message and add a new one.

![New button in action](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jco2wnx2huw721db6jnu.gif)

I would like to test the following scenario

- I see the default message
- I wanna click on the button
- The old message must disappear
- The button must disappear
- A new message must appear

So how can I test this? A simple way would be like this:

```elixir
test "must update the message when click the button, hide the old message and button", %{
    conn: conn
  } do
    {:ok, view, html} =
      conn
      |> get(Routes.example_index_path(conn, :index))
      |> live()

    assert html =~ "Hello Live View"
    assert html =~ "Click me"

    view
    |> element("#button-click-me")
    |> render_click()

    assert has_element?(view, "p", "New message")
    refute has_element?(view, "p", "Hello Live View")
    refute has_element?(view, "button", "Click me")
  end
```

The running result:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tpsp4vvodfbheaeo8g3e.png)

In this example we access the page the same way we did in the previous one and check the raw HTML to assert over the button and default text (*keep in mind this test is over the text on the page and not the element, so it will return true if finds this text in any place of the page, so pay attention when using it*).

After this first check, we simulate the button being clicked by targeting the element using the `element/3` https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html#element/3 function and after targeting it we trigger the `render_click/2` https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html#render_click/2, the same way a user would do, by pointing to the button and hitting click.

In the end, I want to use the updated version of the page and not the initial static one, I do not use `html`, instead I use `view`. If you wanna know more details about the returned tuple from the `live/2` call please check [the docs](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html#live/2)

Well, this is a very tiny example but is the core of how to test and manage testing with LiveView modules, it should be simple and act on top of real user actions.

If you see yourself doing much to test LiveView, like heavy business logic assertions, and socket assertions, maybe consider you possibly doing something wrong or adding too much logic inside the LiveView module.

Full example:

HTML
```html
<%= if @update_messages do %>
  <p>New message</p>
<% else %>
  <p>Hello Live View</p>
<% end %>

<%= unless @update_messages do %>
  <button phx-click="update-messages" id="button-click-me">Click me</button>
<% end %>
```

LiveView:

```elixir
defmodule UrlShortenerWeb.ExampleLive.Index do
  use UrlShortenerWeb, :live_view

  def mount(_params, _session, socket) do
    socket = assign(socket, :update_messages, false)

    {:ok, socket}
  end

  def handle_event("update-messages", _params, socket) do
    socket = assign(socket, :update_messages, true)

    {:noreply, socket}
  end
end

```

Testing:

```elixir
defmodule UrlShortenerWeb.ExampleLive.IndexTest do
  use UrlShortenerWeb.ConnCase, async: true
  import Phoenix.LiveViewTest

  test "must access page and see the initial message and a button", %{conn: conn} do
    {:ok, _view, html} =
      conn
      |> get(Routes.example_index_path(conn, :index))
      |> live()

    assert html =~ "Hello Live View"
    assert html =~ "Click me"
  end

  test "must update the message when click the button, hide the old message and button", %{
    conn: conn
  } do
    {:ok, view, html} =
      conn
      |> get(Routes.example_index_path(conn, :index))
      |> live()

    assert html =~ "Hello Live View"
    assert html =~ "Click me"

    view
    |> element("#button-click-me")
    |> render_click()

    assert has_element?(view, "p", "New message")
    refute has_element?(view, "p", "Hello Live View")
    refute has_element?(view, "button", "Click me")
  end
end

```