diff --git a/Src/FluentAssertions/Collections/CollectionAssertions.cs b/Src/FluentAssertions/Collections/CollectionAssertions.cs index b795e0a79d..3f18f89836 100644 --- a/Src/FluentAssertions/Collections/CollectionAssertions.cs +++ b/Src/FluentAssertions/Collections/CollectionAssertions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -246,7 +246,7 @@ public AndConstraint Equal(IEnumerable expected, string because = " ICollection expectedItems = expectation.ConvertOrCastToCollection(); - AssertionScope assertion = Execute.Assertion.BecauseOf(because, becauseArgs); + IAssertionScope assertion = Execute.Assertion.BecauseOf(because, becauseArgs); if (subjectIsNull) { assertion.FailWith("Expected {context:collection} to be equal to {0}{reason}, but found .", expectedItems); @@ -1371,7 +1371,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/Equivalency/EnumerableEquivalencyValidatorExtensions.cs b/Src/FluentAssertions/Equivalency/EnumerableEquivalencyValidatorExtensions.cs index 32129eefc0..55c3384ba9 100644 --- a/Src/FluentAssertions/Equivalency/EnumerableEquivalencyValidatorExtensions.cs +++ b/Src/FluentAssertions/Equivalency/EnumerableEquivalencyValidatorExtensions.cs @@ -17,7 +17,7 @@ public static Continuation AssertCollectionsHaveSameCount(ICollection .AssertCollectionHasNotTooManyItems(subject, expectation); } - public static Continuation AssertEitherCollectionIsNotEmpty(this AssertionScope scope, ICollection subject, ICollection expectation) + public static Continuation AssertEitherCollectionIsNotEmpty(this IAssertionScope scope, ICollection subject, ICollection expectation) { return scope .ForCondition((subject.Count > 0) || (expectation.Count == 0)) @@ -30,7 +30,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 IAssertionScope scope, ICollection subject, ICollection expectation) { return scope .ForCondition(subject.Count >= expectation.Count) @@ -41,7 +41,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 IAssertionScope 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 /// Represents an implicit or explicit scope within which multiple assertions can be collected. /// - public class AssertionScope : IDisposable + public class AssertionScope : IAssertionScope { #region Private Definitions @@ -25,9 +26,9 @@ public class AssertionScope : IDisposable private static AssertionScope current; private AssertionScope parent; - private Func expectation = null; - private readonly bool evaluateCondition = true; + private Func expectation; private string fallbackIdentifier = "object"; + private bool? succeeded; #endregion @@ -69,21 +70,6 @@ public AssertionScope(string context) /// public string Context { get; set; } - /// - /// Creates a nested scope used during chaining. - /// - internal AssertionScope(AssertionScope sourceScope, bool sourceSucceeded) - { - assertionStrategy = sourceScope.assertionStrategy; - contextData = sourceScope.contextData; - reason = sourceScope.reason; - useLineBreaks = sourceScope.useLineBreaks; - parent = sourceScope.parent; - expectation = sourceScope.expectation; - evaluateCondition = sourceSucceeded; - Context = sourceScope.Context; - } - /// /// Gets the current thread-specific assertion scope. /// @@ -93,10 +79,7 @@ public static AssertionScope Current private set => current = value; } - /// - /// Indicates that every argument passed into is displayed on a separate line. - /// - public AssertionScope UsingLineBreaks + public IAssertionScope UsingLineBreaks { get { @@ -105,25 +88,12 @@ public AssertionScope UsingLineBreaks } } - /// - /// Gets a value indicating whether or not the last assertion executed through this scope succeeded. - /// - public bool Succeeded { get; private set; } + public bool Succeeded + { + get => succeeded.HasValue && succeeded.Value; + } - /// - /// Specify the reason why you expect the condition to be true. - /// - /// - /// A formatted phrase compatible with explaining why - /// the condition should be satisfied. If the phrase does not start with the word because, - /// it is prepended to the message. If the format of or - /// is not compatible with , - /// then a warning message is returned instead. - /// - /// - /// Zero or more values to use for filling in any compatible placeholders. - /// - public AssertionScope BecauseOf(string because, params object[] becauseArgs) + public IAssertionScope BecauseOf(string because, params object[] becauseArgs) { reason = () => { @@ -156,7 +126,7 @@ public AssertionScope BecauseOf(string because, params object[] becauseArgs) /// /// The format string that represents the failure message. /// Optional arguments to any numbered placeholders. - public AssertionScope WithExpectation(string message, params object[] args) + public IAssertionScope WithExpectation(string message, params object[] args) { var localReason = reason; expectation = () => @@ -171,44 +141,30 @@ public AssertionScope WithExpectation(string message, params object[] args) return this; } - /// - /// Allows to safely select the subject for successive assertions, even when the prior assertion has failed. - /// - /// - /// Selector which result is passed to successive calls to . - /// + public Continuation ClearExpectation() + { + expectation = null; + + return new Continuation(this, !succeeded.HasValue || succeeded.Value); + } + public GivenSelector Given(Func selector) { - return new GivenSelector(selector, evaluateCondition, this); + return new GivenSelector(selector, !succeeded.HasValue || succeeded.Value, this); } - /// - /// Specify the condition that must be satisfied. - /// - /// - /// If true the assertion will be treated as successful and no exceptions will be thrown. - /// - public AssertionScope ForCondition(bool condition) + public IAssertionScope ForCondition(bool condition) { - if (evaluateCondition) - { - Succeeded = condition; - } + succeeded = condition; return this; } - /// - /// Sets the failure message when the assertion is not met, or completes the failure message set to a - /// prior call to . - /// will not be called unless the assertion is not met. - /// - /// Function returning object on demand. Called only when the assertion is not met. public Continuation FailWith(Func failReasonFunc) { try { - if (evaluateCondition && !Succeeded) + if (!succeeded.HasValue || !succeeded.Value) { string localReason = reason != null ? reason() : ""; var messageBuilder = new MessageBuilder(useLineBreaks); @@ -222,35 +178,18 @@ public Continuation FailWith(Func failReasonFunc) } assertionStrategy.HandleFailure(result.Capitalize()); + + succeeded = false; } - return new Continuation(this, Succeeded); + return new Continuation(this, succeeded.Value); } finally { - Succeeded = false; + succeeded = null; } } - /// - /// Sets the failure message when the assertion is not met, or completes the failure message set to a - /// prior call to . - /// - /// - /// In addition to the numbered -style placeholders, messages may contain a few - /// specialized placeholders as well. For instance, {reason} will be replaced with the reason of the assertion as passed - /// to . Other named placeholders will be replaced with - /// the scope data passed through - /// and - /// . Finally, a description of the - /// current subject can be passed through the {context:description} placeholder. This is used in the message if no - /// explicit context is specified through the constructor. - /// Note that only 10 are supported in combination with a {reason}. - /// If an expectation was set through a prior call to , - /// then the failure message is appended to that expectation. - /// - /// The format string that represents the failure message. - /// Optional arguments to any numbered placeholders. public Continuation FailWith(string message, params object[] args) { return FailWith(() => new FailReason(message, args)); @@ -288,9 +227,6 @@ public void AddReportable(string key, string value) contextData.Add(key, value, Reportability.Reportable); } - /// - /// Discards and returns the failures that happened up to now. - /// public string[] Discard() { return assertionStrategy.DiscardFailures().ToArray(); @@ -326,9 +262,9 @@ public void Dispose() } } - public AssertionScope WithDefaultIdentifier(string identifier) + public IAssertionScope 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..5d2d02a630 --- /dev/null +++ b/Src/FluentAssertions/Execution/ChainedAssertionScope.cs @@ -0,0 +1,92 @@ +using System; + +namespace FluentAssertions.Execution +{ + /// + /// Allows chaining multiple assertion scopes together using . + /// + /// + /// If the parent scope has captured a failed assertion, this class ensures that successive assertions + /// are no longer evaluated. + /// + public class ContinuedAssertionScope : IAssertionScope + { + private readonly AssertionScope predecessor; + private readonly bool predecessorSucceeded; + + public ContinuedAssertionScope(AssertionScope predecessor, bool predecessorSucceeded) + { + this.predecessorSucceeded = predecessorSucceeded; + this.predecessor = predecessor; + } + + public GivenSelector Given(Func selector) + { + return predecessor.Given(selector); + } + + public IAssertionScope ForCondition(bool condition) + { + if (predecessorSucceeded) + { + return predecessor.ForCondition(condition); + } + + return this; + } + + public Continuation FailWith(Func failReasonFunc) + { + if (predecessorSucceeded) + { + return predecessor.FailWith(failReasonFunc); + } + + return new Continuation(predecessor, false); + } + + public Continuation FailWith(string message, params object[] args) + { + if (predecessorSucceeded) + { + return predecessor.FailWith(message, args); + } + + return new Continuation(predecessor, false); + } + + public IAssertionScope BecauseOf(string because, params object[] becauseArgs) + { + return predecessor.BecauseOf(because, becauseArgs); + } + + public Continuation ClearExpectation() + { + return predecessor.ClearExpectation(); + } + + public IAssertionScope WithExpectation(string message, params object[] args) + { + return predecessor.WithExpectation(message, args); + } + + public IAssertionScope WithDefaultIdentifier(string identifier) + { + return predecessor.WithDefaultIdentifier(identifier); + } + + public IAssertionScope UsingLineBreaks => predecessor.UsingLineBreaks; + + public bool Succeeded => predecessor.Succeeded; + + public string[] Discard() + { + return predecessor.Discard(); + } + + public void Dispose() + { + predecessor.Dispose(); + } + } +} diff --git a/Src/FluentAssertions/Execution/Continuation.cs b/Src/FluentAssertions/Execution/Continuation.cs index db27f75da3..0975974af5 100644 --- a/Src/FluentAssertions/Execution/Continuation.cs +++ b/Src/FluentAssertions/Execution/Continuation.cs @@ -5,32 +5,27 @@ 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; + SourceSucceeded = sourceSucceeded; } /// /// Continuous the assertion chain if the previous assertion was successful. /// - public AssertionScope Then => new AssertionScope(sourceScope, sourceSucceeded); + public IAssertionScope Then => new ContinuedAssertionScope(sourceScope, SourceSucceeded); - public bool SourceSucceeded => sourceSucceeded; + public bool SourceSucceeded { get; } /// /// 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..911689016f 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 = predecessorSucceeded; - 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, predecessorSucceeded); + } } } diff --git a/Src/FluentAssertions/Execution/IAssertionScope.cs b/Src/FluentAssertions/Execution/IAssertionScope.cs new file mode 100644 index 0000000000..4a8bdb6967 --- /dev/null +++ b/Src/FluentAssertions/Execution/IAssertionScope.cs @@ -0,0 +1,111 @@ +using System; + +namespace FluentAssertions.Execution +{ + public interface IAssertionScope : IDisposable + { + /// + /// Allows to safely select the subject for successive assertions, even when the prior assertion has failed. + /// + /// + /// Selector which result is passed to successive calls to . + /// + GivenSelector Given(Func selector); + + /// + /// Specify the condition that must be satisfied. + /// + /// + /// If true the assertion will be treated as successful and no exceptions will be thrown. + /// + IAssertionScope ForCondition(bool condition); + + /// + /// Sets the failure message when the assertion is not met, or completes the failure message set to a + /// prior call to . + /// will not be called unless the assertion is not met. + /// + /// Function returning object on demand. Called only when the assertion is not met. + Continuation FailWith(Func failReasonFunc); + + /// + /// Sets the failure message when the assertion is not met, or completes the failure message set to a + /// prior call to . + /// + /// + /// In addition to the numbered -style placeholders, messages may contain a few + /// specialized placeholders as well. For instance, {reason} will be replaced with the reason of the assertion as passed + /// to . Other named placeholders will be replaced with + /// the scope data passed through + /// and + /// . Finally, a description of the + /// current subject can be passed through the {context:description} placeholder. This is used in the message if no + /// explicit context is specified through the constructor. + /// Note that only 10 are supported in combination with a {reason}. + /// If an expectation was set through a prior call to , + /// then the failure message is appended to that expectation. + /// + /// The format string that represents the failure message. + /// Optional arguments to any numbered placeholders. + Continuation FailWith(string message, params object[] args); + + /// + /// Specify the reason why you expect the condition to be true. + /// + /// + /// A formatted phrase compatible with explaining why + /// the condition should be satisfied. If the phrase does not start with the word because, + /// it is prepended to the message. If the format of or + /// is not compatible with , + /// then a warning message is returned instead. + /// + /// + /// Zero or more values to use for filling in any compatible placeholders. + /// + IAssertionScope BecauseOf(string because, params object[] becauseArgs); + + /// + /// Clears the expectation set by . + /// + // SMELL: It would be better to give the expectation an explicit scope, but that would be a breaking change. + Continuation ClearExpectation(); + + /// + /// Sets the expectation part of the failure message when the assertion is not met. + /// + /// + /// In addition to the numbered -style placeholders, messages may contain a few + /// specialized placeholders as well. For instance, {reason} will be replaced with the reason of the assertion as passed + /// to . Other named placeholders will be replaced with the scope data + /// passed through and . Finally, a description of the + /// current subject can be passed through the {context:description} placeholder. This is used in the message if no + /// explicit context is specified through the constructor. + /// Note that only 10 are supported in combination with a {reason}. + /// If an expectation was set through a prior call to , then the failure message is appended to that + /// expectation. + /// + /// The format string that represents the failure message. + /// Optional arguments to any numbered placeholders. + IAssertionScope WithExpectation(string message, params object[] args); + + /// + /// Defines the name of the subject in case this cannot be extracted from the source code. + /// + IAssertionScope WithDefaultIdentifier(string identifier); + + /// + /// Indicates that every argument passed into is displayed on a separate line. + /// + IAssertionScope UsingLineBreaks { get; } + + /// + /// Gets a value indicating whether or not the last assertion executed through this scope succeeded. + /// + bool Succeeded { get; } + + /// + /// Discards and returns the failures that happened up to now. + /// + string[] Discard(); + } +} 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/Src/FluentAssertions/Primitives/StringValidator.cs b/Src/FluentAssertions/Primitives/StringValidator.cs index dd6a7eaafc..08e3d3db2b 100644 --- a/Src/FluentAssertions/Primitives/StringValidator.cs +++ b/Src/FluentAssertions/Primitives/StringValidator.cs @@ -12,7 +12,7 @@ internal abstract class StringValidator protected readonly string subject; protected readonly string expected; - protected AssertionScope assertion; + protected IAssertionScope assertion; private const int HumanReadableLength = 8; #endregion diff --git a/Src/FluentAssertions/Specialized/ExceptionAssertions.cs b/Src/FluentAssertions/Specialized/ExceptionAssertions.cs index 41beba3ca7..f91674b5e7 100644 --- a/Src/FluentAssertions/Specialized/ExceptionAssertions.cs +++ b/Src/FluentAssertions/Specialized/ExceptionAssertions.cs @@ -63,7 +63,7 @@ public ExceptionAssertions(IEnumerable exceptions) public virtual ExceptionAssertions WithMessage(string expectedMessage, string because = "", params object[] becauseArgs) { - AssertionScope assertion = Execute.Assertion.BecauseOf(because, becauseArgs).UsingLineBreaks; + IAssertionScope assertion = Execute.Assertion.BecauseOf(because, becauseArgs).UsingLineBreaks; assertion .ForCondition(Subject.Any()) diff --git a/Src/FluentAssertions/Xml/XmlReaderValidator.cs b/Src/FluentAssertions/Xml/XmlReaderValidator.cs index f2314a4d1a..1fb33ed0cd 100644 --- a/Src/FluentAssertions/Xml/XmlReaderValidator.cs +++ b/Src/FluentAssertions/Xml/XmlReaderValidator.cs @@ -8,7 +8,7 @@ namespace FluentAssertions.Xml { internal class XmlReaderValidator { - private readonly AssertionScope assertion; + private readonly IAssertionScope assertion; private readonly XmlReader subjectReader; private readonly XmlReader otherReader;