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

Blazor单元测试中ProtectedSessionStorage依赖注入失败问题

Bunit测试Blazor组件时ProtectedSessionStorage依赖注入错误

问题现象

使用Bunit对Blazor的Assets组件进行单元测试时,执行AssetTableTest构造函数中的Context.RenderComponent<Assets>()步骤时,抛出System.InvalidOperationException,错误提示:

激活'Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage.ProtectedSessionStorage'时无法解析类型'Microsoft.AspNetCore.DataProtection.IDataProtectionProvider'的服务。

异常堆栈信息

System.InvalidOperationException
HResult=0x80131509
Message=Unable to resolve service for type 'Microsoft.AspNetCore.DataProtection.IDataProtectionProvider' while attempting to activate 'Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage.ProtectedSessionStorage'.
Source=Microsoft.Extensions.DependencyInjection
StackTrace:
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(ResultCache lifetime, Type serviceType, Type implementationType, CallSiteChain callSiteChain)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, Int32 slot)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateCallSite(Type serviceType, CallSiteChain callSiteChain)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.GetCallSite(Type serviceType, CallSiteChain callSiteChain)
at Microsoft.Extensions.DependencyInjection.ServiceProvider.CreateServiceAccessor(Type serviceType)
at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
at Bunit.TestServiceProvider.GetServiceInternal(Type serviceType)
at Bunit.TestServiceProvider.GetService(Type serviceType)
at Microsoft.AspNetCore.Components.ComponentFactory.<>c__DisplayClass7_0.g__Initialize|1(IServiceProvider serviceProvider, IComponent component)
at Microsoft.AspNetCore.Components.ComponentFactory.PerformPropertyInjection(IServiceProvider serviceProvider, IComponent instance)
at Microsoft.AspNetCore.Components.ComponentFactory.InstantiateComponent(IServiceProvider serviceProvider, Type componentType)
at Microsoft.AspNetCore.Components.RenderTree.Renderer.InstantiateChildComponentOnFrame(RenderTreeFrame& frame, Int32 parentComponentId)
at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InitializeNewComponentFrame(DiffContext& diffContext, Int32 frameIndex)
at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InitializeNewSubtree(DiffContext& diffContext, Int32 frameIndex)
at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InsertNewFrame(DiffContext& diffContext, Int32 newFrameIndex)
at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.AppendDiffEntriesForRange(DiffContext& diffContext, Int32 oldStartIndex, Int32 oldEndIndexExcl, Int32 newStartIndex, Int32 newEndIndexExcl)
at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.ComputeDiff(Renderer renderer, RenderBatchBuilder batchBuilder, Int32 componentId, ArrayRange`1 oldTree, ArrayRange`1 newTree)
at Microsoft.AspNetCore.Components.Rendering.ComponentState.RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment renderFragment, Exception& renderFragmentException)
at Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessRenderQueue()
--- End of stack trace from previous location ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at Bunit.Rendering.TestRenderer.AssertNoUnhandledExceptions()
at Bunit.Rendering.TestRenderer.Render[TResult](RenderFragment renderFragment, Func`2 activator)
at Bunit.Rendering.TestRenderer.RenderFragment(RenderFragment renderFragment)
at Bunit.Extensions.TestContextBaseRenderExtensions.RenderInsideRenderTree(TestContextBase testContext, RenderFragment renderFragment)
at Bunit.Extensions.TestContextBaseRenderExtensions.RenderInsideRenderTree[TComponent](TestContextBase testContext, RenderFragment renderFragment)
at Bunit.TestContext.Render[TComponent](RenderFragment renderFragment)
at Bunit.TestContext.RenderComponent[TComponent](ComponentParameter[] parameters)
at ConfigWebappUnitTesting.AssetTableTest..ctor() in D:\Gleason Projects\ConfigurationWebAppBlazor\ConfigWebappUnitTesting\AssetTableTest.cs:line 19
at System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean wrapExceptions)

测试代码

public AssetTableTest()
{
    // Act
    var session = Context.Services.GetService<FakeSignOutSessionStateManager>();
    var page = Context.Services.GetService<FakeNavigationManager>();
    page.NavigateTo(page.BaseUri.Remove(page.BaseUri.Length - 1, 1) + Configuration["Session"]);
    cut = Context.RenderComponent<Assets>(); //**错误发生在此处**
    //cut = Context.RenderComponent<Assets>(x =>
    //x.Add(p => p.Session, Configuration["Session"])
    //);

    //WaitForAssertion
    cut.WaitForAssertion(() => { }, TimeSpan.FromSeconds(5));
    cut.WaitForState(() => (cut.Markup.Contains("tbody")), TimeSpan.FromSeconds(10));
    cut.WaitForState(() => (!cut.Find("tbody").TextContent.Contains("Loading")), TimeSpan.FromSeconds(50));
    tableBody = cut.Find("tbody");
}

解决方案

问题根源是Bunit的TestContext默认没有注册IDataProtectionProvider服务,而ProtectedSessionStorage依赖该服务才能被激活。需要在测试初始化时手动注册所需服务,有两种常用方式:

方式1:注册模拟的IDataProtectionProvider

使用Moq等模拟库创建IDataProtectionProvider的实例并注册到服务容器:

using Moq;

public AssetTableTest()
{
    // 注册模拟的IDataProtectionProvider
    var mockDataProtection = new Mock<IDataProtectionProvider>();
    Context.Services.AddSingleton(mockDataProtection.Object);

    // 原有测试代码
    var session = Context.Services.GetService<FakeSignOutSessionStateManager>();
    var page = Context.Services.GetService<FakeNavigationManager>();
    page.NavigateTo(page.BaseUri.Remove(page.BaseUri.Length - 1, 1) + Configuration["Session"]);
    cut = Context.RenderComponent<Assets>();

    // ...后续断言代码
}

方式2:替换ProtectedSessionStorage为自定义模拟实现

如果组件实际使用了ProtectedSessionStorage的方法(如GetAsync、SetAsync),可以创建一个模拟实现并注册:

// 自定义模拟ProtectedSessionStorage
public class MockProtectedSessionStorage : ProtectedSessionStorage
{
    public MockProtectedSessionStorage(IDataProtectionProvider dataProtectionProvider) : base(dataProtectionProvider)
    {
    }

    // 重写需要用到的方法,返回测试所需数据
    public override Task<ProtectedBrowserStorageResult<T>> GetAsync<T>(string key)
    {
        // 示例:返回成功结果,可根据测试需求修改返回值
        return Task.FromResult(new ProtectedBrowserStorageResult<T>(true, default(T)));
    }

    public override Task SetAsync<T>(string key, T value)
    {
        return Task.CompletedTask;
    }
}

// 在测试中注册
public AssetTableTest()
{
    var mockDataProtection = new Mock<IDataProtectionProvider>();
    Context.Services.AddSingleton<IProtectedSessionStorage>(new MockProtectedSessionStorage(mockDataProtection.Object));

    // 原有测试代码
    var session = Context.Services.GetService<FakeSignOutSessionStateManager>();
    var page = Context.Services.GetService<FakeNavigationManager>();
    page.NavigateTo(page.BaseUri.Remove(page.BaseUri.Length - 1, 1) + Configuration["Session"]);
    cut = Context.RenderComponent<Assets>();

    // ...后续断言代码
}

选择哪种方式取决于组件是否实际调用了ProtectedSessionStorage的方法:如果只是注入但未使用,方式1足够;如果需要验证存储操作或返回特定数据,方式2更合适。

内容的提问来源于stack exchange,提问作者Chethan D V

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 20:39:27