In our ExUnit tests we initiate a process via delivering a message "at a distance" and don't receive a pid. When the app processes the message it spawns an ephemeral "manager" process that executes a series of tasks, the output of which we test for. We're currently using the venerable Process.sleep() in the hope the output is ready by then, which is not ideal.
It looks like a Registry is appropriate to keep track of dynamic running processes so I modified the init in our GenServer to register itself:
def init(arg) do
Registry.register(App.RunningManagers, :running, true)
{:ok, arg}
end
This works in the test as below, because the test process can now Process.monitor() the running process and receive notification when it ends. But I still need a sleep() before because the process to monitor might not have started yet.
test "outputs a file" do
trigger_message()
Process.sleep(1000) # I want to get rid of this
[{pid, _}] = Registry.lookup(App.RunningManagers, :running)
Process.monitor(pid)
receive do
{:DOWN, _, :process, _, _} -> nil
end
assert file_is_there()
end
Is there a way to nicely wait for it to start? I'm aware that Registry.start_link can take a listeners option, but it requires a named process and I don't want to pollute my production supervision tree with the concerns of tests.
You will always have to wait for the process to start, but the most efficient way would be to continue to check for it until is registered.
trigger_message()
{:ok, pid} = find(Registry.lookup(App.RunningManagers, :running))
# ... rest of test
# Test helper functions
defp find([{pid, _}]), do: {:ok, pid}
defp find([]), do: find(Registry.lookup(App.RunningManagers, :running))
You might want to alter my suggestion to have a timeout check:
defp find(result, timeout_ms \\ :timer.seconds(1), start_ms \\ :os.system_time(:millisecond), run_ms \\ 0)
defp find(_, timeout, _, runtime) when runtime > timeout, do: {:error, :timeout}
defp find([{pid, _}], _, _, _), do: {:ok, pid}
defp find([], timeout, start, runtime) do
runtime = runtime + (:os.system_time(:millisecond) - start)
find(Registry.lookup(App.RunningManagers, :running), timeout, start, runtime)
end
It is worth noting that you do not need to use the Registry if you can edit the GenServer and make it registered via the :name option
# in the MyGenServer module
def start_link(_) do
GenServer.start_link(__MODULE__, [], name: __MODULE__)
end
# In your test, see if MyGenServer is started and registered
{:ok, pid} = Process.whereis(MyGenServer)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With