Тестирование каналов
Исходный кодТребование: предполагается, что вы ознакомились с вводными руководствами и запустили приложение Phoenix, следуя руководству Запуск приложения.
Требование: предполагается, что вы ознакомились с руководством Введение в тестирование.
Требование: предполагается, что вы ознакомились с руководством по каналам.
В руководстве по каналам мы увидели, что «канал» — это многоуровневая система, состоящая из различных компонентов. Поэтому в некоторых случаях модульных тестов функций нашего канала может быть недостаточно. Возможно, мы захотим проверить, что его отдельные части работают вместе так, как ожидается. Такое интеграционное тестирование позволит убедиться, что мы правильно определили маршрут канала, модуль канала и его обратные вызовы, а также что нижележащие уровни, такие как PubSub и Transport, настроены правильно и работают должным образом.
Создание каналов
Продвигаясь по этому руководству, полезно иметь конкретный пример, на который можно опираться. В 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
Подобно ConnCase и DataCase, теперь у нас есть 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 do
{:ok, _, socket} =
HelloWeb.UserSocket
|> socket("user_id", %{some: :assign})
|> subscribe_and_join(HelloWeb.RoomChannel, "room:lobby")
%{socket: socket}
end
Блок setup создаёт Phoenix.Socket на основе модуля UserSocket, который находится в lib/hello_web/channels/user_socket.ex. Затем указывается, что нужно подписаться на RoomChannel и присоединиться к нему; он доступен как "room:lobby" в UserSocket. В конце теста мы возвращаем %{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 мы видим карту %{socket: socket}. Она даёт нам доступ к socket из блока настройки.
Мы имитируем отправку клиентом сообщения каналу с помощью push/3. В строке ref = push(socket, "ping", %{"hello" => "there"}) мы отправляем каналу событие "ping" с полезной нагрузкой %{"hello" => "there"}. Это вызывает обратный вызов handle_in/3 для события "ping" в нашем канале. Обратите внимание, что мы сохраняем ref, поскольку в следующей строке он понадобится для проверки ответа. С помощью assert_reply ref, :ok, %{"hello" => "there"} мы проверяем, что сервер отправляет синхронный ответ :ok, %{"hello" => "there"}. Так мы проверяем, что был вызван обратный вызов handle_in/3 для "ping".
Тестирование широковещательной рассылки
Часто клиент отправляет сообщения, которые нужно разослать всем подписчикам текущей темы. В Phoenix этот распространённый шаблон легко реализовать; он используется в одном из созданных обратных вызовов handle_in/3 в HelloWeb.RoomChannel.
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
Мы видим, что обращаемся к тому же socket, что и в блоке настройки. Как удобно! Кроме того, мы используем тот же push/3, что и в тесте синхронного ответа. Итак, мы выполняем push события "shout" с полезной нагрузкой %{"hello" => "all"}.
Поскольку обратный вызов handle_in/3 для события "shout" просто рассылает то же событие и полезную нагрузку, все подписчики "room:lobby" должны получить сообщение. Чтобы проверить это, мы используем assert_broadcast "shout", %{"hello" => "all"}.
ПРИМЕЧАНИЕ: assert_broadcast/3 проверяет, что сообщение было разослано в системе PubSub. Чтобы проверить, получил ли сообщение клиент, используйте assert_push/3.
Тестирование асинхронной отправки с сервера
Последний тест в HelloWeb.RoomChannelTest проверяет, что широковещательные сообщения с сервера отправляются клиенту. В отличие от предыдущих тестов, здесь мы косвенно проверяем, что вызывается обратный вызов канала handle_out/3. По умолчанию handle_out/3 реализован за нас и просто отправляет сообщение клиенту.
Поскольку событие handle_out/3 срабатывает только при вызове broadcast/3 из нашего канала, в тесте нам нужно это имитировать. Для этого мы вызываем broadcast_from или broadcast_from!. Они выполняют одну и ту же задачу; единственное отличие в том, что broadcast_from! вызывает ошибку, если рассылка завершается неудачно.
Строка broadcast_from!(socket, "broadcast", %{"some" => "data"}) вызовет обратный вызов handle_out/3, который отправит клиенту то же событие и полезную нагрузку. Для проверки используем assert_push "broadcast", %{"some" => "data"}.
Вот и всё. Теперь вы готовы разрабатывать и полноценно тестировать приложения реального времени. Подробнее о других возможностях тестирования каналов можно узнать в документации Phoenix.ChannelTest.
© 2014 Chris McCord
Licensed under the MIT License.
https://phoenix.hexdocs.pm/testing_channels.html