Help us improve
Share bugs, ideas, or general feedback.
Testing Elixir/Phoenix backends with ExUnit: DataCase/ConnCase setup, the Ecto SQL sandbox, fixtures, JSON API tests, changeset/context tests, and assertive test style.
npx claudepluginhub ariesclark/skills --plugin elixir-phoenixHow this skill is triggered — by the user, by Claude, or both
Slash command
/elixir-phoenix:elixir-testingWhen to use
Use when writing or reviewing ExUnit tests, fixtures, or test setup: `DataCase`/`ConnCase`, the Ecto SQL sandbox, assertions, and JSON API tests.
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Pairs with `elixir-conventions`. Tests should be assertive: state the exact expected shape so a failure points at the problem.
Runs automated patient safety test suites for healthcare deployments, blocking on CRITICAL failures in CDSS accuracy, PHI exposure, and data integrity.
Share bugs, ideas, or general feedback.
Pairs with elixir-conventions. Tests should be assertive: state the exact expected shape so a failure points at the problem.
DataCase for context/DB tests, ConnCase for controller/API tests. They wire the Ecto SQL sandbox and helpers.async: true only when the test owns its data via the sandbox and touches no shared global state (named processes, ETS, external services). Otherwise async: false.Enum.all?. A for-comprehension of asserts (or per-element asserts) tells you which element failed; assert Enum.all?(...) just says false.assert {:ok, %User{email: "a@b.c"}} = create_user(attrs) beats assert create_user(attrs).errors_on/1: assert specific field errors, not just refute changeset.valid?.assert_receive, Oban.Testing, or sandbox-aware sync points.# Don't: failure is a useless `false`
assert Enum.all?(users, & &1.active)
# Do: failure names the offending element
for user <- users, do: assert user.active
defmodule MyApp.AccountsTest do
use MyApp.DataCase, async: true
test "create_user/1 requires an email" do
assert {:error, changeset} = Accounts.create_user(%{})
assert %{email: ["can't be blank"]} = errors_on(changeset)
end
test "create_user/1 inserts a user" do
assert {:ok, %User{email: "a@b.c"}} = Accounts.create_user(%{email: "a@b.c", password: "secret123"})
end
end
defmodule MyAppWeb.PostControllerTest do
use MyAppWeb.ConnCase, async: true
setup :register_and_authenticate # assigns a token-authed conn
test "GET /api/posts/:id returns the post", %{conn: conn} do
post = post_fixture()
conn = get(conn, ~p"/api/posts/#{post.id}")
assert %{"data" => %{"id" => id}} = json_response(conn, 200)
assert id == post.id
end
test "GET /api/posts/:id is 404 for a stranger's post", %{conn: conn} do
other = post_fixture(user: user_fixture())
assert json_response(get(conn, ~p"/api/posts/#{other.id}"), 404)
end
end
def user_fixture(attrs \\ %{}) do
{:ok, user} =
attrs
|> Enum.into(%{email: "user#{System.unique_integer([:positive])}@test.dev", password: "secret123"})
|> Accounts.create_user()
user
end
DataCase/ConnCase check out a sandboxed connection per test and roll it back after, so tests don't see each other's writes.Ecto.Adapters.SQL.Sandbox.allow/3) or use shared mode with async: false.Run mix test, mix format --check-formatted, and mix compile --warnings-as-errors. These belong in CI; run them locally before pushing.