What are the principles behind AutoFixture's declarative way of setting up a fixture?

StackOverflow https://stackoverflow.com/questions/16731821

  •  30-05-2022
  •  | 
  •  

Question

I have previously asked a similar question on SO to which I got an answer. At the time, for the sake of expediency, I mechanically applied the answer but now I'm trying to get a handle on how the mechanism for declaratively setting up a fixture is.

So, I am currently looking at Mark Seemann's Dealing With Types Without Public Constructors blog post and converting it to be declarative. It is very similar to my original query but I can't get it to work. Please note that the code given is not actually production code and that this is a learning exercise.

Now if it helps, I've got the imperative code up on GitHub and the code in question is reproduced below:

[Fact]
public static void CanOverrideCtorArgs()
{
    var fixture = new Fixture();

    var knownText = "This text is not anonymous";
    fixture.Register<int, IMyInterface>( i => new FakeMyInterface( i, knownText ) );

    var sut = fixture.Create<MyClass>();
}

This is the code similar to the one given in this post.

Hence, my question is what should I know/read in order to convert this snippet of imperative code to be declarative.

Was it helpful?

Solution

Go read

for great examples of customizations and how to package, mix and mingle them.

Main principle is that you make your Customizations as granular as possible.

Then, you need to feed them in to the processing pipeline via either:

  • AutoData-derived attributes for global stuff (i.e. like MyTestConventions in Mark's answer )
  • A CustomizeWith helper[1] or similar
  • Trickery such as doing a [Freeze( As ... )]

My implementation

Automating this, I'd write:

[Theory, AutoData]
public static void OutOfBandCustomization( 
    [CustomizeWith( typeof( MyFakerCustomization ) )] MyClass sut )
{
}

Using this customization:

public class MyFakerCustomization : ICustomization
{
    void ICustomization.Customize( IFixture fixture )
    {
        var knownText = "This text is not anonymous";
        fixture.Register<int, IMyInterface>( i => 
            new FakeMyInterface( i, knownText ) );
    }
}

Obviously registering a string and/or using AutoMoqCustomization might also be useful.

My general helpers

[1] CustomizeWith is this helper Attribute (hat tip to Adam Jasinski):

[AttributeUsage( AttributeTargets.Parameter, AllowMultiple = true )]
sealed class CustomizeWithAttribute : CustomizeAttribute
{
    readonly Type _type;

    public CustomizeWithAttribute( Type customizationType )
    {
        if ( customizationType == null )
            throw new ArgumentNullException( "customizationType" );
        if ( !typeof( ICustomization ).IsAssignableFrom( customizationType ) )
            throw new ArgumentException( 
                "Type needs to implement ICustomization" );
        _type = customizationType;
    }

    public override ICustomization GetCustomization( ParameterInfo parameter )
    {
        return (ICustomization)Activator.CreateInstance( _type );
    }
}

Aside

One tip: you can express

fixture.Register<int, IMyInterface>( i => 
    new FakeMyInterface( i, knownText ) );

as

 fixture.Customize<IMyInterface>(c =>c.FromFactory((int i)=>
     new FakeMyInterface(i,knownText)));

too. While this doesn't make your case easier, it is a more general way of customizing what's going on.

Internally, Register is [currently]:

fixture.Customize<T>(c => c.FromFactory(creator).OmitAutoProperties());

OTHER TIPS

First of all, I'm going to answer this question under the assumption that TypesWithoutPublicCtrs is defined as in the OP's GitHub repository:

public class TypesWithoutPublicCtrs
{
    private readonly IMyInterface _mi;

    public TypesWithoutPublicCtrs(IMyInterface mi)
    {
        _mi = mi;
    }
}

The reason I'm explicitly calling this out is because the name is a red herring: it does have a public constructor; it just doesn't have a default constructor.

Anyway, AutoFixture easily deals with the absence of default constructors. The problem here isn't the TypesWithoutPublicCtrs class itself, but rather the IMyInterface interface. Interfaces are problematic because they can't be initialized at all.

Thus, you need to somehow map an interface to a concrete class. There are various ways to do that.

One-off solution

Once in a while, I use this one-off solution, although I find it ugly. However, it's easy and doesn't require a lot of sophisticated setup.

[Theory, AutoData]
public void TestSomething(
    [Frozen(As = typeof(IMyInterface))]FakeMyInterface dummy,
    TypesWithoutPublicCtrs sut)
{
    // use sut here, and ignore dummy
}

This isn't particularly nice, because it relies on a side-effect of the [Frozen] attribute, but it works as a self-contained one-off solution.

Convention

However, I much rather like to make a convention out of it, so that the same convention applies for all tests in a test suite. A test using such a convention could look like this:

[Theory, MyTestConventions]
public void TestSomething(TypesWithoutPublicCtrs sut)
{
    // use sut here; it'll automatically have had FakeMyInterface injected
}

The [MyTestConventions] attribute could look like this:

public class MyTestConventionsAttribute : AutoDataAttribute
{
    public MyTestConventionsAttribute() :
        base(new Fixture().Customize(new MyTestConventions())
    {}
}

The MyTestConventions class must implement the interface ICustomization. There are several ways in which you can map IMyInterface to FakeMyInterface; here's one:

public class MyTestConventions : ICustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Customizations.Add(
            new TypeRelay(typeof(IMyInterface), typeof(FakeMyInterface)));
    }
}

Auto-Mocking

However, you may get tired of having to create and maintain all those Fakes, so you can also turn AutoFixture into an Auto-Mocking Container. There are various options for doing that, leveraging Moq, NSubstitute, FakeItEasy and Rhino Mocks.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top