3
votes

Background

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.

Problem

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.

Question

How can I fix this?

1

1 Answers

1
votes

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:

  • catch all stop signals
  • once a stop signal is detected, do a graceful shutdown

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.