2017-05-01 2 views
2

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.

+0

Haben Sie bestätigt, dass die Tests immer bestanden werden, wenn Sie einen langen Schlaf hinzufügen, sagen wir 5 Sekunden? – Dogbert

+0

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. –

+0

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

Antwort

0

Nachdem darüber nachgedacht, verspottete ich die ShortTermMessageStor auch:

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

    push sock, "publish_message", %{"id" => "123", "room_id" => "c78940ab-2514-493e-81fe-64efc63c7bb0"} 

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

dieses Mock benutze, konnte ich die Tests viele Minuten in einer Reihe laufen, ohne Fehler oder Warnmeldungen.

4

Sie können warten, bis der GenServer alle ausstehenden Nachrichten verarbeitet hat, indem Sie :sys.get_state/1 auf dem PID aufrufen. :sys.get_state/1 führt einen synchronen Aufruf an den GenServer durch, um seinen Status abzurufen, der abgeschlossen wird, nachdem alle ausstehenden Nachrichten von ihm verarbeitet wurden.

Also, wenn Sie den folgenden Code am Ende Ihres Tests hinzufügen, wird Ihr Test warten, bis ShortTermMessageStore.address alle Nachrichten verarbeitet hat, die es bereits erhalten hat.

:sys.get_state(ShortTermMessageStore.address) 
Verwandte Themen