Problem testing Bakeware CLI tool with supervisors and genservers

I am testing a Bakeware CLI tool that takes list of servers, opens an SSH connection for each server, and runs the appropriate ssh command on each. The app is very similar to the KV.Registry example in the official Elixir guide Mix and OTP section.

The supervision tree is:

CoordinatorSupervisor (Supervisor, named, strategy: :one_for_all)

  • ConnectionSupervisor (DynamicSupervisor, named, strategy: :one_for_one)
    • Connection (GenServer, restart: :temporary)
    • Connection (GenServer, restart: :temporary)
  • Coordinator (GenServer, named)

Connection deals with creating the SSH connection and passing commands.
Coordinator deals with starting and managing the different connections.

The main function takes some command line arguments with a default list of servers, starts the CoordinatorSupervisor, adds the servers, passes the commands, and waits for results (using a receive loop).

The problem is the state of the Supervisor’s and GenServer’s are difficult to manage before each test. When the tests start, the supervision tree is alive with the default arguments given to the app at startup.

defmodule CoordinatorTest do
use ExUnit.Case, async: false

test “adds server” do
my_server = “localhost”
assert Coordinator.list_servers(Coordinator) == {:ok, %{}}

Coordinator.add_server(Coordinator, my_server)
{:ok, server_list} = Coordinator.list_servers(Coordinator)
servers = Map.keys(server_list)
assert Enum.member?(servers, my_server)

end

test “removes connection on crash” do
my_server = “localhost”
assert Coordinator.list_servers(Coordinator) == {:ok, %{}}

Coordinator.add_server(Coordinator, my_server)
{:ok, server_list} = Coordinator.list_servers(Coordinator)
[server] = Map.keys(server_list)
assert my_server == server
conn = Map.get(server_list, server)

GenServer.stop(conn, :shutdown)
assert Coordinator.list_servers(Coordinator) == {:ok, %{}}

end
end

The assertion that the list of servers is empty (second line of each test) fails because they contain the default servers. As I understand it, when running mix test, it starts the app and, at some arbitrary point, the tests start. The receive loop in main stops the app from shutting down, so the supervision tree is still running with the default servers. I thought that perhaps if I stop the supervisor and restart it:

test “adds server” do
Supervisor.stop(CoordinatorSupervisor)
start_supervised!(CoordinatorSupervisor)
my_server = “localhost”
assert Coordinator.list_servers(Coordinator) == {:ok, %{}}

I receive the error (for both tests):

** (exit) exited in: GenServer.stop(CoordinatorSupervisor, :normal, :infinity)
** (EXIT) no process: the process is not alive or there’s no process currently associated with the given name, possibly because its application isn’t started

So when I run the test, CoordinatorSupervisor is definitely running showing the default server list (making my assertion fail). However, if I run the test and try to stop the supervisor, the supervisor is definitely not running, giving me the error!

If I could stop CoordinatorSupervisor without an error, I could test Coordinator (a GenServer) on its own. Which seems like a good thing, except it uses the ConnectionSupervisor (a DynamicSupervisor) to start the connections (and the dynamic supervisor is a child of the supervisor).

Any suggestions to restart the supervision tree before each test, so the server list is blank?