100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Testing with ExUnit

A practical guide to writing, organizing, and running tests in Elixir using ExUnit's assertions, fixtures, tags, and doctests.

Practical ElixirBeginner9 min readJul 10, 2026
Analogies

Getting Started with ExUnit

ExUnit is Elixir's built-in test framework, included with every Elixir installation — no external dependency required to get started. A test file typically ends in _test.exs, starts with ExUnit.start() (handled automatically by mix test in a Phoenix/Mix project's test_helper.exs), and defines a module using ExUnit.Case, containing one or more test blocks.

🏏

Cricket analogy: ExUnit is like the nets session before a big match — a controlled environment where you throw deliveries (test cases) at your technique before it's tested for real.

Writing Assertions

Inside a test block, assert/1 checks that an expression is truthy, while refute/1 checks that it's falsy; both produce detailed, human-readable failure messages showing the actual vs. expected values when they fail — for instance assert result == 4 will print both sides if result was actually 5. assert_raise/2 checks that a specific exception is raised, and assert_receive/2 checks that a message arrives in a process's mailbox within a timeout.

🏏

Cricket analogy: Writing assert result == 4 is like appealing for LBW — you're asserting a specific claim and the umpire (test runner) either upholds it or rules not out.

elixir
defmodule MyApp.MathTest do
  use ExUnit.Case, async: true

  describe "add/2" do
    test "adds two positive integers" do
      assert MyApp.Math.add(2, 2) == 4
    end

    test "raises on non-numeric input" do
      assert_raise ArithmeticError, fn ->
        MyApp.Math.add(2, "two")
      end
    end
  end
end

Setup, Fixtures, and Tags

A setup block runs before every test in the module, typically returning a map of context values (like a freshly inserted database record) that ExUnit merges into each test's parameters. setup_all runs once before all tests in the module instead of before each one, useful for expensive, read-only fixtures. Tags, applied with @tag, let you mark tests (e.g., @tag :slow or @tag :external) and then include or exclude them at run time with mix test --exclude slow.

🏏

Cricket analogy: A setup block is like preparing the pitch report before play — rolling the surface, checking moisture — so every test that follows starts from the same known conditions.

Doctests and Mocking

A doctest is a code example embedded in a module's @doc string, written in iex> prompt format, that ExUnit can execute and verify automatically via doctest MyApp.Math in a test module — this guarantees your documentation examples never silently go stale. For isolating external dependencies, the Elixir community commonly favors explicit behaviours with Mox, defining a contract via @behaviour and swapping in a mock implementation configured per test environment, rather than dynamically patching modules at runtime.

🏏

Cricket analogy: A doctest is like a player demonstrating a shot in a coaching manual — the example itself is verified live, so the manual can't drift from what actually works.

Use async: true on ExUnit.Case for tests that don't share mutable global state (like a database) — Phoenix's Ecto.Adapters.SQL.Sandbox makes this safe even for database tests by giving each async test its own transaction that's rolled back at the end, letting your suite run many times faster on multi-core machines.

  • ExUnit ships with Elixir; test files end in _test.exs and start with use ExUnit.Case.
  • assert/1 and refute/1 are the core assertion macros, producing detailed diff-style failure output.
  • assert_raise/2 verifies a specific exception is raised; assert_receive/2 checks process mailbox messages.
  • setup and setup_all provide shared context and fixtures to tests, running before each or before all tests respectively.
  • @tag lets you categorize tests (e.g., :slow) and selectively run or exclude them via mix test --exclude/--only.
  • doctest/1 executes iex> examples in @doc strings, keeping documentation verified against real behavior.
  • Mox provides explicit, behaviour-based mocking, favored over dynamic runtime patching.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#ElixirStudyNotes#TestingWithExUnit#Testing#ExUnit#Writing#Assertions#StudyNotes#SkillVeris#ExamPrep