wake-up-neo.net

Moq IServiceProvider/IServiceScope

Ich versuche einen Mock (mit Moq) für eine IServiceProvider zu erstellen, damit ich meine Repository-Klasse testen kann:

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

Mein Versuch, das Mock-Objekt zu erstellen, ist wie folgt:

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

Ich erhalte folgende Fehlermeldung:

System.NotSupportedException: Ausdruck verweist auf eine Methode, die nicht zum verspotteten Objekt gehört: x => x.GetRequiredService ()

9
blgrnboy

Wie bereits erwähnt, können in Moq keine Erweiterungsmethoden eingerichtet werden.

In diesem Fall ist der Quellcode dieser Erweiterungsmethoden jedoch auf Github verfügbar

ServiceProviderServiceExtensions .

Der übliche Weg, um ein Problem wie dieses zu umgehen, besteht darin, herauszufinden, was die Erweiterungsmethoden bewirken, und einen Pfad sicher durch seine Ausführung zu verspotten.

Der Basistyp in all dem ist die IServiceProvider und ihre object Getservice(Type type) Methode. Diese Methode wird letztendlich beim Auflösen des Diensttyps aufgerufen. Und wir haben es nur mit Abstraktion (Schnittstellen) zu tun, was die Verwendung von moq umso einfacher macht.

//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
//...

Überprüfen Sie den obigen Code, und stellen Sie fest, wie die Anordnung das erwartete Verhalten der zu testenden Erweiterungsmethoden und der zu testenden Methode durch Erweiterung (ohne beabsichtigtes Wortspiel) erfüllt.

16
Nkosi

Ich möchte argumentieren, dass, wenn Sie so viel Zeremonie hinzufügen müssen, um eine einfache Methode zu verspotten, Ihr Code möglicherweise nicht sehr testbar ist. Eine andere Möglichkeit wäre es, den Service Locator hinter einer test- und spottfreundlichen Oberfläche zu verstecken (und meiner Meinung nach auch einer schöneren):

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

Ich habe hier nur die GetService<T>-Methode implementiert, aber Sie können sie einfach hinzufügen/entfernen, damit der Locator Ihren Anforderungen besser entspricht. Und ein Beispiel, wie man es benutzt;

public class ALongRunningTask : IRunForALongTime
{
    private readonly IServiceLocator _serviceLocator;

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

    public void Run()
    {
        using (_serviceLocator)
        {
            var repository = _serviceLocator.Get<IRepository>();
        }
    }
}
1
Kjetil Klaussen

Ich habe auch danach gesucht, aber ich musste mich nur über GetService lustig machen. Ich benutze immer AutoFac, um automatisch Mocks zu generieren. In diesem Beispiel gibt 'GetService' immer eine gespielte Instanz zurück. Sie können das Scheinverhalten anschließend mit der Freeze-Methode ändern.

Beispiel:

Klasse zum Testen:

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

Gerätetest:

 [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
            ...
        }

Benutzerdefinierter AutoFac-Anbieter

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);
        }
    }
1
mik3132