[elixir! #0019][译] 测试Phoenix WebSockets by Alex Jensen

原文html

最近我发表了一篇关于如何使用Phoenix的websockets建立一个游戏大厅的文章. 咱们团队很是重视测试, 因此, 今天我将介绍如何测试咱们以前编写的websocket代码.web

准备工做

在开始以前, 咱们须要运行 mix test, 先删除一些默认的测试, 并解决测试中的小问题. 当咱们编写测试以前, 但愿没有其它的错误消息. 接着, 删除全部Phoenix generator 自动生成 channel 测试, 由于咱们将要从头开始编写它们.服务器

Helpers

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

建立 Lobby Channel Test

如今开始编写测试. 首先建立一个新文件 test/channels/lobby_channel_test.exssocket

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 测试桩

在测试中模拟用户的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

Assertions

咱们已经建立了一个用户并加入了 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

在继续以前, 让咱们建立另外一个 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 的文档中的各类功能.

相关文章
相关标签/搜索