3

I'm trying to mock SignalR broadcasting present in ApiController(WebApi), but unable to complete test case, below is my code

SignalRHub

public class HubServer : Hub { }

ApiControllerWithHub

public abstract class ApiControllerWithHubController<THub> : ApiController where THub : IHub
{
    Lazy<IHubContext> hub = new Lazy<IHubContext>(() => GlobalHost.ConnectionManager.GetHubContext<THub>());

    protected IHubContext Hub
    {
        get { return hub.Value; }
    }
}

Controller (Method to Mock)

public class NotificationController : ApiControllerWithHubController<HubServer>
{
    [HttpPost]
    public HttpResponseMessage SendNotification(NotificationInput notification)
    {
        Hub.Clients.Group("GroupName").BroadcastCustomerGreeting("notification");
    }
}

I'm writing following unit test with the help of Mock SignalR Post, I'm stuck here because this is SignalR call from controller not from SignalR Hub.

MockTest

public interface IClientContract
{
    void BroadcastCustomerGreeting(string message);
}

[TestMethod]
public void SendNotificationTest()
{
    NotificationInput notificationInput = new NotificationInput();
    notificationInput.CId = "CUST001";
    notificationInput.CName = "Toney";

    // Arrange
    var mockClients = new Mock<IHubConnectionContext<dynamic>>();
    var mockGroups = new Mock<IClientContract>();

    // Act.
    mockGroups.Setup(_ => _.BroadcastCustomerGreeting("notification")).Verifiable();
    mockClients.Setup(_ => _.Group("GroupName")).Returns(mockGroups.Object);

    // I'm stuck here
    var controller = new NotificationController();

    // Act
    HttpResponseMessage actionResult = controller.SendNotification(notificationInput);
}

Any help is appreciated to complete/correct this unit test.

Nkosi
  • 235,767
  • 35
  • 427
  • 472
Shri
  • 351
  • 3
  • 16

1 Answers1

5

Redesign needed. Base ApiController tightly coupled to static accessor of the hub context. This needs to be refactored out into its own service to allow for more flexibility via constructor injection.

public interface IHubContextProvider {
    IHubContext Hub { get; }
}

public class HubContextProvider<THub> : IHubContextProvider where THub : IHub {
    Lazy<IHubContext> hub = new Lazy<IHubContext>(() => GlobalHost.ConnectionManager.GetHubContext<THub>());
    public IHubContext Hub {
        get { return hub.Value; }
    }
}

Controllers now need to be refactored to explicitly expose its dependencies.

public abstract class ApiControllerWithHubController<THub> : ApiController where THub : IHub {

    private readonly IHubContext hub;

    public ApiControllerWithHubController(IHubContextProvider context) {
        this.hub = context.Hub;
    }

    protected IHubContext Hub {
        get { return hub; }
    }
}


public class NotificationController : ApiControllerWithHubController<HubServer> {

    public NotificationController(IHubContextProvider context)
        : base(context) {

    }

    [HttpPost]
    public IHttpActionResult SendNotification(NotificationInput notification) {
        Hub.Clients.Group("GroupName").BroadcastCustomerGreeting("notification");
        return Ok();
    }
}

Test can now be exercised with necessary mocks of dependencies.

[TestMethod]
public void _SendNotificationTest() {

    // Arrange
    var notificationInput = new NotificationInput();
    notificationInput.CId = "CUST001";
    notificationInput.CName = "Toney";
    var groupName = "GroupName";
    var message = "notification";

    var mockGroups = new Mock<IClientContract>();
    mockGroups.Setup(_ => _.BroadcastCustomerGreeting(message)).Verifiable();

    var mockClients = new Mock<IHubConnectionContext<dynamic>>();
    mockClients.Setup(_ => _.Group(groupName)).Returns(mockGroups.Object).Verifiable();

    var mockHub = new Mock<IHubContext>();
    mockHub.Setup(_ => _.Clients).Returns(mockClients.Object).Verifiable();

    var mockHubProvider = new Mock<IHubContextProvider>();
    mockHubProvider.Setup(_ => _.Hub).Returns(mockHub.Object);

    var controller = new NotificationController(mockHubProvider.Object);

    // Act
    var actionResult = controller.SendNotification(notificationInput);

    //Assert
    mockClients.Verify();
    mockGroups.Verify();
    mockHub.Verify();
}

Just make sure to register new service with DI container so that it can be injected into dependent controllers.

With the redesign the base controller can be removed all together and the hub provider used directly. This is assuming that there was not any other reason to have the base controller.

Nkosi
  • 235,767
  • 35
  • 427
  • 472