Curated library of 38 atomic skills, 7 personas, and 1 orchestrator for Elixir and Phoenix development. Organized by category: fundamentals, phoenix, database, testing, auth, infrastructure, quality, security, integrations, tooling, frameworks, personas, and orchestration. Covers core Elixir patterns, Phoenix LiveView, Ecto, OTP, Oban, testing, security, deployment, real-time, and modern tooling (Req, Swoosh, Cachex, Broadway, Ash).
73
91%
Does it follow best practices?
Impact
—
No eval scenarios have been run
Advisory
Suggest reviewing before use
async: true only when safe — avoid for DB contexts with shared rows, LiveView, Application.put_env, and external servicestest/support/) — never build it inline across multiple testshas_element?/2 and element/2 for LiveView assertions — not html =~ "text" for structure checksFollow these steps in order, with explicit validation at each checkpoint:
test/support/fixtures/ for relevant fixtures before creating new onesmix test to confirm the fixture compiles before writing any testsmix test path/to/file_test.exs and confirm it fails with a meaningful message (not a compile error)mix test path/to/file_test.exs and confirm greendefmodule MyApp.AccountsTest do
use MyApp.DataCase, async: true
alias MyApp.Accounts
import MyApp.AccountsFixtures
enddefmodule MyAppWeb.UserLiveTest do
use MyAppWeb.ConnCase, async: true
import Phoenix.LiveViewTest
import MyApp.AccountsFixtures
endDefine all test data in test/support/fixtures/:
defmodule MyApp.AccountsFixtures do
def user_fixture(attrs \\ %{}) do
{:ok, user} =
attrs
|> Enum.into(%{
email: "user#{System.unique_integer([:positive])}@example.com",
password: "hello world!"
})
|> MyApp.Accounts.register_user()
user
end
enddescribe "create_post/1" do
test "with valid attrs creates a post" do
assert {:ok, %Post{} = post} = Blog.create_post(%{title: "Hello"})
assert post.title == "Hello"
end
test "with invalid attrs returns error changeset" do
assert {:error, %Ecto.Changeset{} = changeset} = Blog.create_post(%{})
assert %{title: ["can't be blank"]} = errors_on(changeset)
end
enddescribe "index" do
test "lists posts", %{conn: conn} do
post = post_fixture()
{:ok, _lv, html} = live(conn, ~p"/posts")
assert html =~ post.title
end
test "unauthorized user is redirected", %{conn: conn} do
{:error, {:redirect, %{to: path}}} = live(conn, ~p"/admin/posts")
assert path == ~p"/login"
end
end
describe "create" do
test "saves post with valid attrs", %{conn: conn} do
{:ok, lv, _html} = live(conn, ~p"/posts/new")
lv
|> form("#post-form", post: %{title: "New Post"})
|> render_submit()
assert has_element?(lv, "p", "Post created")
end
test "shows errors with invalid attrs", %{conn: conn} do
{:ok, lv, _html} = live(conn, ~p"/posts/new")
lv
|> form("#post-form", post: %{title: ""})
|> render_submit()
assert has_element?(lv, "p.alert", "can't be blank")
end
enddescribe "changeset/2" do
test "valid attrs" do
assert %Ecto.Changeset{valid?: true} = Post.changeset(%Post{}, %{title: "Hello"})
end
test "requires title" do
changeset = Post.changeset(%Post{}, %{})
assert %{title: ["can't be blank"]} = errors_on(changeset)
end
endUse setup [:func1, :func2] to compose reusable setup functions; later functions receive assigns from earlier ones.
defmodule MyAppWeb.PostLiveTest do
use MyAppWeb.ConnCase, async: true
import MyApp.AccountsFixtures
import MyApp.BlogFixtures
setup [:register_and_log_in_user, :create_post]
test "owner can edit post", %{conn: conn, post: post} do
{:ok, lv, _html} = live(conn, ~p"/posts/#{post}/edit")
assert has_element?(lv, "#post-form")
end
defp create_post(%{user: user}) do
%{post: post_fixture(user_id: user.id)}
end
end❌ Bad — hardcoded date will eventually be in the past:
assert post.published_at == ~U[2026-01-15 12:00:00Z]✅ Good — relative to now:
now = DateTime.utc_now(:second)
assert DateTime.diff(post.inserted_at, now, :second) < 5✅ Good — build relative dates for filtering/sorting:
past = DateTime.add(DateTime.utc_now(:second), -7, :day)
future = DateTime.add(DateTime.utc_now(:second), 7, :day)
old_post = post_fixture(published_at: past)
new_post = post_fixture(published_at: future)
assert Blog.list_published_posts() == [old_post]ownership timeout or DBConnection.OwnershipError): flip the test to async: false.cannot find ownership process): LiveView tests must use async: false.Application.put_env leaking between tests: restore in an on_exit callback and use async: false.DateTime.diff/3 comparisons (see Timestamp Testing above).register_and_log_in_user setup.See agents/testing-guide.md for comprehensive examples covering async tests, Mox mocking, file upload testing, and Ecto query testing.
| Predecessor | This Skill | Successor |
|---|---|---|
| elixir-essentials | testing-essentials | None (standalone) |
property-based-testing insteadbenchee-profiling insteadproperty-based-testing skill's Mox patterns insteadphoenix/liveview-streams skill instead