Autofixture and read only properties
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
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.
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>