Use ExUnit’s test supervisor to give each test a fresh process, exercise GenServers through their public APIs, and test supervision by triggering a controlled failure and checking the configured restart behavior. The right assertion depends on what you need to prove: a reply, a message, a termination reason, a restarted PID, or the effect on sibling children.
The examples below are illustrative; adapt child IDs, APIs and failure triggers to your application. Elixir’s documentation index reported v1.20.4 as stable on 2026-10-04, while some linked API pages are versioned earlier. Use documentation matching the Elixir version pinned by your project.
How do I start a process in ExUnit and clean it up?
Prefer ExUnit’s test-supervised helpers to starting a linked process directly in each test. A child started with start_supervised/2 is owned by the test supervisor and is stopped before the next test begins, which gives process setup and cleanup a clear boundary. The helper does not link the child to the test process, so an unexpected child crash does not necessarily fail the test.
Use the raising variant when a failed startup should fail setup immediately:
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →#1 Best Overall
use ExUnit.Case, async: true
setup do
server = start_supervised!({MyApp.Counter, 0})
%{server: server}
end
test "increments the counter", %{server: server} do
assert MyApp.Counter.value(server) == 0
assert MyApp.Counter.increment(server) == 1
end
The module and arguments must fit the process’s child specification and start_link contract. ExUnit.Callbacks documentation describes the test-supervised helpers and cleanup behavior.
start_supervised!/2raises on startup failure and returns the PID.start_supervised/2returns a startup result such as{:ok, pid}or{:error, reason}, useful when the result itself is under test.start_link_supervised!/2links the child to the test process; use it when a child crash should propagate and fail the test.stop_supervised/1removes a child before the test ends. Simply terminating a restartable child may cause its supervisor to start it again.
Choose the helper according to the failure contract: an unlinked child is convenient for isolated ownership, while a link makes unexpected termination consequential to the test.
How do I test a GenServer in Elixir?
Test ordinary GenServer behavior through the same public API that callers use. Assert replies or observable state transitions rather than private callback details; callbacks are implementation details unless the callback behavior itself is part of the contract. The GenServer guide demonstrates client-server testing and test-supervised startup.
- For synchronous operations, assert the returned value.
- For asynchronous output that callers are meant to receive, use
assert_receivewith a bounded timeout. - For termination behavior, monitor the process and assert the resulting
:DOWNmessage and reason. - Avoid arbitrary sleeps as synchronization. A reply, expected message, or monitor signal gives the test an observable event to wait for.
Monitoring and linking prove different things. A monitor lets the test assert that a process ended and why; a link makes an unexpected exit reach the test process. Use the mechanism that matches the behavior being specified.
Rank #3
How do I test that a supervisor restarts a process?
Start the supervisor or relevant subtree under the test supervisor, identify the child by its child ID, then cause a controlled failure and assert the result. A child specification determines its start, shutdown and restart behavior, while the supervision strategy determines which other children are affected. See the versioned Supervisor documentation.
Account for restart mode and exit reason
:permanentchildren restart regardless of exit reason.:transientchildren restart after abnormal exits, but not normal termination.:temporarychildren do not restart.
Therefore, an intentional normal stop does not test a transient child’s abnormal-exit restart path. Trigger the relevant kind of exit deliberately and assert the behavior promised by the child specification.
Account for the supervision strategy
:one_for_one: the failed child is restarted; unaffected siblings remain.:one_for_all: failure causes all children in the group to be restarted.:rest_for_one: the failed child and children started after it are restarted.
Check PIDs before and after the failure to verify which children were replaced. For the restarted child, a new PID and a known initialized result are useful observable assertions. When child modules can appear more than once, capture children using their IDs or unique test names rather than assuming the module identifies one child.
Use an observable restart signal
A restart test should not guess completion with a sleep. Use an explicit test-visible signal, a suitable supervisor or monitoring event, or another synchronization point exposed by the application. One possible pattern is:
Free tools Windows power users keep installed
One-click scans. No signup required.
Best Value
test "restarts a permanent worker after an abnormal exit" do
supervisor = start_supervised!({MyApp.WorkerSupervisor, []})
old_pid = MyApp.WorkerSupervisor.worker_pid(supervisor)
send(old_pid, :crash_for_test)
assert_receive {:worker_restarted, new_pid}
refute old_pid == new_pid
assert MyApp.Worker.get_state(new_pid) == :initial_state
end
This sketch assumes the application provides the worker lookup, controlled trigger and restart notification shown. Those are not universal OTP APIs; adapt the test to the real contract rather than adding a brittle production-only hook without need.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.How do I test a DynamicSupervisor?
Start a fresh DynamicSupervisor for the test, then add and remove children through its API. Assert that a child is present after a successful start and absent after a stop or termination consistent with its restart mode. The outer ExUnit test supervisor supplies lifecycle cleanup for the test’s processes. The DynamicSupervisor guide covers the API; check it against the project’s pinned Elixir version.
Which assertion method should I use?
| Test need | Mechanism | What it establishes |
|---|---|---|
| Validate ordinary server behavior | Call the public API and assert its reply or observable state | The caller-facing process contract works |
| Observe asynchronous output | assert_receive with a bounded timeout |
The expected message was emitted |
| Verify a process ended | Monitor and assert the :DOWN reason |
The process terminated with the expected reason |
| Make child crashes fail the test | start_link_supervised!/2 |
A linked child’s exit reaches the test process |
| Ensure test-owned child cleanup | start_supervised!/2 |
The test supervisor stops the child at test completion |
| Verify restart policy | Controlled exit followed by PID and behavior assertions | The child restart contract was applied |
| Verify sibling effects | Capture sibling PIDs before and after failure | The configured strategy’s effect on siblings |
When should these tests run asynchronously?
async: true is appropriate only when concurrent tests cannot interfere through shared resources. A separate process per test does not isolate registered names, shared mutable state, files, ports, external services or other global resources. Use unique names and per-test resources, or disable async execution for tests that share state. ExUnit’s helper documentation describes test lifecycle behavior; the GenServer guide discusses testing process behavior.
ExUnit capabilities can vary by release, including grouping and parameterized runs documented in newer versions. Confirm feature availability against the Elixir version your project pins. The Elixir documentation index reported v1.20.4 as stable on 2026-10-04 and listed Erlang/OTP 27, 28 and 29 as supported at that time.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




