Using `Process.register` and getting "process attempted to call itself"

I’m getting an error that I’m not sure how to deal with when using Process.register to make an assertion against a received message. Does anyone know how I can fix this? I’ve been trying to fix this for about 5 hours.

There’s sample code below, but for more, check: https://gist.github.com/rawkode/ba76c011f0c689130251fcc7a1f1ad7c

** (EXIT) process attempted to call itself
defmodule Module do
  def save(model) do
    GenServer.cast(:eidetic_eventstore_adapter, {:record_event, event})
  end
end
  test "It can loop uncommitted events and delegate them to the adapter" do
    Process.register(self(), :eidetic_eventstore_adapter)
    user = %Example.User{} =
      Example.User.register(forename: "Darrell", surname: "Abbott")
      Eidetic.EventStore.save(user)

    for event <- user.meta.uncommitted_events do
      assert_received {:"$gen_call", {:record, event}}
    end
  end
1 Like

It’s this line: https://gist.github.com/rawkode/ba76c011f0c689130251fcc7a1f1ad7c#file-code-ex-L32

You call Eidetic.EventStore.save(user) in your test which calls the the save function, which ends up doing an Agent.get :eidetic_eventstore_adapter which is of course the current test process. Agent.get is a GenServer.call.

Relatedly, why are you manually calling Agent.start_link inside a supervisor init?

It’s actually the following that is causing the problem:

for event <- model.meta.uncommitted_events do                                                                                         
  GenServer.call(:eidetic_eventstore_adapter, {:record, event})                                                                       
end

I can remove the Agent code and I still get the same error.

As for the Agent, it’s to store the subscribers. I wasn’t sure where else to store them, though I have removed that code for the time being.

This worked previously, when I was using cast rather than call.

Can I not use Process.register to test calls, is there another way to do this?

Thanks for taking the time to look at this, @benwilson512.
It’s got me pretty stumped!

When you do a call, the function awaits the reply. This means that the test will wait for a reply from itself, but since it’s waiting, it can never send it - you have a deadlock. That’s why Elixir’s GenServer will bail out of this situation early.

3 Likes

Thanks for the response, @michalmuskala. That makes sense. How would you test a call, then?

Here you could run it in a separate process spawn_link(fn -> Eidetic.EventStore.save(user) end). You may want to monitor that process as well to ensure the test doesn’t return before it’s done.

Testing the private GenServer API is a strange and brittle thing to do though. If a module A is a client of module B, and B is a GenServer, module A should never call GenServer.call directly. It should call a function in B (e.g. B.save) which then does a GenServer.call. Then you’re free to replace B with an alternate module C during your tests that has the same API but isn’t a GenServer - for instance it could store events in an Agent which the test process can query after the save() to check what was saved. See http://blog.plataformatec.com.br/2015/10/mocks-and-explicit-contracts/

2 Likes

Call is synchronous, so you can test the return value easily enough in your test. For async calls such as cast, if they respond with a message you can use ExUnit’s assert_receive. Otherwise you need to somehow test the GenServer internal state data changes.

I also understand that the way it works, and this seems to be held up by some simple testing here, that given that the messages sent by call/cast are handled in the same process they are therefore serialized in that process by GenServer’s receive loop. So you can do a cast, then a call to check the changed value which will block in your test as it is sync, and be run after the cast’d call in the GenServer giving you a predictable order of events.

If there is no return value and no internal state change, then you don’t need to test it :wink: So I think the above handles all useful cases?

1 Like

I wouldn’t test the call directly. I would start the real process and test the public interface of the behaviour module - something more like an integration test, than a pure unit test of a single function.

If the logic inside the process is so complex, that I feel like I need to test it on a lower level, I usually go for defining a separate module for the state of the gen server exporting pure functions that manage it. Then I can test it separately, and call those functions from inside the GenServer.

1 Like

@dom Thanks for that link, really interesting read.

Thanks for everyone’s responses. I ended up implementing a “GenServer” event store, as well as a MongoDB, which I use in the tests now.

https://github.com/GT8Online/eidetic-elixir/blob/master/test/event_store_test.exs

Thanks for helping out, it’s been fun learning from your comments :thumbsup: