This is one in a series of articles about Time Tortoise, a Universal Windows Platform app for planning and tracking your work schedule. For more on the development of this app and the ideas behind it, see my Time Tortoise category page.
In a recent code coverage check, I noticed that I was missing test coverage for a few sections in the Time Tortoise SignalR code. Although SignalR is intended to be testable, I ran into a few cases that required extra work.
IHubConnection
When a SignalR client (like TimeTortoise) wants to connect to a SignalR server (like Time Tortoise Companion), it first needs to create a HubConnection
object. The HubConnection constructor accepts an URL that contains the server’s address and the port to connect to.
Time Tortoise has a class called SignalRClient
that manages HubConnection and related SignalR objects. Before this week, SignalRClient always created an actual HubConnection. But this isn’t ideal for unit testing, since unit tests can’t assume that there will be an actual server that is available to connect to.
The correct design choice to resolve this testing problem is for the SignalRClient constructor to accept an IHubConnection
interface. Then we can pass in an actual HubConnection in cases where we want to look for a real server, and a mock HubConnection in unit tests.
It turns out that there is an IHubConnection in the Microsoft.AspNet.SignalR.Client.Hubs
namespace, and HubConnection inherits from it. HubConnection also inherits from an interface called IConnection. So far so good. However, HubConnection also inherits from Connection, which is a class, not an interface. And the Start
method, which is necessary for the simplest scenarios, is defined in the class, not in either of the interfaces.
I did some quick research on this issue, and found someone who ran into a similar issue a few years ago. So rather than try to get the existing interfaces to work, I wrote my own simple IHubConnection interface that exposes the few HubConnection members that Time Tortoise uses. Along with a simple wrapper around HubConnection, that allowed me to unit test the remaining untested code.
Dependency Injection
With the new interface and implementation available, SignalRClient now has two constructors that work as follows:
- One constructor accepts an IHubConnection and performs a few variable initialization tasks. Unit tests can call this constructor directly, passing in a mock HubConnection.
- The other constructor accepts a server URL. It just instantiates a SignalRHubConnection (using the provided URL) and chains to the first constructor.
Using Moq, a unit test can create a Mock<IHubConnection>
. By default, the mock object methods do nothing and/or return null. Since SignalRClient uses method return values in some cases, it’s necessary to use Moq’s Setup
method to return appropriate mock values. Let’s see how that works.
Invalid Setup on an Extension Method
SignalRClient has a method called ConnectToServer
that contains the following code (_hubConnection
is already initialized):
_hubProxy = _hubConnection.CreateHubProxy("MessageHub");
_hubProxy.On<DateTime>("ReceiveMessage", ReceiveMessage);
_hubConnection.Start();
For those method calls to work in a unit test context, CreateHubProxy
will at a minimum need to return a mock HubProxy
. Fortunately, there’s an IHubProxy
interface available. However, just mocking that interface doesn’t seem to work. When I tried that, I got a NullReferenceException
on line 2.
To fix that issue, I tried setting up the On
method to return a value of the appropriate type (IDisposable
). This produced the following exception:
System.NotSupportedException : Invalid setup on an extension method
So it looks like the On
method can’t be set up in that way. To see if I could resolve the original exception, I used the SignalR source on GitHub to examine the implementation of On
and find the null reference. It turned out to be the return value of the Subscribe
method. Fortunately, that method can be set up:
mockHubProxy.Setup(s => s.Subscribe(It.IsAny<string>())).
Returns(new Subscription());
With that in place, I wrote a unit test that created a mock proxy and connection, set up the connection’s CreateHubProxy
method to return the proxy, and passed the connection to the SignalRClient constructor. ConnectToServer then ran without errors, allowing me to test the other methods in the class.
(Image credit: haru__q)