85

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()

Nkosi
  • 235,767
  • 35
  • 427
  • 472
blgrnboy
  • 4,877
  • 10
  • 43
  • 94
  • 1
    Is it an extension method? If so you can't moq it. – PmanAce Jun 02 '17 at 20:02
  • Yes, I just looked, and it does seem to be an extension method. I am struggling to see how I can test this class, since it relies on the IServiceProvider. – blgrnboy Jun 02 '17 at 20:06
  • Check this answer for possible solutions. [Mocking Extension Methods with Moq](https://stackoverflow.com/questions/2295960/mocking-extension-methods-with-moq) – PmanAce Jun 02 '17 at 20:19
  • Why are you doing it this way? Use constructor injection for `IConfigurationDbContext` like the example IdentityServer4.EntityFramework library does :/ – Mardoxx Jun 03 '17 at 09:18
  • Also how does this even compile? `_dbSettings = dbSettings` `dbSettings` isn't even defined – Mardoxx Jun 03 '17 at 09:19
  • You should be registering `DbContext`s as Scoped, your `IDbcontext`s (if you want easier mocking) also as scoped, and your `IRepository`s as Transient. Dunno why you want to resolve a new scope inside your repository. – Mardoxx Jun 03 '17 at 09:21
  • @Mardoxx thank you for your input. I am pretty new to dependency injection, so I will have to look into it additionally. – blgrnboy Jun 03 '17 at 16:49

8 Answers8

149

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.

Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • 9
    This code here to mock the scopes deserves to be its own Nuget package. Thanks for providing this - My brain was melting trying to work out what it wanted. – Piotr Kula Apr 08 '20 at 19:54
  • what is "ConfigurationDbContext" ? – IPS Oct 06 '20 at 05:58
  • @IPS the name of the database context class from entity framework. This can have different names depending on how you generated it. – eddex Jan 13 '21 at 07:58
24

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.

Márton Balassa
  • 818
  • 7
  • 11
  • 1
    Can you give some insight into why we shouldn't mock types that we didn't create ourselves? I feel like this is a totally acceptable thing to do. Does it have something to do with the potential for third-parties changing the functionality of their types? If so, wouldn't that be a good thing to have in your test? I try not to create abstractions for the sake of abstractions, you know? – Derek Foulk Jan 03 '21 at 06:30
  • 2
    Mocking something you can't change does not add any value to your tests that would justify the time spent on setting up the mock. There are legit use cases like testing edge case behavior (eg. when GetService throws an exception), or mocking an external dependency that is expensive/complicated to set up in your tests (eg. the SMTP server for an email notification service), but generally you should just consider these externalities as working and tested. The purpose of mocking in a unit test is to isolate some code and embed it in a context where it is the single point of failure. – Márton Balassa Jan 04 '21 at 08:36
  • 8
    I totally agree with this. Using the default implementation is much simpler than trying to mock everything `using Microsoft.Extensions.DependencyInjection; var serviceCollection = new ServiceCollection(); serviceCollection.AddScoped(sp => yourMock.Object); serviceProvider = serviceCollection.BuildServiceProvider();` – Miguel Hughes Jan 06 '22 at 14:18
11

Just in case it's useful to somebody, here's an example of how I created my own ServiceProvider for my unit test as suggested here. I also added ServiceScope and ServiceScopeFactory mocks to serve it all up.

Here's the code in my unit test:

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);
    

I can then pass my serviceScopeFactoryMock to my sut constructor.

Here's the code that's being tested:

using (var scope = _serviceScopeFactory.CreateScope())
{
    var logger = scope.ServiceProvider.GetRequiredService<ILogger>();
    ...
}
mcmcmc
  • 189
  • 2
  • 8
5

I'd like to argue that when you need to add that much ceremony just to mock a simple method, then maybe your code isn't very testable. So another option would be to hide the service locator behind a more test and mock friendly interface (and in my opinion a nicer one too):

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;
    }
}

I've only implemented the GetService<T> method here, but you could easily add/remove so that the locator better suites your need. And an example of how to use it;

public class ALongRunningTask : IRunForALongTime
{
    private readonly IServiceLocator _serviceLocator;

