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

news/2024/6/29 1:33:12

原文

最近我发表了一篇关于如何使用Phoenix的websockets创建一个游戏大厅的文章. 我们团队非常重视测试, 所以, 今天我将介绍如何测试我们之前编写的websocket代码.

准备工作

在开始之前, 我们需要运行 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 设置一个别名:

using do
  quote do
    ...
    alias MyApp.{Repo, User}
    ...
  end
end

创建 Lobby Channel Test

现在开始编写测试. 首先创建一个新文件 test/channels/lobby_channel_test.exs

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 赋值.

使用如下方法创建一个带有 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}, 不过我们没有从大厅系统返回任何值, 所以使用 _ 来忽略.

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


http://www.niftyadmin.cn/n/2752669.html

相关文章

测试计划以及ACC介绍

2019独角兽企业重金招聘Python工程师标准>>> 测试计划是最早出现,最先被遗忘的测试产物.在项目早期,测试计划代表了对软件功能的预期.但是,除非得到持续的关注,它会很快随着新代码的完成,功能特性的改变以及设计的调整而过期.伴随着计划内或计划外的变更,维护一份测…

weblogic机器根目录空闲小于20%处理

处理办法:nohup.out会一直一直自己增长下去,如果你的服务器硬盘不给力的话,很容易把应用也挂掉,但是又不能一味的直接删。因为直接删除,可能会造成应用无法打印后续的错误日志,该问题常见于weblogic服务器&…

基于JQuery的获取鼠标进入和离开容器方向的实现

基于JQuery的获取鼠标进入和离开容器方向的实现 做动画时&#xff0c;需要判断鼠标进入和退出容器的方向。网上找到的基于JQuery的实现方法&#xff0c;用函数封装了一下&#xff0c;写了一个示例。注意绑定鼠标事件用的是on()&#xff0c;所以JQuery版本需高于1.7。 1 <!DO…

android wifi P2P CONNECT, INVITE和JOIN流程选择

android wifi P2P CONNECT, INVITE和JOIN流程选择 android wifi P2P CONNECT, INVITE和JOIN流程选择posted on 2016-12-30 13:56 xuhaohunter 阅读(...) 评论(...) 编辑 收藏 转载于:https://www.cnblogs.com/xuhaohunter/p/6236661.html

Javascript提交表单

<form action"login.do?actlogin" method"post"> <input type"submit" name"submit1" value"登陆"> </form> <form action"login.do?actlogin" name"myform" method&quo…

一切均出自实践

2019独角兽企业重金招聘Python工程师标准>>> 对于大多数中小型组织&#xff0c;甚至个人而言&#xff0c;云计算的魅力来自那些灵活、弹性和随时随处可用的云计算服务。 云计算应该包含两方面的内容&#xff1a;服务和平台。云计算即是商业模式&#xff0c;也是技术…

函数和函数指针的定义

2019独角兽企业重金招聘Python工程师标准>>> 函数的声明形参的名字可以省略&#xff0c;例如函数int main(int a,int b)可以写成int main(int,int)&#xff1b; 函数指针的定义也可以省略形参的名字&#xff0c;例如int (*p)(int a,int b)写成int (*p)(int,int); 起…

Java类型推断将不再支持可变性规范

Java类型推断是一项推荐的Java特性&#xff0c;允许开发人员使用var关键字代替显式的变量类型声明。最近的报道显示&#xff0c;由于社区内无法就区分可变和不可变变量的实现方式达成一致意见&#xff0c;Java类型推断将不再支持使用关键字区分可变的和不可变变量。提议的一些用…