How to setup more complicated (IoC like) registration in AutoFixture
Asked Answered
S

1

3

Is it possible to reuse production IoC container registration in integration tests when using AutoFixture?

The problem is that I need the following fixture setup to inject mocks if dependency is not registered and inject "real" database related dependencies

var fixture = new Fixture().WithMocks().WithRealDatabase()

The solution I've tried

internal static Fixture WithMocks(this Fixture fixture)
{
    fixture.Customize(new AutoMoqCustomization());
}

internal static Fixture WithRealDatabase(this Fixture fixture)
{
    var containerBuilder = new Autofac.ContainerBuilder();
    ...
    containerBuilder.Register(c => c.Resolve<ISessionFactory>().OpenSession())
    containerBuilder.RegisterGeneric(typeof(Repository<>)).AsImplementedInterfaces()        
    containerBuilder.RegisterAssemblyTypes(AppDomain.CurrentDomain.GetAssemblies())
        .Where(t => t.Name.EndsWith("Repository"))
        .AsImplementedInterfaces();
    ...
    fixture.Customizations.Add(new ContainerSpecimenBuilder(containerBuilder.Build()));
}

internal class ContainerSpecimenBuilder : ISpecimenBuilder
{
    private readonly IContainer container;

    public ContainerSpecimenBuilder(IContainer container)
    {
        this.container = container;
    }

    public object Create(object request, ISpecimenContext context)
    {
        var seededRequest = request as SeededRequest;

        if (seededRequest == null)
        {
            return new NoSpecimen(request);
        }

        var result = this.container.ResolveOptional(seededRequest.Request as Type);
        return result ?? new NoSpecimen(request);
    }
}

But the problem with this approach is that container.Resolve will not take into account already registered dependencies in AutoFixture.

Is there any alternative to solve this problem to have more complicated registrations?

Salamone answered 31/7, 2015 at 7:7 Comment(0)
B
2

The general approach looks sound, but you should add the ContainerSpecimenBuilder to ResidueCollectors instead of to Customizations:

fixture.ResidueCollectors.Add(new ContainerSpecimenBuilder(containerBuilder.Build()));

AutoMoqCustomization also adds a node to ResidueCollectors, so you may need to experiment a bit with the specific ordering to figure out exactly how to make it behave like you want it to behave. The ordering matters.

For more information about the difference between Customizations and ResidueCollectors, see the AutoFixture architecture documentation.


A slightly simpler (and safer?) implementation of ContainerSpecimenBuilder could be just handling requests for Type instances directly, instead of for SeededRequest, as almost all SeededRequest values are relayed to requests for Type objects anyway:

internal class ContainerSpecimenBuilder : ISpecimenBuilder
{
    private readonly IContainer container;

    public ContainerSpecimenBuilder(IContainer container)
    {
        this.container = container;
    }

    public object Create(object request, ISpecimenContext context)
    {
        var t = request as Type;

        if (t == null)
            return new NoSpecimen(request);

        var result = this.container.ResolveOptional(t);
        return result ?? new NoSpecimen(request);
    }
}
Bake answered 31/7, 2015 at 9:18 Comment(1)
Mark, thank you for support, it was as simple as using ResidueCollectors instead of Customization, plus changing order of fixture customization. to put container specimen builder first.Salamone

© 2022 - 2024 — McMap. All rights reserved.