From 7ab07973bcae28bf3b719c248419ef008e1888fe Mon Sep 17 00:00:00 2001 From: Dennis Doomen Date: Sun, 9 Sep 2018 19:15:43 +0200 Subject: [PATCH] Comparing an object graph against IEnumerable works now as expected --- .../Collections/CollectionAssertions.cs | 69 ++++++++++++++++++- .../NonGenericCollectionAssertions.cs | 67 ------------------ .../EnumerableEquivalencyValidator.cs | 16 ++++- ...numerableEquivalencyValidatorExtensions.cs | 17 +---- .../GenericDictionaryEquivalencyStep.cs | 4 +- .../Execution/AssertionScope.cs | 39 ++++++----- .../Execution/ChainedAssertionScope.cs | 41 +++++++++++ .../Execution/Continuation.cs | 12 +--- .../Execution/ContinuationOfGiven.cs | 5 +- .../Execution/GivenSelector.cs | 34 +++++---- .../Primitives/DateTimeAssertions.cs | 57 ++++++++++----- .../CollectionEquivalencySpecs.cs | 40 +++++++++++ 12 files changed, 255 insertions(+), 146 deletions(-) create mode 100644 Src/FluentAssertions/Execution/ChainedAssertionScope.cs diff --git a/Src/FluentAssertions/Collections/CollectionAssertions.cs b/Src/FluentAssertions/Collections/CollectionAssertions.cs index b795e0a79d..920b9980a5 100644 --- a/Src/FluentAssertions/Collections/CollectionAssertions.cs +++ b/Src/FluentAssertions/Collections/CollectionAssertions.cs @@ -349,6 +349,71 @@ public AndConstraint BeEquivalentTo(params object[] expectations) return new AndConstraint((TAssertions)this); } + /// + /// Asserts that a collection of objects is equivalent to another collection of objects. + /// + /// + /// Objects within the collections are equivalent when both object graphs have equally named properties with the same + /// value, irrespective of the type of those objects. Two properties are also equal if one type can be converted to another + /// and the result is equal. + /// The type of a collection property is ignored as long as the collection implements and all + /// items in the collection are structurally equal. + /// Notice that actual behavior is determined by the global defaults managed by . + /// + public AndConstraint BeEquivalentTo(IEnumerable expectation, string because = "", params object[] becauseArgs) + { + BeEquivalentTo(expectation, config => config, because, becauseArgs); + + return new AndConstraint((TAssertions)this); + } + + /// + /// Asserts that a collection of objects is equivalent to another collection of objects. + /// + /// + /// Objects within the collections are equivalent when both object graphs have equally named properties with the same + /// value, irrespective of the type of those objects. Two properties are also equal if one type can be converted to another + /// and the result is equal. + /// The type of a collection property is ignored as long as the collection implements and all + /// items in the collection are structurally equal. + /// Notice that actual behavior is determined by the global defaults managed by . + /// + /// + /// A reference to the configuration object that can be used + /// to influence the way the object graphs are compared. You can also provide an alternative instance of the + /// class. The global defaults are determined by the + /// class. + /// + /// + /// An optional formatted phrase as is supported by explaining why the + /// assertion is needed. If the phrase does not start with the word because, it is prepended automatically. + /// + /// + /// Zero or more objects to format using the placeholders in . + /// + public AndConstraint BeEquivalentTo(IEnumerable expectation, + Func, EquivalencyAssertionOptions> config, string because = "", + params object[] becauseArgs) + { + EquivalencyAssertionOptions options = config(AssertionOptions.CloneDefaults()); + + var context = new EquivalencyValidationContext + { + Subject = Subject, + Expectation = expectation, + RootIsCollection = true, + CompileTimeType = typeof(IEnumerable), + Because = because, + BecauseArgs = becauseArgs, + Tracer = options.TraceWriter + }; + + var equivalencyValidator = new EquivalencyValidator(options); + equivalencyValidator.AssertEquality(context); + + return new AndConstraint((TAssertions)this); + } + /// /// Asserts that a collection of objects is equivalent to another collection of objects. /// @@ -1371,7 +1436,9 @@ public AndConstraint HaveElementPreceding(object successor, object .Then .Given(subject => PredecessorOf(successor, subject)) .ForCondition(predecessor => predecessor.IsSameOrEqualTo(expectation)) - .FailWith("but found {0}.", predecessor => predecessor); + .FailWith("but found {0}.", predecessor => predecessor) + .Then + .ClearExpectation(); return new AndConstraint((TAssertions)this); } diff --git a/Src/FluentAssertions/Collections/NonGenericCollectionAssertions.cs b/Src/FluentAssertions/Collections/NonGenericCollectionAssertions.cs index 89cb6d125d..7f462141d6 100644 --- a/Src/FluentAssertions/Collections/NonGenericCollectionAssertions.cs +++ b/Src/FluentAssertions/Collections/NonGenericCollectionAssertions.cs @@ -301,72 +301,5 @@ private int GetMostLocalCount() return base.NotContain(new[] { unexpected }, because, becauseArgs); } - - /// - /// Asserts that a collection of objects is equivalent to another collection of objects. - /// - /// - /// Objects within the collections are equivalent when both object graphs have equally named properties with the same - /// value, irrespective of the type of those objects. Two properties are also equal if one type can be converted to another - /// and the result is equal. - /// The type of a collection property is ignored as long as the collection implements and all - /// items in the collection are structurally equal. - /// Notice that actual behavior is determined by the global defaults managed by . - /// - /// - /// An optional formatted phrase as is supported by explaining why the - /// assertion is needed. If the phrase does not start with the word because, it is prepended automatically. - /// - /// - /// Zero or more objects to format using the placeholders in . - /// - public void BeEquivalentTo(IEnumerable expectation, string because = "", params object[] becauseArgs) - { - BeEquivalentTo(expectation, config => config, because, becauseArgs); - } - - /// - /// Asserts that a collection of objects is equivalent to another collection of objects. - /// - /// - /// Objects within the collections are equivalent when both object graphs have equally named properties with the same - /// value, irrespective of the type of those objects. Two properties are also equal if one type can be converted to another - /// and the result is equal. - /// The type of a collection property is ignored as long as the collection implements and all - /// items in the collection are structurally equal. - /// - /// - /// A reference to the configuration object that can be used - /// to influence the way the object graphs are compared. You can also provide an alternative instance of the - /// class. The global defaults are determined by the - /// class. - /// - /// - /// An optional formatted phrase as is supported by explaining why the - /// assertion is needed. If the phrase does not start with the word because, it is prepended automatically. - /// - /// - /// Zero or more objects to format using the placeholders in . - /// - public void BeEquivalentTo(IEnumerable expectation, - Func, EquivalencyAssertionOptions> config, string because = "", - params object[] becauseArgs) - { - EquivalencyAssertionOptions options = config(AssertionOptions.CloneDefaults()); - - var context = new EquivalencyValidationContext - { - Subject = Subject, - Expectation = expectation, - RootIsCollection = true, - CompileTimeType = typeof(IEnumerable), - Because = because, - BecauseArgs = becauseArgs, - Tracer = options.TraceWriter - }; - - var equivalencyValidator = new EquivalencyValidator(options); - equivalencyValidator.AssertEquality(context); - } } } diff --git a/Src/FluentAssertions/Equivalency/EnumerableEquivalencyValidator.cs b/Src/FluentAssertions/Equivalency/EnumerableEquivalencyValidator.cs index 880b5141b1..c27a63ae0a 100644 --- a/Src/FluentAssertions/Equivalency/EnumerableEquivalencyValidator.cs +++ b/Src/FluentAssertions/Equivalency/EnumerableEquivalencyValidator.cs @@ -31,7 +31,7 @@ public EnumerableEquivalencyValidator(IEquivalencyValidator parent, IEquivalency public void Execute(object[] subject, T[] expectation) { - if (AssertIsNotNull(expectation, subject) && EnumerableEquivalencyValidatorExtensions.AssertCollectionsHaveSameCount(subject, expectation)) + if (AssertIsNotNull(expectation, subject) && AssertCollectionsHaveSameCount(subject, expectation)) { if (Recursive) { @@ -57,6 +57,19 @@ private bool AssertIsNotNull(object expectation, object[] subject) .FailWith("Expected {context:subject} to be , but found {0}.", new object[] { subject }); } + private static Continuation AssertCollectionsHaveSameCount(ICollection subject, ICollection expectation) + { + return AssertionScope.Current + .WithExpectation("Expected {context:subject} to be a collection with {0} item(s){reason}", expectation.Count) + .AssertEitherCollectionIsNotEmpty(subject, expectation) + .Then + .AssertCollectionHasEnoughItems(subject, expectation) + .Then + .AssertCollectionHasNotTooManyItems(subject, expectation) + .Then + .ClearExpectation(); + } + private void AssertElementGraphEquivalency(object[] subjects, T[] expectations) { unmatchedSubjectIndexes = new List(subjects.Length); @@ -90,7 +103,6 @@ private void LooselyMatchAgainst(IList subjects, T expectation, int e var results = new AssertionResultSet(); int index = 0; GetTraceMessage getMessage = path => $"Comparing subject at {path}[{index}] with the expectation at {path}[{expectationIndex}]"; - int count = subjects.Count; int indexToBeRemoved = -1; for (var metaIndex = 0; metaIndex < unmatchedSubjectIndexes.Count; metaIndex++) diff --git a/Src/FluentAssertions/Equivalency/EnumerableEquivalencyValidatorExtensions.cs b/Src/FluentAssertions/Equivalency/EnumerableEquivalencyValidatorExtensions.cs index 32129eefc0..9e4dc9b465 100644 --- a/Src/FluentAssertions/Equivalency/EnumerableEquivalencyValidatorExtensions.cs +++ b/Src/FluentAssertions/Equivalency/EnumerableEquivalencyValidatorExtensions.cs @@ -6,18 +6,7 @@ namespace FluentAssertions.Equivalency { internal static class EnumerableEquivalencyValidatorExtensions { - public static Continuation AssertCollectionsHaveSameCount(ICollection subject, ICollection expectation) - { - return AssertionScope.Current - .WithExpectation("Expected {context:subject} to be a collection with {0} item(s){reason}", expectation.Count) - .AssertEitherCollectionIsNotEmpty(subject, expectation) - .Then - .AssertCollectionHasEnoughItems(subject, expectation) - .Then - .AssertCollectionHasNotTooManyItems(subject, expectation); - } - - public static Continuation AssertEitherCollectionIsNotEmpty(this AssertionScope scope, ICollection subject, ICollection expectation) + public static Continuation AssertEitherCollectionIsNotEmpty(this ContinuedAssertionScope scope, ICollection subject, ICollection expectation) { return scope .ForCondition((subject.Count > 0) || (expectation.Count == 0)) @@ -30,7 +19,7 @@ public static Continuation AssertEitherCollectionIsNotEmpty(this AssertionSco Environment.NewLine); } - public static Continuation AssertCollectionHasEnoughItems(this AssertionScope scope, ICollection subject, ICollection expectation) + public static Continuation AssertCollectionHasEnoughItems(this ContinuedAssertionScope scope, ICollection subject, ICollection expectation) { return scope .ForCondition(subject.Count >= expectation.Count) @@ -41,7 +30,7 @@ public static Continuation AssertCollectionHasEnoughItems(this AssertionScope Environment.NewLine); } - public static Continuation AssertCollectionHasNotTooManyItems(this AssertionScope scope, ICollection subject, ICollection expectation) + public static Continuation AssertCollectionHasNotTooManyItems(this ContinuedAssertionScope scope, ICollection subject, ICollection expectation) { return scope .ForCondition(subject.Count <= expectation.Count) diff --git a/Src/FluentAssertions/Equivalency/GenericDictionaryEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/GenericDictionaryEquivalencyStep.cs index 7a577f1dd4..ff1b181dd5 100644 --- a/Src/FluentAssertions/Equivalency/GenericDictionaryEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/GenericDictionaryEquivalencyStep.cs @@ -184,7 +184,9 @@ private static Type GetIDictionaryInterface(Type expectedType) .FailWith("but has additional key(s) {0}", keyDifference.AdditionalKeys) .Then .ForCondition(!hasMissingKeys || !hasAdditionalKeys) - .FailWith("but it misses key(s) {0} and has additional key(s) {1}", keyDifference.MissingKeys, keyDifference.AdditionalKeys); + .FailWith("but it misses key(s) {0} and has additional key(s) {1}", keyDifference.MissingKeys, keyDifference.AdditionalKeys) + .Then + .ClearExpectation(); } private static KeyDifference CalculateKeyDifference expectation = null; - private readonly bool evaluateCondition = true; + private Func expectation; private string fallbackIdentifier = "object"; #endregion @@ -70,18 +69,12 @@ public AssertionScope(string context) public string Context { get; set; } /// - /// Creates a nested scope used during chaining. + /// Creates a new scope from the current one that can be used to chain multiple + /// assertions that take into account the result of previous results. /// - internal AssertionScope(AssertionScope sourceScope, bool sourceSucceeded) + internal ContinuedAssertionScope CreateContinuation() { - assertionStrategy = sourceScope.assertionStrategy; - contextData = sourceScope.contextData; - reason = sourceScope.reason; - useLineBreaks = sourceScope.useLineBreaks; - parent = sourceScope.parent; - expectation = sourceScope.expectation; - evaluateCondition = sourceSucceeded; - Context = sourceScope.Context; + return new ContinuedAssertionScope(this); } /// @@ -171,6 +164,17 @@ public AssertionScope WithExpectation(string message, params object[] args) return this; } + /// + /// Clears the expectation set by . + /// + // SMELL: It would be better to give the expectation an explicit scope, but that would be a breaking change. + public Continuation ClearExpectation() + { + expectation = null; + + return new Continuation(this, true); + } + /// /// Allows to safely select the subject for successive assertions, even when the prior assertion has failed. /// @@ -179,7 +183,7 @@ public AssertionScope WithExpectation(string message, params object[] args) /// public GivenSelector Given(Func selector) { - return new GivenSelector(selector, evaluateCondition, this); + return new GivenSelector(selector, Succeeded, this); } /// @@ -190,10 +194,7 @@ public GivenSelector Given(Func selector) /// public AssertionScope ForCondition(bool condition) { - if (evaluateCondition) - { - Succeeded = condition; - } + Succeeded = condition; return this; } @@ -221,7 +222,7 @@ public Continuation FailWith(string message, params object[] args) { try { - if (evaluateCondition && !Succeeded) + if (!Succeeded) { string localReason = reason != null ? reason() : ""; var messageBuilder = new MessageBuilder(useLineBreaks); @@ -316,7 +317,7 @@ public void Dispose() public AssertionScope WithDefaultIdentifier(string identifier) { - this.fallbackIdentifier = identifier; + fallbackIdentifier = identifier; return this; } } diff --git a/Src/FluentAssertions/Execution/ChainedAssertionScope.cs b/Src/FluentAssertions/Execution/ChainedAssertionScope.cs new file mode 100644 index 0000000000..e0fb4b288a --- /dev/null +++ b/Src/FluentAssertions/Execution/ChainedAssertionScope.cs @@ -0,0 +1,41 @@ +using System; + +namespace FluentAssertions.Execution +{ + public class ContinuedAssertionScope + { + private readonly AssertionScope predecessor; + private readonly bool predecessorSucceeded; + + public ContinuedAssertionScope(AssertionScope predecessor) + { + this.predecessorSucceeded = predecessor.Succeeded; + this.predecessor = predecessor; + } + + public GivenSelector Given(Func selector) + { + return predecessor.Given(selector); + } + + public ContinuedAssertionScope ForCondition(bool condition) + { + if (predecessorSucceeded) + { + predecessor.ForCondition(condition); + } + + return this; + } + + public Continuation FailWith(string message, params object[] args) + { + return predecessor.FailWith(message, args); + } + + public AssertionScope BecauseOf(string because, params object[] becauseArgs) + { + return predecessor.BecauseOf(because, becauseArgs); + } + } +} diff --git a/Src/FluentAssertions/Execution/Continuation.cs b/Src/FluentAssertions/Execution/Continuation.cs index db27f75da3..fe86f8acaf 100644 --- a/Src/FluentAssertions/Execution/Continuation.cs +++ b/Src/FluentAssertions/Execution/Continuation.cs @@ -5,32 +5,26 @@ namespace FluentAssertions.Execution /// public class Continuation { - #region Private Definition - private readonly AssertionScope sourceScope; - private readonly bool sourceSucceeded; - - #endregion public Continuation(AssertionScope sourceScope, bool sourceSucceeded) { this.sourceScope = sourceScope; - this.sourceSucceeded = sourceSucceeded; } /// /// Continuous the assertion chain if the previous assertion was successful. /// - public AssertionScope Then => new AssertionScope(sourceScope, sourceSucceeded); + public ContinuedAssertionScope Then => sourceScope.CreateContinuation(); - public bool SourceSucceeded => sourceSucceeded; + public bool SourceSucceeded => sourceScope.Succeeded; /// /// Provides back-wards compatibility for code that expects to return a boolean. /// public static implicit operator bool(Continuation continuation) { - return continuation.sourceSucceeded; + return continuation.SourceSucceeded; } } } diff --git a/Src/FluentAssertions/Execution/ContinuationOfGiven.cs b/Src/FluentAssertions/Execution/ContinuationOfGiven.cs index 4d5c1695a1..99097bf99d 100644 --- a/Src/FluentAssertions/Execution/ContinuationOfGiven.cs +++ b/Src/FluentAssertions/Execution/ContinuationOfGiven.cs @@ -7,21 +7,20 @@ public class ContinuationOfGiven { #region Private Definitions - private readonly GivenSelector parent; private readonly bool succeeded; #endregion public ContinuationOfGiven(GivenSelector parent, bool succeeded) { - this.parent = parent; + Then = parent; this.succeeded = succeeded; } /// /// Continuous the assertion chain if the previous assertion was successful. /// - public GivenSelector Then => parent; + public GivenSelector Then { get; } /// /// Provides back-wards compatibility for code that expects to return a boolean. diff --git a/Src/FluentAssertions/Execution/GivenSelector.cs b/Src/FluentAssertions/Execution/GivenSelector.cs index ab3e435075..4e04a1bf57 100644 --- a/Src/FluentAssertions/Execution/GivenSelector.cs +++ b/Src/FluentAssertions/Execution/GivenSelector.cs @@ -12,17 +12,17 @@ public class GivenSelector #region Private Definitions private readonly T subject; - private readonly bool evaluateCondition; - private readonly AssertionScope parentScope; + private readonly bool predecessorSucceeded; + private readonly AssertionScope predecessor; #endregion - public GivenSelector(Func selector, bool evaluateCondition, AssertionScope parentScope) + public GivenSelector(Func selector, bool predecessorSucceeded, AssertionScope predecessor) { - this.evaluateCondition = evaluateCondition; - this.parentScope = parentScope; + this.predecessorSucceeded = predecessorSucceeded; + this.predecessor = predecessor; - subject = evaluateCondition ? selector() : default(T); + subject = predecessorSucceeded ? selector() : default(T); } /// @@ -37,10 +37,7 @@ public GivenSelector(Func selector, bool evaluateCondition, AssertionScope pa /// public GivenSelector ForCondition(Func predicate) { - if (evaluateCondition) - { - parentScope.ForCondition(predicate(subject)); - } + predecessor.ForCondition(predicate(subject)); return this; } @@ -57,7 +54,7 @@ public GivenSelector ForCondition(Func predicate) /// public GivenSelector Given(Func selector) { - return new GivenSelector(() => selector(subject), evaluateCondition, parentScope); + return new GivenSelector(() => selector(subject), predecessorSucceeded, predecessor); } /// @@ -121,15 +118,24 @@ public ContinuationOfGiven FailWith(string message, params Func[] /// Optional arguments to any numbered placeholders. public ContinuationOfGiven FailWith(string message, params object[] args) { - bool succeeded = parentScope.Succeeded; + bool succeeded = predecessor.Succeeded; - if (evaluateCondition) + if (predecessorSucceeded) { - Continuation continuation = parentScope.FailWith(message, args); + Continuation continuation = predecessor.FailWith(message, args); succeeded = continuation.SourceSucceeded; } return new ContinuationOfGiven(this, succeeded); } + + /// + /// Clears the expectation set by . + /// + public ContinuationOfGiven ClearExpectation() + { + predecessor.ClearExpectation(); + return new ContinuationOfGiven(this, predecessor.Succeeded); + } } } diff --git a/Src/FluentAssertions/Primitives/DateTimeAssertions.cs b/Src/FluentAssertions/Primitives/DateTimeAssertions.cs index 9421525fea..a00bbdfebe 100644 --- a/Src/FluentAssertions/Primitives/DateTimeAssertions.cs +++ b/Src/FluentAssertions/Primitives/DateTimeAssertions.cs @@ -386,7 +386,9 @@ public AndConstraint HaveYear(int expected, string because = .Then .ForCondition(Subject.Value.Year == expected) .BecauseOf(because, becauseArgs) - .FailWith(", but found {0}.", Subject.Value.Year); + .FailWith(", but found {0}.", Subject.Value.Year) + .Then + .ClearExpectation(); return new AndConstraint(this); } @@ -438,7 +440,9 @@ public AndConstraint HaveMonth(int expected, string because .Then .ForCondition(Subject.Value.Month == expected) .BecauseOf(because, becauseArgs) - .FailWith(", but found {0}.", Subject.Value.Month); + .FailWith(", but found {0}.", Subject.Value.Month) + .Then + .ClearExpectation(); return new AndConstraint(this); } @@ -464,7 +468,9 @@ public AndConstraint NotHaveMonth(int unexpected, string bec .Then .ForCondition(Subject.Value.Month != unexpected) .BecauseOf(because, becauseArgs) - .FailWith(", but it was."); + .FailWith(", but it was.") + .Then + .ClearExpectation(); return new AndConstraint(this); } @@ -490,7 +496,9 @@ public AndConstraint HaveDay(int expected, string because = .Then .ForCondition(Subject.Value.Day == expected) .BecauseOf(because, becauseArgs) - .FailWith(", but found {0}.", Subject.Value.Day); + .FailWith(", but found {0}.", Subject.Value.Day) + .Then + .ClearExpectation(); return new AndConstraint(this); } @@ -516,7 +524,9 @@ public AndConstraint NotHaveDay(int unexpected, string becau .Then .ForCondition(Subject.Value.Day != unexpected) .BecauseOf(because, becauseArgs) - .FailWith(", but it was."); + .FailWith(", but it was.") + .Then + .ClearExpectation(); return new AndConstraint(this); } @@ -542,7 +552,9 @@ public AndConstraint HaveHour(int expected, string because = .Then .ForCondition(Subject.Value.Hour == expected) .BecauseOf(because, becauseArgs) - .FailWith(", but found {0}.", Subject.Value.Hour); + .FailWith(", but found {0}.", Subject.Value.Hour) + .Then + .ClearExpectation(); return new AndConstraint(this); } @@ -568,8 +580,9 @@ public AndConstraint NotHaveHour(int unexpected, string beca .Then .ForCondition(Subject.Value.Hour != unexpected) .BecauseOf(because, becauseArgs) - .FailWith(", but it was.", unexpected, - Subject.Value.Hour); + .FailWith(", but it was.", unexpected, Subject.Value.Hour) + .Then + .ClearExpectation(); return new AndConstraint(this); } @@ -596,7 +609,9 @@ public AndConstraint NotHaveHour(int unexpected, string beca .Then .ForCondition(Subject.Value.Minute == expected) .BecauseOf(because, becauseArgs) - .FailWith(", but found {0}.", Subject.Value.Minute); + .FailWith(", but found {0}.", Subject.Value.Minute) + .Then + .ClearExpectation(); return new AndConstraint(this); } @@ -623,8 +638,9 @@ public AndConstraint NotHaveHour(int unexpected, string beca .Then .ForCondition(Subject.Value.Minute != unexpected) .BecauseOf(because, becauseArgs) - .FailWith(", but it was.", unexpected, - Subject.Value.Minute); + .FailWith(", but it was.", unexpected, Subject.Value.Minute) + .Then + .ClearExpectation(); return new AndConstraint(this); } @@ -651,7 +667,9 @@ public AndConstraint NotHaveHour(int unexpected, string beca .Then .ForCondition(Subject.Value.Second == expected) .BecauseOf(because, becauseArgs) - .FailWith(", but found {0}.", Subject.Value.Second); + .FailWith(", but found {0}.", Subject.Value.Second) + .Then + .ClearExpectation(); return new AndConstraint(this); } @@ -678,7 +696,8 @@ public AndConstraint NotHaveHour(int unexpected, string beca .Then .ForCondition(Subject.Value.Second != unexpected) .BecauseOf(because, becauseArgs) - .FailWith(", but it was."); + .FailWith(", but it was.") + .Then.ClearExpectation(); return new AndConstraint(this); } @@ -768,7 +787,9 @@ public DateTimeRangeAssertions BeLessThan(TimeSpan timeSpan) .Then .ForCondition(Subject.Value.Date == expectedDate) .BecauseOf(because, becauseArgs) - .FailWith(", but found {1}.", expectedDate, Subject.Value); + .FailWith(", but found {1}.", expectedDate, Subject.Value) + .Then + .ClearExpectation(); return new AndConstraint(this); } @@ -797,7 +818,9 @@ public DateTimeRangeAssertions BeLessThan(TimeSpan timeSpan) .Then .ForCondition(Subject.Value.Date != unexpectedDate) .BecauseOf(because, becauseArgs) - .FailWith(", but it was."); + .FailWith(", but it was.") + .Then + .ClearExpectation(); return new AndConstraint(this); } @@ -888,7 +911,9 @@ public AndConstraint BeIn(DateTimeKind expectedKind, string .Then .ForCondition(Subject.Value.Kind == expectedKind) .BecauseOf(because, becauseArgs) - .FailWith(", but found {0}.", Subject.Value.Kind); + .FailWith(", but found {0}.", Subject.Value.Kind) + .Then + .ClearExpectation(); return new AndConstraint(this); } diff --git a/Tests/Shared.Specs/CollectionEquivalencySpecs.cs b/Tests/Shared.Specs/CollectionEquivalencySpecs.cs index e34c9838e0..086a0c3cce 100644 --- a/Tests/Shared.Specs/CollectionEquivalencySpecs.cs +++ b/Tests/Shared.Specs/CollectionEquivalencySpecs.cs @@ -1142,6 +1142,46 @@ public void When_asserting_equivalence_of_non_generic_collections_it_should_resp .WithMessage("*Wheels*not have*VehicleId*not have*"); } + [Fact] + public void When_comparing_against_a_non_generic_collection_it_should_treat_it_as_unordered_collection_of_objects() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + List actual = new List { typeof(int), typeof(string) }; + IEnumerable expectation = new List { typeof(string), typeof(int) }; + + //----------------------------------------------------------------------------------------------------------- + // Act + //----------------------------------------------------------------------------------------------------------- + Action act = () => actual.Should().BeEquivalentTo(expectation); + + //----------------------------------------------------------------------------------------------------------- + // Assert + //----------------------------------------------------------------------------------------------------------- + act.Should().NotThrow(); + } + + [Fact] + public void When_comparing_against_a_non_generic_collection_it_should_treat_it_as_collection_of_objects() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + List actual = new List { typeof(int), typeof(string) }; + IEnumerable expectation = new List { typeof(string), typeof(int) }; + + //----------------------------------------------------------------------------------------------------------- + // Act + //----------------------------------------------------------------------------------------------------------- + Action act = () => actual.Should().BeEquivalentTo(expectation, o => o.WithStrictOrdering().WithTracing()); + + //----------------------------------------------------------------------------------------------------------- + // Assert + //----------------------------------------------------------------------------------------------------------- + act.Should().NotThrow(); + } + [Fact] public void When_custom_assertion_rules_are_utilized_the_rules_should_be_respected() {