autofixturenunit-3.0

Can Autofixture customizations (via NUnit3 attributes) be composed and if so - how?


I'm working on some tests in which I'd like to compose some object set-up logic which is encapsulated within Autofixture customization classes. Here's the relevant parts of what I have:

public class UseSpecificConstructorCustomization : ICustomization
{
    public void Customize(IFixture fixture) 
    {
        fixture.Customize<MyObject>(c => c.FromFactory((string s) => new MyObject(s)));
    }
}

public class ExecuteMethodCustomization : ICustomization
{
    public void Customize(IFixture fixture) 
    {
        fixture.Customize<MyObject>(c => c.Do(x => x.AMethodIWantToExecute()));
    }
}

For each of these customizations I have a corresponding implementation of CusomizeAttribute which returns the customization instance. Now in my test (NUnit3.x) I would like to do something like this:

[Test,AutoData]
public void ThisIsMyTestMethod([UseSpecificConstructor,ExecuteMethod] MyObject obj)
{
    // Here I would like the object to have been created using the specified
    // string constructor AND the method 'AMethodIWantToExecute' to have been executed.
}

The behaviour I'm seeing though, suggests that in this case only one of the two customizations is used - whichever is specified last, it seems. That makes some kind of sense, because Autofixture is all about "creating" objects and not modifying them after they are created. It doesn't make sense to have two customizations (which correspond internally to specimen builders) to be used to create the same object.

Is there a way, using Autofixture, to achieve what I have described? I suppose what I'm looking for is a mechanism similar to customization which doesn't use a specimen builder, but instead exposes a "specimen customizer", to post-process the object after the specimen builder has done its job. Such a mechanism would only be able to set properties and use .Do() (not .FromFactory() etc). If it doesn't exist then I may go and feature-request it, but perhaps it does exist and I'm not seeing it.

I realise that I could alternatively move this "object configuration after creation" logic into the test itself, but it's more than a few lines long and I'd rather not duplicate it around. Also - that setup logic is not particularly relevant to the test, it's just about getting the object into a basic valid state. "Cognitively speaking" that logic belongs with the object-creation logic; IE: a dev doesn't want to have to think about it.


Solution

  • Having conducted some more research/poking around the source code of my own, I think I have found the answer. The solution lies in Fixture behaviors.

    Behaviors are decorator objects around ISpecimenBuilder and so they can post-process the result from a specimen builder and perform extra work. Behaviors implement the interface ISpecimenBuilderTransformation. It seems that at most one specimen builder can fulfil a request for Autofixture to create an object (following chain of responsibility pattern), but an unlimited number of behaviors can be applied atop of that, doing all manner of tasks, such as modifying the result before it is returned.

    Behaviors are added/removed to/from the fixture via fixture.Behaviors.

    Update April 2021: An example of this technique

    I have now used this technique in an open source project and can link to that as an example. Note that the linked project is using XUnit, but you will see that the attributes & syntax is identical to the NUnit3 Autofixture integration.

    The relevant parts of this are listed below.

    Advice: Name your attributes like "WithXyz"

    Implementation of CustomizeAttribute which use specimen builder transformations (aka "behaviours") do not actually create the specimen, they modify it. So name the attribute based upon how it will be modified, not on how it will be created.

    Create a post-processing specimen command class

    Autofixture provides an interface named ISpecimenCommand which is used for performing the post-processing upon specimens. Create a small class which implements this interface and which wraps a delegate. I found it useful & convenient to make that command generic:

    public class PostprocessingCommand<T> : ISpecimenCommand where T : class
    {
      readonly Action<T,ISpecimenContext> builderCustomizer;
      
      public void Execute(object specimen, ISpecimenContext context)
      {
        var builder = (T) specimen;
        builderCustomizer(builder, context);
      }
      
      public PostprocessingCommand(Action<T,ISpecimenContext> builderCustomizer)
      {
        this.builderCustomizer = builderCustomizer ?? throw new ArgumentNullException(nameof(builderCustomizer));
      }
    }
    

    Use a specification for matching only the right types

    Update Oct 2023: there's a built-in specification which does this, no need to write one! Use DirectBaseTypeSpecification in AutoFixture.Kernel.

    Autofixture has an interface named IRequestSpecification which is used to match a request for a specimen. The purpose is to determine whether a given post-processing operation should be used upon the resultant specimen.

    It is very important to understand that the specification matches the request for the specimen not the created specimen itself. A specimen request will usually be an instance of System.Type.

    Your specimen builder transformation brings these together

    The specimen builder transformation (aka Behaviour) class brings the specimen command and request specification together, along with using Autofixture's built-in Postprocessor class. Once again I found it convenient to make this generic.

    public class PostprocessingTransformation<T> : ISpecimenBuilderTransformation where T : class
    {
      readonly Action<T,ISpecimenContext> builderCustomizer;
    
      public ISpecimenBuilderNode Transform(ISpecimenBuilder builder)
      {
        var command = new PostprocessingCommand<T>(builderCustomizer);
        var spec = new DirectBaseTypeSpecification(typeof(T));
                    
        return new Postprocessor(builder, command, spec);
      }
    
      public PostprocessingTransformation(Action<T,ISpecimenContext> builderCustomizer)
      {
        this.builderCustomizer = builderCustomizer ?? throw new ArgumentNullException(nameof(builderCustomizer));
      }
    }
    

    Write customizations to create a behaviour instance with a delegate

    A small customization class will be used to instantiate the behaviour instance and pass it the Action<T,ISpecimenContext> which will perform the actual post-processing upon the appropriate specimens.

    It is an instance of this customization which should be returned from your implementation of CustomizeAttribute.

    public class MySpecificCustomization : ICustomization
    {
      public void Customize(IFixture fixture)
      {
        var behavior = new PostprocessingTransformation<MySpecificType>((specimen, context) => {
          // Write your logic for modifying an instance of MySpecificType
          // here.  It will be the parameter named 'specimen'.
          // You may also use 'context' to access other Autofixture features.
        });
        fixture.Behaviors.Add(behavior);
      }
    }