69 lines
2.2 KiB
Markdown
69 lines
2.2 KiB
Markdown
%{
|
|
title: "How We Test LiveView at Scale",
|
|
author: "Carlos Rivera",
|
|
tags: ~w(elixir liveview testing),
|
|
description: "Our testing strategy for 200+ LiveView modules"
|
|
}
|
|
---
|
|
*This is a sample blog post, generated to show what blogex can do.*
|
|
|
|
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:
|
|
|
|
1. **Unit tests** for the assign logic — pure functions, no rendering
|
|
2. **Component tests** for individual function components using `render_component/2`
|
|
3. **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.
|
|
|
|
```elixir
|
|
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.
|