Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using FluentAssertions.Execution;

namespace FluentAssertions.Equivalency
{
public class EqualityComparerEquivalencyStep<T> : IEquivalencyStep
{
private readonly IEqualityComparer<T> comparer;

public EqualityComparerEquivalencyStep(IEqualityComparer<T> comparer)
{
this.comparer = comparer ?? throw new ArgumentNullException(nameof(comparer));
}

public bool CanHandle(IEquivalencyValidationContext context, IEquivalencyAssertionOptions config)
{
return config.GetExpectationType(context) == typeof(T);
}

public bool Handle(IEquivalencyValidationContext context, IEquivalencyValidator parent, IEquivalencyAssertionOptions config)
{
Execute.Assertion
.BecauseOf(context.Because, context.BecauseArgs)
.ForCondition(context.Subject is T)
.FailWith("Expected {context:object} to be of type {0}{because}, but found {1}", typeof(T), context.Subject)
.Then
.ForCondition(comparer.Equals((T)context.Subject, (T)context.Expectation))
.FailWith("Expected {context:object} to be equal to {1} according to {0}{because}, but {2} was not.",
comparer.ToString(), context.Expectation, context.Subject);

return true;
}

public override string ToString()
{
return $"Use {comparer} for objects of type {typeof(T)}";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,27 @@ public TSelf Using(IEquivalencyStep equivalencyStep)
return AddEquivalencyStep(equivalencyStep);
}

/// <summary>
/// Ensures the equivalency comparison will create and use an instance of <typeparamref name="TEqualityComparer"/>
/// that implements <see cref="IEqualityComparer{T}"/>, any time
/// when a property is of type <typeparamref name="T"/>.
/// </summary>
public TSelf Using<T, TEqualityComparer>() where TEqualityComparer : IEqualityComparer<T>, new()
{
return Using(new TEqualityComparer());
}

/// <summary>
/// Ensures the equivalency comparison will use the specified implementation of <see cref="IEqualityComparer{T}"/>
/// any time when a property is of type <typeparamref name="T"/>.
/// </summary>
public TSelf Using<T>(IEqualityComparer<T> comparer)
{
userEquivalencySteps.Insert(0, new EqualityComparerEquivalencyStep<T>(comparer));

return (TSelf)this;
}

/// <summary>
/// Causes all collections to be compared in the order in which the items appear in the expectation.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,13 @@ namespace FluentAssertions.Equivalency
public bool CanHandle(FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyAssertionOptions config) { }
public bool Handle(FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator parent, FluentAssertions.Equivalency.IEquivalencyAssertionOptions config) { }
}
public class EqualityComparerEquivalencyStep<T> : FluentAssertions.Equivalency.IEquivalencyStep
{
public EqualityComparerEquivalencyStep(System.Collections.Generic.IEqualityComparer<T> comparer) { }
public bool CanHandle(FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyAssertionOptions config) { }
public bool Handle(FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator parent, FluentAssertions.Equivalency.IEquivalencyAssertionOptions config) { }
public override string ToString() { }
}
public enum EqualityStrategy
{
Equals = 0,
Expand Down Expand Up @@ -811,6 +818,9 @@ namespace FluentAssertions.Equivalency
public TSelf Using(FluentAssertions.Equivalency.IMemberMatchingRule matchingRule) { }
public TSelf Using(FluentAssertions.Equivalency.IMemberSelectionRule selectionRule) { }
public FluentAssertions.Equivalency.SelfReferenceEquivalencyAssertionOptions<TSelf>.Restriction<TProperty> Using<TProperty>(System.Action<FluentAssertions.Equivalency.IAssertionContext<TProperty>> action) { }
public TSelf Using<T>(System.Collections.Generic.IEqualityComparer<T> comparer) { }
public TSelf Using<T, TEqualityComparer>()
where TEqualityComparer : System.Collections.Generic.IEqualityComparer<T>, new () { }
public TSelf WithAutoConversion() { }
public TSelf WithAutoConversionFor(System.Linq.Expressions.Expression<System.Func<FluentAssertions.Equivalency.IMemberInfo, bool>> predicate) { }
public TSelf WithStrictOrdering() { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,13 @@ namespace FluentAssertions.Equivalency
public bool CanHandle(FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyAssertionOptions config) { }
public bool Handle(FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator parent, FluentAssertions.Equivalency.IEquivalencyAssertionOptions config) { }
}
public class EqualityComparerEquivalencyStep<T> : FluentAssertions.Equivalency.IEquivalencyStep
{
public EqualityComparerEquivalencyStep(System.Collections.Generic.IEqualityComparer<T> comparer) { }
public bool CanHandle(FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyAssertionOptions config) { }
public bool Handle(FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator parent, FluentAssertions.Equivalency.IEquivalencyAssertionOptions config) { }
public override string ToString() { }
}
public enum EqualityStrategy
{
Equals = 0,
Expand Down Expand Up @@ -811,6 +818,9 @@ namespace FluentAssertions.Equivalency
public TSelf Using(FluentAssertions.Equivalency.IMemberMatchingRule matchingRule) { }
public TSelf Using(FluentAssertions.Equivalency.IMemberSelectionRule selectionRule) { }
public FluentAssertions.Equivalency.SelfReferenceEquivalencyAssertionOptions<TSelf>.Restriction<TProperty> Using<TProperty>(System.Action<FluentAssertions.Equivalency.IAssertionContext<TProperty>> action) { }
public TSelf Using<T>(System.Collections.Generic.IEqualityComparer<T> comparer) { }
public TSelf Using<T, TEqualityComparer>()
where TEqualityComparer : System.Collections.Generic.IEqualityComparer<T>, new () { }
public TSelf WithAutoConversion() { }
public TSelf WithAutoConversionFor(System.Linq.Expressions.Expression<System.Func<FluentAssertions.Equivalency.IMemberInfo, bool>> predicate) { }
public TSelf WithStrictOrdering() { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,13 @@ namespace FluentAssertions.Equivalency
public bool CanHandle(FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyAssertionOptions config) { }
public bool Handle(FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator parent, FluentAssertions.Equivalency.IEquivalencyAssertionOptions config) { }
}
public class EqualityComparerEquivalencyStep<T> : FluentAssertions.Equivalency.IEquivalencyStep
{
public EqualityComparerEquivalencyStep(System.Collections.Generic.IEqualityComparer<T> comparer) { }
public bool CanHandle(FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyAssertionOptions config) { }
public bool Handle(FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator parent, FluentAssertions.Equivalency.IEquivalencyAssertionOptions config) { }
public override string ToString() { }
}
public enum EqualityStrategy
{
Equals = 0,
Expand Down Expand Up @@ -811,6 +818,9 @@ namespace FluentAssertions.Equivalency
public TSelf Using(FluentAssertions.Equivalency.IMemberMatchingRule matchingRule) { }
public TSelf Using(FluentAssertions.Equivalency.IMemberSelectionRule selectionRule) { }
public FluentAssertions.Equivalency.SelfReferenceEquivalencyAssertionOptions<TSelf>.Restriction<TProperty> Using<TProperty>(System.Action<FluentAssertions.Equivalency.IAssertionContext<TProperty>> action) { }
public TSelf Using<T>(System.Collections.Generic.IEqualityComparer<T> comparer) { }
public TSelf Using<T, TEqualityComparer>()
where TEqualityComparer : System.Collections.Generic.IEqualityComparer<T>, new () { }
public TSelf WithAutoConversion() { }
public TSelf WithAutoConversionFor(System.Linq.Expressions.Expression<System.Func<FluentAssertions.Equivalency.IMemberInfo, bool>> predicate) { }
public TSelf WithStrictOrdering() { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,13 @@ namespace FluentAssertions.Equivalency
public bool CanHandle(FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyAssertionOptions config) { }
public bool Handle(FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator parent, FluentAssertions.Equivalency.IEquivalencyAssertionOptions config) { }
}
public class EqualityComparerEquivalencyStep<T> : FluentAssertions.Equivalency.IEquivalencyStep
{
public EqualityComparerEquivalencyStep(System.Collections.Generic.IEqualityComparer<T> comparer) { }
public bool CanHandle(FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyAssertionOptions config) { }
public bool Handle(FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator parent, FluentAssertions.Equivalency.IEquivalencyAssertionOptions config) { }
public override string ToString() { }
}
public enum EqualityStrategy
{
Equals = 0,
Expand Down Expand Up @@ -810,6 +817,9 @@ namespace FluentAssertions.Equivalency
public TSelf Using(FluentAssertions.Equivalency.IMemberMatchingRule matchingRule) { }
public TSelf Using(FluentAssertions.Equivalency.IMemberSelectionRule selectionRule) { }
public FluentAssertions.Equivalency.SelfReferenceEquivalencyAssertionOptions<TSelf>.Restriction<TProperty> Using<TProperty>(System.Action<FluentAssertions.Equivalency.IAssertionContext<TProperty>> action) { }
public TSelf Using<T>(System.Collections.Generic.IEqualityComparer<T> comparer) { }
public TSelf Using<T, TEqualityComparer>()
where TEqualityComparer : System.Collections.Generic.IEqualityComparer<T>, new () { }
public TSelf WithAutoConversion() { }
public TSelf WithAutoConversionFor(System.Linq.Expressions.Expression<System.Func<FluentAssertions.Equivalency.IMemberInfo, bool>> predicate) { }
public TSelf WithStrictOrdering() { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,13 @@ namespace FluentAssertions.Equivalency
public bool CanHandle(FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyAssertionOptions config) { }
public bool Handle(FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator parent, FluentAssertions.Equivalency.IEquivalencyAssertionOptions config) { }
}
public class EqualityComparerEquivalencyStep<T> : FluentAssertions.Equivalency.IEquivalencyStep
{
public EqualityComparerEquivalencyStep(System.Collections.Generic.IEqualityComparer<T> comparer) { }
public bool CanHandle(FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyAssertionOptions config) { }
public bool Handle(FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator parent, FluentAssertions.Equivalency.IEquivalencyAssertionOptions config) { }
public override string ToString() { }
}
public enum EqualityStrategy
{
Equals = 0,
Expand Down Expand Up @@ -811,6 +818,9 @@ namespace FluentAssertions.Equivalency
public TSelf Using(FluentAssertions.Equivalency.IMemberMatchingRule matchingRule) { }
public TSelf Using(FluentAssertions.Equivalency.IMemberSelectionRule selectionRule) { }
public FluentAssertions.Equivalency.SelfReferenceEquivalencyAssertionOptions<TSelf>.Restriction<TProperty> Using<TProperty>(System.Action<FluentAssertions.Equivalency.IAssertionContext<TProperty>> action) { }
public TSelf Using<T>(System.Collections.Generic.IEqualityComparer<T> comparer) { }
public TSelf Using<T, TEqualityComparer>()
where TEqualityComparer : System.Collections.Generic.IEqualityComparer<T>, new () { }
public TSelf WithAutoConversion() { }
public TSelf WithAutoConversionFor(System.Linq.Expressions.Expression<System.Func<FluentAssertions.Equivalency.IMemberInfo, bool>> predicate) { }
public TSelf WithStrictOrdering() { }
Expand Down
179 changes: 179 additions & 0 deletions Tests/FluentAssertions.Specs/Equivalency/BasicEquivalencySpecs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2237,6 +2237,185 @@ public void
act.Should().NotThrow();
}

[Fact]
public void When_a_nested_property_is_equal_based_on_equality_comparer_it_should_not_throw()
{
// Arrange
var subject = new
{
Timestamp = 22.March(2020).At(19, 30)
};

var expectation = new
{
Timestamp = 1.January(2020).At(7, 31)
};

// Act
Action act = () => subject.Should().BeEquivalentTo(expectation,
opt => opt.Using<DateTime, DateTimeByYearComparer>());


// Assert
act.Should().NotThrow();
}

[Fact]
public void When_a_nested_property_is_unequal_based_on_equality_comparer_it_should_throw()
{
// Arrange
var subject = new
{
Timestamp = 22.March(2020)
};

var expectation = new
{
Timestamp = 1.January(2021)
};

// Act
Action act = () => subject.Should().BeEquivalentTo(expectation,
opt => opt.Using(new DateTimeByYearComparer()));

// Assert
act.Should()
.Throw<XunitException>()
.WithMessage("Expected*equal*2021*DateTimeByYearComparer*2020*");
}

[Fact]
public void When_the_subjects_property_type_is_different_from_the_equality_comparer_it_should_throw()
{
// Arrange
var subject = new
{
Timestamp = 1L
};

var expectation = new
{
Timestamp = 1.January(2021)
};

// Act
Action act = () => subject.Should().BeEquivalentTo(expectation,
opt => opt.Using(new DateTimeByYearComparer()));

// Assert
act.Should()
.Throw<XunitException>()
.WithMessage("Expected*Timestamp*1L*");
}

private class DateTimeByYearComparer : IEqualityComparer<DateTime>
{
public bool Equals(DateTime x, DateTime y)
{
return x.Year == y.Year;
}

public int GetHashCode(DateTime obj) => obj.GetHashCode();
}

[Fact]
public void When_an_invalid_equality_comparer_is_provided_it_should_throw()
{
// Arrange
var subject = new
{
Timestamp = 22.March(2020)
};

var expectation = new
{
Timestamp = 1.January(2021)
};

IEqualityComparer<DateTime> equalityComparer = null;

// Act
Action act = () => subject.Should().BeEquivalentTo(expectation,
opt => opt.Using(equalityComparer));

// Assert
act.Should()
.Throw<ArgumentNullException>()
.WithMessage("*comparer*");
}

[Fact]
public void When_the_compile_time_type_does_not_match_the_equality_comparer_type_it_should_use_the_default_mechanics()
{
// Arrange
var subject = new
{
Property = (IInterface)new ConcreteClass("SomeString")
};

var expectation = new
{
Property = (IInterface)new ConcreteClass("SomeOtherString")
};

// Act
Action act = () => subject.Should().BeEquivalentTo(expectation, opt =>
opt.Using<ConcreteClass, ConcreteClassEqualityComparer>());

// Assert
act.Should().NotThrow();
}

[Fact]
public void When_the_runtime_type_does_match_the_equality_comparer_type_it_should_use_the_default_mechanics()
{
// Arrange
var subject = new
{
Property = (IInterface)new ConcreteClass("SomeString")
};

var expectation = new
{
Property = (IInterface)new ConcreteClass("SomeOtherString")
};

// Act
Action act = () => subject.Should().BeEquivalentTo(expectation, opt => opt
.RespectingRuntimeTypes()
.Using<ConcreteClass, ConcreteClassEqualityComparer>());

// Assert
act.Should().Throw<XunitException>().WithMessage("*ConcreteClassEqualityComparer*");
}

private interface IInterface
{

}

private class ConcreteClass : IInterface
{
private string property;

public ConcreteClass(string propertyValue)
{
property = propertyValue;
}

public string GetProperty() => property;
}

private class ConcreteClassEqualityComparer : IEqualityComparer<ConcreteClass>
{
public bool Equals(ConcreteClass x, ConcreteClass y)
{
return x.GetProperty().Equals(y.GetProperty());
}

public int GetHashCode(ConcreteClass obj) => obj.GetProperty().GetHashCode();
}

#endregion

#region Member Conversion
Expand Down
1 change: 1 addition & 0 deletions docs/_pages/releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ sidebar:
* The `Using`/`When` option on `BeEquivalentTo` will now use the conversion rules when trying to match the predicate - [#1257](https://github.com/fluentassertions/fluentassertions/pull/1257).
* Added `NotBeWritable` to `PropertyInfoSelectorAssertions` to be able to assert that properties are not writable - [#1269](https://github.com/fluentassertions/fluentassertions/pull/1269).
* Added extension to assert `TaskCompletionSource<T>` - [#1267](https://github.com/fluentassertions/fluentassertions/pull/1267).
* Added the ability to pass an `IEqualityComparer<T>` through `BeEquivalentTo(x => x.Using<MyComparer>())` - [#1284](https://github.com/fluentassertions/fluentassertions/pull/1284).

**Fixes**
* Reported actual value when it contained `{{{{` or `}}}}` - [#1234](https://github.com/fluentassertions/fluentassertions/pull/1234).
Expand Down