如何模拟 IServiceProvider 并仍然允许泛型类型的 CreateInstance?

Dan*_*own 0 c# unit-testing moq xunit asp.net-core

我正在尝试对一些代码进行单元测试,这些代码使用IServiceProvider和反射的组合来创建扩展抽象类的每个类的实例BaseCommand

IEnumerable<BaseCommand> commandsInAssembly = typeof(BaseCommand)
    .Assembly.GetTypes()
    .Where(t => t.IsSubclassOf(typeof(BaseCommand)) && !t.IsAbstract)
    .Select(t => (BaseCommand)ActivatorUtilities.CreateInstance(_serviceProvider, t))
    .ToList();
Run Code Online (Sandbox Code Playgroud)

这里棘手的部分_serviceProvider是注入并且需要被模拟(我认为),以允许这段代码成功且独立地运行。每个命令都需要访问 DI 以解决其依赖关系。大多数命令看起来类似于:

public SomeCommand(IAppState appState, ILoggerAdapter<SomeCommand> logger) : base(appState)
Run Code Online (Sandbox Code Playgroud)

我能够IServiceProvider很好地模拟来解决IAppState,但我在ILoggerAdapter<>. 这是我目前的设置:

单元测试构造函数

var serviceProvider = new Mock<IServiceProvider>();

serviceProvider
    .Setup(x => x.GetService(typeof(IAppState)))
    .Returns(new AppState());

serviceProvider
    .Setup(x => x.GetService(typeof(ILoggerAdapter<>)))
    .Returns(typeof(LoggerAdapter<>));

var serviceScope = new Mock<IServiceScope>();
serviceScope
    .Setup(x => x.ServiceProvider)
    .Returns(serviceProvider.Object);

var serviceScopeFactory = new Mock<IServiceScopeFactory>();
serviceScopeFactory
    .Setup(x => x.CreateScope())
    .Returns(serviceScope.Object);

serviceProvider
    .Setup(x => x.GetService(typeof(IServiceScopeFactory)))
    .Returns(serviceScopeFactory.Object); var mocker = new AutoMocker();

_commandDispatcher = new CommandDispatcher(serviceProvider.Object, _mockAppState.Object, _mockLogger.Object);
Run Code Online (Sandbox Code Playgroud)

这会生成以下错误: System.InvalidOperationException :尝试激活“SomeCommand”时无法解析“ILoggerAdapter`1[SomeCommand]”类型的服务。

如果我尝试更明确地设置我的设置(我想避免这种情况,它会使测试更加脆弱)并使用:

serviceProvider
    .Setup(x => x.GetService(typeof(ILoggerAdapter<SomeCommand>)))
    .Returns(typeof(LoggerAdapter<SomeCommand>));
Run Code Online (Sandbox Code Playgroud)

但这也会产生错误:System.ArgumentException : Object of type 'System.RuntimeType' cannot be convert to type 'ILoggerAdapter`1[SomeCommand]'。

我读到使用 AutoMocking 容器或 Fixture 可能更合适,但我不确定从哪里开始。我对 C# 中的单元测试相当陌生。

如何在不IServiceProvider爆炸的情况下模拟/提供给我的 SUT ActivatorUtilities.CreateInstance(IServiceProvider, type)

Hen*_*ema 7

serviceProvider
    .Setup(x => x.GetService(typeof(ILoggerAdapter<>)))
    .Returns(typeof(LoggerAdapter<>));
Run Code Online (Sandbox Code Playgroud)

这个设置的问题typeof(ILoggerAdapter<>)是永远不会被解决,它是一个通用类型,所以ILoggerAdapter<SomeCommand>会被解决。

serviceProvider
    .Setup(x => x.GetService(typeof(ILoggerAdapter<SomeCommand>)))
    .Returns(typeof(LoggerAdapter<SomeCommand>));
Run Code Online (Sandbox Code Playgroud)

通过此设置,您可以解决正确的服务。但是,当您返回表示的实例而不是 的实例时,您返回了错误的结果。您将需要通过-ing 或模拟它来创建一个实例。TypeLoggerAdapter<SomeCommand>LoggerAdapter<SomeCommand>LoggerAdapter<SomeCommand>new


另一个解决方案可能是您不模拟IServiceProvider实例,而是IServiceProvider使用常规 DI 设置创建一个“真实”实例:创建一个新ServiceCollection实例,添加您的服务并调用BuildServiceProvider(). 例如:

var services = new ServiceCollection();
// Add IAppState, ILoggerAdapater, and other services

// Create the service provider instance
var serviceProvider = services.BuildServiceProvider();

// Resolve services from the IServiceProvider and pass it along
var appState = serviceProvider.GetRequiredService<IAppState>();
Run Code Online (Sandbox Code Playgroud)