I am trying to create a Mock (using Moq) for an IServiceProvider
so that I can test my repository class:
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; } }
My attempt at creating the Mock object is as follows:
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);
I am receiving the following error:
System.NotSupportedException : Expression references a method that does not belong to the mocked object: x => x.GetRequiredService()
The IServiceProvider is responsible for resolving instances of types at runtime, as required by the application. These instances can be injected into other services resolved from the same dependency injection container. The ServiceProvider ensures that resolved services live for the expected lifetime.
IServiceScopeFactory is always a Singleton but the IServiceProvider can vary based on the lifetime of the containing class. So if you create a scope and resolve services from this scope, and any of those services take an IServiceProvider, it'll be the scoped one.
As already stated, Moq does not allow setup of extension methods.
In this case however the source code of the said extension methods are available on Github
ServiceProviderServiceExtensions.
The usual way around an issue like this is to find out what the extension methods do and mock a path safely through it's execution.
The base type in all of this is the IServiceProvider
and its object Getservice(Type type)
method. This method is what is ultimately called when resolving the service type. And we are only dealing with abstraction (interfaces) then that makes using moq all the more easier.
//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 //...
Review the code above and you would see how the arrangement satisfies the expected behavior of the extension methods and by extension (no pun intended) the method under test.
The general rule is that you don't mock types that you don't own. Unless you need to verify the calls made to the service provider, just build the IServiceProvider
from a ServiceCollection
in your tests.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With