如何整合_测试那种genserver?正确使用assert_receive?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何整合_测试那种genserver?正确使用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的行为。

答案

我有类似的问题,但在测试中我没有使用assert_receive,而是通过使用Erlangs的:sys.get_state/1解决了它,你必须通过的论点是pid()。此函数将等待,直到处理过程的邮箱中的所有消息,然后它将返回该过程的状态。因此,在获得状态后,您可以与您期望更改的值进行比较。

另一答案

解决方案是跟踪传入的消息

: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}}

以上是关于如何整合_测试那种genserver?正确使用assert_receive?的主要内容,如果未能解决你的问题,请参考以下文章

每次测试后停止 GenServer

在 Elixir ExUnit 中,我如何保证 Supervisor 将创建一个新的 GeNserver?

GenServer 中的回调函数

Elixir GenServer handle_cast 未被调用

如何使 genserver 以频率值运行 Elixir

Elixir 理解 GenServer