Goal: have a personal blog, and try out another point in the 'modular app design with elixir' space. Designing OTP systems with elixir had some interesting ideas.
2.1 KiB
%{ title: "How We Test LiveView at Scale", author: "Carlos Rivera", tags: ~w(elixir liveview testing), description: "Our testing strategy for 200+ LiveView modules" }
With over 200 LiveView modules in our codebase, we needed a testing strategy that was both fast and reliable. Here's what we landed on.
The three-layer approach
We test LiveViews at three levels:
- Unit tests for the assign logic — pure functions, no rendering
- Component tests for individual function components using
render_component/2 - Integration tests for full page flows using
live/2
The key insight is that most bugs live in the assign logic, not in the templates. By extracting assigns into pure functions, we can test the interesting bits without mounting a LiveView at all.
defmodule MyAppWeb.DashboardLive do
use MyAppWeb, :live_view
# Pure function — easy to test
def compute_metrics(raw_data, date_range) do
raw_data
|> Enum.filter(&in_range?(&1, date_range))
|> Enum.group_by(& &1.category)
|> Enum.map(fn {cat, items} ->
%{category: cat, count: length(items), total: Enum.sum_by(items, & &1.value)}
end)
end
end
# In the test file
test "compute_metrics groups and sums correctly" do
data = [
%{category: "sales", value: 100, date: ~D[2026-03-01]},
%{category: "sales", value: 200, date: ~D[2026-03-02]},
%{category: "support", value: 50, date: ~D[2026-03-01]}
]
result = DashboardLive.compute_metrics(data, {~D[2026-03-01], ~D[2026-03-31]})
assert [
%{category: "sales", count: 2, total: 300},
%{category: "support", count: 1, total: 50}
] = Enum.sort_by(result, & &1.category)
end
Speed matters
Our full test suite runs in under 90 seconds on CI. The secret is
async: true everywhere and avoiding database writes in unit tests.
We use Mox for external service boundaries and Ecto.Adapters.SQL.Sandbox
only for integration tests.
What we'd do differently
If starting over, we'd adopt property-based testing with StreamData earlier.
Several production bugs would have been caught by generating edge-case assigns
rather than hand-writing examples.