Using ExUnit's `start_supervised/2` for better cleanup
In a previous post, I wrote about how we can test a named singleton process in our system.
I showed an example of how we can use the test name as the process name to have a unique process per test. That allowed us to test the behavior of the singleton process in isolation while keeping our tests asynchronous.
The final test of our Counter.increment/0
function looked like this:
test "increment/0 increments value by 1", %{test: test_name} do
{:ok, counter} = Counter.start_link(name: test_name)
current_value = Counter.value(counter)
Counter.increment(counter)
assert Counter.value(counter) == current_value + 1
end
@hauleth pointed out one more improvement we could make – start the counter process under the test supervisor with start_supervised/2 for better cleanup:
test "increment/0 increments value by 1", %{test: test_name} do
{:ok, _counter} = start_supervised({Counter, [name: test_name]})
current_value = Counter.value(test_name)
Counter.increment(test_name)
assert Counter.value(test_name) == current_value + 1
end
Rather than linking the counter process to our test process, ExUnit starts the counter under the test supervisor, and it’ll take care of cleaning up the process before the next test starts:
The advantage of starting a process under the test supervisor is that it is guaranteed to exit before the next test starts.
Why do we care?
Though not needed for helping us run tests asynchronously (since we achieved
that through unique names), start_supervised/2
is helpful because it
intentionally cleans up the counter processes.
Our previous example with Counter.start_link/1
terminates the counter process
somewhat incidentally (through the link): when our test process is terminated,
it sends an exit signal to the counter process.
With start_supervised/2
, the test supervisor will ensure the counter is
terminated before the next test starts. So, cleanup is intentional and
synchronous.
We can see that clearly in ExUnit’s callbacks docs:
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.