Search code examples
c#asp.netdependency-injectionmoqautofixture

How can I get the original type of a Mock after the Mock has been cast to an object


I have a ServiceCollection, and I want to make a method where I can pass in a set of parameters, and have the ServiceCollection return those services when the matching type is requested. I am using Autofixture to create my services. When I call fixture.Create<IMyService>, it'll return a Mock<IMyService>. For example

public static IServiceProvider CreateServiceProvider(params object[] services)
{
    var serviceCollection = new ServiceCollection();
    
    foreach (object service in services)
    {
       serviceCollection.AddTransient(factory => service);
    }

    return serviceCollection.BuildServiceProvider();
}

And then I want to use it like this

[Fact]
public void TestSomething()
{
   // This causes myFirstService to be of type of type IMyFirstService,
   // but have a value of type Mock<IMyFirstService> (same for
   // mySecondService)
   var fixture = new Fixture();
   IMyFirstService myFirstService = fixture.Create<IMyFirstService>();
   IMySecondService mySecondService = fixture.Create<IMySecondService>();

   var serviceProvider = CreateServiceProvider(myFirstService, mySecondService);

   // I want the returned objects to be myFirstService and mySecondService
   // that I created above
   var resolvedFirstService = serviceProvider.GetService<IMyFirstService>();
   var resolvedSecondService = serviceProvider.GetService<IMySecondService>();
}

The problem is that myFirstService and mySecondService are not returned, because the services are cast to an object when CreateServiceProvider is called, so they get registered with the ServiceCollection as type object instead of the correct interface type. I tried using serviceProvider.GetService(service.GetType(), factory => service) but the problem is that service.GetType() returns Castle.Proxies.IMyFirstServiceProxy.

I know I can make this work by doing the code below instead, but I want it to work by having the CreateServiceProvider method register those services.

[Fact]
public void TestSomething()
{
   var fixture = new Fixture();
   IMyFirstService myFirstService = fixture.Create<IMyFirstService>();
   IMySecondService mySecondService = fixture.Create<IMySecondService>();

   var serviceCollection = new ServiceCollection();
   serviceCollection.AddTransient(factory => myFirstService);
   serviceCollection.AddTransient(factory => mySecondService);
   var serviceProvider = serviceCollection.BuildServiceProvider();

   var resolvedFirstService = serviceProvider.GetService<IMyFirstService>();
   var resolvedSecondService = serviceProvider.GetService<IMySecondService>();
}

Solution

  • I do not know an easy way to achieve that but if you are adamant on going this way you can go with quite brittle path of the reflection and messing with implementation details:

    static IServiceProvider CreateServiceProvider(params object[] services)
    {
        var serviceCollection = new ServiceCollection();
    
        foreach (object service in services)
        {
            // relying on the mocked object implementing IMocked:
            var mocked = service as IMocked; 
    
            // using internal property:
            var mockedType = typeof(Mock).GetProperty("MockedType", BindingFlags.Instance | BindingFlags.NonPublic)
                .GetValue(mocked.Mock) as Type;
    
            serviceCollection.AddTransient(mockedType, _ => service);
        }
    
        return serviceCollection.BuildServiceProvider();
    }
    

    But again this can be quite brittle so I recommend to reconsider the approach and use Type's as parameters:

    static IServiceProvider CreateServiceProvider(params Type[] services)
    {
        var serviceCollection = new ServiceCollection();
        var fixture = new Fixture().Customize(new AutoMoqCustomization());
        var context = new SpecimenContext(fixture);
        foreach (var serviceType in services)
        {
            var instance = fixture.Create(serviceType, context);
            serviceCollection.AddTransient(serviceType, _ => instance);
        }
    
        return serviceCollection.BuildServiceProvider();
    }
    

    With call like:

    var serviceProvider = CreateServiceProvider(typeof(IMyFirstService), typeof(IMySecondService));