garrettmichaelgeorge

garrettmichaelgeorge

Best Practices for Testing Phoenix (Functional) Components

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.

Most Liked

garrettmichaelgeorge

garrettmichaelgeorge

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?

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

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?

Last Post!

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.

Where Next?

Popular in Questions Top

vegabook
I’m brand new to Phoenix and I have stripped one of the demo applications to the bone. I just want to get an svg up on the screen. Here i...
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New

Other popular topics Top

grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 54006 488
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
chrismccord
Phoenix 1.4.0 released Phoenix 1.4 is out! This release ships with exciting new features, most notably with HTTP2 support, improved deve...
688 31494 112
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New

We're in Beta

About us Mission Statement