测试使用GenServer.cast的代码

时间:2017-05-01 20:20:49

标签: 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"}}}

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

有问题的代码是:

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

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

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

我发现What is the idiomatic testing strategy for GenServers in Elixir?有很好的元素,但在我的情况下,我没有测试异步对象。这只是调用另一个函数的副作用。

我还发现Testing asynchronous code in Elixir让我觉得我可能需要监控一些事情。

2 个答案:

答案 0 :(得分:7)

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

因此,如果您在测试结束时添加以下代码,那么您的测试将等到ShortTermMessageStore.address处理了已收到的所有邮件。

:sys.get_state(ShortTermMessageStore.address)

答案 1 :(得分:0)

在考虑了这个之后,我也嘲笑了ShortTermMessageStore:

rotate

使用这个模拟,我能够连续几分钟运行测试,没有失败或警告信息。