原文html
最近我發表了一篇關於如何使用Phoenix的websockets建立一個遊戲大廳的文章. 咱們團隊很是重視測試, 因此, 今天我將介紹如何測試咱們以前編寫的websocket代碼.web
在開始以前, 咱們須要運行 mix test
, 先刪除一些默認的測試, 並解決測試中的小問題. 當咱們編寫測試以前, 但願沒有其它的錯誤消息. 接着, 刪除全部Phoenix generator 自動生成 channel 測試, 由於咱們將要從頭開始編寫它們.服務器
Phoenix已經在你的項目中建立了一些支持文件, 你能夠在裏面定義經常使用的測試函數, 相似於其它語言裏的 test_helper/spec_helper
. 這裏有一個針對channel test 的文件 test/support/channel_case.ex
, 咱們稍後會在其中加入一些代碼. 咱們在 quote do ... end
結構中添加的代碼會被每一個 channel test 模塊引用. 在這裏, 咱們想要爲 MyApp.User 設置一個別名:websocket
using do quote do ... alias MyApp.{Repo, User} ... end end
如今開始編寫測試. 首先建立一個新文件 test/channels/lobby_channel_test.exs
socket
defmodule MyApp.LobbyChannelTest do use MyApp.ChannelCase alias MyApp.LobbyChannel test "give me a dot" do assert 1 == 1 end end
注意, 咱們須要 use MyApp.ChannelCase
來從 test/support
文件中引入 helpers.函數
運行 mix test
, 若是一切正確你會收到測試經過的消息.測試
在測試中模擬用戶的socket鏈接, 可能比你預想的要簡單. 在咱們的代碼裏, 用戶須要一個token才能鏈接, 它是在 web/channels/user_socket.ex
文件中進行檢查的. 由於咱們能夠直接建立一個鏈接到channel的socket, 因此咱們不須要經過任何驗證. 然而, 咱們仍然須要設置一個由驗證得到的 current_user
賦值.ui
使用以下方法建立一個帶有 current_user
的socket:編碼
def create_user(username) do %User{} |> User.changeset(%{username: username, password: "passw0rd", password_confirmation: "passw0rd"}) |> Repo.insert end test "user receives the current list of users online" do {:ok, user} = create_user("jerry") {:ok, _, socket} = socket("", %{current_user: user}) |> subscirbe_and_join(LobbyChannel, "game:lobby") end
這裏咱們定義了一個使用給定的用戶名建立新用戶的函數. 這個函數也能夠定義在test/support/channel_case.ex
文件中, 但如今先把它留在這裏. 建立用戶以後, 咱們將其傳遞給 socket
函數, 它是Phoenix引入的函數. 這樣就設置好了這個socket的 assigns. 而後將socket 和 channel模塊, 以及要加入的房間名一併傳入 subscribe_and_join
函數. 若是成功會返回 {:ok, response, socket}
, 不過咱們沒有從大廳系統返回任何值, 因此使用 _
來忽略.code
咱們已經建立了一個用戶並加入了 channel, 但如何判斷它是否收到了當前的大廳狀態呢? 咱們加入的 channel 不會返回一個包含當前用戶的迴應, 而是在 join 事件以後將數據廣播出來. Phoenix 提供了一個用於檢查廣播的函數 assert_broadcast
.
當運行一個 channel test 時, Phoenix會保存一個每次發送的廣播的列表, 包含事件名和數據. assert_broadcast
會在這個列表中查找你所指望的廣播, 若是找到則返回 true. 若是你指望的廣播沒有在超時(assert_broadcast
函數的第三個參數)以前被髮送, 這個assertion 便會失敗. 在這裏, 咱們 assert 廣播的事件是 lobby_update
, 附帶包含了 ["jerry"]
的users:
test "user receives the current list of users online" do {:ok, user} = create_user("jerry") {:ok, _, socket} = socket("", %{current_user: user}) |> subscribe_and_join(LobbyChannel, "game:lobby") assert_broadcast "lobby_update", %{users: ["jerry"]} end
這個 lobby_update
測試如今應該能夠經過了, 但還有一個小問題. 在用戶加入時會被添加到當前用戶列表, 在離開時會被刪除, 而咱們如今只加入了. 感謝Phoenix提供了一個離開函數, 咱們能夠這樣使用:
test "user receives the current list of users online" do {:ok, user} = create_user("jerry") {:ok, _, socket} = socket("", %{current_user: user}) |> subscribe_and_join(LobbyChannel, "game:lobby") assert_broadcast "lobby_update", %{users: ["jerry"]} leave socket end
在繼續以前, 讓咱們建立另外一個 helper 函數來建立一個用戶並加入"game:lobby"頻道, 由於咱們須要在後面的測試中重複這個步驟.
def create_user_and_join_lobby(username) do {:ok, user} = create_user(username) socket("", %{current_user: user}) |> subscirbe_and_join(LobbyChannel, "game:lobby") end
如今咱們能夠將以前的測試改成:
test "user receives the current list of users online" do {:ok, _, socket} = create_user_and_join_lobby("jerry") assert_broadcast "lobby_update", %{users: ["jerry"]} leave socket end
爲了測試咱們的遊戲邀請, 咱們將建立兩個用戶, 發送邀請事件到服務器, 並檢查是否有邀請發送給用戶:
test "user receives an invite" do {:ok, _, socket1} = create_user_and_join_lobby("bill") {:ok, _, socket2} = create_user_and_join_lobby("will") push socket1, "game_invite", %{"username" => "will"} end
若是你去看 "game_invite" 的代碼, 你會發現有一個包含發送者和用戶名的廣播被髮出了, 可是廣播被攔截了, 只會發送給正確的用戶. 在這裏, 咱們不可使用 assert_broadcast
, 由於咱們想要檢查的消息並無被廣播. 咱們可使用 assert_push
:
test "user receives an invite" do {:ok, _, socket1} = create_user_and_join_lobby("bill") {:ok, _, socket2} = create_user_and_join_lobby("will") push socket1, "game_invite", %{"username" => "will"} assert_push "game_invite", %{username: "bill"} end
觀察以上代碼, 你可能想知道爲何 username
鍵一下子是一個字符串, 一下子是原子. 一般, 你的socke會將全部東西編碼成JSON發送給客戶端, 這會使全部這些變成字符串. channel 測試擁有對 channel更直接的控制, 因此咱們要確保和實際收送的數據徹底一致.
確保你離開了每一個socket:
test "user receives an invite" do {:ok, _, socket1} = create_user_and_join_lobby("bill") {:ok, _, socket2} = create_user_and_join_lobby("will") push socket1, "game_invite", %{"username" => "will"} assert_push "game_invite", %{username: "bill"} leave socket1 leave socket2 end
咱們簡短地介紹瞭如何測試 Phoenix websockets. 剛開始你可能認爲測試很可怕, 事實上他們很簡單快捷. 對於大型應用, 定義強大的 helper 尤其重要. 記得查看 ExUnit 的文檔中的各類功能.