Skip to content

Commit

Permalink
Adding ISpecimenBuilder as param on With method (#1444)
Browse files Browse the repository at this point in the history
* Adding new method signature

* Fixed validation warnings

* adding new BindindCommand constructor with unit tests

* Replaced factory syntax with proxy call to With method

* Fixed validation error

* Reverted BindingCommand constructor with builder parameter

* Refactored to pattern matching validations

* Added more tests to check new With overload

* Cleaned usings

* Added test for accessing underlying fixture

---------

Co-authored-by: Andrei Ivascu <7030530+aivascu@users.noreply.github.com>
  • Loading branch information
luccanog and aivascu committed Mar 10, 2024
1 parent b864999 commit aa58120
Show file tree
Hide file tree
Showing 12 changed files with 268 additions and 29 deletions.
11 changes: 10 additions & 1 deletion Src/AutoFixture/Dsl/CompositeNodeComposer.cs
Expand Up @@ -173,6 +173,15 @@ public IPostprocessComposer<T> With<TProperty>(Expression<Func<T, TProperty>> pr
when: n => n is NodeComposer<T>);
}

/// <inheritdoc />
public IPostprocessComposer<T> With<TProperty>(Expression<Func<T, TProperty>> propertyPicker, ISpecimenBuilder builder)
{
return (CompositeNodeComposer<T>)this.ReplaceNodes(
with: n =>
(NodeComposer<T>)((NodeComposer<T>)n).With(propertyPicker, builder),
when: n => n is NodeComposer<T>);
}

/// <inheritdoc />
public IPostprocessComposer<T> WithAutoProperties()
{
Expand Down Expand Up @@ -222,4 +231,4 @@ public IEnumerator<ISpecimenBuilder> GetEnumerator()
/// <inheritdoc />
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => this.GetEnumerator();
}
}
}
7 changes: 7 additions & 0 deletions Src/AutoFixture/Dsl/CompositePostprocessComposer.cs
Expand Up @@ -81,6 +81,13 @@ public IPostprocessComposer<T> With<TProperty>(Expression<Func<T, TProperty>> pr
select c.With(propertyPicker, valueFactory));
}

/// <inheritdoc />
public IPostprocessComposer<T> With<TProperty>(Expression<Func<T, TProperty>> propertyPicker, ISpecimenBuilder builder)
{
return new CompositePostprocessComposer<T>(from c in this.Composers
select c.With(propertyPicker, builder));
}

/// <inheritdoc />
public IPostprocessComposer<T> WithAutoProperties()
{
Expand Down
16 changes: 16 additions & 0 deletions Src/AutoFixture/Dsl/IPostprocessComposer.cs
Expand Up @@ -92,6 +92,22 @@ public interface IPostprocessComposer<T> : ISpecimenBuilder
[SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "With", Justification = "It's a part of the public API we currently have.")]
IPostprocessComposer<T> With<TProperty, TInput>(Expression<Func<T, TProperty>> propertyPicker, Func<TInput, TProperty> valueFactory);

/// <summary>
/// Registers that a writable property or field should be assigned generated value as a part of specimen post-processing.
/// </summary>
/// <param name="propertyPicker">
/// An expression that identifies the property or field that will have <paramref name="builder"/> result assigned.
/// </param>
/// <param name="builder">
/// The specimen builder to assign to the property or field identified by <paramref name="propertyPicker"/>.
/// </param>
/// <returns>
/// An <see cref="IPostprocessComposer{T}"/> which can be used to further customize the
/// post-processing of created specimens.
/// </returns>
[SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "With", Justification = "It's a part of the public API we currently have.")]
IPostprocessComposer<T> With<TProperty>(Expression<Func<T, TProperty>> propertyPicker, ISpecimenBuilder builder);

/// <summary>
/// Enables auto-properties for a type of specimen.
/// </summary>
Expand Down
9 changes: 9 additions & 0 deletions Src/AutoFixture/Dsl/NodeComposer.cs
Expand Up @@ -193,6 +193,15 @@ public IPostprocessComposer<T> With<TProperty>(Expression<Func<T, TProperty>> pr
}));
}

/// <inheritdoc />
public IPostprocessComposer<T> With<TProperty>(Expression<Func<T, TProperty>> propertyPicker, ISpecimenBuilder builder)
{
return this.WithCommand(
propertyPicker,
new BindingCommand<T, TProperty>(propertyPicker,
c => (TProperty)builder.Create(typeof(TProperty), c)));
}

