escouten
escouten

Reputation: 2973

How to test that a Phoenix socket is terminated?

I'm looking for a way to test that a socket gets terminated. The code under test does this:

def handle_in("logout", _payload, socket) do
  {:stop, :logout, socket |> assign(:user, nil)}
end

And my test code (adapted from http://elixir-lang.org/getting-started/try-catch-and-rescue.html#exits) does this:

test "logout terminates the socket", %{socket: socket} do
  try do
    push socket, "logout"
  catch
    huh -> IO.puts("Caught something #{IO.inspect huh}")
    :exit, what -> IO.puts("Caught :exit #{IO.inspect what}")
  end
  assert_receive {:DOWN, monitor_ref, _, :normal}
end

but when I run the tests, I get this:

1) test logout without login terminates the socket (Main.AuthChannelTest)
   test/channels/auth_channel_test.exs:47
   ** (EXIT from #PID<0.505.0>) :logout

.....09:06:41.139 [error] GenServer #PID<0.507.0> terminating
** (stop) :logout

How should I be testing for socket closure?

Upvotes: 3

Views: 1054

Answers (2)

Dogbert
Dogbert

Reputation: 222050

The message sent when a process you monitor goes down is {:DOWN, ref, :process, pid, reason}, so

  1. You should have a 5 tuple in the assert_receive

  2. You should terminate with the reason :normal, if you want to match with the reason :normal. If you do this, you won't have to catch any exit signals (see below if you do want to exit with :logout).

The following works for me:

def handle_in("logout", _payload, socket) do
  {:stop, :normal, socket |> assign(:user, nil)}
end
test "...", %{socket: %{channel_pid: channel_pid} = socket} do
  monitor_ref = Process.monitor(channel_pid)
  push socket, "logout", %{}
  assert_receive {:DOWN, ^monitor_ref, _, _, :normal}
end

If you for some reason do want to exit the socket with the reason :logout, you'll also have to trap exits as any reason other than :normal causes an exit signal to be sent to all processes linked to a process, and in the tests, the process running the tests is linked to the socket.channel_pid. The following works:

def handle_in("logout", _payload, socket) do
  {:stop, :logout, socket |> assign(:user, nil)}
end
test "...", %{socket: %{channel_pid: channel_pid} = socket} do
  Process.flag(:trap_exit, true)
  monitor_ref = Process.monitor(channel_pid)
  push socket, "logout", %{}
  assert_receive {:DOWN, ^monitor_ref, _, _, :logout} 
end

Upvotes: 4

escouten
escouten

Reputation: 2973

Received a good answer via Elixir Slack channel:

Revise the test code to trap exit signals. Here's the updated test that's working as intended:

test "logout terminates the socket", %{socket: socket} do
  Process.flag(:trap_exit, true)
  push socket, "logout"
  socket_pid = socket.channel_pid
  assert_receive {:EXIT, ^socket_pid, :logout}
end

Note that there's still console log noise with this, so I have since changed the exit reason to :normal and all is quiet.

Upvotes: 0

Related Questions