Как провести интеграцию_ такого рода genserver?Правильное использование assert_receive? - PullRequest
1 голос
/ 15 апреля 2019

У меня есть приложение, удаленно подключенное к другому узлу. Приложение должно иметь возможность вызывать удаленную функцию, используя этот узел. Он работает при вызове из 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 поведении, которое есть.

Ответы [ 2 ]

0 голосов
/ 18 апреля 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}}
0 голосов
/ 17 апреля 2019

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

...