I'm trying to overcome a scenario in which a class has a string constructor parameter which cannot be satisfied by any old string generated by Autofixture (the Guid-y looking value).
Before you're tempted to answer simply with a link to Mark Seemann's Ploeh blog entry on Convention-based Customizations, let me say that I've been referencing it and other blog entries of his for this test, which I can't get to pass.
When I step through in debug, I can see that at some point the constructor parameter is passed in with the valid value, but the test still fails with the Guid-y Color value. I think this has something to do with the fact that there is both a 'color' parameter value, and a 'Color' property to be populated by Autofixture. Is it that I've written an ISpecimenBuilder that addresses the constructor parameter, but I'm testing the public property value (two different things)?
I know that all this is overkill for the example, but I envision a more complicated scenario in which using the Build<T>().With()
method would not be DRY.
The Failing Test
[Fact]
public void Leaf_Color_Is_Brown()
{
// arrange
var fixture = new Fixture().Customize(new LeafColorCustomization());
// act
var leaf = fixture.Create<Leaf>();
// using .Build<>.With(), test passes
//var leaf = fixture.Build<Leaf>().With(l => l.Color, "brown").CreateAnonymous();
// assert
Assert.True(leaf.Color == "brown");
}
The SUT
public class Leaf
{
public Leaf(string color)
{
if (color != "brown")
throw new ArgumentException(@"NO LEAF FOR YOU!");
this.Color = color;
}
public string Color { get; set; }
}
The CompositeCustomization implementation (I know the AutoMoqCustomization() isn't needed in this example)
public class LeafCustomization : CompositeCustomization
{
public LeafCustomization()
: base(
new LeafColorCustomization(),
new AutoMoqCustomization()) { }
}
The Leaf-specific ICustomization
public class LeafColorCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
if (fixture == null)
throw new ArgumentNullException("fixture");
fixture.Customizations.Add(new LeafBuilder());
}
}
The String-constructor-with-name-of-Color-specific ISpecimenBuilder
public class LeafBuilder : ISpecimenBuilder
{
public object Create(object request, ISpecimenContext context)
{
var pi = request as ParameterInfo;
if (pi == null)
return new NoSpecimen(request);
if (pi.ParameterType != typeof(string) || pi.Name != "color")
return new NoSpecimen(request);
return "brown";
}
}
Solution 1:
Register that the Color
writable property should not be assigned any automatic value as part of the post-processing:
internal class LeafColorCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customize<Leaf>(c => c
.Without(x => x.Color));
fixture.Customizations.Add(new LeafBuilder());
}
}
Solution 2:
Make the Color
property read-only:
public class Leaf
{
private readonly string color;
public Leaf(string color)
{
if (color != "brown")
throw new ArgumentException(@"NO LEAF FOR YOU!");
this.color = color;
}
public string Color
{
get { return this.color; }
}
}
Since the Color
property is read-only AutoFixture is not going to assign a value for it.
The above solutions apply also to AutoFixture 2.
Assuming you deal with the property setting stuff separately, here's a constructor argument restriction Customization
which does the trick:
class BrownLeavesCustomization : ICustomization
{
void ICustomization.Customize( IFixture fixture )
{
Func<string> notBrownGenerator = fixture.Create<Generator<string>>()
.SkipWhile( x => x == "Brown" )
.First;
fixture.Customizations.Add(
ArgumentGeneratorCustomization<Leaf>.ForConstructorArgument(
"color",
notBrownGenerator ) );
}
static class ArgumentGeneratorCustomization<T>
{
public static ISpecimenBuilder ForConstructorArgument<TArg>( string argumentName, Func<TArg> generator )
{
return new ConstructorArgumentGenerator<TArg>( argumentName, generator );
}
class ConstructorArgumentGenerator<TArg> : ISpecimenBuilder
{
readonly string _argumentName;
readonly Func<TArg> _generator;
public ConstructorArgumentGenerator( string argumentName, Func<TArg> generator )
{
Assert.Contains( argumentName, from ctor in typeof( T ).GetConstructors() from param in ctor.GetParameters() select param.Name );
_argumentName = argumentName;
_generator = generator;
}
object ISpecimenBuilder.Create( object request, ISpecimenContext context )
{
var pi = request as ParameterInfo;
if ( pi == null )
return new NoSpecimen( request );
if ( pi.Member.DeclaringType != typeof( T ) )
return new NoSpecimen( request );
if ( pi.Member.MemberType != MemberTypes.Constructor )
return new NoSpecimen( request );
if ( pi.ParameterType != typeof( TArg ) )
return new NoSpecimen( request );
if ( pi.Name != _argumentName )
return new NoSpecimen( request );
return _generator();
}
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With