Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
signalr / SignalR Programming in Microsoft ASP.NET.pdf
Скачиваний:
65
Добавлен:
25.05.2015
Размер:
19.23 Mб
Скачать

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 topicsChapter 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 9Advanced 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 topicsChapter 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 9Advanced topics

www.it-ebooks.info

Соседние файлы в папке signalr