Series of GenServer Tests Report :already_started(1.5.2)

I have four tests which I originally did NOT call GenServer.stop(pid) on after each test. When doing mix test the last test would sometimes fail because it would appear that state would be retained from a previous run:

➜  fb_manager git:(master) ✗ mix test
...

  1) test starts with an empty roster (FbManagerTest)
     test/fb_manager_test.exs:5
     Assertion with == failed
     code:  assert FbManager.FFServer.roster() == %{}
     left:  %{"Russell Wilson" => %FFNerd.Player{active: "1", college: "", display_name: "Russell Wilson", dob: "0000-00-00", fname: "Russell", height: "", jersey: "3", lname: "Wilson", player_id: "1847", position: "QB", star: nil, team: "SEA", twitter_id: nil, weight: ""}}
     right: %{}
     stacktrace:
       test/fb_manager_test.exs:8: (test)



Finished in 3.0 seconds
4 tests, 1 failure

Randomized with seed 140594
➜  fb_manager git:(master) ✗ mix test
....

Finished in 2.8 seconds
4 tests, 0 failures

Randomized with seed 452906
➜  fb_manager git:(master) ✗

So in an attempt to fix this, I captured the pid on start_link() and at the end of the test I would call GenServer.stop(pid)

This doesn’t seem to help, because Elixir now reports that the process has already started? Not sure what is going on here … log follows. Full code is at https://github.com/mazz/fb_manager

➜  fb_manager git:(master) ✗ mix test


  1) test can find a player on the roster (FbManagerTest)
     test/fb_manager_test.exs:23
     ** (MatchError) no match of right hand side value: {:error, {:already_started, #PID<0.170.0>}}
     code: {:ok, pid} = FbManager.FFServer.start_link
     stacktrace:
       test/fb_manager_test.exs:24: (test)



  2) test can add a player (FbManagerTest)
     test/fb_manager_test.exs:13
     ** (MatchError) no match of right hand side value: {:error, {:already_started, #PID<0.170.0>}}
     code: {:ok, pid} = FbManager.FFServer.start_link
     stacktrace:
       test/fb_manager_test.exs:14: (test)



  3) test starts with an empty roster (FbManagerTest)
     test/fb_manager_test.exs:5
     ** (MatchError) no match of right hand side value: {:error, {:already_started, #PID<0.170.0>}}
     code: {:ok, pid} = FbManager.FFServer.start_link
     stacktrace:
       test/fb_manager_test.exs:6: (test)



  4) test can remove a player on the roster (FbManagerTest)
     test/fb_manager_test.exs:32
     ** (MatchError) no match of right hand side value: {:error, {:already_started, #PID<0.170.0>}}
     code: {:ok, pid} = FbManager.FFServer.start_link
     stacktrace:
       test/fb_manager_test.exs:33: (test)



Finished in 0.04 seconds
4 tests, 4 failures

Before mix runs the tests, it starts your application. This sets up your supervision tree, which includes an instance of FFServer:

Since that process is registered under the name :ffnerd, your tests are unable to spawn another one, because it also tries to register itself as :ffnerd. The problem was just hidden before because you were not checking the return value of start_link.

Thanks, I didn’t know that. So stop naming the process and just pass in the pid to each of the client api in the GenServer? Is there any way I can avoid passing in a pid but still test the GenServer?

1 Like

Name the process in your app’s supervision tree, but not in tests. Your app code would use the name e.g. FFServer.roster(FFServer), while the test code would pass the pid of the process under test e.g. FFServer.roster(pid).

If passing the name bothers you, you can provide two versions of each function, one that takes a pid/name and one that uses the default one. Ecto and Phoenix do something a bit like this albeit with macros. Another slightly less orthodox approach is to lookup the GenServer’s pid from the process dictionary, in a key named e.g. FFServer, falling back to the default registered name if not defined.

(I would recommend FFServer rather than :ffnerd as a name, it makes maintenance easier when named processes are named after their module.)

1 Like