View Source ExUnit.Callbacks (ExUnit v1.19.0-dev)
Defines ExUnit callbacks.
This module defines the setup/1
, setup/2
, setup_all/1
, and
setup_all/2
macros, as well as process lifecycle and management
functions, such as on_exit/2
, start_supervised/2
, stop_supervised/1
and start_link_supervised!/2
.
The setup callbacks may be used to define test fixtures
and run any initialization code which help bring the system into a known
state. They are defined via macros and each one can optionally receive a map
with test state and metadata, usually referred to as the context
.
Optionally, the context to be used in the tests can be extended by the
setup callbacks by returning a properly structured value (see below).
The setup_all
callbacks are invoked only once per module, before any
test is run. All setup
callbacks are run before each test. No callback
is run if the test case has no tests or all tests have been filtered out.
setup
and setup_all
callbacks can be defined by either a block, an atom
naming a local function, a {module, function}
tuple, or a list of atoms/tuples.
Both can opt to receive the current context by specifying it as parameter if defined by a block. Functions used to define a test setup must accept the context as single argument.
A test module can define multiple setup
and setup_all
callbacks,
and they are invoked in order of appearance.
start_supervised/2
is used to start processes under a supervisor. The
supervisor is linked to the current test process. The supervisor as well
as all child processes are guaranteed to terminate before any on_exit/2
callback runs.
on_exit/2
callbacks are registered on demand, usually to undo an action
performed by a setup callback. on_exit/2
may also take a reference,
allowing the callback to be overridden in the future. A registered on_exit/2
callback will always run, while failures in setup
and setup_all
will stop
all remaining setup callbacks from executing.
Finally, setup_all
callbacks run in a separate process per module, while
all setup
callbacks run in the same process as the test itself. on_exit/2
callbacks always run in a separate process, as implied by their name. The
test process always exits with reason :shutdown
, which means any process
linked to the test process will also exit, although asynchronously. Therefore
it is preferred to use start_supervised/2
to guarantee synchronous termination.
Here is a rundown of the life cycle of the test process:
- the test process is spawned
- it runs
setup/2
callbacks - it runs the test itself
- it stops all supervised processes
- the test process exits with reason
:shutdown
on_exit/2
callbacks are executed in a separate process
Context
setup_all
or setup
may return one of:
- the atom
:ok
- a keyword list or map
a tuple in the shape of
{:ok, keyword() | map()}
If a keyword list or map is returned, it be merged into the current context
and will be available in all subsequent setup_all
, setup
, and the test
itself.
Returning anything else from setup_all
will force all tests to fail,
while a bad response from setup
causes the current test to fail.
Examples
defmodule AssertionTest do
use ExUnit.Case, async: true
# "setup_all" is called once per module before any test runs
setup_all do
IO.puts("Starting AssertionTest")
# Context is not updated here
:ok
end
# "setup" is called before each test
setup do
IO.puts("This is a setup callback for #{inspect(self())}")
on_exit(fn ->
IO.puts("This is invoked once the test is done. Process: #{inspect(self())}")
end)
# Returns extra metadata to be merged into context.
# Any of the following would also work:
#
# {:ok, %{hello: "world"}}
# {:ok, [hello: "world"]}
# %{hello: "world"}
#
[hello: "world"]
end
# Same as above, but receives the context as argument
setup context do
IO.puts("Setting up: #{context.test}")
# We can simply return :ok when we don't want to add any extra metadata
:ok
end
# Setups can also invoke a local or imported function that returns a context
setup :invoke_local_or_imported_function
test "always pass" do
assert true
end
test "uses metadata from setup", context do
assert context[:hello] == "world"
assert context[:from_named_setup] == true
end
defp invoke_local_or_imported_function(context) do
[from_named_setup: true]
end
end
It is also common to define your setup as a series of functions,
which are put together by calling setup
or setup_all
with a
list of function names. Each of these functions receive the context and can
return any of the values allowed in setup
blocks:
defmodule ExampleContextTest do
use ExUnit.Case
setup [:step1, :step2, :step3, {OtherModule, :step4}]
defp step1(_context), do: [step_one: true]
defp step2(_context), do: {:ok, step_two: true} # return values with shape of {:ok, keyword() | map()} allowed
defp step3(_context), do: :ok # Context not modified
test "context was modified", context do
assert context[:step_one] == true
assert context[:step_two] == true
end
end
Finally, as discussed in the ExUnit.Case
documentation, remember
that the initial context metadata can also be set via @tag
s, which
can then be accessed in the setup
block:
defmodule ExampleTagModificationTest do
use ExUnit.Case
setup %{login_as: username} do
{:ok, current_user: username}
end
@tag login_as: "max"
test "tags modify context", context do
assert context[:login_as] == "max"
assert context[:current_user] == "max"
end
end
Summary
Functions
Registers a callback that runs once the test exits.
Defines a callback to be run before each test in a case.
Defines a callback to be run before each test in a case.
Defines a callback to be run before all tests in a case.
Defines a callback to be run before all tests in a case.
Same as start_supervised!/2
but links the started process to the test process.
Starts a child process under the test supervisor.
Same as start_supervised/2
but returns the PID on success and raises if
not started properly.
Stops a child process started via start_supervised/2
.
Same as stop_supervised/1
but raises if it cannot be stopped.
Functions
Registers a callback that runs once the test exits.
callback
is a function that receives no arguments and
runs in a separate process than the caller. Its return
value is irrelevant and is discarded.
on_exit/2
is usually called from setup/1
and setup_all/1
callbacks, often to undo the action performed during the setup.
However, on_exit/2
may also be called dynamically. An "ID" (the
name_or_ref
argument) can be used to guarantee that the callback
will be invoked only once. ExUnit uses this term to identify an
on_exit/2
handler: if you want to override a previous handler, for
example, use the same name_or_ref
across multiple on_exit/2
calls.
If on_exit/2
is called inside setup/1
or inside a test, it's
executed in a blocking fashion after the test exits and before
running the next test. This means that no other test from the same
test case will be running while the on_exit/2
callback for a
previous test is running. on_exit/2
is executed in a different
process than the test process. On the other hand, if on_exit/2
is
called inside a setup_all/1
callback then callback
is executed
after running all tests (see setup_all/1
for more information).
Examples
setup do
File.write!("fixture.json", "{}")
on_exit(fn -> File.rm!("fixture.json") end)
end
You can use the same name_or_ref
across multiple on_exit/2
calls
to "override" the registered handler:
setup do
on_exit(:drop_table, fn ->
Database.drop_table()
end)
end
test "a test that shouldn't drop the table" do
on_exit(:drop_table, fn -> :ok end)
end
Relying too much on overriding callbacks like this can lead to test cases that are hard to understand and with too many layers of indirection. However, it can be useful in some cases or for library authors, for example.
Defines a callback to be run before each test in a case.
Accepts one of these:
- a block
- an atom naming a local function
- a
{module, function}
tuple - a list of atoms and
{module, function}
tuples
Can return values to be merged into the context, to set up the state for tests. For more details, see the "Context" section shown above.
setup/1
callbacks are executed in the same process as the test process.
Examples
defp clean_up_tmp_directory(context) do
# perform setup
:ok
end
setup :clean_up_tmp_directory
setup [:clean_up_tmp_directory, :another_setup]
setup do
[conn: Plug.Conn.build_conn()]
end
setup {MyModule, :my_setup_function}
Defines a callback to be run before each test in a case.
This is similar to setup/1
, but the first argument is the context.
The block
argument can only be a block.
For more details, see the "Context" section shown above.
Examples
setup context do
[conn: Plug.Conn.build_conn()]
end
Defines a callback to be run before all tests in a case.
Accepts one of these:
- a block
- an atom naming a local function
- a
{module, function}
tuple - a list of atoms and
{module, function}
tuples
Can return values to be merged into the context
, to set up the state for
tests. For more details, see the "Context" section shown above.
setup_all/1
callbacks are executed in a separate process than tests.
All setup_all/1
callbacks are executed in order in the same process.
On-Exit Handlers
On-exit handlers that you register inside setup_all/1
callbacks
are executed at once after all tests in the module have been run.
They are all executed in the same process, which is a separate
process dedicated to running these handlers. These handlers are
executed in the reverse order of their respective setup_all/1
callbacks.
Examples
# One-arity function name
setup_all :clean_up_tmp_directory
# A module and function
setup_all {MyModule, :my_setup_function}
# A list of one-arity functions and module/function tuples
setup_all [:clean_up_tmp_directory, {MyModule, :my_setup_function}]
defp clean_up_tmp_directory(_context) do
# perform setup
:ok
end
# A block
setup_all do
[conn: Plug.Conn.build_conn()]
end
The context returned by setup_all/1
will be available in all subsequent
setup_all
, setup
, and the test
itself. For instance, the conn
from
the previous example can be accessed as:
test "fetches current users", %{conn: conn} do
# ...
end
Handlers
You can define "global" on-exit handlers in setup_all/1
callbacks:
setup_all do
Database.create_table_for(__MODULE__)
on_exit(fn ->
Database.drop_table_for(__MODULE__)
end)
:ok
end
The handler in the example above will be executed only once, after running all tests in the module.
Defines a callback to be run before all tests in a case.
Similar as setup_all/1
but also takes a context. The second argument
must be a block. See the "Context" section in the module documentation.
Examples
setup_all _context do
[conn: Plug.Conn.build_conn()]
end
@spec start_link_supervised!( Supervisor.child_spec() | module() | {module(), term()}, keyword() ) :: pid()
Same as start_supervised!/2
but links the started process to the test process.
If the process that was started crashes, the crash is propagated to the test process, failing the test and printing the cause of the crash.
Note that if the started process terminates before it is linked to the test process,
this function will exit with reason :noproc
.
@spec start_supervised( Supervisor.child_spec() | module() | {module(), term()}, keyword() ) :: Supervisor.on_start_child()
Starts a child process under the test supervisor.
It expects a child specification or a module, similar to the ones
given to Supervisor.start_link/2
. For example, if your application
starts a supervision tree by running:
Supervisor.start_link([MyServer, {OtherSupervisor, ...}], ...)
You can start those processes under test in isolation by running:
start_supervised(MyServer)
start_supervised({OtherSupervisor, :initial_value})
A keyword list can also be given if there is a need to change the child specification for the given child process:
start_supervised({MyServer, :initial_value}, restart: :temporary)
See the Supervisor
module for a discussion on child specifications
and the available specification keys.
The started process is not linked to the test process and a crash will
not necessarily fail the test. To start and link a process to guarantee
that any crash would also fail the test use start_link_supervised!/2
.
This function returns {:ok, pid}
in case of success, otherwise it
returns {:error, reason}
.
The advantage of starting a process under the test supervisor is that
it is guaranteed to exit before the next test starts. Therefore, you
don't need to remove the process at the end of your tests via
stop_supervised/1
. You only need to use stop_supervised/1
if you
want to remove a process from the supervision tree in the middle of a
test, as simply shutting down the process would cause it to be restarted
according to its :restart
value.
Finally, since Elixir v1.17.0, the test supervisor has both $ancestors
and $callers
key in its process dictionary pointing to the test process.
This means developers can invoke Process.get(:"$callers", [])
in their
start_link
function and forward it to the spawned process, which may set
Process.put(:"$callers", callers)
during its initialization. This may be
useful in projects who track process ownership during tests. You can learn
more about these keys in the Task
module.
@spec start_supervised!( Supervisor.child_spec() | module() | {module(), term()}, keyword() ) :: pid()
Same as start_supervised/2
but returns the PID on success and raises if
not started properly.
@spec stop_supervised(id :: term()) :: :ok | {:error, :not_found}
Stops a child process started via start_supervised/2
.
This function expects the id
in the child specification.
For example:
{:ok, _} = start_supervised(MyServer)
:ok = stop_supervised(MyServer)
It returns :ok
if there is a supervised process with such
id
, {:error, :not_found}
otherwise.
@spec stop_supervised!(id :: term()) :: :ok
Same as stop_supervised/1
but raises if it cannot be stopped.