
- •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
hub.Clients = mockClients.Object;
// Act
hub.PrivateMessage(destinationId, text).Wait();
// Assert
Assert.IsNotNull(messageSent, "Message not sent"); Assert.AreEqual(user, messageSent.Sender); Assert.AreEqual(text, messageSent.Text);
}
Another possibility that we have for capturing calls to client-side methods is to use a mock interface with the operations that are allowed and to configure a callback function on the call, to retrieve the information sent. The main section of the setup code would be as follows:
...
var mockClients = new Mock<IHubCallerConnectionContext>(); var mockClientOperations = new Mock<IClientOperations>(); mockClientOperations
.Setup(c => c.PrivateMessage(It.IsAny<Message>()))
.Returns(Task.FromResult(true))
.Callback<Message>(msg =>
{
messageSent = msg;
});
mockClients.Setup(c => c.Client(destinationId))
.Returns(mockClientOperations.Object); hub.Clients = mockClients.Object;
...
The definition of the interface, used only to configure the behavior of the PrivateMessage() method with Moq, would be as follows:
public interface IClientOperations
{
Task PrivateMessage(Message msg);
}
Unit testing persistent connections
We have seen that performing unit tests on hubs need not be especially complicated if we master a mocking framework, which, incidentally, is absolutely recommended. If the class has been built
without rigid dependencies, we will always find a way to perform isolated tests on the aspects that we want. The SignalR architecture itself also makes it very easy for us, because we work on abstractions of components that can be easily replaced with other ones.
With persistent connections, things change a bit, because SignalR does not offer so much flexibility anymore. From its base, testing this type of component is a little more difficult because the custom code is implemented on protected methods inherited from their parent class, PersistentConnection. Thus, any attempt to invoke one of the overridable methods such as
Advanced topics Chapter 9 |
215 |
www.it-ebooks.info
OnReceived or OnConnected from the outside—for example, from a unit test—will generate a compilation error because they are not visible from outside the base class or its descendants:
// Arrange
var connection = new MyConnection();
...
// Act
connection.OnReceived(mockRequest.Object, connId, data); // Error
...
The solution to this problem is really simple: we just need to create in the test project a new class inherited from the connection that we want to test and create public methods in it that function as “bridges” to the methods of the base class. We would do the tests on these new methods because they would be visible from the test classes:
public class MyTestableConnection : MyConnection
{
public new Task OnReceived(IRequest request,
string connectionId, string data)
{
return base.OnReceived(request, connectionId, data);
}
}
Note that, in the constructor of this new class, we would also have to include the dependencies that the original persistent connection requires, if any.
However, when this obstacle is overcome, as soon as we begin testing the code, we will see that there are members that we would like to replace so as to take control in tests, but there is no way to do it. Perhaps the most evident case is found in the Connection property that we use from persistent connections to make direct submissions or broadcasts to clients; its setter is private, and it is set internally in the class. The process is unable to be intercepted or altered from any point.
Therefore, the recommendation regarding unit testing on this type of component is to remove as much code as possible from the methods to be tested (OnConnected, OnReceived, and so on), especially code related to Connection or other non-replaceable components, taking them to points where we can control them, using dependencies, inheritance or any other mechanism that can provide alternative implementations.
To illustrate these problems and how to solve them, we are going to implement some tests on the following persistent connection. Note that we have omitted all references to the Connection property of PersistentConnection, and to make submissions, we are using a custom abstraction, which we have named IConnectionWrapper, whose code we will look at later on:
public class EchoConnection : PersistentConnection
{
private IConnectionWrapper _connection;
public EchoConnection()
{
216 Chapter 9 Advanced topics
www.it-ebooks.info
_connection = new ConnectionWrapper(this);
}
public EchoConnection(IConnectionWrapper connection)
{
_connection = connection;
}
protected override Task OnConnected(IRequest request, string connectionId)
{
var newConnMsg = "New connection " + connectionId + "!"; return _connection.Send(connectionId, "Howdy!")
.ContinueWith(_ => _connection.Broadcast(newConnMsg)
);
}
}
That is, we are replacing the references to this.Connection, rigid and difficult to control from unit tests, with others with no coupling whatsoever. We also define two constructors: the first one will be the one normally used at run time, whereas the second one is prepared to be able to inject the dependencies from the tests.
The abstractions that we have used are the following:
public interface IConnectionWrapper
{
Task Send(string connectionId, object value);
Task Broadcast(object value, params string[] exclude);
}
public class ConnectionWrapper: IConnectionWrapper
{
private PersistentConnection _connection;
public ConnectionWrapper(PersistentConnection connection)
{
_connection = connection;
}
public Task Send(string connectionId, object value)
{
return _connection.Connection.Send(connectionId, value);
}
public Task Broadcast(object value, params string[] exclude)
{
return _connection.Connection.Broadcast(value, exclude);
}
}
From the testing project, we cannot use the EchoConnection class directly because its members have protected visibility, so we must now create the wrapper that will serve as a gateway to said class:
public class TestableEchoConnection : EchoConnection
{
Advanced topics Chapter 9 |
217 |
www.it-ebooks.info