I recently wrapped up a new project for one of our clients. In that project, we start a supervised GenServer that sends an email and immediately terminates. This process records the email being sent before terminating normally.

We had passing tests but in most test runs we had a big ugly Ecto sandbox warning because the test finished and terminated before the recording that the email was sent. Not great. Below is a simplified version of what that process looks like.

defmodule App.EmailSender do use GenServer def start_link(), do: ... def init(_) do {:ok, %{}, {:continue, :deliver}} end def handle_continue(:deliver, state) do # do the delivery # record what happened, this is where it explodes in tests {:stop, :normal, state} end end

In order to get around this, I added in a Process.monitor to the PID that just launched.

With the processed monitored, we'll receive a :DOWN message on process termination. We can then assert_receive on that message to let the test process pause just long enough to make sure everything stays green and ERROR free. This works because assert_receive blocks for up to 100ms (before failing) letting the other process send and record without issue.

defmodule App.EmailTest do use ExUnit.Case alias App.Email test "sending an email" do email = create_email() {:ok, pid} = Email.deliver(email) ref = Process.monitor(pid) # assert whatever else assert_receive {:DOWN, ^ref, :process, _object, :normal} end end

With this in place for every test that uses that process, we can now make sure the delivery process isn't outpaced by the test process in regards to the Ecto sandbox.

Header photo by Dean Brierley on Unsplash