22

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?

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
Marcin Konrad Ceglarek
  • 1,442
  • 2
  • 13
  • 20
  • 1
    That isn't an immutable object. That's read only. Immutability is different – Liam Nov 20 '17 at 11:57
  • What are you asking? If we all want to give our personal opinions on how Autofixture should work? Why not open an issue on Github if you think changes are warranted? – Jeroen Mostert Nov 20 '17 at 12:04
  • Liam: good point. I'm digging into that right now. Jeroen: my question is why It was done this way. I'm pretty sure there is some particular reason for it I would just like to know it – Marcin Konrad Ceglarek Nov 20 '17 at 12:57

3 Answers3

25

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

Fabio Marreco
  • 2,186
  • 2
  • 23
  • 24
11

AutoFixture is, indeed, capable of creating constructor arguments, and invoke constructors. How to control a particular constructor argument is a FAQ, so if that had been the only question, I'd had closed it as a duplicate of Easy way to specify the value of a single constructor parameter?

This post, however, also asks about the design choice behind the behaviour of the Build API, and I will answer that here.

In the second example, Name is a read-only property, and you can't change the value of a read-only property. That's part of .NET (and most other languages) and not a design choice of AutoFixture.

Let's be absolutely clear on this: Name is a property. Technically, it has nothing to do with the class' constructor.

I assume that you consider Name to be associated with the constructor's name argument, because one exposes the other, but we only know that because we have the source code. There's no technically safe way for an external observer to be sure that these two are connected. An outside observer, such as AutoFixture, could attempt to guess that such a connection exists, but there are no guarantees.

It's technically possible to write code like this:

public class Person
{
    public Person(string firstName, string lastName)
    {
        this.FirstName = lastName;
        this.LastName = firstName;
    }

    public string FirstName { get; }

    public string LastName { get; }
}

This compiles just fine, even though the values are switched around. AutoFixture would be unable to detect issues like that.

It might be possible to give AutoFixture a heuristic where the Build API attempts to guess 'what you mean' when you refer to a read-only property, but back when I was still the benevolent dictator of the project, I considered that to be a feature with unwarranted complexity. It's possible that the new maintainers may look differently on the topic.

As a general observation, I consider the entire Build API a mistake. In the last many years I wrote tests with AutoFixture, I never used that API. If I still ran the project today, I'd deprecate that API because it leads people into using AutoFixture in a brittle way.

So this is very much an explicit design choice.

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
  • 3
    Mark, could you please elaborate on this - "I consider the entire Build API a mistake". What are you using instead when you need to create an instance with prepopulated data so that if in future somebody adds a new property it does not fail old test because the value is not set to the new property? – Pavels Ahmadulins May 02 '18 at 09:16
  • @PavelsAhmadulins I'd still use AutoFixture (or something similar), but just not the `Build` API... – Mark Seemann May 02 '18 at 10:15
3

Hi I had a similar problem I solved it using `Freeze

      _formFileMock = _fixture.Freeze<Mock<IFormFile>>();
      _formFileMock.Setup(m => m.ContentType).Returns("image/jpeg");
    _fixture.Create<P>

Chris Lamothe
  • 1,473
  • 13
  • 11