Reputation: 4879
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.
Upvotes: 2
Views: 2052
Reputation: 3041
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)
Upvotes: 1