blg*_*boy 18 c# unit-testing moq .net-core asp.net-core
我正在尝试创建一个模拟(使用Moq),IServiceProvider以便我可以测试我的存储库类:
public class ApiResourceRepository : IApiResourceRepository
{
private readonly IServiceProvider _serviceProvider;
public ApiResourceRepository(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
_dbSettings = dbSettings;
}
public async Task<ApiResource> Get(int id)
{
ApiResource result;
using (var serviceScope = _serviceProvider.
GetRequiredService<IServiceScopeFactory>().CreateScope())
{
var context = serviceScope.ServiceProvider.GetRequiredService<ConfigurationDbContext>();
result = await
context.ApiResources
.Include(x => x.Scopes)
.Include(x => x.UserClaims)
.FirstOrDefaultAsync(x => x.Id == id);
}
return result;
}
}
Run Code Online (Sandbox Code Playgroud)
我创建Mock对象的尝试如下:
Mock<IServiceProvider> serviceProvider = new Mock<IServiceProvider>();
serviceProvider.Setup(x => x.GetRequiredService<ConfigurationDbContext>())
.Returns(new ConfigurationDbContext(Options, StoreOptions));
Mock<IServiceScope> serviceScope = new Mock<IServiceScope>();
serviceScope.Setup(x => x.ServiceProvider).Returns(serviceProvider.Object);
serviceProvider.Setup(x => x.CreateScope()).Returns(serviceScope.Object);
Run Code Online (Sandbox Code Playgroud)
我收到以下错误:
System.NotSupportedException:Expression引用不属于模拟对象的方法:x => x.GetRequiredService()
Nko*_*osi 35
如前所述,Moq不允许设置扩展方法.
然而,在这种情况下,所述扩展方法的源代码在Github上可用
ServiceProviderServiceExtensions.
解决这类问题的常用方法是找出扩展方法的作用,并通过它的执行安全地模拟路径.
所有这些中的基本类型是IServiceProvider它的object Getservice(Type type)方法.此方法是解析服务类型时最终调用的方法.而且我们只处理抽象(接口),这使得使用moq变得更加容易.
//Arrange
var serviceProvider = new Mock<IServiceProvider>();
serviceProvider
.Setup(x => x.GetService(typeof(ConfigurationDbContext)))
.Returns(new ConfigurationDbContext(Options, StoreOptions));
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 sut = new ApiResourceRepository(serviceProvider.Object);
//Act
var actual = sut.Get(myIntValue);
//Asssert
//...
Run Code Online (Sandbox Code Playgroud)
查看上面的代码,您将看到该安排如何满足扩展方法的预期行为,并通过扩展(无双关语)测试方法.
小智 14
以防万一它对某人有用,这里有一个示例,说明我如何按照此处的建议为我的单元测试创建自己的 ServiceProvider 。我还添加了 ServiceScope 和 ServiceScopeFactory 模拟来提供所有服务。
这是我的单元测试中的代码:
var serviceCollection = new ServiceCollection();
// Add any DI stuff here:
serviceCollection.AddSingleton<ILogger>(loggerMock.Object);
// Create the ServiceProvider
var serviceProvider = serviceCollection.BuildServiceProvider();
// serviceScopeMock will contain my ServiceProvider
var serviceScopeMock = new Mock<IServiceScope>();
serviceScopeMock.SetupGet<IServiceProvider>(s => s.ServiceProvider)
.Returns(serviceProvider);
// serviceScopeFactoryMock will contain my serviceScopeMock
var serviceScopeFactoryMock = new Mock<IServiceScopeFactory>();
serviceScopeFactoryMock.Setup(s => s.CreateScope())
.Returns(serviceScopeMock.Object);
Run Code Online (Sandbox Code Playgroud)
然后我可以将 serviceScopeFactoryMock 传递给 sut 构造函数。
这是正在测试的代码:
using (var scope = _serviceScopeFactory.CreateScope())
{
var logger = scope.ServiceProvider.GetRequiredService<ILogger>();
...
}
Run Code Online (Sandbox Code Playgroud)
一般规则是您不要模拟您不拥有的类型。除非您需要验证对服务提供者的调用,否则只需在您的测试中IServiceProvider从 a构建ServiceCollection。
我想说的是,当您需要添加那么多仪式只是为了模拟一个简单的方法时,那么您的代码可能不太可测试。因此,另一种选择是将服务定位器隐藏在更加测试和模拟友好的界面后面(在我看来也是一个更好的界面):
public interface IServiceLocator : IDisposable
{
T Get<T>();
}
public class ScopedServiceLocator : IServiceLocator
{
private readonly IServiceScopeFactory _factory;
private IServiceScope _scope;
public ScopedServiceLocator(IServiceScopeFactory factory)
{
_factory = factory;
}
public T Get<T>()
{
if (_scope == null)
_scope = _factory.CreateScope();
return _scope.ServiceProvider.GetService<T>();
}
public void Dispose()
{
_scope?.Dispose();
_scope = null;
}
}
Run Code Online (Sandbox Code Playgroud)
我只GetService<T>在这里实现了该方法,但您可以轻松添加/删除,以便定位器更好地满足您的需求。以及如何使用它的示例;
public class ALongRunningTask : IRunForALongTime
{
private readonly IServiceLocator _serviceLocator;
public ALongRunningTask(IServiceLocator serviceLocator)
{
_serviceLocator = serviceLocator;
}
public void Run()
{
using (_serviceLocator)
{
var repository = _serviceLocator.Get<IRepository>();
}
}
}
Run Code Online (Sandbox Code Playgroud)
这是我在范围服务提供商内部进行模拟的片段。对于测试 IHostedService 等很有用:
Mock<IServiceProvider> CreateScopedServicesProvider(params (Type @interface, Object service)[] services)
{
var scopedServiceProvider = new Mock<IServiceProvider>();
foreach (var (@interfcae, service) in services)
{
scopedServiceProvider
.Setup(s => s.GetService(@interfcae))
.Returns(service);
}
var scope = new Mock<IServiceScope>();
scope
.SetupGet(s => s.ServiceProvider)
.Returns(scopedServiceProvider.Object);
var serviceScopeFactory = new Mock<IServiceScopeFactory>();
serviceScopeFactory
.Setup(x => x.CreateScope())
.Returns(scope.Object);
var serviceProvider = new Mock<IServiceProvider>();
serviceProvider
.Setup(s => s.GetService(typeof(IServiceScopeFactory)))
.Returns(serviceScopeFactory.Object);
return serviceProvider;
}
Run Code Online (Sandbox Code Playgroud)
用法:
var service = new Mock<IMyService>();
var serviceProvider = CreateScopedServicesProvider((typeof(IMyService), scopedService.Object));
var sut = new ServiceThatUsesScopes(serviceProvider.Object)
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
7558 次 |
| 最近记录: |