Hello all. I am having an issue where start_supervisor/2 and stop_supervisor/1 aren’t working as expected in ExUnit tests. In each test case I call start_supervisor/2, and after the test I call stop_supervisor/1 so that the process under test begins anew. However, the process state somehow persists between tests! Here is the source code
defmodule NectarDb.OtherNodes do
use Agent
@me __MODULE__
@spec start_link(any) :: {:ok, pid}
def start_link(_args) do
Agent.start_link(fn -> [] end, name: @me)
end
@spec add_node(String.t) :: :ok
def add_node(node) do
Agent.update(@me, fn nodes -> [node | nodes] end)
end
@spec get_nodes() :: [String.t]
def get_nodes() do
Agent.get(@me, fn nodes -> nodes end)
end
end
and here is the test code:
defmodule NectarDb.OtherNodesTest do
use ExUnit.Case, async: false
alias NectarDb.OtherNodes
describe "adding and retrieving nodes from OtherNodes" do
test "succeeds for addition" do
start_supervised({OtherNodes,[]})
OtherNodes.add_node("a@a")
assert ["a@a"] == OtherNodes.get_nodes()
:ok = stop_supervised(OtherNodes)
end
test "succeeds for multiple additions" do
start_supervised({OtherNodes,[]})
OtherNodes.add_node("a@a")
OtherNodes.add_node("a@b")
OtherNodes.add_node("a@c")
assert ["a@c","a@b","a@a"] == OtherNodes.get_nodes()
stop_supervised(OtherNodes)
end
end
end
I would also like to add that my application starts these Agents as workers, which may be causing the issues
defmodule NectarDb.Application do
# See https://hexdocs.pm/elixir/Application.html
# for more information on OTP Applications
@moduledoc false
use Application
import Supervisor.Spec
alias NectarDb.Oplog
alias NectarDb.OtherNodes
alias NectarDb.Store
def start(_type, _args) do
# List all child processes to be supervised
children = [
# Starts a worker by calling: NectarDb.Worker.start_link(arg)
# {NectarDb.Worker, arg},
worker(Oplog,[nil]),
worker(OtherNodes,[nil]),
worker(Store,[nil]),
]
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: NectarDb.Supervisor]
Supervisor.start_link(children, opts)
end
end
Yes, the workers we are trying to start on test is going to conflict with the ones in your application. You need to make the name a parameter and pass different ones.
1 Like
Could someone (@josevalim, if you have time) expand on this?
something like this?:
def start_link(test_name) do
Supervisor.start_link(__MODULE__, :ok, name: test_name)
end
def start_link() do
Supervisor.start_link(__MODULE__, :ok, name: __MODULE__)
end
ATM I’m getting
Application heroes_engine exited: HeroesEngine.Application.start(:normal, []) returned an error: shutdown: failed to start child: HeroesEngine.HeroesSupervisor
** (EXIT) an exception was raised:
** (ArgumentError) expected :name option to be one of the following:
* nil
* atom
* {:global, term}
* {:via, module, term}
Got: []
code at https://gitlab.com/smedegaard/heroes_engine
When you don’t pass a tuple to start_supervised, we call start_link([])
and not start_link()
. 
Thanks a bunch for the reply Jose!
But I’m afraid I just can’t wrap my head around how start_supervised
works. It might just be a lack of my knowledge of OTP.
I can’t seem to get the function to pass any arguments. In desperation I tried
start_supervised!(HeroesSupervisor)
start_supervised!(HeroesSupervisor,{:test})
start_supervised!(HeroesSupervisor, {name: :test})
start_supervised!({HeroesSupervisor, {:test}})
and the same for non-bang version start_supervised
But it seems to pass []
no mater what I do.
I ended up matching on []
just to see if it got me somewhere.
It sort of did
defmodule HeroesEngine.HeroesSupervisor do
use Supervisor
def start_link([]) do
Supervisor.start_link(__MODULE__, :ok, name: :test)
end
def start_link() do
Supervisor.start_link(__MODULE__, :ok, name: __MODULE__)
end
...
Now it calls start_link([])
but that leads me back to my original problem of the process already being started.
** (RuntimeError) failed to start child with the spec HeroesEngine.HeroesSupervisor.
Reason: already started: #PID<0.158.0>
If anyone has some example code showing how to pass an argument to a Supervisor
from start_supervised
or start_supervised!
I’d love to take a look. Any advice is appreciated 
The first argument to start_supervised
is the child_spec
, so in your case I believe you want:
start_supervised!({HeroesSupervisor, :test})
Thanks again!
I got past the errors and I’ve learned something new

For future reference:
My GenServer
and it’s child_spec
defmodule HeroesEngine do
use GenServer, start: {__MODULE__, :start_link, []}, restart: :transient, type: :worker
...
My supervisor:
defmodule HeroesEngine.HeroesSupervisor do
use Supervisor
def start_link(opts) do
Supervisor.start_link(__MODULE__, :ok, name: :test)
end
def start_link() do
Supervisor.start_link(__MODULE__, :ok, name: __MODULE__)
end
def init(:ok) do
Supervisor.init([HeroesEngine], strategy: :simple_one_for_one)
end
...
My test setup
defmodule HeroesEngineTest do
...
setup do
# Random list_name so I avoid testing on an old state
# Might be a more bullet proof way to randomize?
list_name =
Enum.random(1..1_000_000)
|> Integer.to_string
pid = start_supervised!(HeroesSupervisor,
[start: {HeroesEngine, :start_link, [list_name]}, restart: :transient, type: :worker])
hero = Hero.new(@hero_name, @hero_power)
[hero: hero, pid: pid]
end
...
Example test
test "add_hero() adds a hero to list", context do
{:ok, {:hero_added, new_state}} = HeroesEngine.add_hero(context[:pid], context[:hero])
assert Map.has_key?(new_state[:heroes], String.to_atom(@hero_name))
end