I'm encountering this strange behavior on AutoFixture 3.50.7 and 4.0 (Nunit 3.7) :
When I generate an object containing two properties of the same type using the vanilla Fixture
object, the test passes.
When I apply this (admittedly dummy) customization, the test fails. The generated lists are in fact the same instance.
What is happening ?
[TestFixture]
public class Test
{
[Test]
public void Succeeds()
{
var fixture = new Fixture();
var container = fixture.Create<Container>();
ReferenceEquals(container.Content1.Strings, container.Content2.Strings).Should().BeFalse();
}
[Test]
public void Fails()
{
var fixture = new Fixture().Customize(new TestContentCustomization());
var container = fixture.Create<Container>();
ReferenceEquals(container.Content1.Strings, container.Content2.Strings).Should().BeFalse();
}
}
public class TestContentCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customize<Content>(c => c
.With(x => x.Strings, new List<string>()));
}
}
public class Container
{
public Content Content1 { get; set; }
public Content Content2 { get; set; }
}
public class Content
{
public IList<string> Strings { get; set; }
}
The test also fails if I ask AutoFixture to generate fixture.CreateMany<string>(3).ToList()
instead of new List<string>()
but of course, this time the list contains 3 different strings.
The Customize<T>
API may be confusing, which is all my fault, but the delegates inside only run once, when you're done defining the customization. I understand why using delegates looks like deferred execution, but it isn't; it's a DSL. This sometimes causes confusion, which is an indication that it should have been designed differently, but now, ten years later, this is the API we have.
The customization in the OP is equivalent to doing this:
public void Customize(IFixture fixture)
{
var strings = new List<string>();
fixture.Customize<Content>(c => c
.With(x => x.Strings, strings));
}
The Customize(IFixture)
method only runs once per fixture. Notice that the arguments to With
are made up of a lambda expression, and an object. The second argument is just an object, and since C# is eagerly evaluated, even if you put a method call there, that method call evaluates before With
is called.
What this customization does, then, is to create an empty list of strings (or, if you use CreateMany
, a populated list of strings), and register Content
such that, every time a Content
object is created, its Strings
property is assigned that particular object.
To work around the problem, you could do something like this:
public void Customize(IFixture fixture)
{
fixture.Customize<Content>(c => c
.Without(x => x.Strings)
.Do(x =>
{
x.Strings = fixture.CreateMany<string>().ToList();
}));
}
The Do
method gives you true deferred execution, but (IIRC) the order in which it executes isn't strictly defined, so you should also use .Without(x => x.Strings)
to ensure that AutoFixture doesn't overwrite the effects of the Do
block.
This passes both tests.
All that said, you should avoid writable collection properties:
DO NOT provide settable collection properties.
If you follow the official Microsoft design guidelines, AutoFixture tends to work better for you.