查看源代码 测试通道

要求: 本指南假设您已经阅读过入门指南,并成功启动了一个 Phoenix 应用程序运行起来.

要求: 本指南假设您已经阅读过测试简介指南.

要求: 本指南假设您已经阅读过通道指南.

在通道指南中,我们了解到“通道”是一个分层系统,具有不同的组件。因此,在某些情况下,为通道函数编写单元测试可能不够。我们可能需要验证其不同的移动部件是否按预期协同工作。这种集成测试将确保我们正确定义了通道路由、通道模块及其回调;并且确保底层层(如 PubSub 和传输)配置正确,并按预期工作。

生成通道

随着我们逐步完成本指南,拥有一个可以作为参考的具体示例将会很有帮助。Phoenix 提供了一个 Mix 任务用于生成基本的通道和测试。这些生成的代码文件可以作为编写通道及其对应测试的良好参考。让我们开始生成我们的通道

$ mix phx.gen.channel Room
* creating lib/hello_web/channels/room_channel.ex
* creating test/hello_web/channels/room_channel_test.exs
* creating test/support/channel_case.ex

The default socket handler - HelloWeb.UserSocket - was not found.

Do you want to create it? [Yn]  
* creating lib/hello_web/channels/user_socket.ex
* creating assets/js/user_socket.js

Add the socket handler to your `lib/hello_web/endpoint.ex`, for example:

    socket "/socket", HelloWeb.UserSocket,
      websocket: true,
      longpoll: false

For the front-end integration, you need to import the `user_socket.js`
in your `assets/js/app.js` file:

    import "./user_socket.js"

这将创建一个通道、其测试,并指示我们在 lib/hello_web/channels/user_socket.ex 中添加通道路由。添加通道路由至关重要,否则我们的通道将无法正常工作!

ChannelCase

打开 test/hello_web/channels/room_channel_test.exs,您会找到以下内容

defmodule HelloWeb.RoomChannelTest do
  use HelloWeb.ChannelCase

ConnCaseDataCase 类似,我们现在拥有一个 ChannelCase。这三个都在我们启动 Phoenix 应用程序时为我们生成了。让我们看一下它。打开 test/support/channel_case.ex

defmodule HelloWeb.ChannelCase do
  use ExUnit.CaseTemplate

  using do
    quote do
      # Import conveniences for testing with channels
      import Phoenix.ChannelTest
      import HelloWeb.ChannelCase

      # The default endpoint for testing
      @endpoint HelloWeb.Endpoint
    end
  end

  setup _tags do
    Hello.DataCase.setup_sandbox(tags)
    :ok
  end
end

它非常简单。它设置了一个用例模板,该模板在使用时导入所有 Phoenix.ChannelTest。在 setup 块中,它启动了 SQL Sandbox,我们在测试上下文指南 中讨论过。

订阅和加入

现在我们已经知道 Phoenix 提供了一个专门用于通道的自定义测试用例,以及它提供的功能,我们可以继续了解 test/hello_web/channels/room_channel_test.exs 的其余部分。

首先是 setup 块

setup do
  {:ok, _, socket} =
    HelloWeb.UserSocket
    |> socket("user_id", %{some: :assign})
    |> subscribe_and_join(HelloWeb.RoomChannel, "room:lobby")

  %{socket: socket}
end

setup 块基于 UserSocket 模块设置了一个 Phoenix.Socket,您可以在 lib/hello_web/channels/user_socket.ex 中找到它。然后它指出我们想要订阅并加入 RoomChannel,在 UserSocket 中可以访问为 "room:lobby"。在测试结束时,我们将 %{socket: socket} 作为元数据返回,以便我们可以在每个测试中重复使用它。

简而言之,subscribe_and_join/3 模拟了客户端加入通道并将测试进程订阅到给定主题。这是必要的步骤,因为客户端需要在向通道发送和接收事件之前加入通道。

测试同步回复

我们生成的通道测试中的第一个测试块如下所示

test "ping replies with status ok", %{socket: socket} do
  ref = push(socket, "ping", %{"hello" => "there"})
  assert_reply ref, :ok, %{"hello" => "there"}
end

这测试了我们 HelloWeb.RoomChannel 中的以下代码

# Channels can be used in a request/response fashion
# by sending replies to requests from the client
def handle_in("ping", payload, socket) do
  {:reply, {:ok, payload}, socket}
end

如上面的注释中所述,我们看到 reply 是同步的,因为它模拟了我们对 HTTP 熟悉的请求/响应模式。这种同步回复最适合我们只想在服务器上处理完消息后才向客户端发送事件的情况。例如,当我们将某些内容保存到数据库中,然后仅在保存完成后向客户端发送消息时。

test "ping replies with status ok", %{socket: socket} do 行,我们看到我们拥有 map %{socket: socket}。这让我们可以在 setup 块中访问 socket

我们使用 push/3 模拟了客户端向通道推送消息。在行 ref = push(socket, "ping", %{"hello" => "there"}) 中,我们将事件 "ping" 与有效负载 %{"hello" => "there"} 推送到通道。这将触发我们在通道中为 "ping" 事件定义的 handle_in/3 回调。请注意,我们存储了 ref,因为我们将在下一行需要它来断言回复。使用 assert_reply ref, :ok, %{"hello" => "there"},我们断言服务器发送了同步回复 :ok, %{"hello" => "there"}。这就是我们如何检查是否触发了 "ping"handle_in/3 回调。

测试广播

常见情况是接收来自客户端的消息并广播给所有订阅了当前主题的人。这种常见模式在 Phoenix 中非常容易表达,并且是我们 HelloWeb.RoomChannel 中生成的 handle_in/3 回调之一。

def handle_in("shout", payload, socket) do
  broadcast(socket, "shout", payload)
  {:noreply, socket}
end

其对应的测试如下所示

test "shout broadcasts to room:lobby", %{socket: socket} do
  push(socket, "shout", %{"hello" => "all"})
  assert_broadcast "shout", %{"hello" => "all"}
end

我们注意到我们访问了与 setup 块相同的 socket。多方便!我们还执行了与同步回复测试相同的 push/3 操作。因此,我们使用 %{"hello" => "all"} 有效负载 push"shout" 事件。

由于 "shout" 事件的 handle_in/3 回调只是广播了相同的事件和有效负载,因此所有订阅了 "room:lobby" 的订阅者都应该收到消息。为了检查这一点,我们执行了 assert_broadcast "shout", %{"hello" => "all"}

注意:assert_broadcast/3 测试消息是否在 PubSub 系统中广播。要测试客户端是否接收消息,请使用 assert_push/3

测试来自服务器的异步推送

我们 HelloWeb.RoomChannelTest 中的最后一个测试验证了来自服务器的广播是否被推送到客户端。与之前讨论的测试不同,我们间接测试了通道的 handle_out/3 回调是否被触发。默认情况下,handle_out/3 为我们实现,只是将消息推送到客户端。

由于只有在从通道中调用 broadcast/3 时才会触发 handle_out/3 事件,因此我们需要在测试中模拟这一点。我们通过调用 broadcast_frombroadcast_from! 来实现。两者服务于相同目的,唯一的区别是 broadcast_from! 在广播失败时会引发错误。

broadcast_from!(socket, "broadcast", %{"some" => "data"}) 将触发 handle_out/3 回调,该回调将相同的事件和有效负载推回客户端。为了测试这一点,我们执行了 assert_push "broadcast", %{"some" => "data"}

就是这样。现在您可以开发和全面测试实时应用程序。要详细了解测试通道时提供的其他功能,请查看 Phoenix.ChannelTest 的文档。