garrettmichaelgeorge

garrettmichaelgeorge

Are there any recommended patterns or best practices for testing Phoenix Components? In particular, how thorough should a test be, and what specifically should be verified?

Since Phoenix components are a newer feature, one might consider looking to existing Phoenix approaches to testing rendered HTML. In that case, the standard way seems to follow the form assert my_html =~ a_string (see, for example, the code snippets at Testing Controllers — Phoenix v1.8.8 or Phoenix.ConnTest — Phoenix v1.8.8, or of course the tests generated by mix phx.gen.html).

This approach may be sufficient when the test subject is a controller, but in the case of view component that will be reused across many contexts, I wonder if it is thorough enough.

Example component

Consider, for example, the table function component outlined in the docs (https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.Helpers.html#render_slot/2):

def table(assigns) do
  ~H"""
  <table>
    <th>
      <%= for col <- @col do %>
        <td><%= col.label %></td>
      <% end >
    </th>
    <%= for row <- @rows do %>
      <tr>
        <%= for col <- @col do %>
          <td><%= render_slot(col, row) %></td>
        <% end %>
      </tr>
    <% end %>
  </table>
  """
end

To test it, would I want to:

  • assert that certain strings appear in the output, a la assert rendered_to_string(my_table_heex) =~ my_col.label ?
  • verify the HTML structure itself using something like Floki?
  • …or something else altogether?

Example test

I might test such a table component like so:

defmodule TableComponentTest do
  import Phoenix.LiveView.Helpers
  import Phoenix.LiveViewTest

  # Example test
  test "table" do
    # the ~H sigil expects an `assigns` variable to be defined
    assigns = []
    item = %{name: "an item", category: "a category"}
    col1 = %{label: "a column"}
    col2 = %{label: "another column"}

    table_str =
      rendered_to_string(~H"""
      <TableComponent.table rows={[item]}>
        <:col let={item} label={col1.label}>
          <%= item.name %>
        </:col>

        <:col let={item} label={col2.label}>
          <%= item.category %>
        </:col>
      </TableComponent.table>
      """)

    # EXAMPLE ASSERTIONS
    # Option 1
    assert table_str
           |> Floki.parse_fragment!()
           |> Floki.find("th td")
           |> Floki.raw_html() == "<td>#{col1.label}</td><td>#{col2.label}</td>"
    # etc.

    # Option 2
    for fragment <- [col1.label, col2.label, item.name, item.category] do
      assert table_str =~ fragment
    end
  end
end

Option 1 feels thorough, but I don’t typically like checking return value internals if I can help it. On the other hand, Option 2 feels too lax and doesn’t give me enough confidence that I can use the component in practice. I feel like I need a middle way.

Outside of the Elixir ecosystem, I’ve found the ViewComponent (Rails) testing guide helpful.

Do feel free to share any thoughts, ideas, and experience! If there is an officially recommended approach, I would welcome it; if not, perhaps discussions like this could help lead to one.

Showing Posts 1 to 8

al2o3cr

al2o3cr

In both cases, matching HTML output against the inputs with == can cause flaky tests if you’re using randomized data via faker or similar: an assertion like Floki.raw_html() == "<td>#{col1.label}</td><td>#{col2.label}</td>" will fail if col1.label or col2.label contain characters that are escaped in HTML. For instance, sometimes generating a last name with Faker will produce O'Brien which escapes to O&apos;Brien :frowning:

garrettmichaelgeorge

garrettmichaelgeorge OP

Ah good point – I am randomizing test data and would likely run into this at some point.

So how should the expected value in an assertion be formatted? As an HTML-escaped string? Or something else?

garrettmichaelgeorge

garrettmichaelgeorge OP

Would Wallaby be a good fit for unit testing Phoenix Components?

garrettmichaelgeorge

garrettmichaelgeorge OP

A different point of view, drawing on this talk by Ian Cooper.

What if function components are the wrong level altogether to test? Maybe the correct granularity is where the component is used in context, and it is not necessary to test the function component in isolation.

With the table component, that would mean instead of testing the table component directly, one would test the live view or controller that uses the component. If the live view or controller renders the expected elements, then the behavior has been verified. Whether a component is used or not is an implementation detail. The developer should be free to extract or inline components as desired without worrying about breaking tests.

josefrichter

josefrichter

With LiveViewTest you can very precisely target html elements using advanced selectors and check their content. You don’t need Floki nor Wallaby I guess. You probably need those only for static pages.

See element selector here: Phoenix.LiveViewTest — Phoenix LiveView v1.2.5

garrettmichaelgeorge

garrettmichaelgeorge OP

Thank you for the suggestion. Looking at the docs and source for Phoenix.LiveViewTest.element/3, it seems like the function requires a %Phoenix.LiveViewTest.View{} to be passed. The same appears true for most helper functions in LiveViewTest.

Given that, is it possible to use these helper functions when testing a functional component in isolation? That is, is it possible to create and pass a %View{} when not testing an actual Live View? If so, then the toolkit provided by LiveViewTest should be sufficient for navigating and asserting on functional components.

The section on testing function components recommends two functions: LiveViewTest.render_component/3 and LiveViewTest.rendered_to_string/1. The examples provided don’t appear to do more than simply asserting string equality.

With Conn Tests (for “dead views,” as it were), we can generate a %Conn{} struct for testing using ConnTest.build_conn/0. Is the same possible for a %View{} in a function component test?

Sebb

Sebb

I was about to ask exactly this question.
How do I leverage the more powerful test-helpers like has_element? with a function component?

jbosse

jbosse

For both function components and live view html I have been pretty happy with AssertHTML: Elixir Library for testing HTML and XML using CSS selectors — AssertHTML v0.2.3

My only complaint is that the selector has to match then you can assert attributes on the matching node. I would prefer it to be like the Rails assert_select where the assertion used the selector and attributes and simply asserted that one or more node matched. I dislike having to use :nth-child when I don’t necessarily care about the index of the element.

— All posts loaded —

Where Next? Top

Trending in Questions Top

stjefim
Hello! Suppose you are building workflow (order / task / payment) processing system with the following requirements: Each workflow con...
New
Blokh
Hey guys, I’ve got a huge CSV ( around 10 GB ) that needs to be processed hourly Do you guys have any suggestions what is the best prac...
New
roeland
Kia ora, We have been using elixir-google-api to connect to Google Drive. However, with the updates to Tesla due to CVEs this is now bro...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
Onor.io
I have what I’ve heard referred to as a “lookup table” in my database. This is a way of assigning codes to common values. One common lo...
New
jaybe78
Hello, I’m developing a online persistent chat system (what’s app) like using elixir/dynamodb/aws for a mobile app(flutter). The diffic...
New
Trolleger
What approach to take when sending live updates to “random” users Hi! I have a question, I have a little chat app, and when I create a DM...
New

Other Trending Topics Top

garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
wintermeyer
There are three potential reasons for members of this forum to have a look at https://vutuv.de You are tired or annoyed of LinkedIn. Yo...
New
aseigo
ICal is a library for interacting with iCalendar data. It parses iCalendars into typed Elixir structs via ICal.from_ics, and can prepare ...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews