How to integration_test that kind of genserver?

genserver
testing
troubleshooting
#1

Hello,

I have an app remotely connected to another one’s node. The app needs to be able to call a distant function using this node. It works when called from iex, but I really struggle to get my integration tests right. I would like to check what is the return of the remote app, and if it fits what is expected.

Here is my genserver’s code (code insights welcome as well, still not really comfortable with it) :

defmodule MyApp.MyExternalAppModule do
  use GenServer
  @external_app_node Application.get_env(:my_app, :external_app_node)
  @mailer Application.get_env(:my_app, :mailer)

  def start_link(_args) do
    GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
  end

  def insert(field1, field2, field3) do
    GenServer.call(__MODULE__, {:insert, field1, field2, field3})
  end

  def init(%{}) do
    {:ok, %{ref: nil}}
  end

  def handle_call(
        {:insert, _field1, _field2, _field3},
        _from,
        %{ref: ref} = state
      )
      when is_reference(ref) do

    {:reply, :ok, state}
  end

  def handle_call({:insert, field1, field2, field3}, _from, %{ref: nil}) do
    task =
      Task.Supervisor.async_nolink(
        {MyExternalApp.TaskSupervisor, @external_app_node},
        MyExternalApp.MyExternalAppModule,
        :my_function,
        [field1, field2, field3]
      )

    {:reply, :ok, %{field1: field1, field2: field2, field3: field3, ref: task.ref}}
  end

  def handle_info(
        {ref, {:ok, _external_element}},
        %{ref: ref, field1: field1, field2: field2, field3: field3} = state
      ) do
    Process.demonitor(ref, [:flush])

    @mailer.send_mail("(...)success")

    {:noreply, %{state | ref: nil}}
  end

  def handle_info(
        {ref, {:error, reason}},
        %{ref: ref, field1: field1, field2: field2, field3: field3} = state
      )
      when is_atom(reason) do
    Process.demonitor(ref, [:flush])

    @mailer.send_mail("(...)failure")

    {:noreply, %{state | ref: nil}}
  end

  def handle_info(
        {ref, {:error, _changeset}},
        %{ref: ref, field1: field1, field2: field2, field3: field3} = state
      ) do
    Process.demonitor(ref, [:flush])

    @mailer.send_mail("(...)failure")

    {:noreply, %{state | ref: nil}}
  end
end

Tests :

defmodule MyApp.MyExternalAppModuleTest do
  use ExUnit.Case, async: true

  @my_external_app_module Application.get_env(:my_app, :my_external_app_module)

  describe "insert/3" do
    test "when my_external_app node is up and the data doesn't exist returns (TODO)" do
      assert_receive {_, {:ok, _}}, 3000
      assert :ok == @my_external_app_module.insert("field1", "field2", "field3")
    end
  end
end

So assert_receive {_, {:ok, _}}, 3000 doesn’t work, obviously… I tried to mold it in a lot of ways without finding how it should work. What I want to do is check that it’s the right handle_info that is called and the data is as expected.

Thanks a lot ahead.

0 Likes

#2

If you’re looking to test using multiple nodes I’d suggest you to take at the firenest testsuite.

2 Likes

#3

Well, it doesn’t seem very up to date or future proof, since…

Note: Work on firenest has been halted. We plan to incorporate some of those features in Phoenix.PubSub instead.
0 Likes

#4

Just because the project itself was halted doesn’t lessen its testsuite in being a good example in how to tests things, which run on multiple nodes.

3 Likes

#5

Oh, you meant in that way. You’re right, I will take a look at it at some point, thanks.

0 Likes