    public ALongRunningTask(IServiceLocator serviceLocator)
    {
        _serviceLocator = serviceLocator;
    }

    public void Run()
    {
        using (_serviceLocator)
        {
            var repository = _serviceLocator.Get<IRepository>();
        }
    }
}
Kjetil Klaussen
  • 6,266
  • 1
  • 35
  • 29
3

Here is my snippet for mocking inside scoped service provider. Useful for testing IHostedService etc:

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;
}

Usage:

var service = new Mock<IMyService>();
var serviceProvider = CreateScopedServicesProvider((typeof(IMyService), scopedService.Object));
var sut = new ServiceThatUsesScopes(serviceProvider.Object)
dimaaan
  • 861
  • 13
  • 16
2

I was also looking for this, but i only needed to mock GetService. I always use AutoFac to auto generate mocks. In this example 'GetService' always returns a mocked instance. You can change the mock behavior afterwards with the freeze method.

Example:

Class to test:

public class ApiResourceRepository : ApiResourceRepository {
            private readonly IServiceProvider _serviceProvider;

            public ApiResourceRepository(IServiceProvider serviceProvider) {
                _serviceProvider = serviceProvider;
            }

            public object Get(int id) {
                using (var serviceScope = _serviceProvider.CreateScope()) {
                    var repo = serviceScope.ServiceProvider.GetService<IPersonRepository>();
                    return repo.GetById(id);
                }
            }
        }

Unit Test:

 [Fact]
        public void Test() {
            // arrange
            var fixture = new Fixture()
             .Customize(new AutoMoqCustomization())
             .Customize(new ServiceProviderCustomization());

            fixture.Freeze<Mock<IPersonRepository>>()
                .Setup(m => m.GetById(It.IsAny<int>()))
                .Returns(new Person(Name = "John"));

            // Act
            var apiResource = _fixture.Create<ApiResourceRepository>();
            var person = apiResource.Get(1);

            // Assert
            ...
        }

Custom AutoFac provider

public class ServiceProviderCustomization : ICustomization {

        public void Customize(IFixture fixture) {
            var serviceProviderMock = fixture.Freeze<Mock<IServiceProvider>>();

            // GetService
            serviceProviderMock
               .Setup(m => m.GetService(It.IsAny<Type>()))
               .Returns((Type type) => {
                   var mockType = typeof(Mock<>).MakeGenericType(type);
                   var mock = fixture.Create(mockType, new SpecimenContext(fixture)) as Mock;

                   // Inject mock again, so the behavior can be changed with _fixture.Freeze()
                   MethodInfo method = typeof(FixtureRegistrar).GetMethod("Inject");
                   MethodInfo genericMethod = method.MakeGenericMethod(mockType);
                   genericMethod.Invoke(null, new object[] { fixture, mock });

                   return mock.Object;
               });

            // Scoped
            var serviceScopeMock = fixture.Freeze<Mock<IServiceScope>>();
            serviceProviderMock
               .As<IServiceScopeFactory>()
               .Setup(m => m.CreateScope())
               .Returns(serviceScopeMock.Object);

            serviceProviderMock.As<ISupportRequiredService>()
                .Setup(m => m.GetRequiredService(typeof(IServiceScopeFactory)))
                .Returns(serviceProviderMock.Object);
        }
    }
Michael Vonck
  • 296
  • 2
  • 5
  • Thanks Michael. Just updated a snippet I have with your code. I referenced you in it. https://gist.github.com/grgoncal/63b03e36cc89722f408d9f4a50c5822c – Guilherme Rocha Aug 29 '23 at 13:34
1

DISCLAIMER: The embedded links point to subpages of my GitHub and NuGet Page. But I hope it helps you or someone else, never the less.


I just created exaclty such a thing because I couldn't find any. It implements IServiceCollection and IServiceProvider to test my Startup-Configuration, especially, whether all types are registered properly to the DI-Container. And it is a general purpose replacement for those interfaces, providing Mocks (Moq) as singletons for each Registered Type. Foo<Bar> is different from Foo<Bus>.

There is a readme.md on GitHub and the code base is not that big.

There is also a nuget package called MockProvider and - as mentioned - the code is on GitHub. I put it under MIT, so do what you want with it. It is free to use and contribute.

