I have a set of tests that need a GenServer to be started before. As a rule of thumb, I understand it is a good practice to cleanup after each test, so I also want to stop the GenServer after each test.
The problem here is that I don't know how to stop a GenServer after the test has finished. I always end up with some concurrency issue.
defmodule MyModuleTest do
use ExUnit.Case
alias MyModule
setup do
MyModule.Server.start_link(nil)
context_info = 1
more_info = 2
%{context_info: context_info, more_info: more_info}
end
describe "some tests" do
test "returns {:ok, order_id} if order was deleted correctly", context do
# do test here that uses created server and passed context
assert actual == expected
#clean up?
end
end
end
Now, I have tried on_exit
/2 like the following:
setup do
{:ok, server} = MyModule.Server.start_link(nil)
context_info = 1
more_info = 2
on_exit(fn -> GenServer.stop(server) end)
%{context_info: context_info, more_info: more_info}
end
But i get this error:
** (exit) exited in: GenServer.stop(#PID<0.296.0>, :normal, :infinity)
** (EXIT) no process: the process is not alive or there's no process currently associated with the given name, possibly because its application isn't started
I have the feeling this is exiting too soon.
I also tried using start_supervised
however since my GenServer
has a lengthy initialization in handle_continue
the tests run before the server is ready.
How can I fix this?
I finally figured out what was going on.
Turns out start_supervised
is working as expected and is in fact waiting for the GenServer to end handle_continue
(well, it is not exactly waiting, it still sends messages and these are put into the queue waiting for the proper time to get executed).
The issue here was the fact that I didn't do a full cleanup from all the stuff I initiated in my handle_continue
. Turns out some of the connections and processes I started remained even after the original GenServer died.
The solution was two-folded:
In code, this is translated into:
def init(_) do
Process.flag(:trap_exit, true) # trap all exits!
{:ok, %{}, {:continue, :setup_queue}}
end
def handle_continue(:setup_queue, state) do
# do heavy lifting
{:noreply, state}
end
def terminate(_reason, state), do:
# undo heavy lifting
With this and start_supervised
inside my setup
block, everything works nicely.