我试图对一些代码进行单元测试,这些代码使用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();这里的棘手之处在于,_serviceProvider被注入,需要被模拟(我认为),这样才能让这段代码成功地独立运行。每个命令都需要访问DI来解决其依赖关系。大多数命令看起来类似于:
public SomeCommand(IAppState appState, ILoggerAdapter<SomeCommand> logger) : base(appState)我能够很好地模拟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);这会产生以下错误:System.InvalidOperationException :无法解析“ILoggerAdapter1SomeCommand”类型的服务,同时试图激活“SomeCommand”.
如果我试图更明确地设置我的设置(这是我想要避免的,它会使测试变得更加脆弱)并使用:
serviceProvider
.Setup(x => x.GetService(typeof(ILoggerAdapter<SomeCommand>)))
.Returns(typeof(LoggerAdapter<SomeCommand>));但是这也会产生一个错误:System.ArgumentException :类型为'System.RuntimeType‘的对象不能转换为类型System.RuntimeType
我正在阅读使用AutoMocking容器或夹具可能更合适,但我不知道从哪里开始。我对C#中的单元测试相当陌生。
如何在没有IServiceProvider ActivatorUtilities.CreateInstance(IServiceProvider, type) 的情况下模拟/提供给SUT?
发布于 2018-08-28 13:50:38
serviceProvider
.Setup(x => x.GetService(typeof(ILoggerAdapter<>)))
.Returns(typeof(LoggerAdapter<>));这个设置的问题是typeof(ILoggerAdapter<>)永远不会被解决,它是一个泛型类型,所以ILoggerAdapter<SomeCommand>将被解决。
serviceProvider
.Setup(x => x.GetService(typeof(ILoggerAdapter<SomeCommand>)))
.Returns(typeof(LoggerAdapter<SomeCommand>));使用此设置,您将解析正确的服务。但是,在返回表示LoggerAdapter<SomeCommand>的LoggerAdapter<SomeCommand>实例时,返回的结果是错误的,而不是LoggerAdapter<SomeCommand>的实例。您将需要创建一个LoggerAdapter<SomeCommand>实例,要么通过new-ing up,要么对其进行模拟。
另一个解决方案可能是,您不模拟IServiceProvider实例,而是使用常规的DI设置创建一个“真实的”IServiceProvider实例:创建一个新的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>();发布于 2018-08-28 16:01:35
您的SUT不调用IServiceProvider的方法,因此根本不需要对它们进行模拟。您想要测试的是,对于每个BaseCommand的具体子类,SUT是否将_serviceProvider和t传递给CreateInstance。
其中一种方法是将静态方法ActivatorUtilities.CreateInstance转换为CommandDispatcher的可注入依赖项,例如
interface IActivator
{
object CreateInstance(IServiceProvider serviceProvider, Type t)
}那么测试可能会如下所示
pivate class TestCommand : BaseCommand
{
public TestCommand(Type realCommandType)
{
}
}
// ...
// that's all the IServiceProvider mocking you need
var serviceProvider = new Mock<IServiceProvider>();
var activator = new Mock<IActivator>();
activator.Setup(_ => _.CreateInstance(serviceProvider, It.IsAny<Type>())
.Returns<IServiceProvider, Type>((sp, t) => new TestCommand(t));
// ...
foreach (var expectedType in typeof(CommandDispatcher).Assembly.GetTypes()
.Where(t => t.IsSubclassOf(typeof(BaseCommand)) && !t.IsAbstract))
{
// check, whether whatever you do with commandsInAssembly
// contains a TestCommand with expectedType
}https://stackoverflow.com/questions/52059180
复制相似问题