Consider it a way of giving back.

Andreas
  • 828
  • 4
  • 15
0

I use Moq and xUnit for my testing. I've ran into a similar issue before and my solution was to extract data transactions into a SqlExecuter class with an interface so I can directly mock the responses from the database. That simplified everything enough to be able to just build the service provider and pass that in. You'll need xUnit, Moq, and some Microsoft packages (Microsoft.EntityFrameworkCore & Microsoft.EntityFrameworkCore.InMemory).

SqlExecuter.cs

public interface ISqlExecuter
{
    Task<List<SqlParameter>> FirstOrDefaultApiResource(ConfigurationDbContext context, int id);
}

public class SqlExecuter : ISqlExecuter
{
    public async Task<ApiResource> FirstOrDefaultApiResource(ConfigurationDbContext context, int id) =>
        return await context.ApiResources
            .Include(x => x.Scopes)
            .Include(x => x.UserClaims)
            .FirstOrDefaultAsync(x => x.Id == id);
} 

ApiResourceRepository.cs

public class ApiResourceRepository : IApiResourceRepository
{
    private readonly IServiceProvider _serviceProvider;
    private readonly ISqlExecuter _sqlExecuter;

    public ApiResourceRepository(IServiceProvider serviceProvider, ISqlExecuter sqlExecuter)
    {
        _serviceProvider = serviceProvider;
        _sqlExecuter = sqlExecuter;
        _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 _sqlExecuter.FirstOrDefaultApiResource(context, id);
        }

        return result;
    }
}

ApiResourceRepositoryTests.cs

[Fact]
public async Task Get_Success()
{
    // Arrange
    var id = 42069;
    var scope = "Scope";
    var claim = "UserClaims";
    var services = new ServiceCollection();
    services.AddDbContext<ConfigurationDbContext>(opt => opt
            .UseInMemoryDatabase(databaseName: $"ConfigurationDbContext-{ DateTime.Now.ToString() }")
            .ConfigureWarnings(x => x.Ignore(InMemoryEventId.TransactionIgnoredWarning)),
        ServiceLifetime.Singleton, ServiceLifetime.Singleton);
    var serviceProvider = services.BuildServiceProvider();
    var mockSqlExecuter = new Mock<SqlExecuter>();
    mockSqlExecuter.Setup(x => x.FirstOrDefaultApiResource(It.IsAny<ConfigurationDbContext>(), It.IsAny<int>()))
        .Returns(new ApiResource() { Id = id , Scope = scope, UserClaims = claim })
    var mockApiResourceRepository = new Mock<ApiResourceRepository>(serviceProvider, mockSqlExecuter.Object);

    // Act
    var result = await mockApiResourceRepository.Object.Get(id);

    
    // Assert
    Assert.NotNull(response);
    Assert.Equal(id, result.Id);
    Assert.Equal(scope, result.Scope);
    Assert.Equal(claim, result.UserClaims);
}

Alternatively instead of using the SqlExecuter class, in other cases I have seeded the context that is set in the service provider.

// Arrange
var id = 42069;
var scope = "Scope";
var claim = "UserClaims";
var services = new ServiceCollection();
services.AddDbContext<ConfigurationDbContext>(opt => opt
        .UseInMemoryDatabase(databaseName: $"ConfigurationDbContext-{DateTime.Now.ToString()}")
        .ConfigureWarnings(x => x.Ignore(InMemoryEventId.TransactionIgnoredWarning)),
    ServiceLifetime.Singleton, ServiceLifetime.Singleton);
var serviceProvider = services.BuildServiceProvider();
var context = Interfaces.ServiceProvider.GetService<ComQueMDSContext>();
context.ApiResources.RemoveRange(context.ApiResources);
context.ApiResources.AddRange(new List<ApiResource>(){ new ApiResource(){ Id = id, Scope = scope, UserClaims = claim } });
context.SaveChanges();
var mockApiResourceRepository = new Mock<ApiResourceRepository>(serviceProvider);

I have also extracted much of this work to a Fixture class and collection, as suggested by xUnit to centralize contexts & reduce test time.

https://xunit.net/docs/shared-context

DMendoza
  • 412
  • 4
  • 8