简体   繁体   中英

Wait for a process to start and end in Elixir ExUnit tests

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)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM