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.

Want my latest thoughts, posts, and projects in your inbox?

    I will never send you spam. Unsubscribe any time.