首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何模拟IServiceProvider,仍然允许泛型类型的CreateInstance?

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

Stack Overflow用户
提问于 2018-08-28 13:30:33
回答 2查看 10.7K关注 0票数 1

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

代码语言:javascript
复制
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来解决其依赖关系。大多数命令看起来类似于:

代码语言:javascript
复制
public SomeCommand(IAppState appState, ILoggerAdapter<SomeCommand> logger) : base(appState)

我能够很好地模拟IServiceProvider以解决IAppState问题,但我在ILoggerAdapter<>方面遇到了困难。下面是我当前的设置:

单元测试构造器

代码语言:javascript
复制
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”.

如果我试图更明确地设置我的设置(这是我想要避免的,它会使测试变得更加脆弱)并使用:

代码语言:javascript
复制
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?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2018-08-28 13:50:38

代码语言:javascript
复制
serviceProvider
    .Setup(x => x.GetService(typeof(ILoggerAdapter<>)))
    .Returns(typeof(LoggerAdapter<>));

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

代码语言:javascript
复制
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()。例如:

代码语言:javascript
复制
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>();
票数 9
EN

Stack Overflow用户

发布于 2018-08-28 16:01:35

您的SUT不调用IServiceProvider的方法,因此根本不需要对它们进行模拟。您想要测试的是,对于每个BaseCommand的具体子类,SUT是否将_serviceProvidert传递给CreateInstance

其中一种方法是将静态方法ActivatorUtilities.CreateInstance转换为CommandDispatcher的可注入依赖项,例如

代码语言:javascript
复制
interface IActivator
{
    object CreateInstance(IServiceProvider serviceProvider, Type t)
}

那么测试可能会如下所示

代码语言:javascript
复制
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 
}
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/52059180

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档