【问题标题】:Mocking ApiController SignalR broadcasting模拟 ApiController SignalR 广播
【发布时间】:2017-11-11 10:38:55
【问题描述】:

我正在尝试模拟 ApiController(WebApi) 中存在的 SignalR 广播,但无法完成测试用例,下面是我的代码

信号RHub

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; }
    }
}

控制器(模拟方法)

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

我正在 Mock SignalR Post 的帮助下编写以下单元测试,我被困在这里,因为这是来自控制器而不是来自 SignalR Hub 的 SignalR 调用。

模拟测试

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);
}

感谢任何帮助以完成/更正此单元测试。

【问题讨论】:

    标签: c# unit-testing asp.net-web-api signalr moq


    【解决方案1】:

    需要重新设计。基础ApiController 与集线器上下文的静态访问器紧密耦合。这需要重构为自己的服务,以便通过构造函数注入提供更大的灵活性。

    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; }
        }
    }
    

    现在需要重构控制器以显式公开其依赖项。

    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();
        }
    }
    

    现在可以使用必要的依赖模拟来进行测试。

    [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();
    }
    

    只需确保向 DI 容器注册新服务,以便可以将其注入到依赖的控制器中。

    通过重新设计,可以将基本控制器一起移除,直接使用集线器提供程序。这是假设没有任何其他理由拥有基本控制器。

    【讨论】: