使用Autofac.Moq写单元测试遇异常:无法获取Autofac根生命周期域
GetAutofacRoot()异常及测试优化方案 首先,咱们先拆解你遇到的核心问题:你在CommandBus里调用_serviceProvider.GetAutofacRoot()时抛出异常,是因为Autofac.Moq的AutoMock默认生成的IServiceProvider是Castle动态代理对象,并非Autofac原生的AutofacServiceProvider,自然无法通过扩展方法获取到Autofac的根生命周期作用域。
接下来咱们一步步解决你的疑问:
一、如何模拟/获取Autofac Root作用域?
有两种靠谱的思路可以解决这个问题:
1. 调整CommandBus的依赖(推荐最佳实践)
与其依赖通用的IServiceProvider,不如直接依赖Autofac原生的ILifetimeScope——这既符合Autofac的设计初衷,也能彻底避免跨容器适配的问题。
修改CommandBus代码:
public class CommandBus : ICommandBus { private readonly ILifetimeScope _lifetimeScope; // 直接注入ILifetimeScope而非IServiceProvider public CommandBus(ILifetimeScope lifetimeScope) { _lifetimeScope = lifetimeScope; } public void Send<TCommand>(TCommand command) where TCommand : ICommand { // 直接从根作用域创建子作用域 using (var scope = _lifetimeScope.BeginLifetimeScope()) { var handlers = scope.Resolve<IEnumerable<ICommandHandler<TCommand>>>().ToList(); if (handlers.Count == 1) { handlers[0].Handle(command); } else if (handlers.Count == 0) { throw new Exception($"Command does not have any handler {command.GetType().Name}"); } else { throw new Exception($"Too many registred handlers - {handlers.Count} for command {command.GetType().Name}"); } } } }
这样修改后,测试中AutoMock会自动提供ILifetimeScope的有效实例,不需要再处理GetAutofacRoot()的问题。
2. 手动构建Autofac原生容器(适配现有代码)
如果你不想修改CommandBus的现有依赖,可以手动构建Autofac容器并包装为AutofacServiceProvider,这样就能正常调用GetAutofacRoot():
[Fact] public void TestCommandBus() { // 手动构建Autofac容器 var builder = new ContainerBuilder(); // 注册CommandBus和所需的处理器 builder.RegisterType<CommandBus>().As<ICommandBus>(); builder.RegisterType<TestCommandHandler>().As<ICommandHandler<TestCommand>>(); using (var container = builder.Build()) { // 转换成标准IServiceProvider var serviceProvider = new AutofacServiceProvider(container); var bus = new CommandBus(serviceProvider); bus.Send(new TestCommand()); Assert.Equal(1, TestCommandHandler.Value); } }
这种方式绕开了AutoMock的代理问题,直接使用Autofac原生容器,自然能正常获取根作用域。
二、你的单元测试是否正确使用了Autofac.Moq?
目前的测试存在两个关键问题:
- 未注册
TestCommandHandler:Autofac.Moq的AutoMock不会自动扫描注册你的自定义处理器类,所以CommandBus尝试解析IEnumerable<ICommandHandler<TestCommand>>时会得到空集合,触发“没有找到处理器”的异常。 - 依赖
IServiceProvider导致的代理不兼容:前面已经分析过,AutoMock生成的IServiceProvider不是Autofac原生实现。
修正后的Autofac.Moq测试代码(配合前面修改的CommandBus依赖ILifetimeScope):
[Fact] public void TestCommandBus() { using (var mock = AutoMock.GetLoose()) { // 手动注册TestCommandHandler到AutoMock的容器中 mock.Provide<ICommandHandler<TestCommand>>(new TestCommandHandler()); // 创建CommandBus实例 var bus = mock.Create<CommandBus>(); bus.Send(new TestCommand()); Assert.Equal(1, TestCommandHandler.Value); } }
这里通过mock.Provide()手动注册了处理器,确保Autofac能正确解析到对应的实现。
三、测试有效性的验证建议
要确保测试真正有效,建议覆盖以下场景:
- 正常处理场景:验证命令能被正确处理(就是你当前的测试用例)。
- 无处理器场景:断言没有注册处理器时会抛出预期异常。
- 多处理器场景:断言注册多个处理器时会抛出预期异常。
举个无处理器场景的测试用例例子:
[Fact] public void TestCommandBus_NoHandler_ThrowsException() { using (var mock = AutoMock.GetLoose()) { var bus = mock.Create<CommandBus>(); // 断言没有处理器时抛出异常 Assert.Throws<Exception>(() => bus.Send(new TestCommand())); } }
内容的提问来源于stack exchange,提问作者RyannnnnnR

