Testing Elixir/Phoenix backends with ExUnit: DataCase/ConnCase setup, the Ecto SQL sandbox, fixtures, JSON API tests, changeset/context tests, and assertive test style.
How 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.
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: "[email protected]"}} = 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: "[email protected]"}} = Accounts.create_user(%{email: "[email protected]", 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.
Blocks Edit/Write/Bash actions until Claude investigates importers, data schemas, and user instructions. Improves output quality by forcing concrete facts before edits.
npx claudepluginhub ariesclark/skills --plugin elixir-phoenix