Blazor+Bunit测试报错:无法从单例消费IComponentActivator服务
在使用SpecFlow和Bunit对Blazor+Blazorise项目做单元测试时,调用RenderComponent<Home>()触发以下错误:
Cannot consume scoped service 'Microsoft.AspNetCore.Components.IComponentActivator' from singleton 'Bunit.Rendering.WebTestRenderer'
问题原因
Blazorise默认注册的部分依赖是**作用域(Scoped)服务,但Bunit的WebTestRenderer属于单例(Singleton)**服务,单例服务无法直接获取作用域服务,导致作用域冲突。另外你手动将IJSUtilitiesModule注册为单例,进一步加剧了这个冲突。
解决方案
移除错误的单例注册
删掉手动添加的_ctx.Services.AddSingleton<IJSUtilitiesModule, JSUtilitiesModule>();,Blazorise已经默认完成了相关服务的适配注册,不需要额外单例声明。改用BlazoriseCore适配Bunit上下文
Bunit测试环境不需要完整的ASP.NET Core服务集,用AddBlazoriseCore替代AddBlazorise,避免引入冲突的服务:模拟JS模块依赖(避免后续JS相关报错)
Blazorise组件依赖JS模块,测试时需要模拟这部分依赖,防止找不到JS的异常。
修改后的完整测试代码
using BlazorApp1.Pages; using Blazorise; using Blazorise.Bootstrap; using Blazorise.Icons.FontAwesome; using Blazorise.Modules; using Bunit; using Microsoft.Extensions.DependencyInjection; using Moq; using TechTalk.SpecFlow; namespace TestProject1 { [Binding] public sealed class Feature1StepDefinitions { private IRenderedComponent<Home> _cut; private readonly TestContext _ctx; public Feature1StepDefinitions() { _ctx = new TestContext(); // 使用AddBlazoriseCore适配Bunit测试环境 _ctx.Services.AddBlazoriseCore(options => { }) .AddBootstrapProviders() .AddFontAwesomeIcons(); // 模拟JSUtilitiesModule,避免Blazorise组件因JS依赖报错 var mockJSModule = new Mock<IJSUtilitiesModule>(); _ctx.Services.AddScoped(_ => mockJSModule.Object); } [Given("a list of stores")] public void GivenTheComponentIsRendered() { } [When("the page is rendered")] public void WhenThePageIsRendered() { _cut = _ctx.RenderComponent<Home>(); } [Then("the stores are loaded")] public void ThenTheStoresAreLoaded() { _cut.Instance.stores.Should().NotBeNull(); _cut.Instance.stores.Should().HaveCount(2); } } }
调整后,服务作用域与Bunit测试上下文兼容,不会再出现作用域冲突错误,同时模拟的JS模块能避免Blazorise组件的依赖异常。
内容的提问来源于stack exchange,提问作者Pikachuuuu

