DEV Community

Luiz Cezer
Luiz Cezer

Posted on

Começando com testes unitários nos módulos LiveView

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:

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

  def mount(_params, _session, socket) do
    {:ok, socket}
  end
end
Enter fullscreen mode Exit fullscreen mode

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.

<p>Hello Live View</p>
Enter fullscreen mode Exit fullscreen mode

O retorno será esse quando rodar o projeto

Page rendered

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

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
Enter fullscreen mode Exit fullscreen mode

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

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

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:

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
Enter fullscreen mode Exit fullscreen mode

O resultado:

Image description

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.

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

<%= 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 %>
Enter fullscreen mode Exit fullscreen mode

LiveView:

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

Enter fullscreen mode Exit fullscreen mode

Testing:

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

Enter fullscreen mode Exit fullscreen mode

Top comments (2)

Collapse
 
luisfelipeqt profile image
Luis Felipe Rodrigues

me pega como aluno sensei e me ensina liveview, to apanhando demais kkkk

Collapse
 
lcezermf profile image
Luiz Cezer

Opa tudo certo? Alguma dúvida em especial? Se tiver alguma sugestão de post posso elaborar um se te ajudar. LiveView permite criar muita coisa do básico ao mais avançado (nisso eu ainda preciso aprender também).