Ich habe einen Test, der den App-weiten Repo spottet. Die meiste Zeit sind die Tests grün. Wenn ich den Test in einer Schleife ausgeführt, das gleiche Saatgut verwendet wird, gelingt vielleicht 10% der Pisten, aber mit einer GenServer Abschlussmeldung:Testcode, der GenServer.cast verwendet
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"}}}
Ich glaube, ich das Problem auf einen einzigen Test verfolgt. Wenn ich diesen Test auskommentiere, kann ich die Tests jeweils für Minuten durchführen, ohne eine einzige Warnung zu sehen.
Der Code in Frage:
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
Der Fluss der Code ist: RoomChannel
‚s handle_in({:publish_message})
genannt wird. Dies ruft die Geschäftslogik auf und ruft dann die ShortTermMessageStore
auf. Dies macht eine GenServer.cast
, und kehrt dann zurück. Die Testläufe gehen dort weiter, wo sie aufgehört haben, und der Test und der Schein werden abgebrochen. Ich glaube, dass in einigen Fällen der Test und der Schein zu früh abgebrochen werden: Die cast
hat noch nicht begonnen, und die eigentliche Implementierung von Repo wird ausgeführt. Dies erzeugt die Nachricht, die ich oben eingefügt habe, wo wir versuchen, eine SQL-Anweisung INSERT
auszuführen, aber es schlägt fehl, weil die Datenbank nicht für Fremdschlüssel eingerichtet wurde.
Ich weiß, dass Casts keine Antworten unterstützen sollen. Wie kann ich für meine Tests helfen? Muss ich schlafen, bevor ich zurückkehre? Das scheint grob und unelegant.
Ich habe What is the idiomatic testing strategy for GenServers in Elixir? gefunden, die gute Elemente hat, aber in meinem Fall teste ich das asynchrone Objekt nicht. Es ist nur ein Nebeneffekt des Aufrufs in eine andere Funktion.
Ich fand auch Testing asynchronous code in Elixir, die mich denken lassen, dass ich etwas überwachen müsste.
Haben Sie bestätigt, dass die Tests immer bestanden werden, wenn Sie einen langen Schlaf hinzufügen, sagen wir 5 Sekunden? – Dogbert
Schlafen 100ms ermöglicht meine Tests zu bestehen, also ja, ich bin mir ziemlich sicher, dass dieser Test das Problem ist, und dass die Reihenfolge, in der der Test abgebrochen wird, Auswirkungen auf die Protokolle hat. –
Können Sie versuchen, dies anstelle von Schlaf zu tun: ': sys.get_state (ShortTermMessageStore.address)'. Das sollte warten, bis alle an "ShortTermMessageStore.address" gesendeten Nachrichten verarbeitet wurden, bevor der Status zurückgegeben wird. Lassen Sie es mich wissen, wenn es funktioniert, wenn Sie die Tests in einer Schleife ausführen. – Dogbert