Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Autofixture and read only properties

Let's consider two version (one with read only properties) of the same very simple entity:

public class Client
{
    public Guid Id { get; set; }

    public string Name { get; set; }
}

vs

public class Client
{
    public Client(Guid id, string name)
    {
        this.Id = id;
        this.Name = name;
    }

    public Guid Id { get; }

    public string Name { get; }
}

When I try to use Autofixture, it will work correctly and as expected with both of them. The problems start, when I try to predefine one of the parameters using .with() method:

var obj = this.fixture.Build<Client>().With(c => c.Name, "TEST").Build();

This will throw error

System.ArgumentException: The property "Name" is read-only.

But it seems that Autofixture knows how to use constructors! And it seems that actual Build<>() method creates an instance of an object not Create()! If build would just prepare builder, with would setup properties, and then Create would instantiate object it would work properly with read only properties.

So why was this (misleading) strategy used here? I've found an answer here that states it's to amplify feedback with tests, but I don't see the usefulness to use FromFactory() especially when a list of parameters is extensive. Wouldn't moving object instantiation from Build() method to Create() method be more intuitive?

like image 714
Marcin Konrad Ceglarek Avatar asked Nov 20 '17 11:11

Marcin Konrad Ceglarek


People also ask

What are the properties of read only?

Read only means that we can access the value of a property but we can't assign a value to it. When a property does not have a set accessor then it is a read only property. For example in the person class we have a Gender property that has only a get accessor and doesn't have a set accessor.

How do you know if a property is ReadOnly?

With PropertyDescriptor , check IsReadOnly . With PropertyInfo , check CanWrite (and CanRead , for that matter). You may also want to check [ReadOnly(true)] in the case of PropertyInfo (but this is already handled with PropertyDescriptor ): ReadOnlyAttribute attrib = Attribute.

What is a read only property in Java?

Defining read-only class in Java If we make a class read-only, then we can't modify the properties or data members value of the class. If we make a class read-only, then we can only read the properties or data members value of the class.


1 Answers

I too have struggled with this, since most of my classes are usually readonly. Some libraries like Json.Net use naming conventions to understand what are the constructor arguments that impact each property.

There is indeed a way to customize the property using ISpecimenBuilder interface:

public class OverridePropertyBuilder<T, TProp> : ISpecimenBuilder
{
    private readonly PropertyInfo _propertyInfo;
    private readonly TProp _value;

    public OverridePropertyBuilder(Expression<Func<T, TProp>> expr, TProp value)
    {
        _propertyInfo = (expr.Body as MemberExpression)?.Member as PropertyInfo ??
                        throw new InvalidOperationException("invalid property expression");
        _value = value;
    }

    public object Create(object request, ISpecimenContext context)
    {
        var pi = request as ParameterInfo;
        if (pi == null)
            return new NoSpecimen();

        var camelCase = Regex.Replace(_propertyInfo.Name, @"(\w)(.*)",
            m => m.Groups[1].Value.ToLower() + m.Groups[2]);

        if (pi.ParameterType != typeof(TProp) || pi.Name != camelCase)
            return new NoSpecimen();

        return _value;
    }
}

Trying to use this on the Build<> api was a dead end as you´ve noticed. So I had to create the extensions methods for myself:

public class FixtureCustomization<T>
{
    public Fixture Fixture { get; }

    public FixtureCustomization(Fixture fixture)
    {
        Fixture = fixture;
    }

    public FixtureCustomization<T> With<TProp>(Expression<Func<T, TProp>> expr, TProp value)
    {
        Fixture.Customizations.Add(new OverridePropertyBuilder<T, TProp>(expr, value));
        return this;
    }

    public T Create() => Fixture.Create<T>();
}

public static class CompositionExt
{
    public static FixtureCustomization<T> For<T>(this Fixture fixture)
        => new FixtureCustomization<T>(fixture);
}

which enabled me to use as such:

var obj = 
  new Fixture()
  .For<Client>()
  .With(x => x.Name, "TEST")
  .Create();

hope this helps

like image 145
Fabio Marreco Avatar answered Sep 22 '22 23:09

Fabio Marreco