Testing {:stop, :some_error} in Genserver.init/1 with ExUnit

I have a Genserver like this:

defmodule Foo do
  use Genserver

  def start_link(x) do
    GenServer.start_link(__MODULE__, x)
  end
  def init (x) do
    {:stop, :some_error}
  end
end

and I want to test this with ExUnit:

```elixir
defmodule FooTest do
  use ExUnit.Case
  test "start_link fails" do
    {:error, :some_error} = Foo.start_link(0)
  end
end

But this fails with ** (EXIT from …) ** because the test process is linked to the process.

So I tried to wrap the call with catch_exit but that doesn’t work either.

Is there an easy way to test this?

I’ve used Process.flag(:trap_exit, true) (Process.flag/2) in the past inside the test module - just make sure to purge the resulting :EXIT message from the test process mailbox.

Looking at catch_exit/1 it seems to be designed to catch an exit inside the same process that triggered it with Kernel.exit/1

Example: see test "FrequencySup demo"

defmodule FrequencySupTest do
  use ExUnit.Case

  setup do
    {:ok, sup} = FrequencySup.start_link()

    on_exit fn ->
      refute (Process.alive? sup), "Supervisor still running!"
    end

    [sup: sup]
  end

  defp wait(_, _, left) when left < 1 do
    nil
  end
  defp wait(fun, timeout, left) do
    case fun.() do
      nil ->
        Process.sleep timeout
        wait fun, timeout, (left - 1)
      result ->
        result
    end
  end

  defp whereisFrequency,
    do: Process.whereis Frequency

  defp terminate_wait({pid, terminate}) do
    ref = Process.monitor pid

    terminate.()

    receive do # wait for the supervisor to terminate
      {:DOWN, ^ref, :process, _object, _reason} ->
        Process.demonitor ref, [:flush]
    end
  end

  test "FrequencySup demo", context do
    sup = context[:sup]
    Process.flag :trap_exit, true

    initial_pid = whereisFrequency()
    assert (Kernel.is_pid initial_pid)

    fovl_pid = Process.whereis FreqOverload
    assert (Kernel.is_pid fovl_pid)

    # Kill off frequency
    terminate_wait({initial_pid, (fn -> Process.exit initial_pid, :kill end)})

    # Wait until replacement Frequency is up and running
    freq_pid = wait &whereisFrequency/0, 10, 10
    assert (Kernel.is_pid freq_pid)

    # Use Supervisor.which_children/1
    expected_children = Enum.sort [
      {Frequency, freq_pid, :worker, [Frequency]},
      {FreqOverload, fovl_pid, :supervisor, [FreqOverload]}
    ]
    assert (expected_children == (Enum.sort (Supervisor.which_children sup)))

    # Use Supervisor.count_children/1
    expected_count = %{active: 2, specs: 2, supervisors: 1, workers: 1}
    assert (Map.equal? expected_count, (Supervisor.count_children sup))

    # Now stop the supervisor
    terminate_wait({sup, &FrequencySup.stop/0})
    assert_received {:EXIT, _, :shutdown}
    Process.flag :trap_exit, false
  end

end

I wasn’t writing a unit test as much as I was solidifying my understanding of the API.

2 Likes