C#单元测试Mock IStringLocalizerFactory抛出NullReferenceException
问题根因
空引用的核心原因是Mock配置执行时机晚于服务实例化时机:
- 你在测试类构造函数中直接初始化了
EventService实例,构造函数执行时会立刻调用IStringLocalizerFactory.Create()方法给_localizer赋值 - 但
Create()方法的Mock Setup逻辑写在了具体的测试方法内部,服务初始化时这个配置还没生效,Moq对未配置的引用类型返回值默认返回null,最终导致_localizer字段为null - 你在测试方法中写的Setup是服务实例化完成后才执行的,无法回溯修改构造函数阶段已经拿到的返回值
另外你当前的写法还有一个潜在问题:就算配置时机正确,你返回的真实StringLocalizer<Resource>实例是传入Mock的IStringLocalizerFactory构造的,内部逻辑依赖factory的其他未配置方法,运行时可能出现其他非预期错误。
解决方案
推荐直接MockIStringLocalizer实例,跳过真实StringLocalizer的内部实现逻辑,同时把Mock配置放在服务实例化之前执行即可。
通用配置写法(适合所有测试用例复用同一套本地化逻辑)
直接在测试类构造函数中先完成所有Mock的通用配置,再初始化服务:
public class EventServiceTests { private readonly Mock<IEventRepository> _eventRepoMock = new Mock<IEventRepository>(); private readonly Mock<IMemberEventRepository> _memberEventRepoMock = new Mock<IMemberEventRepository>(); private readonly Mock<IStringLocalizerFactory> _stringLocalizerFactoryMock = new Mock<IStringLocalizerFactory>(); private readonly EventService _service; public EventServiceTests() { // 1. 先配置Localizer相关Mock var localizerMock = new Mock<IStringLocalizer>(); localizerMock.Setup(l => l["This member already participates in this event."]) .Returns(new LocalizedString( "This member already participates in this event.", "该成员已报名此活动" )); _stringLocalizerFactoryMock.Setup(f => f.Create(typeof(Resource))) .Returns(localizerMock.Object); // 2. 所有Mock配置完成后再实例化待测服务 _service = new EventService( _eventRepoMock.Object, _memberEventRepoMock.Object, _stringLocalizerFactoryMock.Object ); } [Fact] public async Task CreateEventRegistrationAsync_ShouldThrowArgumentException_WhenMemberAlreadyRegisteredForEvent() { // Arrange int eventId = 456; int memberId = 123; var registration = new MemberEvent { EventId = eventId, MemberId = memberId }; _memberEventRepoMock.Setup(x => x.GetMemberEvent(eventId, memberId)) .ReturnsAsync(registration); // Act & Assert var exception = await Assert.ThrowsAsync<ArgumentException>( () => _service.CreateEventRegistrationAsync(registration) ); Assert.Equal("该成员已报名此活动", exception.Message); } }
单测试用例独立配置写法(适合不同用例需要不同本地化返回的场景)
如果不同测试方法需要不同的本地化返回值,不要在构造函数中初始化待测服务,把服务初始化逻辑挪到每个测试方法内部,完成针对性Mock配置后再实例化服务即可,避免用例之间的配置互相污染。
内容的提问来源于stack exchange,提问作者Bramist
相关产品推荐
相关产品推荐

