You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SignalR 1.2.2+Autofac+NUnit测试报错:已添加相同键项

解决SignalR 1.2.2 + Autofac + NUnit测试中"同键重复添加"异常

我完全懂你这种被测试环境卡壳的烦躁——Web上跑好好的,一到单元测试就炸,堆栈还指向SignalR的内部字典,确实头大。先给你拆解问题根源,再给两个可行的解决方案:

问题到底出在哪?

这个异常是SignalR 1.2.2的ReflectedHubDescriptorProvider在构建Hub缓存时,把同一个ProductHub重复往字典里加导致的。

  • Web环境里Application_Start只执行一次,Hub扫描注册也只做一次,所以没问题;
  • 但NUnit测试中,每次测试都会重新触发初始化逻辑,而GlobalHost是静态单例,它的Hub缓存是懒加载的,多次初始化就会导致同一个Hub被重复注册,直接抛出重复键异常。

方案1:测试中重置SignalR静态状态

既然GlobalHost是静态的,那我们就在每个测试前手动清理它的状态,避免跨测试污染。你可以在测试类里加个SetUp方法:

[SetUp]
public void SetupTestEnvironment()
{
    // 1. 先重置GlobalHost的默认依赖解析器
    GlobalHost.DependencyResolver = new DefaultDependencyResolver();

    // 2. 反射清除HubDescriptorProvider的缓存(SignalR 1.2.2没公开清理方法,只能硬来)
    var hubManager = GlobalHost.DependencyResolver.Resolve<IHubManager>();
    if (hubManager is DefaultHubManager defaultHubManager)
    {
        var providersField = typeof(DefaultHubManager).GetField("_providers", BindingFlags.Instance | BindingFlags.NonPublic);
        if (providersField != null)
        {
            var providers = (List<IHubDescriptorProvider>)providersField.GetValue(defaultHubManager);
            foreach (var provider in providers.OfType<ReflectedHubDescriptorProvider>())
            {
                var cacheField = typeof(ReflectedHubDescriptorProvider).GetField("_hubs", BindingFlags.Instance | BindingFlags.NonPublic);
                if (cacheField != null)
                {
                    var lazyCache = (Lazy<IDictionary<string, HubDescriptor>>)cacheField.GetValue(provider);
                    var resetMethod = typeof(Lazy<>).GetMethod("ResetValue", BindingFlags.Instance | BindingFlags.NonPublic);
                    resetMethod?.Invoke(lazyCache, null);
                }
            }
        }
    }

    // 3. 重新初始化Autofac容器和SignalR配置(和Application_Start保持一致)
    var builder = new ContainerBuilder();
    builder.RegisterType<Product_WCF_Service>().AsSelf();
    builder.RegisterType<DbFactory>().As<IDbFactory>();
    builder.RegisterType<UnitOfWork>().As<IUnitOfWork>();
    builder.RegisterAssemblyTypes(typeof(ProductService).Assembly)
           .Where(t => t.Name.EndsWith("Service"))
           .AsImplementedInterfaces();
    builder.RegisterAssemblyTypes(typeof(ProductRepository).Assembly)
           .Where(t => t.Name.EndsWith("Repository"))
           .AsImplementedInterfaces();
    builder.RegisterType<ProductHub>().ExternallyOwned();

    var container = builder.Build();
    GlobalHost.DependencyResolver = new AutofacDependencyResolver(container);
    AutofacHostFactory.Container = container;
}

方案2:抽象HubContext获取逻辑(更优雅)

如果你的WCF服务只是在某个地方需要调用Hub推送,那不如把GlobalHost.GetHubContext()的逻辑抽象成接口,测试时直接模拟这个接口,彻底绕过SignalR的静态依赖:

第一步:定义抽象接口

public interface IHubContextProvider
{
    IHubContext GetProductHubContext();
}

第二步:生产环境实现

public class DefaultHubContextProvider : IHubContextProvider
{
    public IHubContext GetProductHubContext()
    {
        return GlobalHost.ConnectionManager.GetHubContext<ProductHub>();
    }
}

第三步:修改WCF服务构造函数

把IHubContextProvider注入进去,代替直接调用GlobalHost:

public class Product_WCF_Service : IProduct_WCF_Service
{
    readonly IUnitOfWork _unitOfWork;
    readonly IProductService _productService;
    readonly IHubContextProvider _hubContextProvider;

    public Product_WCF_Service(IUnitOfWork unitOfWork, IProductService productService, IHubContextProvider hubContextProvider)
    {
        _unitOfWork = unitOfWork;
        _productService = productService;
        _hubContextProvider = hubContextProvider;
    }

    // 原来用到GlobalHost.GetHubContext的地方,改成_hubContextProvider.GetProductHubContext()
}

第四步:测试中模拟依赖

[TestFixture]
public class WCF_Product_Service_Tests
{
    private Mock<IUnitOfWork> _unitOfWorkMock;
    private Mock<IProductService> _productServiceMock;
    private Mock<IHubContextProvider> _hubContextProviderMock;
    private Mock<IHubContext> _productHubContextMock;

    public WCF_Product_Service_Tests()
    {
        _unitOfWorkMock = new Mock<IUnitOfWork>();
        _productServiceMock = new Mock<IProductService>();
        _productHubContextMock = new Mock<IHubContext>();
        _hubContextProviderMock = new Mock<IHubContextProvider>();
        
        // 配置模拟:调用GetProductHubContext时返回我们的Mock对象
        _hubContextProviderMock.Setup(p => p.GetProductHubContext())
                               .Returns(_productHubContextMock.Object);
    }

    [Test]
    public void RetreiveDataFromWCF_Service()
    {
        byte commitStatus = 1;
        string response = string.Empty;
        var service = new Product_WCF_Service(
            _unitOfWorkMock.Object, 
            _productServiceMock.Object, 
            _hubContextProviderMock.Object);
            
        service.CreateProduct("", out response, out commitStatus);
        
        // 如果需要验证Hub的调用逻辑,直接验证Mock即可
        // _productHubContextMock.Verify(h => h.Clients.All.someMethod(It.IsAny<string>()), Times.Once);
    }
}

这种方式完全隔离了测试和SignalR的静态组件,不仅解决了当前的异常,还让测试更纯粹,只关注业务逻辑。


额外提醒

SignalR 1.2.2确实太老了,虽然你没法升级,但尽量在测试中避免直接依赖它的静态类——依赖注入抽象是解决这类问题的通用思路。

内容的提问来源于stack exchange,提问作者DKar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 03:29:27