Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix multiplied ConversionSelector's descriptions appearing in assertion message #941

Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -39,9 +39,9 @@ public OrderingRuleCollection OrderingRules

public ConversionSelector ConversionSelector => inner.ConversionSelector;

public IEnumerable<IEquivalencyStep> UserEquivalencySteps
public IEnumerable<IEquivalencyStep> GetUserEquivalencySteps(ConversionSelector conversionSelector)
{
get { return inner.UserEquivalencySteps.Select(step => new CollectionMemberAssertionRuleDecorator(step)).ToArray(); }
return inner.GetUserEquivalencySteps(conversionSelector).Select(step => new CollectionMemberAssertionRuleDecorator(step)).ToArray();
}

public bool IsRecursive => inner.IsRecursive;
Expand Down
57 changes: 41 additions & 16 deletions Src/FluentAssertions/Equivalency/ConversionSelector.cs
Expand Up @@ -3,7 +3,6 @@
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using FluentAssertions.Execution;

namespace FluentAssertions.Equivalency
{
Expand All @@ -12,46 +11,72 @@ namespace FluentAssertions.Equivalency
/// </summary>
public class ConversionSelector
{
private List<Func<IMemberInfo, bool>> inclusions = new List<Func<IMemberInfo, bool>>();
private List<Func<IMemberInfo, bool>> exclusions = new List<Func<IMemberInfo, bool>>();
private StringBuilder description = new StringBuilder();
private class ConversionSelectorRule
{
public Func<IMemberInfo, bool> Predicate { get; }
public string Description { get; }

public ConversionSelectorRule(Func<IMemberInfo, bool> predicate, string description)
{
Predicate = predicate;
Description = description;
}
}
private List<ConversionSelectorRule> inclusions = new List<ConversionSelectorRule>();
private List<ConversionSelectorRule> exclusions = new List<ConversionSelectorRule>();

public void IncludeAll()
{
inclusions.Add(_ => true);
description.Append("Try conversion of all members. ");
inclusions.Add(new ConversionSelectorRule(_ => true, "Try conversion of all members. "));
}

public void Include(Expression<Func<IMemberInfo, bool>> predicate)
{
inclusions.Add(predicate.Compile());
description.Append("Try conversion of member ").Append(predicate.Body).Append(". ");
inclusions.Add(new ConversionSelectorRule(
predicate.Compile(),
$"Try conversion of member {predicate.Body}. "));
}

public void Exclude(Expression<Func<IMemberInfo, bool>> predicate)
{
exclusions.Add(predicate.Compile());
description.Append("Do not convert member ").Append(predicate.Body).Append(". ");
exclusions.Add(new ConversionSelectorRule(
predicate.Compile(),
$"Do not convert member {predicate.Body}."));
}

public bool RequiresConversion(IMemberInfo info)
{
return inclusions.Any(p => p(info)) && !exclusions.Any(p => p(info));
return inclusions.Any(p => p.Predicate(info)) && !exclusions.Any(p => p.Predicate(info));
}

public override string ToString()
{
string result = description.ToString();
return (result.Length > 0) ? result : "Without automatic conversion.";
if(inclusions.Count == 0 && exclusions.Count == 0)
{
return "Without automatic conversion.";
}

StringBuilder descriptionBuilder = new StringBuilder();

foreach (var inclusion in inclusions)
{
descriptionBuilder.Append(inclusion.Description);
}

foreach (var exclusion in exclusions)
{
descriptionBuilder.Append(exclusion.Description);
}

return descriptionBuilder.ToString();
}

public ConversionSelector Clone()
{
return new ConversionSelector
{
inclusions = new List<Func<IMemberInfo, bool>>(inclusions),
exclusions = new List<Func<IMemberInfo, bool>>(exclusions),
description = description
inclusions = new List<ConversionSelectorRule>(inclusions),
exclusions = new List<ConversionSelectorRule>(exclusions),
};
}
}
Expand Down
Expand Up @@ -53,7 +53,7 @@ public interface IEquivalencyAssertionOptions
/// <summary>
/// Gets an ordered collection of Equivalency steps how a subject is compared with the expectation.
/// </summary>
IEnumerable<IEquivalencyStep> UserEquivalencySteps { get; }
IEnumerable<IEquivalencyStep> GetUserEquivalencySteps(ConversionSelector conversionSelector);
krajek marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Gets a value indicating whether the runtime type of the expectation should be used rather than the declared type.
Expand Down
Expand Up @@ -10,13 +10,13 @@ public class RunAllUserStepsEquivalencyStep : IEquivalencyStep
{
public bool CanHandle(IEquivalencyValidationContext context, IEquivalencyAssertionOptions config)
{
return config.UserEquivalencySteps.Any(s => s.CanHandle(context, config));
return config.GetUserEquivalencySteps(config.ConversionSelector).Any(s => s.CanHandle(context, config));
}

public bool Handle(IEquivalencyValidationContext context, IEquivalencyValidator parent,
IEquivalencyAssertionOptions config)
{
return config.UserEquivalencySteps
return config.GetUserEquivalencySteps(config.ConversionSelector)
.Any(step => step.CanHandle(context, config) && step.Handle(context, parent, config));
}
}
Expand Down
Expand Up @@ -77,11 +77,12 @@ protected SelfReferenceEquivalencyAssertionOptions(IEquivalencyAssertionOptions
includeProperties = defaults.IncludeProperties;
includeFields = defaults.IncludeFields;

ConversionSelector = defaults.ConversionSelector.Clone();

selectionRules.AddRange(defaults.SelectionRules);
userEquivalencySteps.AddRange(defaults.UserEquivalencySteps);
userEquivalencySteps.AddRange(defaults.GetUserEquivalencySteps(ConversionSelector));
matchingRules.AddRange(defaults.MatchingRules);
orderingRules = new OrderingRuleCollection(defaults.OrderingRules);
ConversionSelector = defaults.ConversionSelector.Clone();

getDefaultEqualityStrategy = defaults.GetEqualityStrategy;
TraceWriter = defaults.TraceWriter;
Expand Down Expand Up @@ -125,8 +126,8 @@ IEnumerable<IMemberSelectionRule> IEquivalencyAssertionOptions.SelectionRules
/// <summary>
/// Gets an ordered collection of Equivalency steps how a subject is compared with the expectation.
/// </summary>
IEnumerable<IEquivalencyStep> IEquivalencyAssertionOptions.UserEquivalencySteps =>
userEquivalencySteps.Concat(new[] { new TryConversionStep(ConversionSelector) });
IEnumerable<IEquivalencyStep> IEquivalencyAssertionOptions.GetUserEquivalencySteps(ConversionSelector convertionSelector) =>
krajek marked this conversation as resolved.
Show resolved Hide resolved
userEquivalencySteps.Concat(new[] {new TryConversionStep(convertionSelector) });

public ConversionSelector ConversionSelector { get; } = new ConversionSelector();

Expand Down
1 change: 0 additions & 1 deletion Src/FluentAssertions/Primitives/ObjectAssertions.cs
Expand Up @@ -12,7 +12,6 @@ namespace FluentAssertions.Primitives
/// <summary>
/// Contains a number of methods to assert that an <see cref="object"/> is in the expected state.
/// </summary>
[DebuggerNonUserCode]
public class ObjectAssertions : ReferenceTypeAssertions<object, ObjectAssertions>
{
public ObjectAssertions(object value)
Expand Down
5 changes: 4 additions & 1 deletion Tests/Shared.Specs/BasicEquivalencySpecs.cs
Expand Up @@ -2581,7 +2581,10 @@ public void When_a_specific_mismatching_property_is_excluded_from_conversion_it_
//-----------------------------------------------------------------------------------------------------------
// Assert
//-----------------------------------------------------------------------------------------------------------
act.Should().Throw<XunitException>().WithMessage("Expected*<1973-09-20>*\"1973-09-20\"*");
string exceptionMessage = act.Should().Throw<XunitException>().Which.Message;
krajek marked this conversation as resolved.
Show resolved Hide resolved
exceptionMessage.Should().Match("Expected*<1973-09-20>*\"1973-09-20\"*", "{0} field is of mismatched type", nameof(expectation.Birthdate));
exceptionMessage.Should().Match("*Try conversion of all members*", "conversion description should be present");
exceptionMessage.Should().NotMatch("*Try conversion of all members*Try conversion of all members*", "conversion description should not be duplicated");
}

[Fact]
Expand Down