I’m attempting to provide unit test coverage for a GenServer in an “application” that I’ve created, but I’m running into an issue with an already started process, namely the registry. I won’t share the code for the supervisor but it declares a child spec with a named registry, __MODULE__.Registry
. I’m trying to follow the example for the KV.Registry
from elixir-lang.org here: https://elixir-lang.org/getting-started/mix-otp/dynamic-supervisor.html and here: https://elixir-lang.org/getting-started/mix-otp/genserver.html, but having a little trouble decomposing it and mapping it back to what I’m trying to accomplish
0) Test: failure on setup_all callback, all tests have been invalidated
10 ** (EXIT from #PID<0.205.0>) shutdown: failed to start child: .FooRegistry
9 ** (EXIT) already started: #PID<0.180.0>
I have a specific name that I want to use for both my registry and dynamic supervisor: Foo.Registry
and Foo.DynamicSupervisor
that I declare with child specs inside my Foo.Supervisor
.
defmodule Foo do
use GenServer
alias __MODULE__.FooReducer
@supervisor __MODULE__.Supervisor
@registry __MODULE__.Registry
# client
def start(id, strategy, opts \\ []) do
DynamicSupervisor.start_child(
@supervisor,
{__MODULE__, id: id, strategy: strategy, opts: opts}
)
end
def fun1(id), do: GenServer.cast(registry_key(id), :increment)
defp registry_key(id), do: {:via, Registry, {@registry, id}}
# server (callbacks)
def start_link(args) do
case NimbleOptions.validate(args[:opts], args[:strategy].options_schema()) do
{:ok, opts} ->
state = struct(args[:strategy], opts)
reg_key = registry_key(args[:id])
GenServer.start_link(__MODULE__, state, name: reg_key)
{:error, %NimbleOptions.ValidationError{} = err} ->
{:error, Exception.message(err)}
end
end
# other callbacks
end
test file
defmodule FooTest do
use ExUnit.Case, async: true
alias Foo.Strategy
@id 0
# BEFORE
setup_all do
{:ok, _} = Foo.Supervisor.start_link() # starts both my named registry and named dynamic-sup
:ok
end
# FOLLOWING suggestion on elixir-lang site's KV.Registry example
# uncertainty about how to do this properly
# attempting to start a single supervisor for all tests in this file as the IDs are unique
setup_all do
_supervisor = start_supervised!(Foo.Supervisor)
:ok
end
# the default ID of 0 should be restarted for all tests
setup do
opts = [increment: 100, decrement: 100, step: 3]
{:ok, _} = Foo.start(@id, Strategy, opts)
:ok
end
test "invalid options" do
expected_message = "required option :decrement not found, received options: [:bar]"
opts = []
assert {:error, expected_message} == Foo.start(@id + 1, Strategy, opts)
end
test "multiple IDs" do
opts = [bar: "asdf"]
ids = 1..1_000
for id <- ids, do: {:ok, _} = Foo.start(id, Strategy, opts)
for id <- ids, do: for(_ <- 1..150, do: Foo.fun1(id))
for id <- ids, do: Foo.stop(id) # unsure if this is needed
end
end
I’m not sure how to go about setting up my test correctly when my GenServer depends on my registry to be started. I couple the GenServer implementation to the name of my registry and dynamic supervisor. Should I decouple this and just let Elixir start a registry like the example on elixir-lang.org? I’ve been wrangling with this for some time and resources online are limited. My GenServer behaves as expected otherwise
Thanks in advance for any support!