Blazor组件BUnit测试:Moq模拟异步方法失效引发空引用异常
在为Blazor组件编写BUnit测试时,尝试用Moq模拟一个公开异步方法,但真实方法的代码仍被执行,导致System.NullReferenceException——原因是Service.MockedMethod()中调用的_authenticationStateProvider.GetAuthenticationStateAsync()返回的authState为null。
核心原因分析
注入的是真实服务而非Mock实例
测试代码的Setup方法中,注册的是真实的AuthenticationService实例,而非你创建的Mock<IAuthenticationStateService>对象:Services.AddSingleton<IAuthenticationStateService>(new AuthenticationService(_mockServiceProvider.Object));这意味着组件最终使用的是真实服务,自然会执行其内部逻辑。
组件依赖具体类而非接口
组件中直接注入的是具体类AuthenticationStateService,而非IAuthenticationStateService接口:@inject AuthenticationStateService service即便你注册了接口的Mock实例,组件也不会使用它,因为它依赖的是具体实现类。
Mock的Setup未关联到实际使用的服务
你对_mockAuthenticationStateService做的方法Setup完全无效,因为这个Mock对象根本没被注入到服务容器中,组件从未接触过它。
修正方案
推荐方案:依赖接口+注册Mock实例
这种方式符合依赖倒置原则,是最灵活的测试方案。
修改组件的依赖为接口
将组件中的注入改为接口类型,同时替换.Result为await避免死锁风险:@inject IAuthenticationStateService service @code { protected override async Task OnInitializedAsync() { UserName = await service.MockedMethod(); } }调整测试代码,注册Mock接口实例
修改Setup方法,直接将Mock的接口实例注入服务容器:internal class MyComponentTests : BunitTestContext { private Mock<IAuthenticationStateService> _mockAuthenticationStateService; [SetUp] public void Setup() { _mockAuthenticationStateService = new Mock<IAuthenticationStateService>(); // 注册Mock实例到服务容器 Services.AddSingleton<IAuthenticationStateService>(_mockAuthenticationStateService.Object); } [Test] public void MyComponentRendersCorrectly() { _mockAuthenticationStateService.Setup(s => s.MockedMethod()).ReturnsAsync("user"); var cut = RenderComponent<MyComponent>(); cut.MarkupMatches("<h1>TEST</h1>"); } }此时组件会使用Mock对象,
MockedMethod的真实代码不会被执行,直接返回预设的"user"。
备选方案:Mock具体类(不推荐)
若因特殊原因无法修改组件依赖为接口,可通过Mock具体类实现,但需要将目标方法改为virtual:
修改服务类的方法为virtual
namespace Services; public class AuthenticationStateService(AuthenticationStateProvider authenticationStateProvider) : IService { private readonly AuthenticationStateProvider _authenticationStateProvider = authenticationStateProvider; // 添加virtual关键字,允许Moq拦截 public virtual async Task<string> MockedMethod() { // 原有代码 } }调整测试代码Mock具体类
internal class MyComponentTests : BunitTestContext { private Mock<AuthenticationStateService> _mockAuthService; [SetUp] public void Setup() { var mockAuthProvider = new Mock<AuthenticationStateProvider>(); // Mock具体服务类 _mockAuthService = new Mock<AuthenticationStateService>(mockAuthProvider.Object); Services.AddSingleton<AuthenticationStateService>(_mockAuthService.Object); } [Test] public void MyComponentRendersCorrectly() { _mockAuthService.Setup(s => s.MockedMethod()).ReturnsAsync("user"); var cut = RenderComponent<MyComponent>(); cut.MarkupMatches("<h1>TEST</h1>"); } }
内容的提问来源于stack exchange,提问作者user26425271

