Since ASP.NET Core 2.1, Microsoft has brought SignalR back to life again. SignalR is a library that allows you to add real-time functionality to your web applications. It supports both “server to client” and “client to server” communication. It does all sorts of magic like handling connection management, scaling and automatically choosing the best supported transport method.
If you’re not familiar with SignalR, I suggest going throughthe docs before continuing further, as the rest of the article assumes that you have at least some basic knowledge about using the library.
Editorial Note: If you are new to creating webapps with SignalR and ASP.NET Core, check out this rock solid tutorial www.dotnetcurry.com/aspnet-core/1480/aspnet-core-vuejs-signalr-app.
If you don’t feel like going through the docs right now, you may get away with knowing that SignalR uses a concept called a “hub”. A SignalR hub is basically an endpoint to which clients can connect to start receiving or sending messages.
On the server, a hub is represented by a class. You can define methods on it that can be called by the clients, or send messages to those clients through the IHubContext<THub> interface.
Integration Testing of SignalR apps - Setting up the test scenario
For our test case, the hub won’t define any methods for the clients to call. It will just sit there and wait for connections.
public class TestHub : Hub
{
}
We will expose it on the /testHub route using the UseSignalR middleware.
app.UseSignalR(routes =>
{
routes.MapHub<TestHub>("/testHub");
});
What MapHub does is it creates an endpoint that clients can connect to. If we wanted to have methods that the clients could call, we could’ve defined them inside the TestHub class.
In our test scenario, however, we will be testing only “server to client” communication. Let’s define an object that will provide us the ability to send messages to the hub subscribers. As we mentioned earlier, the implementation will make use of the IHubContext<THub> interface.
public interface ITestHubDispatcher
{
Task Dispatch(Notification notification);
}
public class TestHubDispatcher : ITestHubDispatcher
{
private readonly IHubContext<TestHub> _hubContext;
public TestHubDispatcher(IHubContext<TestHub> hubContext)
{
_hubContext = hubContext;
}
public Task Dispatch(Notification notification) =>
_hubContext
.Clients
.All
.SendAsync(nameof(Notification), notification);
}
public class Notification
{
public string Message { get; set; }
}
I like to call such objects “dispatchers”.
In most applications, events or notifications will usually be dispatched after some API operation has completed.
Let’s create a mock API endpoint that will use our new dispatcher to send a notification to all hub subscribers.
[Route("[controller]")]
public class HubController : Controller
{
private readonly ITestHubDispatcher _dispatcher;
public HubController(ITestHubDispatcher dispatcher)
{
_dispatcher = dispatcher;
}
[HttpPost("test")]
public async Task<IActionResult> Test([FromBody] Notification notification)
{
await _dispatcher.Dispatch(notification);
return Ok();
}
}
The integration tests we will be writing will test whether when a POST request is submitted to the hub/test endpoint all subscribers to the TestHub are properly notified.
Why integration tests and not just unit tests?
Unit tests are nice, but all of the mocking and setup can easily distract from what we’re actually testing. We need to get intimately familiar with how objects are constructed, the application interfaces, their behavior and role in the implementation.
Of course, unit tests play an important part in the act of delivering quality software. It’s not worth it to spin up a whole integration testing infrastructure just to cover a few pure, reusable components.
However, for interactive functionality that you will expose to users, integration tests are substantially more valuable.
Editorial Note: Read more about Integration Testing for ASP.NET Core Applications at www.dotnetcurry.com/aspnet-core/1420/integration-testing-aspnet-core
Sure, the infrastructure setup may be a bit tedious sometimes, but with tools such as Docker, this shouldn’t be a problem. After the initial setup, we are free to test the system in the same way it’s going to be used by users and without polluting our tests with implementation details.
For our test setup, we will be aiming for our tests to look more like this:
// Connect to the TestHub on {appUrl}/testHub
// Submit a POST request on {appUrl}/hub/test with a valid message
// Verify that a correct message was received
and less like this:
// Mock the IHubContext<TestHub>
// Create a new instance of TestHubDispatcher using the HubContext mock
// Call the dispatcher's Dispatch function
// Verify that the HubContext mock was called with the proper arguments
// ...repeat for the controller
You see how in the second case we are required to know that there is a TestHubDispatcher implementation that uses an IHubContext<TestHub>, and that the HubController depends on a TestHubDispatcher instance, and so on.
All of the mocking and setup distracts us from what we’re trying to test. And what we’re trying to test is whether the system behaves as expected when interacted with from the outside.
Setting up the tests infrastructure
Normally, as we will find in the docs, to write integration tests for an ASP.NET Core application, we would use theTestServer class. TestServer can be used for calling the controller HTTP endpoint, but after that, we will quickly (or not so quickly, depends on how much time we spend debugging) find out that SignalR won’t work, because TestServer does not yet support WebSockets (more info about thathere).
Fortunately, there is an easy solution to this problem, and it lies just in front of us - inside the Program.cs file. If you open it, it probably looks something like this:
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
What this call to CreateDefaultBuilder does is call UseKestrel behind the scenes. If you haven’t heard of Kestrel up until now, it’s the web server that was introduced together with ASP.NET Core.
I won’t get into details, but if you’ve seen the console window that pops up when you press “Ctrl + F5” in Visual Studio, then you’ve seen Kestrel (you can learn more about it by reading the docs).
Kestrel is what allows .NET Core apps to be cross platform. For the sake of this article, think about Kestrel as our application.
And how does that help?
Well, Kestrel decouples our application from specific server implementations such as IIS, Apache or Nginx by providing a consistent startup pipeline.
We can execute this pipeline ourselves to get a running instance of our application that we can use for integration testing. This goes around the problem of TestServer not supporting WebSockets by not using TestServer at all.
We just need to create a class that will encapsulate this startup logic.
public class AppFixture
{
public const string BaseUrl = "http://localhost:54321";
static AppFixture()
{
var webhost = WebHost
.CreateDefaultBuilder(null)
.UseStartup<Startup>()
.UseUrls(BaseUrl)
.Build();
webhost.Start();
}
public string GetCompleteServerUrl(string route)
{
route = route?.TrimStart('/', '\\');
return $"{BaseUrl}/{route}";
}
}
AppFixture is simply mimicking what our application’s Main method is doing - starting the Kestrel web server.
When this class is instantiated for the first time, an instance of our app will be started on port 54321.
Why a static constructor you may ask? Because we really only need one server running per test run.
AppFixture also provides a neat way of building urls through GetCompleteServerUrl, which will later come in handy.
An example usage would look like
// Gives us a running server at http://localhost:54321
var fixture = new AppFixture();
// Returns http://localhost:54321/some/route
var url = fixture
.GetCompleteServerUrl("/some/route");
There are a few more things we need to be clear on about before starting to write the actual test code.
Communicating with our SignalR hub
For communicating with the SignalR hub, we will be using theSignalR.Client package. It gives us a way of creating persistent connections to our hub and listening for messages that are emitted from it. Some example usage:
HubConnection connection = new HubConnectionBuilder()
.WithUrl("http://localhost:54321/testHub")
.Build();
await connection.StartAsync();
// "Notification" being the expected event name
connection.On("Notification", notification =>
{
// Do something with the notification
});
You can read more about SignalR.Client in the docs.
For our tests, in order to instantiate new connections, we’ll be using the following helper function.
private static async Task<HubConnection> StartConnectionAsync(string hubUrl)
{
var connection = new HubConnectionBuilder()
.WithUrl(hubUrl)
.Build();
await connection.StartAsync();
return connection;
}
Verifying handlers were called with proper arguments
You see how the HubConnection’s .On method accepts a callback? Later on, when verifying whether a correct message was received, we’ll need to check whether the callback we’ve passed has been called with the proper arguments.
This can be a pain to implement.
Fortunately, there is Moq. Moq allows us to create a mock function and then use its built-in Verify method to check whether it was called with the correct parameters. The following snippet will create a mock Action<Notification> and assert that it was called with a message of “whatever”.
var mockHandler = new Mock<Action<Notification>>();
mockHandler.Verify(
x => x(It.Is<Notification>(n => n.Message == "whatever")),
Times.Once());
It even gives us a Times struct. How cool is that!
Converting our test description to actual code
We’ve collected enough knowledge to start converting our test description into actual, working code. How about we take one more look at it?
// 1. Connect to the TestHub on {appUrl}/testHub
// 2. Submit a POST request on {appUrl}/hub/test with a valid message
// 3. Verify that a correct message was received
Let’s go operation by operation.
// 1. Connect to the TestHub on {appUrl}/testHub
// Arrange
var fixture = new AppFixture();
var connection = await StartConnectionAsync(fixture.GetCompleteServerUrl("/testHub"));
// Using a mock handler so we can make use of the Verify method
var mockHandler = new Mock<Action<Notification>>();
connection.On(nameof(Notification), mockHandler.Object);
This code looks pretty obvious after we’ve gotten familiar with SignalR.Client and Moq.
// 2. Submit a POST request on {appUrl}/hub/test with a valid message
var notificationToSend = new Notification { Message = "test message" };
// Act
using (var httpClient = new HttpClient())
{
// POST the notification to http://localhost:54321/hub/test
await httpClient.PostAsJsonAsync(fixture.GetCompleteServerUrl("/hub/test"), notificationToSend);
}
We’re using the built-in HttpClient. If you need more info about it, check out the docs.
// 3. Verify that a correct message was received
// Assert
mockHandler.Verify(
x => x(It.Is<Notification>(n => n.Message == notificationToSend.Message)),
Times.Once());
This is where we thank Moq’s creators for the awesome syntax.
The whole class now looks like this.
public class TestHubTests
{
[Fact]
public async Task ShouldNotifySubscribers()
{
// Arrange
var fixture = new AppFixture();
// 1. Connect to the TestHub on {appUrl}/testHub
var connection = await StartConnectionAsync(fixture.GetCompleteServerUrl("/testHub"));
// Using a mock handler so we can make use of the Verify method
var mockHandler = new Mock<Action<Notification>>();
connection.On(nameof(Notification), mockHandler.Object);
var notificationToSend = new Notification { Message = "test message" };
// Act
using (var httpClient = new HttpClient())
{
// 2. Submit a POST request on {appUrl}/hub/test with a valid message
await httpClient.PostAsJsonAsync(fixture.GetCompleteServerUrl("/hub/test"), notificationToSend);
}
// Assert
// 3. Verify that a correct message was received
mockHandler.Verify(x => x(It.Is<Notification>(n => n.Message == notificationToSend.Message)), Times.Once());
}
private static async Task<HubConnection> StartConnectionAsync(string hubUrl)
{
var connection = new HubConnectionBuilder()
.WithUrl(hubUrl)
.Build();
await connection.StartAsync();
return connection;
}
}
A bit verbose, but working nonetheless. Sadly, there is one gotcha.
This test will pass, but only sometimes.
Testing real web sockets
You see, testing real web sockets isn’t that simple.
Since the nature of websocket communication is asynchronous and there is a real web server running in the background, there is no guarantee that the Assert part of the test will be executed after the message has been received.
In other words, the test may be valid, but might exit too early for the assertion to pass.

