Course contentsShow
← C#
Lesson 1512 of 1,90813. Real-time Communication with SignalRFree lesson

Automated Testing of Hub Workflows

Build integration suites that exercise the full message lifecycle from client request to server broadcast.

In this lesson, you will learn how to build integration suites that validate the full lifecycle of a SignalR message, ensuring that a request sent by a client correctly triggers a server-side broadcast to other connected clients.

Testing the End-to-End Lifecycle

In automated testing, simply invoking a Hub method is insufficient because it ignores the side effects—the server broadcasts that follow. To build a robust integration suite, you need a test harness that spins up an in-memory TestServer (using Microsoft.AspNetCore.TestHost) and manages multiple concurrent client connections.

The core challenge is synchronization: the test must act as both the "triggering" client and the "observing" client simultaneously. You can achieve this by creating a mock client implementation that tracks received messages in a thread-safe collection.

Implementation Pattern

Here is how you structure a test that triggers an action and verifies the resulting broadcast:

[Fact]
public async Task HubMethod_ShouldBroadcastUpdate_ToAllClients()
{
    // Arrange
    var factory = new WebApplicationFactory<Program>();
    var hubConnection = factory.CreateHubConnectionBuilder().Build();
    var receivedMessages = new ConcurrentBag<string>();

    hubConnection.On<string>("ReceiveUpdate", msg => receivedMessages.Add(msg));
    await hubConnection.StartAsync();

    // Act
    await hubConnection.InvokeAsync("SendAction", "TestPayload");

    // Assert
    // Poll or use a task completion source to wait for the broadcast
    Assert.Contains("TestPayload", receivedMessages);
}

Orchestrating the Flow

When writing these tests, avoid using Task.Delay to wait for messages. Instead, use a TaskCompletionSource<T> to create an asynchronous "gate." The ReceiveUpdate handler should call tcs.SetResult(payload) when the expected message arrives. This allows your test to timeout gracefully if the broadcast fails, rather than waiting for a fixed period.

By simulating multiple connections, you ensure your Hub’s Groups and Clients.Others logic behaves correctly under load. Always ensure your test cleanup shuts down both the server and all hub connections to prevent port conflicts or state leakage between test runs.

Key Takeaway: To verify hub workflows, treat the Hub as an asynchronous event processor by using a dedicated observer client that signals the completion of the broadcast lifecycle via TaskCompletionSource.