- •Contents at a Glance
- •Introduction
- •Who should read this book
- •Assumptions
- •Who should not read this book
- •Organization of this book
- •Finding your best starting point in this book
- •Conventions and features in this book
- •System requirements
- •Code samples
- •Notes on the version
- •Installing the code samples
- •Using the code samples
- •Acknowledgments
- •Errata & book support
- •We want to hear from you
- •Stay in touch
- •HTTP operations
- •Polling: The answer?
- •Push: The server takes the initiative
- •WebSockets
- •Server-Sent Events (API Event Source)
- •Push today
- •The world needs more than just push
- •What does SignalR offer?
- •Two levels of abstraction
- •Supported platforms
- •OWIN and Katana: The new kids on the block
- •Installing SignalR
- •Implementation on the server side
- •Mapping and configuring persistent connections
- •Events of a persistent connection
- •Sending messages to clients
- •Asynchronous event processing
- •Connection groups
- •The OWIN startup class
- •Implementation on the client side
- •Initiating the connection by using the JavaScript client
- •Support for older browsers
- •Support for cross-domain connections
- •Sending messages
- •Receiving messages
- •Sending additional information to the server
- •Other events available at the client
- •Transport negotiation
- •Adjusting SignalR configuration parameters
- •Complete example: Tracking visitors
- •Project creation and setup
- •Implementation on the client side
- •Implementation on the server side
- •Server implementation
- •Hub registration and configuration
- •Creating hubs
- •Receiving messages
- •Sending messages to clients
- •Sending messages to specific users
- •State maintenance
- •Accessing information about the request context
- •Notification of connections and disconnections
- •Managing groups
- •Maintaining state at the server
- •Client implementation
- •JavaScript clients
- •Generating the proxy
- •Manual generation of JavaScript proxies
- •Establishing the connection
- •Sending messages to the server
- •Sending additional information
- •Receiving messages sent from the server
- •Logging
- •State maintenance
- •Implementing the client without a proxy
- •Complete example: Shared drawing board
- •Project creation and setup
- •Implementation on the client side
- •Implementation on the server side
- •Access from other threads
- •External access using persistent connections
- •Complete example: Monitoring connections at the server
- •Project creation and setup
- •Implementing the website
- •System for tracing requests (server side)
- •System for tracing requests (client side)
- •External access using hubs
- •Complete example: Progress bar
- •Project creation and setup
- •Implementation on the client side
- •Implementation on the server side
- •Multiplatform SignalR servers
- •SignalR hosting in non-web applications
- •SignalR hosting in platforms other than Windows
- •Multiplatform SignalR clients
- •Accessing services from .NET non-web clients
- •Consumption of services from other platforms
- •Growing pains
- •Scalability in SignalR
- •Scaling on backplanes
- •Windows Azure Service Bus
- •SQL Server
- •Redis
- •Custom backplanes
- •Improving performance in SignalR services
- •Server configuration
- •Monitoring performance
- •Authorization in SignalR
- •Access control in persistent connections
- •Access control in hubs
- •Client authentication
- •An extensible framework
- •Dependency Injection
- •Manual dependency injection
- •Releasing dependencies
- •Inversion of Control containers
- •Unit testing with SignalR
- •Unit testing of hubs
- •Unit testing persistent connections
- •Intercepting messages in hubs
- •Integration with other frameworks
- •Knockout
- •AngularJS
- •Index
- •About the author
Unit testing of hubs
Hubs are normal classes and, as such, they can be instantiated and fed with the dependencies they need, so we can decouple them from the remaining components of our application very easily and therefore subject them to unit tests without too much trouble with the techniques and tools that we have been looking at.
Furthermore, SignalR was designed from the outset with decoupling, dependency injection, and unit testing in mind, so it will facilitate performing unit testing of our applications. Virtually all communication that we can have between a hub and the components of the framework is done using abstractions (interfaces) that allow their replacement by mock objects that make unit testing easier.
For example, look at the following code of a hub, where we can see several interactions with the platform. The first one is to get the value of the header variable "Host", the second one is to get the connection identifier, and the third one is to send an information message to all connected clients:
public class Broadcaster: Hub
{
...
public override Task OnConnected()
{
var host = Context.Headers["host"]; var id = Context.ConnectionId;
var message = "New connection " + id + " at " + host; return Clients.All.Message(message);
}
}
Thanks to the abstractions used by the SignalR hub class and the magic of mocking frameworks such as Moq, we can create a complete dependency infrastructure whose behavior will be set up in advance so that we can focus on checking that in this method the Message method is invoked in all connected clients, sending them the exact message that we expect.
The verification code for the OnConnected() method of the hub would be as follows. Below we will go over it step by step:
[TestMethod]
public void On_Connected_sends_a_broadcast_with_a_specific_message()
{
// 1) ARRANGE
var hub = new Broadcaster(); var connId = "1234";
var host = "myhost";
var expectedMessage = "New connection "+connId+" at "+host;
// 1.a) Set up headers
var mockRequest = new Mock<IRequest>();
var mockHeaders = new Mock<INameValueCollection>(); mockHeaders.Setup(h => h["host"]).Returns(host); mockRequest.Setup(r => r.Headers).Returns(mockHeaders.Object);
// 1.b) Set up context & connection id
Advanced topics Chapter 9 |
211 |
www.it-ebooks.info
hub.Context = new HubCallerContext(mockRequest.Object, connId);
// 1.c) Set up capture of client method call var clientMethodInvoked = false;
var messageSent = "";
dynamic all = new ExpandoObject();
all.Message = new Func<string, Task>((string message) =>
{
clientMethodInvoked = true; messageSent = message; return Task.FromResult(true);
});
var mockClients = new Mock<IHubCallerConnectionContext>(); mockClients.Setup(c => c.All).Returns((ExpandoObject)all); hub.Clients = mockClients.Object;
//2) ACT hub.OnConnected().Wait();
//3) ASSERT
Assert.IsTrue(clientMethodInvoked, "No client methods invoked."); Assert.AreEqual(expectedMessage, messageSent);
}
Note how, little by little, we are creating the scaffolding needed to execute the method to be tested, to ensure that it can retrieve the information from its environment, and to capture the calls to the client side:
1.We first create the instance of the hub on which we will perform our tests. If its constructor received dependencies as parameters, we supply them to it at this point too. Next, we create variables to store environment values that we will use later, and we already define the message that we hope will be sent to all connected clients.
a.We prepare the request headers to be read from the hub. A mock object is created that implements the IRequest interface, and we configure its Headers property so that a query for the "host" key returns the value that we want.
b.Because the method to be checked needs to obtain the connection identifier, we create a
HubCallerContext object to which we supply the aforementioned IRequest interface, as well as a connection identifier representing the client whose call we will simulate. This object is entered into the Context property of the hub, from which it will be possible to access the ConnectionId.
c.We create a dynamic object called all, in which we implement the Message() method to simulate the invocation to the client side. Inside it, we simply set values in local variables indicating that this method was invoked from the hub. Next, we replace the Clients object of the hub with a new mock object whose All property is set with
the all object that we created earlier. Thus, when Clients.All.Message() is called from the hub, the method that will really be invoked will be the one implemented in the lambda.
212 Chapter 9 Advanced topics
www.it-ebooks.info
4.We invoke the OnConnected() method. Because it returns a background task represented by a Task, we wait for it to end.
5.We check the value of the variables where we have stored the marks indicating that the method was invoked correctly and with the desired values.
Because preparing this entire infrastructure is sometimes rather tedious, it is often included in helper methods so that all tests can quickly obtain an instance of the hub to be checked, but already configured and ready for us to focus directly on implementing the various tests:
[TestClass]
public class BroadcasterTests
{
// Helper method
private Broadcaster GetConfiguredHub()
{
var hub = new Broadcaster(); var connId = "1234";
var host = "myhost";
// Set up headers
var mockRequest = new Mock<IRequest>();
var mockHeaders = new Mock<INameValueCollection>(); mockHeaders.Setup(h => h["host"]).Returns(host); mockRequest.Setup(r => r.Headers)
.Returns(mockHeaders.Object);
// Set up user
var identity = new GenericPrincipal(
new GenericIdentity("jmaguilar"), new[] { "admin" }); mockRequest.Setup(r => r.User).Returns(identity);
// Set up context & connection id
hub.Context = new HubCallerContext(mockRequest.Object, connId);
return hub;
}
[TestMethod]
public void Test1()
{
// Arrange
var hub = GetConfiguredHub();
...// Test hub
}
[TestMethod]
public void Test2()
{
// Arrange
var hub = GetConfiguredHub();
... // Test hub
Advanced topics Chapter 9 |
213 |
www.it-ebooks.info
}
}
Let’s look at one more example to show how to resolve other common scenarios when performing this type of testing. The hub method to be tested will be the following:
[Authorize]
public Task PrivateMessage(string destConnectionId, string text)
{
var sender = Context.User.Identity.Name;
var message = new Message() { Sender = sender, Text = text };
return Clients.Client(destConnectionId).PrivateMessage(message);
}
In this case, we will first need to inject information about the active user into the instance of the hub. To do this, we preset the request context so that when its User property is accessed, a generic identity is returned. The code is similar to the example that we have recently seen:
var mockRequest = new Mock<IRequest>(); var identity = new GenericPrincipal(
new GenericIdentity("jmaguilar"), new[] { "admin" }); mockRequest.Setup(r => r.User).Returns(identity);
...
// Set up context & connection id
hub.Context = new HubCallerContext(mockRequest.Object, connId);
...
We must also capture the call to the PrivateMessage method of the dynamic object returned by the call to Clients.Client(connectionId), which can be achieved in very much the same way as we did earlier:
[TestMethod]
public void PrivateMessage_Sends_A_Private_Mesage()
{
// Arrange
var hub = GetConfiguredHub(); var destinationId = "666";
var user = hub.Context.User.Identity.Name; var text = "Hi there!";
var mockClients = new Mock<IHubCallerConnectionContext>(); Message messageSent = null;
dynamic client = new ExpandoObject();
client.PrivateMessage = new Func<Message, Task>((Message data) =>
{
messageSent = data;
return Task.FromResult(true);
});
mockClients.Setup(c => c.Client(destinationId))
.Returns((ExpandoObject)client);
214 Chapter 9 Advanced topics
www.it-ebooks.info