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.6.6 or Phoenix.ConnTest — Phoenix v1.6.6, 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 (Phoenix.LiveView.Helpers — Phoenix LiveView v0.17.5):
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.