Red-Green-Code

Deliberate practice techniques for software developers

  • Home
  • About
  • Contact
  • Project 462
  • CP FAQ
  • Newsletter

Time Tortoise: Unit Testing SignalR

By Duncan Smith Sep 27 0

Signal

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)

Categories: TT

Prev
Next

Stay in the Know

I'm trying out the latest learning techniques on software development concepts, and writing about what works best. Sound interesting? Subscribe to my free newsletter to keep up to date. Learn More
Unsubscribing is easy, and I'll keep your email address private.

Getting Started

Are you new here? Check out my review posts for a tour of the archives:

  • 2023 in Review: 50 LeetCode Tips
  • 2022 in Review: Content Bots
  • 2021 in Review: Thoughts on Solving Programming Puzzles
  • Lessons from the 2020 LeetCode Monthly Challenges
  • 2019 in Review
  • Competitive Programming Frequently Asked Questions: 2018 In Review
  • What I Learned Working On Time Tortoise in 2017
  • 2016 in Review
  • 2015 in Review
  • 2015 Summer Review

Archives

Recent Posts

  • Do Coding Bots Mean the End of Coding Interviews? December 31, 2024
  • Another Project for 2024 May 8, 2024
  • Dynamic Programming Wrap-Up May 1, 2024
  • LeetCode 91: Decode Ways April 24, 2024
  • LeetCode 70: Climbing Stairs April 17, 2024
  • LeetCode 221: Maximal Square April 10, 2024
  • Using Dynamic Programming for Maximum Product Subarray April 3, 2024
  • LeetCode 62: Unique Paths March 27, 2024
  • LeetCode 416: Partition Equal Subset Sum March 20, 2024
  • LeetCode 1143: Longest Common Subsequence March 13, 2024
Red-Green-Code
  • Home
  • About
  • Contact
  • Project 462
  • CP FAQ
  • Newsletter
Copyright © 2025 Duncan Smith