What EUnit Is and How Tests Are Discovered
EUnit is Erlang's built-in unit testing framework, shipped as part of standard OTP, so no external dependency is required to start writing tests — just add -include_lib("eunit/include/eunit.hrl"). A function whose name ends in _test/0 is automatically discovered and run as a plain test when you call eunit:test(Module) or rebar3 eunit. Test modules conventionally live in a project's test/ directory and are named after the module they exercise, such as calc_tests.erl testing calc.erl.
Cricket analogy: EUnit shipping as part of core OTP rather than a separate download is like the DRS review system being built into every ICC-sanctioned match rather than something a team has to bring themselves — it's simply there and ready to use.
Writing Assertions
EUnit provides a family of assertion macros: ?assert(Bool), ?assertEqual(Expected, Actual), ?assertMatch(Pattern, Expr), ?assertError(Reason, Expr), and ?assertException(Class, Reason, Expr) among others. Each macro expands to code that, on failure, raises an informative error term including the expected value, the actual value, the module, and the exact line number — far more useful for debugging than a bare if statement that only tells you something was wrong.
Cricket analogy: ?assertEqual reporting both the expected and actual value on failure is like a third umpire's review showing both the batter's claimed and the ball-tracking predicted trajectory side by side, rather than just saying 'out' with no explanation.
-module(calc_tests).
-include_lib("eunit/include/eunit.hrl").
%% Plain test: runs once, no setup needed
add_test() ->
?assertEqual(4, calc:add(2, 2)),
?assertEqual(-1, calc:add(2, -3)).
%% Test generator using a fixture to start/stop a process around each test
server_test_() ->
{setup,
fun() -> {ok, Pid} = calc_server:start_link(), Pid end,
fun(Pid) -> calc_server:stop(Pid) end,
fun(Pid) ->
[?_assertEqual(0, calc_server:value(Pid)),
?_assertEqual(5, begin calc_server:add(Pid, 5), calc_server:value(Pid) end)]
end}.
%% Checking that a bad argument raises the expected error
bad_input_test() ->
?assertError(badarith, calc:add(2, not_a_number)).Test Generators and Fixtures
A function whose name ends in _test_() (note the extra trailing underscore) is a test generator, expected to return a fun, tuple, or list describing tests rather than performing assertions directly. This lets you build fixtures like {setup, Setup, Cleanup, Tests} or {foreach, Setup, Cleanup, [Tests]}, where Setup runs once (or before each test for foreach) to prepare shared state — such as starting a gen_server — and Cleanup guarantees teardown afterward, even if a test fails. Inside a generator, use the underscore-prefixed macros like ?_assertEqual, which produce deferred test objects instead of asserting immediately.
Cricket analogy: A {setup, Setup, Cleanup, Tests} fixture is like a ground staff preparing the exact same pitch conditions before every net session and rolling it flat again afterward, so each batter practices against a consistent, freshly reset surface.
rebar3 eunit runs every *_test/0 and *_test_/0 function it finds under test/ (and inline in src/ if -ifdef(TEST) blocks are used); pass rebar3 eunit --module=calc_tests to run a single suite, and follow with rebar3 cover --verbose to see which lines your tests actually exercised.
Common Pitfalls
The single most common EUnit mistake is confusing the plain _test suffix with the generator _test_ suffix — using the wrong one means EUnit either treats a generator function as a plain test (and fails oddly on its return value) or fails to collect the tests a generator was supposed to produce. Another frequent issue is leaking real OTP processes started inside a test without cleaning them up in a fixture's Cleanup function, which causes flaky, order-dependent failures as stray processes from one test interfere with the next.
Cricket analogy: Forgetting the trailing underscore that turns a plain _test into a _test_ generator is like a scorer misreading 'not out' as 'out' from similar-looking handwriting — one tiny character changes how the whole entry is interpreted by everyone downstream.
- EUnit ships as part of standard OTP — no external dependency is needed to write and run unit tests.
- A function named foo_test/0 is a plain test; a function named foo_test_/0 is a test generator returning a fun, tuple, or list of tests.
- Assertion macros like ?assertEqual, ?assertMatch, and ?assertError report both expected and actual values on failure for easier debugging.
- {setup, Setup, Cleanup, Tests} and {foreach, Setup, Cleanup, [Tests]} fixtures let tests share consistent setup/teardown logic, such as starting and stopping a gen_server.
- rebar3 eunit is the standard command to run a project's EUnit suite; --module=Name scopes it to one test module.
- Test generators use ?_assert-family macros (leading underscore) instead of ?assert to defer execution until EUnit collects the generated tests.
- Leaked processes from tests that don't clean up in a fixture's Cleanup function are a common source of flaky, order-dependent test failures.
Practice what you learned
1. What naming convention distinguishes a plain EUnit test from a test generator?
2. Which include is required to use EUnit's assertion macros in a module?
3. What is the main advantage of ?assertEqual(Expected, Actual) over a plain if/case check?
4. What does a {setup, Setup, Cleanup, Tests} tuple provide in an EUnit test generator?
5. What command runs only the tests in a single EUnit module named calc_tests via rebar3?
Was this page helpful?
You May Also Like
Erlang and Rebar3
Learn how Rebar3 standardizes building, testing, and dependency management for Erlang projects.
Hot Code Reloading
Understand how the BEAM VM loads new module versions into a running system without downtime, and how to do it safely.
Erlang and Mnesia
Understand Mnesia, Erlang's built-in distributed database, its table types, transactions, and how it fits into OTP applications.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics