Search code examples
c#moqautofixture

Why doesn't AutoFixture return injected services when the services are of type object (even though they have an actual type)?


Due to the way that I'm trying to integrate AutoFixture with BUnit to run some Blazor tests, I need to create an IServiceProvider that will have an internal Fixture, and I can inject objects into that fixture to have those same objects returned when AutoFixture is request to create an object of that type.

My IServiceProvider looks like this

public class MockServiceProvider : IServiceProvider
{
   private readonly Fixture fixture;

   public MockServiceProvider()
   {
        this.fixture = new Fixture();
        this.fixture.Customize(new AutoMoqCustomization { ConfigureMembers = true });
   }

   public void RegisterServices(params object[] serviceInstances)
   {
      foreach (object serviceInstance in serviceInstances)
      {
         this.fixture.Inject(serviceInstance);
      }
   }

   public object GetService(Type serviceType)
   {
      return this.fixture.Create(serviceType, new SpecimenContext(this.fixture));
   }
}

Then I register my service instances like this:

[Theory, AutoDomainData]
public void TestSomething(
    IMyFirstService firstService,
    IMySecondService secondService,
    MockServiceProvider serviceProvider)
{  
   serviceProvider.RegisterServices(firstService, secondService);

   // test stuff
}

The problem is that any time GetService is called, the fixture returns a new service instance instead of the one I injected.

I think that the problem is that since I'm passing my service instances to RegisterServices as objects, AutoFixture doesn't register them as the type that they actually are.

I can fix the problem by registering each service instance one at a time like this...

public void RegisterService<T>(T serviceInstance)
{
   this.fixture.Inject(serviceInstance);
}

... but I'm curious as to why the first method doesn't work. Even though the parameters are an object array, shouldn't AutoFixture still know their actual type? I've tried this using RegisterServices(params dynamic[] serviceInstances) as well, but that doesn't work either.


Solution

  • The reason why Inject<T> doesn't infer the type of the instance but rather relies on the generic parameter is because it needs to enable scenarios where it is necessary to register values for base types or interfaces fixture.Inject<AbstractBaseType>(instance).

    The way Inject works behind the scenes is that it delegates the registration to Register<T>(Func<T> creator) which then delegates it to a fixture.Customize<T>(c => c.FromFactory(creator).OmitAutoProperties()). So you can see how it wouldn't work when you need type inference.

    You could as you said, make registrations one by one, or you could implement your own injection. Note the implementation below is a lighter implementation of the builder that would be normally generated by AutoFixture, and should cover most scenarios. If you find that some features are not working, like seeded requests, you might need a more complete implementation of the injection.

    public static void InjectAsReflectedType(this IFixture fixture, object instance)
    {
       var builder = new FilteringSpecimenBuilder(
          new FixedBuilder(instance),
          new ExactTypeSpecification(instance.GetType()));
       fixture.Customizations.Insert(0, builder);
    }
    

    Alternatively you could always invoke the Inject method through a wrapper non-generic method that calls the target method via reflection.