private IPostprocessComposer<T> WithCommand<TProperty>(Expression<Func<T, TProperty>> propertyPicker, ISpecimenCommand command)
{
ExpressionReflector.VerifyIsNonNestedWritableMemberExpression(propertyPicker);
Expand Down
3 changes: 3 additions & 0 deletions Src/AutoFixture/Dsl/NullComposer.cs
Expand Up @@ -81,6 +81,9 @@ public NullComposer(Func<ISpecimenBuilder> factory)
/// <inheritdoc />
public IPostprocessComposer<T> With<TProperty, TInput>(Expression<Func<T, TProperty>> propertyPicker, Func<TInput, TProperty> valueFactory) => this;

/// <inheritdoc />
public IPostprocessComposer<T> With<TProperty>(Expression<Func<T, TProperty>> propertyPicker, ISpecimenBuilder builder) => this;

/// <inheritdoc />
public IPostprocessComposer<T> WithAutoProperties() => this;

Expand Down
44 changes: 17 additions & 27 deletions Src/AutoFixture/Kernel/BindingCommand.cs
Expand Up @@ -30,7 +30,7 @@ public class BindingCommand<T, TProperty> : ISpecimenCommand, ObsoletedMemberShi
/// </remarks>
public BindingCommand(Expression<Func<T, TProperty>> propertyPicker)
{
if (propertyPicker == null) throw new ArgumentNullException(nameof(propertyPicker));
if (propertyPicker is null) throw new ArgumentNullException(nameof(propertyPicker));

this.Member = propertyPicker.GetWritableMember().Member;
this.ValueCreator = this.CreateAnonymousValue;
Expand All @@ -43,15 +43,11 @@ public BindingCommand(Expression<Func<T, TProperty>> propertyPicker)
/// </summary>
/// <param name="propertyPicker">An expression that identifies a property or field.</param>
/// <param name="propertyValue">
/// The value to assign to the property or field identified by
/// <paramref name="propertyPicker"/>.
/// The value to assign to the property or field identified by <paramref name="propertyPicker"/>.
/// </param>
public BindingCommand(Expression<Func<T, TProperty>> propertyPicker, TProperty propertyValue)
: this(propertyPicker, _ => propertyValue)
{
if (propertyPicker == null) throw new ArgumentNullException(nameof(propertyPicker));

this.Member = propertyPicker.GetWritableMember().Member;
this.ValueCreator = c => propertyValue;
}

/// <summary>
Expand All @@ -66,8 +62,8 @@ public BindingCommand(Expression<Func<T, TProperty>> propertyPicker, TProperty p
/// </param>
public BindingCommand(Expression<Func<T, TProperty>> propertyPicker, Func<ISpecimenContext, TProperty> valueCreator)
{
if (propertyPicker == null) throw new ArgumentNullException(nameof(propertyPicker));
if (valueCreator == null) throw new ArgumentNullException(nameof(valueCreator));
if (propertyPicker is null) throw new ArgumentNullException(nameof(propertyPicker));
if (valueCreator is null) throw new ArgumentNullException(nameof(valueCreator));

this.Member = propertyPicker.GetWritableMember().Member;
this.ValueCreator = valueCreator;
Expand Down Expand Up @@ -105,19 +101,17 @@ public BindingCommand(Expression<Func<T, TProperty>> propertyPicker, Func<ISpeci
[Obsolete("This method is no longer used and will be removed in future versions. Please use the Execute(object, ISpecimenContext) overload instead.")]
public void Execute(T specimen, ISpecimenContext context)
{
if (specimen == null) throw new ArgumentNullException(nameof(specimen));
if (context == null) throw new ArgumentNullException(nameof(context));
if (specimen is null) throw new ArgumentNullException(nameof(specimen));
if (context is null) throw new ArgumentNullException(nameof(context));

var bindingValue = this.ValueCreator(context);

var pi = this.Member as PropertyInfo;
if (pi != null)
if (this.Member is PropertyInfo pi)
{
pi.SetValue(specimen, bindingValue, null);
}

var fi = this.Member as FieldInfo;
if (fi != null)
if (this.Member is FieldInfo fi)
{
fi.SetValue(specimen, bindingValue);
}
Expand All @@ -135,7 +129,7 @@ public void Execute(T specimen, ISpecimenContext context)
[Obsolete("This method is no longer used and will be removed in future versions. Please use this.Member property for specification instead.")]
public bool IsSatisfiedBy(object request)
{
if (request == null) throw new ArgumentNullException(nameof(request));
if (request is null) throw new ArgumentNullException(nameof(request));

IEqualityComparer comparer = new MemberInfoEqualityComparer();
return comparer.Equals(this.Member, request);
Expand All @@ -144,11 +138,12 @@ public bool IsSatisfiedBy(object request)
private TProperty CreateAnonymousValue(ISpecimenContext container)
{
var bindingValue = container.Resolve(this.Member);
if ((bindingValue != null) && !(bindingValue is TProperty))
if (bindingValue is not null and not TProperty)
{
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
"The specimen created for assignment is not compatible with {0}.", typeof(TProperty)));
}

return (TProperty)bindingValue;
}

Expand All @@ -165,13 +160,12 @@ private TProperty CreateAnonymousValue(ISpecimenContext container)
/// </remarks>
public void Execute(object specimen, ISpecimenContext context)
{
if (specimen == null) throw new ArgumentNullException(nameof(specimen));
if (context == null) throw new ArgumentNullException(nameof(context));
if (specimen is null) throw new ArgumentNullException(nameof(specimen));
if (context is null) throw new ArgumentNullException(nameof(context));

var bindingValue = this.ValueCreator(context);

var pi = this.Member as PropertyInfo;
if (pi != null)
if (this.Member is PropertyInfo pi)
{
TrySetValue(
specimen,
Expand All @@ -180,8 +174,7 @@ public void Execute(object specimen, ISpecimenContext context)
(s, v) => pi.SetValue(s, v, null));
}

var fi = this.Member as FieldInfo;
if (fi != null)
if (this.Member is FieldInfo fi)
{
TrySetValue(
specimen,
Expand All @@ -201,11 +194,8 @@ public void Execute(object specimen, ISpecimenContext context)
{
setValue(specimen, value);
}
catch (ArgumentException)
catch (ArgumentException) when (value is IConvertible)
{
if (!(value is IConvertible))
throw;

setValue(
specimen,
Convert.ChangeType(
Expand Down
30 changes: 30 additions & 0 deletions Src/AutoFixtureUnitTest/Dsl/CompositeNodeComposerTests.cs
@@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Text;
using AutoFixture;
using AutoFixture.Dsl;
using AutoFixture.Kernel;
using AutoFixtureUnitTest.Kernel;
Expand Down Expand Up @@ -532,6 +533,35 @@ public void WithSingleArgValueFactoryReturnsCorrectResult(string value)
Assert.True(expected.GraphEquals(n, new NodeComparer()));
}

[Theory]
[InlineData("")]
[InlineData("foo")]
[InlineData("bar")]
public void WithSpecimenBuilderReturnsCorrectResult(string value)
{
// Arrange
var node = new CompositeSpecimenBuilder(
new DelegatingSpecimenBuilder(),
SpecimenBuilderNodeFactory.CreateComposer<PropertyHolder<string>>(),
SpecimenBuilderNodeFactory.CreateComposer<Version>(),
SpecimenBuilderNodeFactory.CreateComposer<PropertyHolder<string>>(),
new DelegatingSpecimenBuilder());
var sut = new CompositeNodeComposer<PropertyHolder<string>>(node);
var builder = new ElementsBuilder<string>(value);
// Act
var actual = sut.With(x => x.Property, builder);
// Assert
var expected = new CompositeNodeComposer<PropertyHolder<string>>(
new CompositeSpecimenBuilder(
new DelegatingSpecimenBuilder(),
(ISpecimenBuilder)SpecimenBuilderNodeFactory.CreateComposer<PropertyHolder<string>>().With(x => x.Property, builder),
SpecimenBuilderNodeFactory.CreateComposer<Version>(),
(ISpecimenBuilder)SpecimenBuilderNodeFactory.CreateComposer<PropertyHolder<string>>().With(x => x.Property, builder),
new DelegatingSpecimenBuilder()));
var n = Assert.IsAssignableFrom<ISpecimenBuilderNode>(actual);
Assert.True(expected.GraphEquals(n, new NodeComparer()));
}

[Fact]
public void WithoutReturnsCorrectResult()
{
Expand Down
22 changes: 22 additions & 0 deletions Src/AutoFixtureUnitTest/Dsl/CompositePostprocessComposerTest.cs
Expand Up @@ -181,6 +181,28 @@ public void WithSingleArgValueFactoryReturnsCorrectResult()
Assert.True(expectedComposers.SequenceEqual(composite.Composers));
}

[Fact]
public void WithSpecimenBuilderReturnsCorrectResult()
{
// Arrange
Expression<Func<PropertyHolder<object>, object>> expectedExpression = x => x.Property;
var builder = new AutoFixture.ElementsBuilder<object>(new object());

var expectedComposers = Enumerable.Range(1, 3).Select(i => new DelegatingComposer<PropertyHolder<object>>()).ToArray();
var initialComposers = (from c in expectedComposers
select new DelegatingComposer<PropertyHolder<object>>
{
OnWithOverloadFactory = (f, vf) =>
f == expectedExpression && object.Equals(vf, builder) ? c : new DelegatingComposer<PropertyHolder<object>>()
}).ToArray();
var sut = new CompositePostprocessComposer<PropertyHolder<object>>(initialComposers);
// Act
var result = sut.With(expectedExpression, builder);
// Assert
var composite = Assert.IsAssignableFrom<CompositePostprocessComposer<PropertyHolder<object>>>(result);
Assert.True(expectedComposers.SequenceEqual(composite.Composers));
}

[Fact]
public void WithAutoPropertiesReturnsCorrectResult()
{
Expand Down
3 changes: 3 additions & 0 deletions Src/AutoFixtureUnitTest/Dsl/DelegatingComposer.cs
Expand Up @@ -61,6 +61,9 @@ public DelegatingComposer()
public IPostprocessComposer<T> With<TProperty, TInput>(Expression<Func<T, TProperty>> propertyPicker, Func<TInput, TProperty> valueFactory) =>
this.OnWithOverloadFactory(propertyPicker, valueFactory);

public IPostprocessComposer<T> With<TProperty>(Expression<Func<T, TProperty>> propertyPicker, ISpecimenBuilder builder) =>
this.OnWithOverloadFactory(propertyPicker, builder);

public IPostprocessComposer<T> WithAutoProperties() => this.OnWithAutoProperties();

public IPostprocessComposer<T> Without<TProperty>(Expression<Func<T, TProperty>> propertyPicker) =>
Expand Down
44 changes: 43 additions & 1 deletion Src/AutoFixtureUnitTest/Dsl/NodeComposerTest.cs
@@ -1,7 +1,7 @@
using System;
using System.Linq;
using System.Reflection;
using System.Text;
using AutoFixture;
using AutoFixture.Dsl;
using AutoFixture.Kernel;
using AutoFixtureUnitTest.Kernel;
Expand Down Expand Up @@ -612,6 +612,48 @@ public void WithExplicitSingleArgValueFactoryReturnsCorrectResult()
Assert.True(expected.GraphEquals(n, new NodeComparer()));
}

[Fact]
public void WithSpecimenBuilderReturnsCorrectResult()
{
// Arrange
var sut = SpecimenBuilderNodeFactory.CreateComposer<PropertyHolder<string>>();
var pi = typeof(PropertyHolder<string>).GetProperty("Property");
Func<string, string> valueFactory = v => v;
var builder = new ElementsBuilder<string>(Guid.NewGuid().ToString());
// Act
var actual = sut.With(x => x.Property, builder);
// Assert
var expected = new NodeComposer<PropertyHolder<string>>(
new FilteringSpecimenBuilder(
new CompositeSpecimenBuilder(
new Postprocessor(
new Postprocessor(
new NoSpecimenOutputGuard(
new MethodInvoker(
new ModestConstructorQuery()),
new InverseRequestSpecification(
new SeedRequestSpecification(
typeof(PropertyHolder<string>)))),
new AutoPropertiesCommand(
typeof(PropertyHolder<string>),
new InverseRequestSpecification(
new EqualRequestSpecification(
pi,
new MemberInfoEqualityComparer()))),
new FalseRequestSpecification()),
new BindingCommand<PropertyHolder<string>, string>(x => x.Property, c => (string)builder.Create(typeof(string), c)),
new OrRequestSpecification(
new SeedRequestSpecification(typeof(PropertyHolder<string>)),
new ExactTypeSpecification(typeof(PropertyHolder<string>)))),
new SeedIgnoringRelay()),
new OrRequestSpecification(
new SeedRequestSpecification(typeof(PropertyHolder<string>)),
new ExactTypeSpecification(typeof(PropertyHolder<string>)))));

var n = Assert.IsAssignableFrom<ISpecimenBuilderNode>(actual);
Assert.True(expected.GraphEquals(n, new NodeComparer()));
}

[Fact]
public void WithoutReturnsCorrectResult()
{
Expand Down
12 changes: 12 additions & 0 deletions Src/AutoFixtureUnitTest/Dsl/NullComposerTest.cs
Expand Up @@ -217,6 +217,18 @@ public void WithSingleArgValueFactoryReturnsCorrectResult()
Assert.Same(sut, result);
}

[Fact]
public void WithSpecimenBuilderReturnsCorrectResult()
{
// Arrange
var sut = new NullComposer<PropertyHolder<object>>();
var dummyFactory = new DelegatingSpecimenBuilder();
// Act
var result = sut.With(x => x.Property, dummyFactory);
// Assert
Assert.Same(sut, result);
}

[Fact]
public void WithAutoPropertiesReturnsCorrectResult()
{
Expand Down

0 comments on commit aa58120

Please sign in to comment.