Reputation: 3025
In our codebase, we have lots of tests that involve interacting with the database (via Postgrex). We have a handful of shared ExUnit.CaseTemplate
s whose setup
hook prepares the Ecto sandbox and such, and this works great.
The problem I'm running into is that processes spawned by our tests may still be communicating with the database when the test process exits, so we get errors in the logs that look like this:
09:56:51.517 [error] Postgrex.Protocol (#PID<0.2127.0>) disconnected: ** (DBConnection.ConnectionError) owner #PID<0.11626.0> exited
(The "owner" PID here is the test process, which of course spawned the database connection in its setup.)
Things I've tried:
In the shared setup
hook, adding an on_exit/2
handler that will clean up the database processes.
on_exit
runs in a separate process after the test process dies, there's a race condition here that manifests maybe 5% of the time: Postgrex can try to communicate with the now-dead test process before the on_exit
handler has a chance to do its work.As as the last line of every test, explicitly call a shared cleanup function to wait on the database transactions to finish.
Copy & paste the test
macro from the ExUnit source into our shared test code, with the one change being that it appends the call to our cleanup function to the end of test body.
test
with my_test
or whatever, and we'll be stuck maintaining the copypasta.try/catch
block and call the cleanup function regardless of how the test process exits.I gather from spelunking in the Elixir core mailing list that before on_exit/2
existed, there was once a teardown
hook that ran synchronously at the end of the test process. I'd really appreciate any solution that could imitate such functionality.
Edited to add: Here's a sample of what our shared ExUnit.CaseTemplate
looks like:
defmodule PersistenceTestCase do
use ExUnit.CaseTemplate
setup tags do
# This line would be in test_helper.exs, but it breaks tests not using
# this test case that have implicit data-persistence side-effects
Ecto.Adapters.SQL.Sandbox.mode(App.Repo, :manual)
:ok = Ecto.Adapters.SQL.Sandbox.checkout(App.Repo)
unless tags[:async] do
Ecto.Adapters.SQL.Sandbox.mode(App.Repo, {:shared, self()})
end
on_exit(fn ->
# Reset on exit to not interfere with tests not using this test case
Ecto.Adapters.SQL.Sandbox.mode(App.Repo, :auto)
end)
end
end
Upvotes: 2
Views: 580