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