Перейти к основному содержимому
Версия: 5.x

Тестирование обработчиков

DarkWS.Testing запускает обработчики без HTTP-сервера, WebSocket и Redis. Пакет не зависит от тестового фреймворка, поэтому работает с NUnit, xUnit, MSTest и чем угодно ещё.

dotnet add package DarkWS.Testing

Вызов действия через конвейер​

DarkWsTestHost собирает провайдер сервисов с вашими регистрациями DarkWS и вызывает зарегистрированные действия точно так же, как это делает сервер:

using System.Text.Json;
using DarkWS.Testing;
using Microsoft.Extensions.DependencyInjection;

await using var host = new DarkWsTestHost(builder => {
builder.AddHandlersFromAssemblyContaining<MessageHandler>();
builder.Services.AddScoped<IMessageStore, FakeMessageStore>();
});

var connection = host.CreateConnection(); // anonymous
await host.InvokeAsync(connection, "message:echo",
JsonSerializer.SerializeToElement("hello"), requestId: "1");

using var response = JsonDocument.Parse(connection.SentMessages.Single());
Assert.That(response.RootElement.GetProperty("data").GetString(), Is.EqualTo("hello"));

InvokeAsync использует настоящие регистрацию, авторизацию, привязку JSON, инициализаторы области, фильтры действий, преобразование ошибок, сериализацию и асинхронное освобождение области. Каждый вызов создаёт новую область сообщения. Необязательный второй аргумент конструктора настраивает DarkWsOptions, например options => options.AllowNullPayloads = true. host.Services — корневой провайдер сервисов.

Аутентифицированные вызовы​

Передайте сессию в CreateConnection. Её principal должен быть аутентифицирован (Identity.IsAuthenticated равно true):

var user = new ClaimsPrincipal(new ClaimsIdentity([new Claim("sub", "42")], "test"));
var connection = host.CreateConnection(new AppSession("session-1", user, accountId, userId));

Сессия тестового подключения фиксируется при его создании.

Рассылки​

host.Broadcasts записывает каждую опубликованную рассылку с её целью и JSON-данными. Настоящий broadcaster также направляет их подключениям, созданным хостом, поэтому их SentMessages содержат и ответы, и конверты рассылок (id равен "@"):

var sender = host.CreateConnection(aliceSession);
var receiver = host.CreateConnection(bobSession); // same account group

await host.InvokeAsync(sender, "message:send",
JsonSerializer.SerializeToElement(new { text = "hi" }), requestId: "1");

Assert.That(host.Broadcasts.Single().Action, Is.EqualTo("message:created"));
Assert.That(receiver.SentMessages, Has.Count.EqualTo(1));

Чтобы проверить выбор получателей, создайте несколько подключений с разными сессиями и группами. Захваченные массивы байтов — это копии; считайте их доступными только для чтения.

Модульный тест обработчика напрямую​

Чтобы вызвать метод обработчика без диспетчера, инициализируйте его контекст из области:

await using var scope = host.CreateScope(connection);
var handler = new MessageHandler(new FakeMessageStore());
scope.Initialize(handler);

var result = handler.Echo("hello");
await result.WriteResultAsync(new ResponseContext(connection, "direct", new DarkWsOptions()));

scope.Services разрешает зависимости конструктора и зарегистрированные обработчики. Прямая инициализация задаёт подключение, сессию, отмену, сервисы и broadcaster, но пропускает регистрацию, авторизацию, инициализаторы области и фильтры, а метаданные действия равны null. Используйте InvokeAsync, когда эти проверки важны. Не освобождайте область, пока не проверите или не запишете результат.

Что хост не покрывает​

Хост всегда использует изолированный in-memory backplane, даже если ваша конфигурация регистрирует Redis. Он не запускает hosted-сервисы, HTTP middleware, обмены аутентификации, хуки жизненного цикла подключения, транспортные очереди и таймауты, а также ограничения фреймов. Покрывайте это интеграционными тестами с настоящим сервером, например с помощью WebApplicationFactory и DarkWS.Client.

Настраивайте получателей до вызова параллельных действий, а перед освобождением хоста дожидайтесь завершения всех вызовов и освобождайте созданные вручную области.