原文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 的文档中的各类功能.