I have a test fixture for my CompositionRoot
static class that pretty much just enumerates my Autofac IContainer
's services and attempts to instantiate them. If it can instantiate them, that's a test pass. The goal is to ensure I did not forget to register new interfaces with my Autofac container.
However, some types that are registered consume types in their constructors that are not intended to be, or cannot be, registered with Autofac. For example I have a class that takes a string
in its constructor. The way I inject this class into my code base is:
Func<string, ITypeThatRequiresAFactory>
Here is my test fixture (using NUnit3 + FluentAssertions):
[TestFixture]
[Parallelizable(ParallelScope.All)]
public class CompositionRootTest
{
private sealed class ConcreteTypeEnumerator : IEnumerable
{
private readonly IContainer _container;
public ConcreteTypeEnumerator()
{
_container = CompositionRoot.Setup();
}
public IEnumerator GetEnumerator()
{
return _container.ComponentRegistry.Registrations
.SelectMany(x => x.Services)
.OfType<TypedService>()
.GetEnumerator();
}
}
[TestCaseSource(typeof(ConcreteTypeEnumerator))]
public void Service_should_be_instantiable(Service service)
{
using var container = CompositionRoot.Setup();
container.Invoking(c => c.ResolveService(service))
.Should().NotThrow()
.And.NotBeNull();
}
}
The test Service_should_be_instantiable
will fail when it tries to instantiate the service implementing ITypeThatRequiresAFactory
because of that string
parameter in its constructor.
How can I refactor my test fixture to:
Service_should_be_instantiable
Func<>
)?The goal is to use Autofac's implicit relationship type for automatic factories (Func<>
) and possibly other implicit relationships later (like Lazy<>
), so a solution requiring me to explicitly register hand-written factories, for instance, is a non-starter.
What you'll need to do is:
.As<IFoo>()
you won't run that test twice).Autofac
types (because ILifetimeScope
and IComponentContext
are inherently registered and that shouldn't be part of your tests).That LINQ looks like:
var exclude = new []
{
typeof(ThingToSkip),
typeof(OtherThingToSkip),
};
// Get all the registrations
// Pull out the services
// Only look at typed (reflection) services
// Get the actual type from the service
// Filter down to unique types
// Exclude the types you don't want to test
// Exclude the Autofac types
var typesToTest = container.ComponentRegistry.Registrations
.SelectMany(x => x.Services)
.OfType<TypedService>()
.Select(t => t.ServiceType)
.Distinct()
.Except(exclude)
.Where(t => !t.FullName.StartsWith("Autofac."));
Your resolve line will change to:
container.Resolve(type);
...because you'll resolve based on a type rather than a service.
I think that should get you where you're going.
Note this won't cover things like...
IEnumerable<T>
, Func<T>
, or any of the other "built-in relationships" supported by AutofacILifetimeScope
or IComponentContext
...and so on. I mean, you probably got that because you're already filtering by TypedService
but I figured I'd be explicit. That's the primary reason something like this isn't built into Autofac - folks would assume we have the ability to check all the registrations and that's not really possible.