Как интегрировать_тестировать такой генсервер? Правильное использование assert_receive?

У меня есть приложение, удаленно подключенное к другому узлу. Приложение должно иметь возможность вызывать удаленную функцию, используя этот узел. Он работает при вызове из iex, но я действительно изо всех сил пытаюсь правильно выполнить интеграционные тесты. Я хотел бы проверить, что возвращает удаленное приложение, и соответствует ли оно ожидаемому.

Вот код моего genserver (понимание кода также приветствуется, но все еще не очень удобно с ним):

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

Тесты:

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

Итак, assert_receive {_, {:ok, _}}, 3000 не работает, очевидно... Я пытался создать множество способов, но так и не нашел, как это должно работать. Что я хочу сделать, так это проверить, что вызывается правильный handle_info и данные соответствуют ожидаемым.

В основном о assert_receive поведении.


person Jeremy Belolo    schedule 14.04.2019    source источник


Ответы (2)


У меня была проблема, похожая на эту, но в тестах я не использовал assert_receive, вместо этого я решил ее с помощью Erlangs :sys.get_state/1, где аргумент, который вы должны передать, - это pid(). Эта функция будет ждать, пока все сообщения в почтовом ящике процесса будут обработаны, а затем вернет состояние этого процесса. Таким образом, после получения состояния вы можете сравнить его со значениями, которые вы ожидали изменить.

person Tano    schedule 17.04.2019
comment
Эй, кажется милым, я мог бы проверить это... Но меня больше интересовало само полученное сообщение, я хотел проверить, получил ли я, например, кортеж :ok или :error... Есть ли способ распечатать весь стек сообщений, может быть? - person Jeremy Belolo; 17.04.2019
comment
Существует функция erlang: erlang.process_info(pid), но я не уверен, как она может вам помочь:/ - person Tano; 17.04.2019

Решением было бы отслеживать входящие сообщения с помощью чего-то вроде

:erlang.trace(pid, true, [:receive])

И затем вы следите за сообщениями с

assert_received {:trace, ^pid, :receive, {:"$gen_call", _, :something}}

Убедитесь, что call работает, а затем

:timer.sleep(100) # Just to make sure not tu run into a race condition
assert_receive {:trace, ^pid, :receive, {ref, :returned_data}}
person Jeremy Belolo    schedule 18.04.2019