Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mocking ApiController SignalR broadcasting

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.

like image 333
Shri Avatar asked Jan 23 '26 05:01

Shri


1 Answers

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.

like image 169
Nkosi Avatar answered Jan 24 '26 19:01

Nkosi



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!