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

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

217

www.it-ebooks.info

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