How to handle an exit during testing?

I’m trying to assert an exception with assert_raise, but it seems to fail because there is an exit:

** (EXIT from #PID<0.504.0>) an exception was raised:
         ** (MatchError) no match of right hand side value: {0, []}

Is there a way to handle this?

The raise is in a linked process, it’s not in the test process. There is no way to assert for this; if you truly want to do this you have to trap exits and catch the inbound exit message, but I think there is probably a better strategy that involves either not spawning a separate process, or testing the raise at the unit level and not at the Integration test level.

To be a bit more explicit: Your test doesn’t crash because of an exception in the first place, which explains why assert_raise doesn’t help (it uses try do/rescue). You can catch exits with try do / catch: Kernel.SpecialForms — Elixir v1.13.4

Interestingly, try do / catch doesn’t seem to catch the exit. I’m not quite sure what is going on, but for some reason try do / catch doesn’t catch errors or exists originating from a LiveView handle_event.

If I do:

try do
  exit(:shutdown)
catch
  _, value -> IO.inspect(value)
end

Everything works fine. But if I do:

try do
  handle_event_causing_MatchError()
catch
  _, value -> IO.inspect(value)
end

I get EXIT from #PID<0.x.0>.

EDIT: I tried calling the handle_event directly and raising from there, but it worked, so it’s not the handle_event that’s the problem.

EDIT2: In case someone has an idea about what’s happening, this is basically the setup:

try do
  |> render_click(elem, %{"comment-id" => to_string(false_id)})
catch
  _, value -> IO.inspect(value)
end

The false_id causes a MatchError in the handle_event called by render_click, which then results in the EXIT from #PID<0.x.0>.

The behavior isn’t specific to LiveView, because this doesn’t work either

try do
  spawn_link fn -> 1 = 2 end
  Process.sleep(1000)
catch
  _, value -> IO.inspect(value)
end

** (EXIT from #PID<0.121.0>) shell process exited with reason: an exception was raised:
    ** (MatchError) no match of right hand side value: 2
        iex:3: (file)

07:28:41.358 [error] Process #PID<0.131.0> raised an exception
** (MatchError) no match of right hand side value: 2
    iex:3: (file)

If you are testing for this error then I assume it must be expected as a possibility, so why not explicitly handle it in your LiveView? If that’s not an option you like, maybe assert_receive/3 will help. The helpful documentation for you is actually under catch_exit/1

This seemed to do the trick, though it’s hardly elegant.

I’m trying to favor end-to-end testing over unit testing, but I guess this is one of those instances where unit testing is the way to go.

1 Like

A quick comment on where I ended up, in case it’s of help to someone.

I finally recognized that it makes no sense to test for an exit, since the crucial thing is whether or not the action has an unwanted effect on the database/system. So I just wrapped the exiting code into a spawn/1 and let it fail and then asserted it had no effect.

This does litter the test log with the exit error, but I got rid of that by putting capture_log: true into ExUnit.start() in test_helpers.exs.

1 Like