Começando com testes unitários nos módulos LiveView
Tags: elixir, liveview, phoenix, testing
Published: 2023-10-26 16:46:34

---
title: Começando com testes unitários nos módulos LiveView
published: true
description: Aprenda como começar com testes unitários nos módulos LiveView
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
---

Nos últimos 18 meses, tenho trabalhado integralmente com Elixir & Phoenix, e mais recentemente, tenho tido a chance de trabalhar também com LiveView para algumas interfaces que minha equipe tem desenvolvido.

No início, não estava muito seguro sobre o que estava fazendo ou se meu código estava no lugar apropriado dentro dos módulos LiveView. "Será que deveria manter essa lógica aqui?", "Devo criar funções utilitárias dentro do mesmo módulo LiveView ou devo criar um módulo separado?".

Depois de alguns meses, tenho usado uma regra: tento lidar com os módulos LiveView da mesma forma como lido com os Controllers.

Isso significa que tento manter os módulos o mais limpos possível, apenas com os callbacks necessários, como mount, handle_event, e os assigns de dados para o socket, além de poucas funções privadas.

Prefiro evitar adicionar lógica de negócio do produto dentro desses módulos, pois essa lógica geralmente precisa de mais testes, e testar todos os cenários possíveis diretamente com LiveView pode ser desnecessariamente trabalhoso e não considerar todos os cenários.

Adotando essa abordagem, conseguimos simplificar e tornar mais diretos tanto o código quanto os testes nos módulos LiveView.

Idealmente, ao examinar os testes LiveView, eles devem ser interpretados como se estivessem descrevendo o fluxo que o usuário seguirá ao acessar e interagir com a interface.

Vamos começar com um exemplo simples:

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

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

Nesse exemplo de módulo, não há nada de particularmente complexo. Apenas a função `mount` é utilizada, e na estrutura inicial do projeto, já existem os arquivos `example_live/index.ex` e `example_live/index.html.heex`. Dessa forma, não é necessário implementar a função `render`.

A página HTML contém apenas um texto simples.

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

O retorno será esse quando rodar o projeto

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

Com base nesse código, um jeito simples de testar a funcionalidade seria:

```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
```

Neste exemplo, não há necessidade de testar a variável `view`, portanto, ela foi omitida. Neste caso, o objetivo não é testar a view conectada, que contém o socket e desencadeia alguns comportamentos na página, mas sim o HTML puro.

Rodando isso, esse é o resultado:

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

Agora irei adicionar um botão na página que ao ser clicado irá remover a mensagem anterior e adicionar uma nova mensagem.

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

O que eu quero testar agora é o seguinte cenário:

- Eu vejo a mensagem padrão
- Quero clicar no botão
- A mensagem antiga deve desaparecer
- O botão deve desaparecer
- Uma nova mensagem deve aparecer

Então, como posso testar isso? Uma maneira simples seria a seguinte:

```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
```

O resultado:


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

Neste exemplo, acessamos a página da mesma forma que fizemos no exemplo anterior e verificamos o HTML bruto para verificar a existência do botão e do texto padrão (*lembre-se de que este teste é baseado no texto da página e não no elemento; portanto, retornará verdadeiro se encontrar esse texto em qualquer lugar da página, portanto, preste atenção ao usá-lo*).

Após esta primeira verificação, simulamos o clique no botão direcionando o elemento usando a função `element/3` (https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html#element/3) e, após selecioná-lo, acionamos o `render_click/2` (https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html#render_click/2) da mesma forma que um usuário faria, apontando para o botão e clicando.

No final, desejo usar a versão atualizada da página e não a estática inicial. Não utilizo `html`, em vez disso, utilizo `view`. Se desejar obter mais detalhes sobre a tupla retornada pela chamada `live/2`, consulte [a documentação](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html#live/2).

Bem, este é um exemplo muito simples, mas é o cerne de como testar e gerenciar testes com módulos LiveView. Deve ser simples e basear-se nas ações reais do usuário.

Se você perceber que está fazendo muitas verificações para testar o LiveView, como verificações de lógica de negócios intensas e verificações de socket, talvez deva considerar a possibilidade de estar fazendo algo errado ou adicionando muita lógica dentro do módulo LiveView.

O exemplo completo:

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

```