测试使用GenServer.cast的代码

Fra*_*eil 3 elixir

我有一个测试,它模拟了应用程序范围内的回购.大多数时候,测试是绿色的.当我在循环中运行测试时,使用相同的种子,可能有10%的运行成功,但使用GenServer终止消息:

15:39:34.632 [error] GenServer ShortTermMessageStore terminating
** (CaseClauseError) no case clause matching: {:error, %Postgrex.Error{connection_id: 11136, message: nil, postgres: %{code: :foreign_key_violation, constraint: "chat_messages_room_id_ref_fkey", detail: "Key (room_id_ref)=(c78940ab-2514-493e-81fe-64efc63c7bb0) is not present in table \"chat_rooms\".", file: "ri_triggers.c", line: "3324", message: "insert or update on table \"chat_messages\" violates foreign key constraint \"chat_messages_room_id_ref_fkey\"", pg_code: "23503", routine: "ri_ReportViolation", schema: "public", severity: "ERROR", table: "chat_messages", unknown: "ERROR"}}}
    (chat_web) lib/chat_web/repo.ex:78: ChatWeb.Repo.append_message_to_store/3
    (chat_web) lib/short_term_message_store.ex:23: ChatWeb.ShortTermMessageStore.handle_cast/2
    (stdlib) gen_server.erl:601: :gen_server.try_dispatch/4
    (stdlib) gen_server.erl:667: :gen_server.handle_msg/5
    (stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3
Last message: {:"$gen_cast", {:published, "USERID", "c78940ab-2514-493e-81fe-64efc63c7bb0", %{"id" => "123", "room_id" => "c78940ab-2514-493e-81fe-64efc63c7bb0"}}}
Run Code Online (Sandbox Code Playgroud)

我相信我将问题追溯到单一测试.当我评论该测试时,我可以一次运行几分钟的测试,而不会看到一个警告.

有问题的代码是:

test_with_mock "publish_message appends message to room", Repo, [], [append_message_to_store: fn(_, _, _) -> true end] do
  room_id = "c78940ab-2514-493e-81fe-64efc63c7bb0"
  {:ok, _room} = open_room room_id
  sock = open_socket()
        |> subscribe_and_join!(RoomChannel, "room:#{room_id}")

  push sock, "publish_message", %{"id" => "123", "room_id" => room_id}

  assert_broadcast "publish_message", %{"id" => "123", "room_id" => room_id, "sequence" => 1}
  {:ok, mailbox} = Rooms.mailbox(room_id)
  assert [%{"id" => "123", "room_id" => "c78940ab-2514-493e-81fe-64efc63c7bb0", "sequence" => 1}] = mailbox
end

defmodule RoomChannel do
  def handle_in("publish_message", payload, socket) do
    {:ok, message} = Rooms.publish(payload["room_id"], payload)
    broadcast(socket, "publish_message", message)

    ShortTermMessageStore.publish(socket.assigns[:user_id], payload["room_id"], payload)

    {:reply, {:ok, %{payload: payload}}, socket}
  end
end

defmodule ShortTermMessageStore do
  def publish(user_id, room_id, message) do
    GenServer.cast(ShortTermMessageStore.address, {:published, user_id, room_id, message})
  end

  def handle_cast({:published, user_id, room_id, message}, state) do
    Logger.debug fn -> "STMS: #{inspect(message)}" end
    Repo.append_message_to_store(user_id, room_id, message)
    {:noreply, state}
  end
end
Run Code Online (Sandbox Code Playgroud)

代码的流程是:RoomChannelhandle_in({:publish_message})叫.这会调用业务逻辑,然后调用ShortTermMessageStore.这样做GenServer.cast,然后返回.测试运行从它停止的地方开始,测试和模拟被拆除.我相信在某些情况下,测试和模拟很快就会被拆除:cast尚未开始,并且Repo的真正实现正在运行.这会生成我上面粘贴的消息,我们尝试运行SQL INSERT语句,但它失败了,因为没有为外键设置数据库.

我知道,按照设计,演员阵容不应该支持回复.为了我的测试的好处,我该如何帮助?回来之前我需要睡觉吗?这看起来粗暴而不雅.

我发现Elixir中GenServers的惯用测试策略是什么?它有很好的元素,但在我的情况下,我没有测试异步对象.这只是调用另一个函数的副作用.

我还发现在Elixir中测试异步代码,这让我觉得我可能需要监视一些东西.

Dog*_*ert 7

您可以等到GenServer通过调用:sys.get_state/1Pid 处理所有待处理的消息.:sys.get_state/1对GenServer进行同步调用以获取其状态,该状态在所有待处理消息被处理后完成.

因此,如果您在测试结束时添加以下代码,那么您的测试将一直等到ShortTermMessageStore.address处理完所有已收到的消息.

:sys.get_state(ShortTermMessageStore.address)
Run Code Online (Sandbox Code Playgroud)