So what do we do?
Verify with timeout
Thankfully, we’re using C# and we can easily “plug into” Moq through an extension method.
public static async Task VerifyWithTimeoutAsync<T>(this Mock<T> mock, Expression<Action<T>> expression, Times times, int timeoutInMs)
where T : class
{
bool hasBeenExecuted = false;
bool hasTimedOut = false;
var stopwatch = new Stopwatch();
stopwatch.Start();
while (!hasBeenExecuted && !hasTimedOut)
{
if (stopwatch.ElapsedMilliseconds > timeoutInMs)
{
hasTimedOut = true;
}
try
{
mock.Verify(expression, times);
hasBeenExecuted = true;
}
catch (Exception)
{
}
// Feel free to make this configurable
await Task.Delay(20);
}
}
What VerifyWithTimeoutAsync does is retry the built-in Verify until either it has been completed successfully or a timeout has been reached.
mockHandler.Verify(x => x(It.Is<Notification>(n => n.Message == notificationToSend.Message)), Times.Once());
..now becomes
await mockHandler.VerifyWithTimeoutAsync(x => x(It.Is<Notification>(n => n.Message == notificationToSend.Message)), Times.Once(), 1000);
If the first .Verify fails, Moq will continue retrying for 1 more second.
Our test now looks like this.
[Fact]
public async Task ShouldNotifySubscribers()
{
// Arrange
var fixture = new AppFixture();
// 1. Connect to the TestHub on {appUrl}/testHub
var connection = await StartConnectionAsync(fixture.GetCompleteServerUrl("/testHub"));
// Using a mock handler so we can make use of the Verify method
var mockHandler = new Mock<Action<Notification>>();
connection.On(nameof(Notification), mockHandler.Object);
var notificationToSend = new Notification
{
Message = "test message"
};
// Act
using (var httpClient = new HttpClient())
{
// 2. Submit a POST request on {appUrl}/hub/test with a valid message
await httpClient.PostAsJsonAsync(fixture.GetCompleteServerUrl("/hub/test"), notificationToSend);
}
// Assert
// 3. Verify that a correct message was received
await mockHandler.VerifyWithTimeoutAsync(x => x(It.Is<Notification>(n => n.Message == notificationToSend.Message)), Times.Once(), 1000);
}
It definitely isn’t horrible, and it works, but it’s still not as simple as the description we started with.
// 1. Connect to the TestHub on {appUrl}/testHub
// 2. Submit a POST request on {appUrl}/hub/test with a valid message
// 3. Verify that a correct message was received
Let’s think a bit about how we could refactor things so the test looks more like the example description without distancing us from the details too much.
// 1. Connect to the TestHub on {appUrl}/testHub
If we wrapped SignalR.Client’s HubConnection class into our own, we could perhaps end up with a builder allowing us to do something like
var connection = new TestHubConnectionBuilder()
.OnHub(_fixture.GetCompleteServerUrl("/testHub"))
.WithExpectedEvent<Notification>(nameof(Notification))
.Build();
await connection.StartAsync();
It definitely makes it more obvious that we’re connecting to the /testHub endpoint and expecting a message called “Notification”.
// 2. Submit a POST request on {appUrl}/hub/test with a valid message
What we can do is move the HttpClient construction into the AppFixture class itself.
public async Task ExecuteHttpClientAsync(Func<HttpClient, Task> action)
{
var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri(BaseUrl);
using (httpClient)
{
await action(httpClient);
}
}
Our test’s Act part then becomes
await fixture.ExecuteHttpClientAsync(httpClient =>
httpClient.PostAsJsonAsync("/hub/test", notificationToSend));
// 3. Verify that a correct message was received
Now that we’ve wrapped SignalR’s HubConnection into a TestHubConnection, we cannot call VerifyWithTimeoutAsync on the message handler, as it is not in scope.
What we’ll do is move the verification to the test connection itself.
await connection.VerifyMessageReceived(
n => n.Message == notificationToSend.Message,
Times.Once());
..instead of
await mockHandler.VerifyWithTimeoutAsync(
x => x(It.Is<Notification>(n => n.Message == notificationToSend.Message)),
Times.Once(),
1000);
Now move the AppFixture into a private field and our test class looks like this:
public class TestHubTests
{
private readonly AppFixture _fixture;
public TestHubTests()
{
_fixture = new AppFixture();
}
[Fact]
public async Task ShouldNotifySubscribers()
{
// Arrange
var notificationToSend = new Notification { Message = "test message" };
var connection = new TestHubConnectionBuilder()
.OnHub(_fixture.GetCompleteServerUrl("/testHub"))
.WithExpectedEvent<Notification>(nameof(Notification))
.Build();
await connection.StartAsync();
// Act
await _fixture.ExecuteHttpClientAsync(httpClient =>
httpClient.PostAsJsonAsync("/hub/test", notificationToSend));
// Assert
await connection.VerifyMessageReceived(
n => n.Message == notificationToSend.Message,
Times.Once());
}
}
Much better, isn’t it? Except for the fact that it doesn’t compile, but we’ll get to that in a second.
Implementing our HubConnection wrapper
The implementation is fairly straightforward, since the squiggly red underlines tell us exactly what methods we will need to expose. (StartAsync and VerifyMessageReceived)
Initially, the class will look something like this:
public class TestHubConnection
{
private readonly HubConnection _connection;
private readonly Dictionary<Type, object> _handlersMap;
private readonly int _verificationTimeout;
internal TestHubConnection(string url, int verificationTimeout = 1000)
{
_connection = new HubConnectionBuilder()
.WithUrl(url)
.Build();
_verificationTimeout = verificationTimeout;
_handlersMap = new Dictionary<Type, object>();
}
}
We keep some default verification timeout, the underlying connection (SignalR.Client.HubConnection) and a collection of mappings between types and their handlers.
Dictionary<Type, object> may look intimidating at first, but things will become clearer in a second.
This dictionary will hold expected event types and a collection of their handlers. These handlers, as we saw earlier, will be just mock functions created using the Moq library.
Some example key-value pairs that could be stored are:
{ key: typeof(Notification), value: new List<Mock<Action<Notification>>>() },
{ key: typeof(Notification2), value: new List<Mock<Action<Notification2>>>() },
{ key: typeof(Notification3), value: new List<Mock<Action<Notification3>>>() }
You see how we’re storing different generic types inside the values? This is why we need to use object as the value type, so we can merge them under a common abstraction.
Later, if we want to assert that an event of type Notification was received, we can just take all its handlers and run a predicate against them.
StartAsync will simply wrap around HubConnection’s StartAsync.
public Task StartAsync() =>
_connection.StartAsync();
..and VerifyMessageReceived<TEvent> will check whether we have a registered handler for the specified TEvent, and if we do, call VerifyWithTimeoutAsync on it.
public async Task VerifyMessageReceived<TEvent>(
Expression<Func<TEvent, bool>> predicate,
Times times)
{
if (!_handlersMap.ContainsKey(typeof(TEvent)))
// Just a custom exception
throw new HandlerNotRegisteredException(typeof(TEvent));
var handlersForType = _handlersMap[typeof(TEvent)];
foreach (var handler in (List<Mock<Action<TEvent>>>)handlersForType)
{
await handler.VerifyWithTimeoutAsync(
x => x(It.Is(predicate)),
times,
_verificationTimeout);
}
}
Implementing TestHubConnectionBuilder
There’s not much to comment on implementing the builder, it’s a very standard implementation you’ll find hundreds of tutorials for.
public class TestHubConnectionBuilder
{
private List<(Type Type, string Name)> _expectedEventNames;
private string _hubUrl;
public TestHubConnection Build()
{
if (string.IsNullOrEmpty(_hubUrl))
throw new InvalidOperationException($"Use {nameof(OnHub)} to set the hub url.");
if (_expectedEventNames == null || _expectedEventNames.Count == 0)
throw new InvalidOperationException($"Use {nameof(WithExpectedEvent)} to set the expected event name.");
var testConnection = new TestHubConnection(_hubUrl);
foreach (var expected in _expectedEventNames)
{
testConnection.Expect(expected.Name, expected.Type);
}
Clear();
return testConnection;
}
public TestHubConnectionBuilder OnHub(string hubUrl)
{
_hubUrl = hubUrl;
return this;
}
public TestHubConnectionBuilder WithExpectedEvent<TEvent>(string eventName)
{
if (_expectedEventNames == null)
_expectedEventNames = new List<(Type, string)>();
_expectedEventNames.Add((typeof(TEvent), eventName));
return this;
}
private void Clear()
{
_expectedEventNames = null;
_hubUrl = null;
}
}
The only missing item that we find out while implementing it is that we need an Expect method on the TestHubConnection. Let’s implement that.
public class TestHubConnection
{
public void Expect<TEvent>(string expectedName)
{
var handlerMock = new Mock<Action<TEvent>>();
RegisterHandler(handlerMock);
_connection.On(expectedName, handlerMock.Object);
}
public void Expect(string expectedName, Type expectedType)
{
var genericExpectMethod = GetGenericMethod(
nameof(Expect),
new[] { expectedType });
genericExpectMethod.Invoke(this, new[] { expectedName });
}
private MethodInfo GetGenericMethod(string name, Type[] genericArguments)
{
var method = typeof(TestHubConnection)
.GetMethods()
.First(m => m.ContainsGenericParameters && m.Name == name)
.MakeGenericMethod(genericArguments);
return method;
}
private void RegisterHandler<TEvent>(Mock<Action<TEvent>> handler)
{
if (!_handlersMap.TryGetValue(typeof(TEvent), out object handlersForType))
{
handlersForType = new List<Mock<Action<TEvent>>>();
_handlersMap[typeof(TEvent)] = handlersForType;
}
var handlers = (List<Mock<Action<TEvent>>>)handlersForType;
handlers.Add(handler);
}
}
It’s very verbose, but that’s what you get when you want to have cool syntax. What Expect does is register a new mock handler for the type we’ve given. We can later use this handler to call VerifyWithTimeoutAsync and assert that a correct message was received.
Sadly, this requires some reflection gymnastics, but implementation details can be ugly sometimes.
Let’s take one more look at the completed test.
[Fact]
public async Task ShouldNotifySubscribers()
{
// Arrange
var notificationToSend = new Notification { Message = "test message" };
var connection = new TestHubConnectionBuilder()
.OnHub(_fixture.GetCompleteServerUrl("/testHub"))
.WithExpectedEvent<Notification>(nameof(Notification))
.Build();
await connection.StartAsync();
// Act
await _fixture.ExecuteHttpClientAsync(httpClient =>
httpClient.PostAsJsonAsync("/hub/test", notificationToSend));
// Assert
await connection.VerifyMessageReceived<Notification>(
n => n.Message == notificationToSend.Message,
Times.Once());
}
..and if we click the “Run” button.

Perfect.
We’ve now set up the foundation for a readable and functional SignalR integration test suite. For more advanced examples of this approach that include support for access tokens, tests for notifying a specific user, etc. visithttps://github.com/dnikolovv/cafe. Look for the Api/Hubs tests in the /server folder.
And if you just want to check out and play around with the complete code of this article – you can also find it on GitHub here - https://github.com/dnikolovv/signalr-integration-tests.
That’s it! The only thing left now is to show off your newly acquired knowledge by writing some robust and well-tested real-time functionality!
This article was technically reviewed by Damir Arh.
This article has been editorially reviewed by Suprotim Agarwal.
C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.
We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).
Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.
Click here to Explore the Table of Contents or Download Sample Chapters!
Was this article worth reading? Share it with fellow developers too. Thanks!
Dobromir Nikolov is a software developer working mainly with Microsoft technologies, with his specialty being enterprise web applications and services. Very driven towards constantly improving the development process, he is an avid supporter of functional programming and test-driven development. In his spare time, you’ll find him tinkering with Haskell, building some project on GitHub (
https://github.com/dnikolovv), or occasionally talking in front of the local tech community.