Integration testing di un Hub SignalR - UGIdotNET
Questo sito si serve dei cookie per fornire servizi. Utilizzando questo sito acconsenti all'utilizzo dei cookie. Ulteriori informazioni Ok

Integration testing di un Hub SignalR

di pubblicato il 02/12/2025

ASP.NET Core  SignalR 

In questo tip vediamo come scrivere dei test che ci permettano di verificare che il comportamento di un hub SignalR sia quello desiderato.

Abbiamo già parlato tempo fa di come andare a scrivere dei test di integrazione per le nostre API ASP.NET Core.

Lo stesso approccio può essere utilizzato anche per scrivere degli integration test che verifichino il corretto funzionamento di un hub SignalR.

Partiamo da un semplice hub SignalR come questo:

public class ChatHub : Hub
{
  public async Task SendMessage(string message)
  {
    await Clients.All.SendAsync("ReceivedMessage", message.ToUpper());
  }
}

Come si può vedere questo hub riceve un messaggio nel metodo SendMessage e lo restituisce in uppercase tramite l'evento ReceivedMessage.

Assicuriamoci anche di averlo registrato nel Program.cs della nostra applicazione ASP.NET Core in questo modo:

// ....
app.MapHub<ChatHub>("/chat");

app.Run();

A questo punto andiamo nel nostro progetto di test (in questo esempio utilizzeremo xUnit, ma i concetti sono indipendenti dalla scelta del framework di testing) e assicuriamoci di avere installato i seguenti pacchetti:

  • Microsoft.AspNetCore.Mvc.Testing che ci da la possibilità di instanziare la nostra classe WebApplicationFactory, che ci permette di instanziare un server in-memory per i nostri test.
  • Microsoft.AspNetCore.SignalR.Client che ci pemetterà di utilizzare la classe HubConnection per collegarci al nostro Hub SignalR.

Verifichiamo anche di cambiare l'SDK del nostro csproj di test in Microsoft.NET.Sdk.Web come segue:

<Project Sdk="Microsoft.NET.Sdk.Web">
...
</Project>

Dati questi prerequisiti andiamo a scrivere il nostro test creando una classe ChatHubTest in questo modo:

public class ChatHubTest
{
  [Fact]
  public async Task SendMessage_Should_Rase_ReceivedMessage_With_Uppercased_Text()
  {
    using var factory = new WebApplicationFactory<Program>();

    var httpMessageHandler = factory.Server.CreateHandler();

    var hubUrl = new Uri(factory.Server.BaseAddress, "chat");

    await using var hubConnection = new HubConnectionBuilder()
      .WithUrl(hubUrl, options =>
      {
        options.HttpMessageHandlerFactory = _ => httpMessageHandler;
      })
      .WithAutomaticReconnect()
      .Build();

    string receivedMessage = string.Empty;
    hubConnection.On("ReceivedMessage", (string message) =>
    {
      receivedMessage = message;
    });

    await hubConnection.StartAsync();

    await hubConnection.InvokeAsync("SendMessage", "test");
    Assert.Equal("TEST", receivedMessage);
  }
}

Analizzando questo codice possiamo vedere come una volta creata la classe WebApplicationFactory venga generata una istanza della classe HttpMessageHandler tramite il metodo factory.Server.CreateHandler() e venga composto l'URL completo dell'Hub da invocare utilizzando la proprietà BaseAddress di factory.Server.

Fatto questo possiamo utilizzare la classe HubConnectionBuilder che mette a disposizione SignalR per creare l'istanza della mia connessione in questo modo:

await using var hubConnection = new HubConnectionBuilder()
  .WithUrl(hubUrl, options =>
  {
    options.HttpMessageHandlerFactory = _ => httpMessageHandler;
  })
  .WithAutomaticReconnect()
  .Build();

come possiamo notare l'istanza di HttpMessageHandler viene passata alla proprietà HttpMessageHandlerFactory, istruendo SignalR su come andare ad eseguire le richieste HTTP ed assicurandoci che queste richieste vengano fatte al nostro server di test.

Creata la connection non ci resta che registrarci all'evento o agli eventi che vogliamo verificare, avviare la connessione e invocare il metodo da testare, andando a scrivere i relativi assert:

string receivedMessage = string.Empty;
hubConnection.On("ReceivedMessage", (string message) =>
{
  receivedMessage = message;
});

await hubConnection.StartAsync();

await hubConnection.InvokeAsync("SendMessage", "test");
Assert.Equal("TEST", receivedMessage);

Andando ad eseguire il test potremmo trovarci nella situazione in cui questo si concluda con errori. Questo potrebbe essere dovuto al fatto che l'evento SignalR scatta troppo tardi, per risolvere la situazione potremmo andare ad utilizzare la classe TaskCompletionSource, modificando lo snippet precendete in questo modo:

var taskCompletionSource = new TaskCompletionSource<string>();
hubConnection.On("ReceivedMessage", (string message) =>
{
  taskCompletionSource.TrySetResult(message);
});

await hubConnection.StartAsync();

await hubConnection.InvokeAsync("SendMessage", "test");
string receivedMessage = await taskCompletionSource.Task;

Assert.Equal("TEST", receivedMessage);