Skip to content

testing

Elixir testing patterns — ExUnit, Mox, factories, LiveView test helpers. Use when working on *_test.exs, test/support/, factory files, or fixing test failures. · effort: medium

example · testing
# Setup chain
setup [:create_user, :authenticate]
# Pattern matching assertion
assert {:ok, %User{name: name}} = create_user(attrs)
# Async message assertion
assert_receive {:user_created, _}, 5000
# Mox setup
setup :verify_on_exit!
expect(MockAPI, :call, fn _ -> {:ok, "data"} end)
# LiveView async
html = render_async(view) # MUST call for assign_async

Elixir Testing Reference

Quick reference for Elixir testing patterns.

Iron Laws — Never Violate These

  1. ASYNC BY DEFAULT — Use async: true unless tests modify global state
  2. SANDBOX ISOLATION — All database tests use Ecto.Adapters.SQL.Sandbox
  3. MOCK ONLY AT BOUNDARIES — Never mock database, internal modules, or stdlib
  4. BEHAVIOURS AS CONTRACTS — All mocks must implement a defined @callback behaviour
  5. BUILD BY DEFAULT — Use build/2 in factories; insert/2 only when DB needed
  6. NO PROCESS.SLEEP — Use assert_receive with timeout for async operations
  7. VERIFY_ON_EXIT! — Always call in Mox tests setup
  8. FACTORIES MATCH SCHEMA REQUIRED FIELDS — Factory definitions must include all fields that have validate_required in the schema changeset. Missing fields cause cascading test failures

Quick Decisions

Which Test Case?

TestingUse
Controller/APIuse MyAppWeb.ConnCase
Context/Schemause MyApp.DataCase
LiveViewuse MyAppWeb.ConnCase + import Phoenix.LiveViewTest
Pure logicuse ExUnit.Case, async: true

When to use async: true?

  • ✅ Pure functions, no shared state
  • ✅ Database tests with Sandbox (PostgreSQL)
  • ❌ Tests modifying Application.put_env
  • ❌ Tests using Mox global mode

Mock or not?

  • ✅ Mock: External APIs, email services, file storage
  • ❌ Don’t mock: Database, internal modules, stdlib

build() or insert()?

  • Use build() by default for speed
  • Use insert() only when you need DB ID, constraints, or persisted associations

Quick Patterns

# Setup chain
setup [:create_user, :authenticate]
# Pattern matching assertion
assert {:ok, %User{name: name}} = create_user(attrs)
# Async message assertion
assert_receive {:user_created, _}, 5000
# Mox setup
setup :verify_on_exit!
expect(MockAPI, :call, fn _ -> {:ok, "data"} end)
# LiveView async
html = render_async(view) # MUST call for assign_async

Common Anti-patterns

WrongRight
Process.sleep(100)assert_receive {:done, _}, 5000
insert(:user) in factorybuild(:user) in factory
async: true with set_mox_global()async: false
Mock internal modulesTest through public API

References

For detailed patterns, see:

  • ${CLAUDE_SKILL_DIR}/references/exunit-patterns.md - Setup, assertions, tags
  • ${CLAUDE_SKILL_DIR}/references/mox-patterns.md - Behaviours, expect/stub, async
  • ${CLAUDE_SKILL_DIR}/references/liveview-testing.md - Forms, async, uploads
  • ${CLAUDE_SKILL_DIR}/references/factory-patterns.md - ExMachina, sequences, traits