From 29a0a25c781ae181e541ec4034c0a79dc0c1cd3e Mon Sep 17 00:00:00 2001 From: Dennis Doomen Date: Sun, 27 Aug 2023 20:04:52 +0200 Subject: [PATCH] Refactored parts of AssertionScope into AssertionChain and used that to improve the identifier in .Which constructs. --- .editorconfig | 5 + Build/Build.cs | 4 - Build/_build.csproj | 1 + FluentAssertions.sln | 1 - FluentAssertions.sln.DotSettings | 14 +- Src/FluentAssertions/AndWhichConstraint.cs | 100 +- Src/FluentAssertions/AssertionExtensions.cs | 137 +- .../GenericCollectionAssertions.cs | 1107 +++++++++-------- .../GenericDictionaryAssertions.cs | 186 ++- .../Collections/StringCollectionAssertions.cs | 68 +- .../SubsequentOrderingAssertions.cs | 5 +- ...uentOrderingGenericCollectionAssertions.cs | 9 +- .../Common/MethodInfoExtensions.cs | 1 - .../Common/ObjectExtensions.cs | 9 + .../Common/StringExtensions.cs | 11 + .../CustomAssertionAttribute.cs | 2 +- .../CustomAssertionsAssemblyAttribute.cs | 2 +- .../EnumAssertionsExtensions.cs | 5 +- .../Equivalency/AssertionChainExtensions.cs | 19 + .../Equivalency/EquivalencyStep.cs | 2 +- .../EquivalencyValidationContext.cs | 2 +- .../Equivalency/EquivalencyValidator.cs | 31 +- .../Execution/CyclicReferenceDetector.cs | 13 +- .../Equivalency/IEquivalencyStep.cs | 3 +- .../Equivalency/IMemberMatchingRule.cs | 11 +- .../Matching/MappedMemberMatchingRule.cs | 3 +- .../Matching/MappedPathMatchingRule.cs | 3 +- .../Matching/MustMatchByNameRule.cs | 6 +- .../Matching/TryMatchByNameRule.cs | 3 +- .../MultiDimensionalArrayEquivalencyStep.cs | 31 +- Src/FluentAssertions/Equivalency/Node.cs | 11 +- .../Equivalency/Steps/AssertionResultSet.cs | 2 +- .../Steps/AssertionRuleEquivalencyStep.cs | 30 +- .../Steps/DictionaryEquivalencyStep.cs | 40 +- .../Steps/DictionaryInterfaceInfo.cs | 6 +- .../Equivalency/Steps/EnumEqualityStep.cs | 18 +- .../Steps/EnumerableEquivalencyStep.cs | 16 +- .../Steps/EnumerableEquivalencyValidator.cs | 31 +- ...numerableEquivalencyValidatorExtensions.cs | 54 +- .../Steps/EqualityComparerEquivalencyStep.cs | 3 +- .../Steps/GenericDictionaryEquivalencyStep.cs | 68 +- .../Steps/GenericEnumerableEquivalencyStep.cs | 18 +- .../Steps/SimpleEqualityEquivalencyStep.cs | 6 + .../Steps/StringEqualityEquivalencyStep.cs | 25 +- .../StructuralEqualityEquivalencyStep.cs | 14 +- .../Steps/ValueTypeEquivalencyStep.cs | 5 + .../Steps/XAttributeEquivalencyStep.cs | 8 +- .../Steps/XDocumentEquivalencyStep.cs | 8 +- .../Steps/XElementEquivalencyStep.cs | 8 +- .../EventRaisingExtensions.cs | 13 +- .../Events/EventAssertions.cs | 20 +- Src/FluentAssertions/Events/EventMonitor.cs | 3 +- .../ExceptionAssertionsExtensions.cs | 3 +- .../Execution/AssertionChain.cs | 355 ++++++ .../Execution/AssertionScope.cs | 386 +----- .../Execution/ContextDataDictionary.cs | 9 +- .../Execution/Continuation.cs | 26 +- .../Execution/ContinuationOfGiven.cs | 17 +- .../Execution/ContinuedAssertionScope.cs | 156 --- Src/FluentAssertions/Execution/Execute.cs | 21 - Src/FluentAssertions/Execution/FailReason.cs | 6 +- ...eBuilder.cs => FailureMessageFormatter.cs} | 119 +- .../Execution/GivenSelector.cs | 68 +- .../Execution/IAssertionScope.cs | 186 --- .../Formatting/FormattedObjectGraph.cs | 19 +- Src/FluentAssertions/Formatting/Formatter.cs | 4 +- .../Formatting/MethodInfoFormatter.cs | 40 + .../Formatting/PropertyInfoFormatter.cs | 9 +- .../Numeric/ByteAssertions.cs | 5 +- .../Numeric/ComparableTypeAssertions.cs | 34 +- .../Numeric/DecimalAssertions.cs | 5 +- .../Numeric/DoubleAssertions.cs | 5 +- .../Numeric/Int16Assertions.cs | 5 +- .../Numeric/Int32Assertions.cs | 5 +- .../Numeric/Int64Assertions.cs | 5 +- .../Numeric/NullableByteAssertions.cs | 5 +- .../Numeric/NullableDecimalAssertions.cs | 5 +- .../Numeric/NullableDoubleAssertions.cs | 5 +- .../Numeric/NullableInt16Assertions.cs | 5 +- .../Numeric/NullableInt32Assertions.cs | 5 +- .../Numeric/NullableInt64Assertions.cs | 5 +- .../Numeric/NullableNumericAssertions.cs | 17 +- .../Numeric/NullableSByteAssertions.cs | 5 +- .../Numeric/NullableSingleAssertions.cs | 5 +- .../Numeric/NullableUInt16Assertions.cs | 5 +- .../Numeric/NullableUInt32Assertions.cs | 5 +- .../Numeric/NullableUInt64Assertions.cs | 5 +- .../Numeric/NumericAssertions.cs | 45 +- .../Numeric/SByteAssertions.cs | 5 +- .../Numeric/SingleAssertions.cs | 5 +- .../Numeric/UInt16Assertions.cs | 5 +- .../Numeric/UInt32Assertions.cs | 5 +- .../Numeric/UInt64Assertions.cs | 5 +- .../NumericAssertionsExtensions.cs | 194 ++- .../ObjectAssertionsExtensions.cs | 5 +- Src/FluentAssertions/OccurrenceConstraint.cs | 5 +- .../Primitives/BooleanAssertions.cs | 38 +- .../Primitives/DateOnlyAssertions.cs | 136 +- .../Primitives/DateTimeAssertions.cs | 337 +++-- .../Primitives/DateTimeOffsetAssertions.cs | 388 +++--- .../DateTimeOffsetRangeAssertions.cs | 17 +- .../Primitives/DateTimeRangeAssertions.cs | 17 +- .../Primitives/EnumAssertions.cs | 79 +- .../Primitives/GuidAssertions.cs | 17 +- .../HttpResponseMessageAssertions.cs | 53 +- .../Primitives/IStringComparisonStrategy.cs | 2 +- .../Primitives/NullableBooleanAssertions.cs | 23 +- .../Primitives/NullableDateOnlyAssertions.cs | 15 +- .../Primitives/NullableDateTimeAssertions.cs | 15 +- .../NullableDateTimeOffsetAssertions.cs | 15 +- .../Primitives/NullableEnumAssertions.cs | 17 +- .../Primitives/NullableGuidAssertions.cs | 17 +- .../NullableSimpleTimeSpanAssertions.cs | 17 +- .../Primitives/NullableTimeOnlyAssertions.cs | 15 +- .../Primitives/ObjectAssertions.cs | 39 +- .../Primitives/ReferenceTypeAssertions.cs | 72 +- .../Primitives/SimpleTimeSpanAssertions.cs | 29 +- .../Primitives/StringAssertions.cs | 141 ++- .../Primitives/StringContainsStrategy.cs | 4 +- .../Primitives/StringEndStrategy.cs | 12 +- .../Primitives/StringEqualityStrategy.cs | 20 +- .../Primitives/StringStartStrategy.cs | 12 +- .../Primitives/StringValidator.cs | 14 +- .../StringValidatorSupportingNull.cs | 11 +- .../StringWildcardMatchingStrategy.cs | 6 +- .../Primitives/TimeOnlyAssertions.cs | 176 ++- .../Specialized/ActionAssertions.cs | 22 +- .../Specialized/AsyncFunctionAssertions.cs | 71 +- .../Specialized/DelegateAssertions.cs | 34 +- .../Specialized/DelegateAssertionsBase.cs | 40 +- .../Specialized/ExceptionAssertions.cs | 104 +- .../Specialized/ExecutionTimeAssertions.cs | 14 +- .../Specialized/FunctionAssertionHelpers.cs | 57 - .../Specialized/FunctionAssertions.cs | 71 +- .../GenericAsyncFunctionAssertions.cs | 41 +- .../NonGenericAsyncFunctionAssertions.cs | 32 +- .../TaskCompletionSourceAssertions.cs | 44 +- .../Streams/BufferedStreamAssertions.cs | 38 +- .../Streams/StreamAssertions.cs | 103 +- .../Types/AssemblyAssertions.cs | 57 +- .../Types/ConstructorInfoAssertions.cs | 8 +- .../Types/MemberInfoAssertions.cs | 21 +- .../Types/MethodBaseAssertions.cs | 34 +- .../Types/MethodInfoAssertions.cs | 57 +- .../Types/MethodInfoSelectorAssertions.cs | 21 +- .../Types/PropertyInfoAssertions.cs | 214 ++-- .../Types/PropertyInfoSelectorAssertions.cs | 20 +- Src/FluentAssertions/Types/TypeAssertions.cs | 378 +++--- .../Types/TypeSelectorAssertions.cs | 33 +- .../Xml/Equivalency/XmlReaderValidator.cs | 12 +- .../Xml/XAttributeAssertions.cs | 17 +- .../Xml/XDocumentAssertions.cs | 39 +- .../Xml/XElementAssertions.cs | 47 +- .../Xml/XmlElementAssertions.cs | 19 +- Src/FluentAssertions/Xml/XmlNodeAssertions.cs | 16 +- .../XmlAssertionExtensions.cs | 5 +- .../FluentAssertions/net47.verified.txt | 320 +++-- .../FluentAssertions/net6.0.verified.txt | 333 +++-- .../netstandard2.0.verified.txt | 318 +++-- .../netstandard2.1.verified.txt | 313 +++-- Tests/Benchmarks/Program.cs | 1 - .../BasicSpecs.cs | 3 +- .../CollectionSpecs.cs | 3 +- .../DictionarySpecs.cs | 23 + .../ExtensibilitySpecs.cs | 10 +- .../AssertionExtensions.cs | 13 +- .../AssertionExtensionsSpecs.cs | 42 +- .../AssertionFailureSpecs.cs | 17 +- .../AssertionOptionsSpecs.cs | 2 +- ...CollectionAssertionSpecs.BeEquivalentTo.cs | 6 +- .../CollectionAssertionSpecs.Contain.cs | 21 +- ...ctionAssertionSpecs.ContainEquivalentOf.cs | 31 +- ...CollectionAssertionSpecs.ContainInOrder.cs | 5 +- .../CollectionAssertionSpecs.ContainSingle.cs | 34 +- .../CollectionAssertionSpecs.HaveCount.cs | 5 +- .../CollectionAssertionSpecs.HaveElementAt.cs | 14 + ...ctionAssertionSpecs.OnlyHaveUniqueItems.cs | 4 +- .../Collections/CollectionAssertionSpecs.cs | 19 +- ...tionAssertionOfStringSpecs.ContainMatch.cs | 16 +- ...icDictionaryAssertionSpecs.ContainValue.cs | 41 +- ...cDictionaryAssertionSpecs.ContainValues.cs | 19 +- .../FunctionExceptionAssertionSpecs.cs | 32 +- .../Execution/AssertionChainSpecs.Chaining.cs | 599 +++++++++ .../AssertionChainSpecs.MessageFormating.cs | 390 ++++++ .../AssertionScope.ChainingApiSpecs.cs | 588 --------- .../AssertionScope.ContextDataSpecs.cs | 68 - .../AssertionScope.MessageFormatingSpecs.cs | 522 -------- ...> AssertionScopeSpecs.ScopedFormatters.cs} | 0 .../Execution/AssertionScopeSpecs.cs | 64 +- ...rSpecs.cs => CallerIdentificationSpecs.cs} | 43 +- .../Execution/GivenSelectorSpecs.cs | 82 +- .../Formatting/FormatterSpecs.cs | 86 +- .../OccurrenceConstraintSpecs.cs | 4 +- .../Primitives/ObjectAssertionSpecs.cs | 3 +- .../ReferenceTypeAssertionsSpecs.cs | 8 +- .../SimpleTimeSpanAssertionSpecs.cs | 3 +- .../Primitives/StringComparisonSpecs.cs | 8 +- .../Specialized/AssemblyAssertionSpecs.cs | 18 +- .../ExecutionTimeAssertionsSpecs.cs | 3 +- .../TaskCompletionSourceAssertionSpecs.cs | 2 +- .../Specialized/TaskOfTAssertionSpecs.cs | 50 +- .../Types/MethodBaseAssertionSpecs.cs | 20 +- .../Types/PropertyInfoAssertionSpecs.cs | 68 +- .../PropertyInfoSelectorAssertionSpecs.cs | 23 +- ...ionSpecs.HaveExplicitConversionOperator.cs | 28 +- ...ionSpecs.HaveImplicitConversionOperator.cs | 19 + .../Types/TypeAssertionSpecs.HaveProperty.cs | 33 +- .../Xml/XDocumentAssertionSpecs.cs | 37 + docs/_pages/exceptions.md | 18 +- docs/_pages/executiontime.md | 4 +- docs/_pages/extensibility.md | 58 +- docs/_pages/introduction.md | 62 +- docs/_pages/releases.md | 7 + docs/_pages/upgradingtov7.md | 139 +++ qodana.yaml | 1 + 215 files changed, 6220 insertions(+), 5941 deletions(-) create mode 100644 Src/FluentAssertions/Equivalency/AssertionChainExtensions.cs create mode 100644 Src/FluentAssertions/Execution/AssertionChain.cs delete mode 100644 Src/FluentAssertions/Execution/ContinuedAssertionScope.cs delete mode 100644 Src/FluentAssertions/Execution/Execute.cs rename Src/FluentAssertions/Execution/{MessageBuilder.cs => FailureMessageFormatter.cs} (68%) delete mode 100644 Src/FluentAssertions/Execution/IAssertionScope.cs create mode 100644 Src/FluentAssertions/Formatting/MethodInfoFormatter.cs delete mode 100644 Src/FluentAssertions/Specialized/FunctionAssertionHelpers.cs create mode 100644 Tests/FluentAssertions.Specs/Execution/AssertionChainSpecs.Chaining.cs create mode 100644 Tests/FluentAssertions.Specs/Execution/AssertionChainSpecs.MessageFormating.cs delete mode 100644 Tests/FluentAssertions.Specs/Execution/AssertionScope.ChainingApiSpecs.cs delete mode 100644 Tests/FluentAssertions.Specs/Execution/AssertionScope.ContextDataSpecs.cs delete mode 100644 Tests/FluentAssertions.Specs/Execution/AssertionScope.MessageFormatingSpecs.cs rename Tests/FluentAssertions.Specs/Execution/{AssertionScope.ScopedFormatters.cs => AssertionScopeSpecs.ScopedFormatters.cs} (100%) rename Tests/FluentAssertions.Specs/Execution/{CallerIdentifierSpecs.cs => CallerIdentificationSpecs.cs} (93%) diff --git a/.editorconfig b/.editorconfig index f4874fecc7..0ea4ede3a9 100644 --- a/.editorconfig +++ b/.editorconfig @@ -199,6 +199,11 @@ dotnet_diagnostic.SA1116.severity = none dotnet_diagnostic.SA1117.severity = none # SA1200: Using directive should appear within a namespace declaration dotnet_diagnostic.SA1200.severity = none + +# Purpose: Use string. Empty for empty strings +# Reason: There's no performance difference. See https://medium.com/@dk.kravtsov/string-empty-vs-in-c-70c64971161f +dotnet_diagnostic.SA1122.severity = none + # SA1124: Do not use regions dotnet_diagnostic.SA1124.severity = none # SA1201: A property should not follow a method diff --git a/Build/Build.cs b/Build/Build.cs index 1859443ae5..a3f1c2695b 100644 --- a/Build/Build.cs +++ b/Build/Build.cs @@ -1,10 +1,6 @@ using System; -using System.Collections.Generic; using System.Linq; -using System.Net.Http; -using System.Runtime.InteropServices; using LibGit2Sharp; -using Microsoft.Build.Tasks; using Nuke.Common; using Nuke.Common.CI.GitHubActions; using Nuke.Common.Execution; diff --git a/Build/_build.csproj b/Build/_build.csproj index 43668e90a3..5d31f778d9 100644 --- a/Build/_build.csproj +++ b/Build/_build.csproj @@ -7,6 +7,7 @@ ..\ ..\ 8.1.0 + 1 OS_WINDOWS diff --git a/FluentAssertions.sln b/FluentAssertions.sln index 2e6167875b..43bf07b29f 100644 --- a/FluentAssertions.sln +++ b/FluentAssertions.sln @@ -7,7 +7,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .editorconfig = .editorconfig Tests\Default.testsettings = Tests\Default.testsettings Directory.Build.props = Directory.Build.props - Src\JetBrainsAnnotations.cs = Src\JetBrainsAnnotations.cs nuget.config = nuget.config README.md = README.md docs\_pages\releases.md = docs\_pages\releases.md diff --git a/FluentAssertions.sln.DotSettings b/FluentAssertions.sln.DotSettings index e58cf3d554..785299fbe3 100644 --- a/FluentAssertions.sln.DotSettings +++ b/FluentAssertions.sln.DotSettings @@ -1,4 +1,4 @@ - + True True False @@ -143,7 +143,7 @@ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - + OUTLINE SOLUTION_FOLDER True @@ -161,17 +161,17 @@ 4 False True - True - 1 - True - 0 + False + + False + aaa Arrange-Act-Assert [Fact] public void $END$() { // Arrange - + // Act diff --git a/Src/FluentAssertions/AndWhichConstraint.cs b/Src/FluentAssertions/AndWhichConstraint.cs index a30126ba2f..858423955f 100644 --- a/Src/FluentAssertions/AndWhichConstraint.cs +++ b/Src/FluentAssertions/AndWhichConstraint.cs @@ -2,45 +2,84 @@ using System.Collections.Generic; using System.Linq; using FluentAssertions.Common; +using FluentAssertions.Execution; using FluentAssertions.Formatting; namespace FluentAssertions; /// -/// Constraint which can be returned from an assertion which matches a condition and which will allow -/// further matches to be performed on the matched condition as well as the parent constraint. +/// Provides a property that can be used in chained assertions where the prior assertions returns a +/// single object that the assertion continues on. /// -/// The type of the original constraint that was matched -/// The type of the matched object which the parent constraint matched -public class AndWhichConstraint : AndConstraint +public class AndWhichConstraint : AndConstraint { - private readonly Lazy matchedConstraint; + private readonly AssertionChain assertionChain; + private readonly string pathPostfix; + private readonly Lazy getSubject; - public AndWhichConstraint(TParentConstraint parentConstraint, TMatchedElement matchedConstraint) - : base(parentConstraint) + /// + /// Creates an object that allows continuing an assertion executed through and + /// which resulted in a single . + /// + public AndWhichConstraint(TParent parent, TSubject subject) + : base(parent) + { + getSubject = new Lazy(() => subject); + } + + /// + /// Creates an object that allows continuing an assertion executed through and + /// which resulted in a single on an existing , but where + /// the previous caller identifier is post-fixed with . + /// + public AndWhichConstraint(TParent parent, TSubject subject, AssertionChain assertionChain, string pathPostfix = "") + : base(parent) { - this.matchedConstraint = - new Lazy(() => matchedConstraint); + getSubject = new Lazy(() => subject); + + this.assertionChain = assertionChain; + this.pathPostfix = pathPostfix; } - public AndWhichConstraint(TParentConstraint parentConstraint, IEnumerable matchedConstraint) - : base(parentConstraint) + /// + /// Creates an object that allows continuing an assertion executed through and + /// which resulted in a potential collection of objects through . + /// + /// + /// If contains more than one object, a clear exception is thrown. + /// + public AndWhichConstraint(TParent parent, IEnumerable subjects) + : base(parent) { - this.matchedConstraint = - new Lazy( - () => SingleOrDefault(matchedConstraint)); + getSubject = new Lazy(() => Single(subjects)); } - private static TMatchedElement SingleOrDefault( - IEnumerable matchedConstraint) + /// + /// Creates an object that allows continuing an assertion executed through and + /// which resulted in a potential collection of objects through on an + /// existing , but where + /// the previous caller identifier is post-fixed with . + /// + /// + /// If contains more than one object, a clear exception is thrown. + /// + public AndWhichConstraint(TParent parent, IEnumerable subjects, AssertionChain assertionChain, string pathPostfix) + : base(parent) { - TMatchedElement[] matchedElements = matchedConstraint.ToArray(); + getSubject = new Lazy(() => Single(subjects)); + + this.assertionChain = assertionChain; + this.pathPostfix = pathPostfix; + } + + private static TSubject Single(IEnumerable subjects) + { + TSubject[] matchedElements = subjects.ToArray(); if (matchedElements.Length > 1) { string foundObjects = string.Join(Environment.NewLine, - matchedElements.Select( - ele => "\t" + Formatter.ToString(ele))); + matchedElements.Select(ele => "\t" + Formatter.ToString(ele))); string message = "More than one object found. FluentAssertions cannot determine which object is meant." + $" Found objects:{Environment.NewLine}{foundObjects}"; @@ -54,13 +93,24 @@ private static TMatchedElement SingleOrDefault( /// /// Returns the single result of a prior assertion that is used to select a nested or collection item. /// - public TMatchedElement Which => matchedConstraint.Value; + /// + /// Just a convenience property that returns the same value as . + /// + public TSubject Subject => Which; /// /// Returns the single result of a prior assertion that is used to select a nested or collection item. /// - /// - /// Just a convenience property that returns the same value as . - /// - public TMatchedElement Subject => Which; + public TSubject Which + { + get + { + if (pathPostfix is not null and not "") + { + assertionChain.WithCallerPostfix(pathPostfix).ReuseOnce(); + } + + return getSubject.Value; + } + } } diff --git a/Src/FluentAssertions/AssertionExtensions.cs b/Src/FluentAssertions/AssertionExtensions.cs index 0c69d58c41..47630b6d65 100644 --- a/Src/FluentAssertions/AssertionExtensions.cs +++ b/Src/FluentAssertions/AssertionExtensions.cs @@ -11,6 +11,7 @@ using System.Xml.Linq; using FluentAssertions.Collections; using FluentAssertions.Common; +using FluentAssertions.Execution; using FluentAssertions.Numeric; using FluentAssertions.Primitives; using FluentAssertions.Specialized; @@ -149,7 +150,6 @@ public static ExecutionTime ExecutionTime(this Action action, StartTimer createT /// /// Provides methods for asserting the execution time of an async action. /// - /// An async action to measure the execution time of. /// /// Returns an object for asserting that the execution time matches certain conditions. /// @@ -167,7 +167,7 @@ public static ExecutionTime ExecutionTime(this Func action) [Pure] public static ExecutionTimeAssertions Should(this ExecutionTime executionTime) { - return new ExecutionTimeAssertions(executionTime); + return new ExecutionTimeAssertions(executionTime, AssertionChain.GetOrCreate()); } /// @@ -177,7 +177,7 @@ public static ExecutionTimeAssertions Should(this ExecutionTime executionTime) [Pure] public static AssemblyAssertions Should([NotNull] this Assembly assembly) { - return new AssemblyAssertions(assembly); + return new AssemblyAssertions(assembly, AssertionChain.GetOrCreate()); } /// @@ -187,7 +187,7 @@ public static AssemblyAssertions Should([NotNull] this Assembly assembly) [Pure] public static XDocumentAssertions Should([NotNull] this XDocument actualValue) { - return new XDocumentAssertions(actualValue); + return new XDocumentAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -197,7 +197,7 @@ public static XDocumentAssertions Should([NotNull] this XDocument actualValue) [Pure] public static XElementAssertions Should([NotNull] this XElement actualValue) { - return new XElementAssertions(actualValue); + return new XElementAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -207,7 +207,7 @@ public static XElementAssertions Should([NotNull] this XElement actualValue) [Pure] public static XAttributeAssertions Should([NotNull] this XAttribute actualValue) { - return new XAttributeAssertions(actualValue); + return new XAttributeAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -217,9 +217,11 @@ public static XAttributeAssertions Should([NotNull] this XAttribute actualValue) [Pure] public static StreamAssertions Should([NotNull] this Stream actualValue) { - return new StreamAssertions(actualValue); + return new StreamAssertions(actualValue, AssertionChain.GetOrCreate()); } +#if NET6_0_OR_GREATER || NETSTANDARD2_1 + /// /// Returns an object that can be used to assert the /// current . @@ -227,9 +229,11 @@ public static StreamAssertions Should([NotNull] this Stream actualValue) [Pure] public static BufferedStreamAssertions Should([NotNull] this BufferedStream actualValue) { - return new BufferedStreamAssertions(actualValue); + return new BufferedStreamAssertions(actualValue, AssertionChain.GetOrCreate()); } +#endif + /// /// Forces enumerating a collection. Should be used to assert that a method that uses the /// keyword throws a particular exception. @@ -284,7 +288,7 @@ private static void ForceEnumeration(T subject, Func enumerab [Pure] public static ObjectAssertions Should([NotNull] this object actualValue) { - return new ObjectAssertions(actualValue); + return new ObjectAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -294,7 +298,7 @@ public static ObjectAssertions Should([NotNull] this object actualValue) [Pure] public static BooleanAssertions Should(this bool actualValue) { - return new BooleanAssertions(actualValue); + return new BooleanAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -304,7 +308,7 @@ public static BooleanAssertions Should(this bool actualValue) [Pure] public static NullableBooleanAssertions Should(this bool? actualValue) { - return new NullableBooleanAssertions(actualValue); + return new NullableBooleanAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -314,7 +318,7 @@ public static NullableBooleanAssertions Should(this bool? actualValue) [Pure] public static HttpResponseMessageAssertions Should([NotNull] this HttpResponseMessage actualValue) { - return new HttpResponseMessageAssertions(actualValue); + return new HttpResponseMessageAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -324,7 +328,7 @@ public static HttpResponseMessageAssertions Should([NotNull] this HttpResponseMe [Pure] public static GuidAssertions Should(this Guid actualValue) { - return new GuidAssertions(actualValue); + return new GuidAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -334,7 +338,7 @@ public static GuidAssertions Should(this Guid actualValue) [Pure] public static NullableGuidAssertions Should(this Guid? actualValue) { - return new NullableGuidAssertions(actualValue); + return new NullableGuidAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -344,7 +348,7 @@ public static NullableGuidAssertions Should(this Guid? actualValue) [Pure] public static GenericCollectionAssertions Should([NotNull] this IEnumerable actualValue) { - return new GenericCollectionAssertions(actualValue); + return new GenericCollectionAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -354,7 +358,7 @@ public static GenericCollectionAssertions Should([NotNull] this IEnumerabl [Pure] public static StringCollectionAssertions Should([NotNull] this IEnumerable @this) { - return new StringCollectionAssertions(@this); + return new StringCollectionAssertions(@this, AssertionChain.GetOrCreate()); } /// @@ -365,7 +369,7 @@ public static StringCollectionAssertions Should([NotNull] this IEnumerable, TKey, TValue> Should( [NotNull] this IDictionary actualValue) { - return new GenericDictionaryAssertions, TKey, TValue>(actualValue); + return new GenericDictionaryAssertions, TKey, TValue>(actualValue, AssertionChain.GetOrCreate()); } /// @@ -376,7 +380,8 @@ public static GenericDictionaryAssertions, TKey, TValu public static GenericDictionaryAssertions>, TKey, TValue> Should( [NotNull] this IEnumerable> actualValue) { - return new GenericDictionaryAssertions>, TKey, TValue>(actualValue); + return new GenericDictionaryAssertions>, TKey, TValue>(actualValue, + AssertionChain.GetOrCreate()); } /// @@ -388,7 +393,7 @@ public static GenericDictionaryAssertions Should> { - return new GenericDictionaryAssertions(actualValue); + return new GenericDictionaryAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -398,7 +403,7 @@ public static GenericDictionaryAssertions Should @@ -408,7 +413,7 @@ public static DateTimeAssertions Should(this DateTime actualValue) [Pure] public static DateTimeOffsetAssertions Should(this DateTimeOffset actualValue) { - return new DateTimeOffsetAssertions(actualValue); + return new DateTimeOffsetAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -418,7 +423,7 @@ public static DateTimeOffsetAssertions Should(this DateTimeOffset actualValue) [Pure] public static NullableDateTimeAssertions Should(this DateTime? actualValue) { - return new NullableDateTimeAssertions(actualValue); + return new NullableDateTimeAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -428,7 +433,7 @@ public static NullableDateTimeAssertions Should(this DateTime? actualValue) [Pure] public static NullableDateTimeOffsetAssertions Should(this DateTimeOffset? actualValue) { - return new NullableDateTimeOffsetAssertions(actualValue); + return new NullableDateTimeOffsetAssertions(actualValue, AssertionChain.GetOrCreate()); } #if NET6_0_OR_GREATER @@ -439,7 +444,7 @@ public static NullableDateTimeOffsetAssertions Should(this DateTimeOffset? actua [Pure] public static DateOnlyAssertions Should(this DateOnly actualValue) { - return new DateOnlyAssertions(actualValue); + return new DateOnlyAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -449,7 +454,7 @@ public static DateOnlyAssertions Should(this DateOnly actualValue) [Pure] public static NullableDateOnlyAssertions Should(this DateOnly? actualValue) { - return new NullableDateOnlyAssertions(actualValue); + return new NullableDateOnlyAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -459,7 +464,7 @@ public static NullableDateOnlyAssertions Should(this DateOnly? actualValue) [Pure] public static TimeOnlyAssertions Should(this TimeOnly actualValue) { - return new TimeOnlyAssertions(actualValue); + return new TimeOnlyAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -469,7 +474,7 @@ public static TimeOnlyAssertions Should(this TimeOnly actualValue) [Pure] public static NullableTimeOnlyAssertions Should(this TimeOnly? actualValue) { - return new NullableTimeOnlyAssertions(actualValue); + return new NullableTimeOnlyAssertions(actualValue, AssertionChain.GetOrCreate()); } #endif @@ -481,7 +486,7 @@ public static NullableTimeOnlyAssertions Should(this TimeOnly? actualValue) [Pure] public static ComparableTypeAssertions Should([NotNull] this IComparable comparableValue) { - return new ComparableTypeAssertions(comparableValue); + return new ComparableTypeAssertions(comparableValue, AssertionChain.GetOrCreate()); } /// @@ -491,7 +496,7 @@ public static ComparableTypeAssertions Should([NotNull] this IComparable Should(this int actualValue) { - return new Int32Assertions(actualValue); + return new Int32Assertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -501,7 +506,7 @@ public static NumericAssertions Should(this int actualValue) [Pure] public static NullableNumericAssertions Should(this int? actualValue) { - return new NullableInt32Assertions(actualValue); + return new NullableInt32Assertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -511,7 +516,7 @@ public static NullableNumericAssertions Should(this int? actualValue) [Pure] public static NumericAssertions Should(this uint actualValue) { - return new UInt32Assertions(actualValue); + return new UInt32Assertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -521,7 +526,7 @@ public static NumericAssertions Should(this uint actualValue) [Pure] public static NullableNumericAssertions Should(this uint? actualValue) { - return new NullableUInt32Assertions(actualValue); + return new NullableUInt32Assertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -531,7 +536,7 @@ public static NullableNumericAssertions Should(this uint? actualValue) [Pure] public static NumericAssertions Should(this decimal actualValue) { - return new DecimalAssertions(actualValue); + return new DecimalAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -541,7 +546,7 @@ public static NumericAssertions Should(this decimal actualValue) [Pure] public static NullableNumericAssertions Should(this decimal? actualValue) { - return new NullableDecimalAssertions(actualValue); + return new NullableDecimalAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -551,7 +556,7 @@ public static NullableNumericAssertions Should(this decimal? actualValu [Pure] public static NumericAssertions Should(this byte actualValue) { - return new ByteAssertions(actualValue); + return new ByteAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -561,7 +566,7 @@ public static NumericAssertions Should(this byte actualValue) [Pure] public static NullableNumericAssertions Should(this byte? actualValue) { - return new NullableByteAssertions(actualValue); + return new NullableByteAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -571,7 +576,7 @@ public static NullableNumericAssertions Should(this byte? actualValue) [Pure] public static NumericAssertions Should(this sbyte actualValue) { - return new SByteAssertions(actualValue); + return new SByteAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -581,7 +586,7 @@ public static NumericAssertions Should(this sbyte actualValue) [Pure] public static NullableNumericAssertions Should(this sbyte? actualValue) { - return new NullableSByteAssertions(actualValue); + return new NullableSByteAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -591,7 +596,7 @@ public static NullableNumericAssertions Should(this sbyte? actualValue) [Pure] public static NumericAssertions Should(this short actualValue) { - return new Int16Assertions(actualValue); + return new Int16Assertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -601,7 +606,7 @@ public static NumericAssertions Should(this short actualValue) [Pure] public static NullableNumericAssertions Should(this short? actualValue) { - return new NullableInt16Assertions(actualValue); + return new NullableInt16Assertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -611,7 +616,7 @@ public static NullableNumericAssertions Should(this short? actualValue) [Pure] public static NumericAssertions Should(this ushort actualValue) { - return new UInt16Assertions(actualValue); + return new UInt16Assertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -621,7 +626,7 @@ public static NumericAssertions Should(this ushort actualValue) [Pure] public static NullableNumericAssertions Should(this ushort? actualValue) { - return new NullableUInt16Assertions(actualValue); + return new NullableUInt16Assertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -631,7 +636,7 @@ public static NullableNumericAssertions Should(this ushort? actualValue) [Pure] public static NumericAssertions Should(this long actualValue) { - return new Int64Assertions(actualValue); + return new Int64Assertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -641,7 +646,7 @@ public static NumericAssertions Should(this long actualValue) [Pure] public static NullableNumericAssertions Should(this long? actualValue) { - return new NullableInt64Assertions(actualValue); + return new NullableInt64Assertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -651,7 +656,7 @@ public static NullableNumericAssertions Should(this long? actualValue) [Pure] public static NumericAssertions Should(this ulong actualValue) { - return new UInt64Assertions(actualValue); + return new UInt64Assertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -661,7 +666,7 @@ public static NumericAssertions Should(this ulong actualValue) [Pure] public static NullableNumericAssertions Should(this ulong? actualValue) { - return new NullableUInt64Assertions(actualValue); + return new NullableUInt64Assertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -671,7 +676,7 @@ public static NullableNumericAssertions Should(this ulong? actualValue) [Pure] public static NumericAssertions Should(this float actualValue) { - return new SingleAssertions(actualValue); + return new SingleAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -681,7 +686,7 @@ public static NumericAssertions Should(this float actualValue) [Pure] public static NullableNumericAssertions Should(this float? actualValue) { - return new NullableSingleAssertions(actualValue); + return new NullableSingleAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -691,7 +696,7 @@ public static NullableNumericAssertions Should(this float? actualValue) [Pure] public static NumericAssertions Should(this double actualValue) { - return new DoubleAssertions(actualValue); + return new DoubleAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -701,7 +706,7 @@ public static NumericAssertions Should(this double actualValue) [Pure] public static NullableNumericAssertions Should(this double? actualValue) { - return new NullableDoubleAssertions(actualValue); + return new NullableDoubleAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -711,7 +716,7 @@ public static NullableNumericAssertions Should(this double? actualValue) [Pure] public static StringAssertions Should([NotNull] this string actualValue) { - return new StringAssertions(actualValue); + return new StringAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -721,7 +726,7 @@ public static StringAssertions Should([NotNull] this string actualValue) [Pure] public static SimpleTimeSpanAssertions Should(this TimeSpan actualValue) { - return new SimpleTimeSpanAssertions(actualValue); + return new SimpleTimeSpanAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -731,7 +736,7 @@ public static SimpleTimeSpanAssertions Should(this TimeSpan actualValue) [Pure] public static NullableSimpleTimeSpanAssertions Should(this TimeSpan? actualValue) { - return new NullableSimpleTimeSpanAssertions(actualValue); + return new NullableSimpleTimeSpanAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -741,7 +746,7 @@ public static NullableSimpleTimeSpanAssertions Should(this TimeSpan? actualValue [Pure] public static TypeAssertions Should([NotNull] this Type subject) { - return new TypeAssertions(subject); + return new TypeAssertions(subject, AssertionChain.GetOrCreate()); } /// @@ -754,7 +759,7 @@ public static TypeSelectorAssertions Should(this TypeSelector typeSelector) { Guard.ThrowIfArgumentIsNull(typeSelector); - return new TypeSelectorAssertions(typeSelector.ToArray()); + return new TypeSelectorAssertions(AssertionChain.GetOrCreate(), typeSelector.ToArray()); } /// @@ -765,7 +770,7 @@ public static TypeSelectorAssertions Should(this TypeSelector typeSelector) [Pure] public static ConstructorInfoAssertions Should([NotNull] this ConstructorInfo constructorInfo) { - return new ConstructorInfoAssertions(constructorInfo); + return new ConstructorInfoAssertions(constructorInfo, AssertionChain.GetOrCreate()); } /// @@ -775,7 +780,7 @@ public static ConstructorInfoAssertions Should([NotNull] this ConstructorInfo co [Pure] public static MethodInfoAssertions Should([NotNull] this MethodInfo methodInfo) { - return new MethodInfoAssertions(methodInfo); + return new MethodInfoAssertions(methodInfo, AssertionChain.GetOrCreate()); } /// @@ -789,7 +794,7 @@ public static MethodInfoSelectorAssertions Should(this MethodInfoSelector method { Guard.ThrowIfArgumentIsNull(methodSelector); - return new MethodInfoSelectorAssertions(methodSelector.ToArray()); + return new MethodInfoSelectorAssertions(AssertionChain.GetOrCreate(), methodSelector.ToArray()); } /// @@ -800,7 +805,7 @@ public static MethodInfoSelectorAssertions Should(this MethodInfoSelector method [Pure] public static PropertyInfoAssertions Should([NotNull] this PropertyInfo propertyInfo) { - return new PropertyInfoAssertions(propertyInfo); + return new PropertyInfoAssertions(propertyInfo, AssertionChain.GetOrCreate()); } /// @@ -814,7 +819,7 @@ public static PropertyInfoSelectorAssertions Should(this PropertyInfoSelector pr { Guard.ThrowIfArgumentIsNull(propertyInfoSelector); - return new PropertyInfoSelectorAssertions(propertyInfoSelector.ToArray()); + return new PropertyInfoSelectorAssertions(AssertionChain.GetOrCreate(), propertyInfoSelector.ToArray()); } /// @@ -824,7 +829,7 @@ public static PropertyInfoSelectorAssertions Should(this PropertyInfoSelector pr [Pure] public static ActionAssertions Should([NotNull] this Action action) { - return new ActionAssertions(action, Extractor); + return new ActionAssertions(action, Extractor, AssertionChain.GetOrCreate()); } /// @@ -834,7 +839,7 @@ public static ActionAssertions Should([NotNull] this Action action) [Pure] public static NonGenericAsyncFunctionAssertions Should([NotNull] this Func action) { - return new NonGenericAsyncFunctionAssertions(action, Extractor); + return new NonGenericAsyncFunctionAssertions(action, Extractor, AssertionChain.GetOrCreate()); } /// @@ -844,7 +849,7 @@ public static NonGenericAsyncFunctionAssertions Should([NotNull] this Func [Pure] public static GenericAsyncFunctionAssertions Should([NotNull] this Func> action) { - return new GenericAsyncFunctionAssertions(action, Extractor); + return new GenericAsyncFunctionAssertions(action, Extractor, AssertionChain.GetOrCreate()); } /// @@ -854,7 +859,7 @@ public static GenericAsyncFunctionAssertions Should([NotNull] this Func Should([NotNull] this Func func) { - return new FunctionAssertions(func, Extractor); + return new FunctionAssertions(func, Extractor, AssertionChain.GetOrCreate()); } /// @@ -864,7 +869,7 @@ public static FunctionAssertions Should([NotNull] this Func func) [Pure] public static TaskCompletionSourceAssertions Should(this TaskCompletionSource tcs) { - return new TaskCompletionSourceAssertions(tcs); + return new TaskCompletionSourceAssertions(tcs, AssertionChain.GetOrCreate()); } #if !NETSTANDARD2_0 @@ -906,7 +911,7 @@ public static IMonitor Monitor(this T eventSource, Action : GenericCollectionAssertions, T, GenericCollectionAssertions> { - public GenericCollectionAssertions(IEnumerable actualValue) - : base(actualValue) + public GenericCollectionAssertions(IEnumerable actualValue, AssertionChain assertionChain) + : base(actualValue, assertionChain) { } } @@ -28,22 +28,26 @@ public class GenericCollectionAssertions : GenericCollectionAssertions> where TCollection : IEnumerable { - public GenericCollectionAssertions(TCollection actualValue) - : base(actualValue) + public GenericCollectionAssertions(TCollection actualValue, AssertionChain assertionChain) + : base(actualValue, assertionChain) { } } #pragma warning disable CS0659, S1206 // Ignore not overriding Object.GetHashCode() #pragma warning disable CA1065 // Ignore throwing NotSupportedException from Equals + [DebuggerNonUserCode] public class GenericCollectionAssertions : ReferenceTypeAssertions where TCollection : IEnumerable where TAssertions : GenericCollectionAssertions { - public GenericCollectionAssertions(TCollection actualValue) - : base(actualValue) + private readonly AssertionChain assertionChain; + + public GenericCollectionAssertions(TCollection actualValue, AssertionChain assertionChain) + : base(actualValue, assertionChain) { + this.assertionChain = assertionChain; } /// @@ -63,9 +67,10 @@ public GenericCollectionAssertions(TCollection actualValue) /// Zero or more objects to format using the placeholders in . /// public AndWhichConstraint> AllBeAssignableTo( - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected type to be {0}{reason}, but found {context:the collection} is .", @@ -73,20 +78,18 @@ public AndWhichConstraint> AllBeAssignabl IEnumerable matches = []; - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected type to be {0}{reason}, ", typeof(TExpectation).FullName) - .ForCondition(Subject!.All(x => x is not null)) - .FailWith("but found a null element.") - .Then - .ForCondition(Subject.All(x => typeof(TExpectation).IsAssignableFrom(GetType(x)))) - .FailWith("but found {0}.", () => $"[{string.Join(", ", Subject.Select(x => GetType(x).FullName))}]") - .Then - .ClearExpectation(); + .WithExpectation("Expected type to be {0}{reason}, ", typeof(TExpectation).FullName, chain => chain + .ForCondition(Subject!.All(x => x is not null)) + .FailWith("but found a null element.") + .Then + .ForCondition(Subject.All(x => typeof(TExpectation).IsAssignableFrom(GetType(x)))) + .FailWith("but found {0}.", () => $"[{string.Join(", ", Subject.Select(x => GetType(x).FullName))}]")); - matches = Subject.OfType(); + matches = Subject!.OfType(); } return new AndWhichConstraint>((TAssertions)this, matches); @@ -109,20 +112,18 @@ public AndConstraint AllBeAssignableTo(Type expectedType, { Guard.ThrowIfArgumentIsNull(expectedType); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected type to be {0}{reason}, ", expectedType.FullName) - .Given(() => Subject) - .ForCondition(subject => subject is not null) - .FailWith("but found {context:collection} is .") - .Then - .ForCondition(subject => subject.All(x => x is not null)) - .FailWith("but found a null element.") - .Then - .ForCondition(subject => subject.All(x => expectedType.IsAssignableFrom(GetType(x)))) - .FailWith("but found {0}.", subject => $"[{string.Join(", ", subject.Select(x => GetType(x).FullName))}]") - .Then - .ClearExpectation(); + .WithExpectation("Expected type to be {0}{reason}, ", expectedType.FullName, chain => chain + .Given(() => Subject) + .ForCondition(subject => subject is not null) + .FailWith("but found {context:collection} is .") + .Then + .ForCondition(subject => subject.All(x => x is not null)) + .FailWith("but found a null element.") + .Then + .ForCondition(subject => subject.All(x => expectedType.IsAssignableFrom(GetType(x)))) + .FailWith("but found {0}.", subject => $"[{string.Join(", ", subject.Select(x => GetType(x).FullName))}]")); return new AndConstraint((TAssertions)this); } @@ -180,7 +181,8 @@ public AndConstraint AllBeEquivalentTo(TExpectation e /// is . public AndConstraint AllBeEquivalentTo(TExpectation expectation, Func, EquivalencyOptions> config, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(config); @@ -210,9 +212,10 @@ public AndConstraint AllBeEquivalentTo(TExpectation e /// Zero or more objects to format using the placeholders in . /// public AndWhichConstraint> AllBeOfType( - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected type to be {0}{reason}, but found {context:collection} is .", @@ -220,20 +223,18 @@ public AndWhichConstraint> AllBeOfType matches = []; - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected type to be {0}{reason}, ", typeof(TExpectation).FullName) - .ForCondition(Subject!.All(x => x is not null)) - .FailWith("but found a null element.") - .Then - .ForCondition(Subject.All(x => typeof(TExpectation) == GetType(x))) - .FailWith("but found {0}.", () => $"[{string.Join(", ", Subject.Select(x => GetType(x).FullName))}]") - .Then - .ClearExpectation(); + .WithExpectation("Expected type to be {0}{reason}, ", typeof(TExpectation).FullName, chain => chain + .ForCondition(Subject!.All(x => x is not null)) + .FailWith("but found a null element.") + .Then + .ForCondition(Subject.All(x => typeof(TExpectation) == GetType(x))) + .FailWith("but found {0}.", () => $"[{string.Join(", ", Subject.Select(x => GetType(x).FullName))}]")); - matches = Subject.OfType(); + matches = Subject!.OfType(); } return new AndWhichConstraint>((TAssertions)this, matches); @@ -251,25 +252,23 @@ public AndWhichConstraint> AllBeOfType. /// /// is . - public AndConstraint AllBeOfType(Type expectedType, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint AllBeOfType(Type expectedType, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(expectedType); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected type to be {0}{reason}, ", expectedType.FullName) - .Given(() => Subject) - .ForCondition(subject => subject is not null) - .FailWith("but found {context:collection} is .") - .Then - .ForCondition(subject => subject.All(x => x is not null)) - .FailWith("but found a null element.") - .Then - .ForCondition(subject => subject.All(x => expectedType == GetType(x))) - .FailWith("but found {0}.", subject => $"[{string.Join(", ", subject.Select(x => GetType(x).FullName))}]") - .Then - .ClearExpectation(); + .WithExpectation("Expected type to be {0}{reason}, ", expectedType.FullName, chain => chain + .Given(() => Subject) + .ForCondition(subject => subject is not null) + .FailWith("but found {context:collection} is .") + .Then + .ForCondition(subject => subject.All(x => x is not null)) + .FailWith("but found a null element.") + .Then + .ForCondition(subject => subject.All(x => expectedType == GetType(x))) + .FailWith("but found {0}.", subject => $"[{string.Join(", ", subject.Select(x => GetType(x).FullName))}]")); return new AndConstraint((TAssertions)this); } @@ -287,17 +286,16 @@ public AndConstraint AllBeOfType(Type expectedType, public AndConstraint BeEmpty([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { var singleItemArray = Subject?.Take(1).ToArray(); - Execute.Assertion + + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected {context:collection} to be empty{reason}, ") - .Given(() => singleItemArray) - .ForCondition(subject => subject is not null) - .FailWith("but found .") - .Then - .ForCondition(subject => subject.Length == 0) - .FailWith("but found at least one item {0}.", singleItemArray) - .Then - .ClearExpectation(); + .WithExpectation("Expected {context:collection} to be empty{reason}, ", chain => chain + .Given(() => singleItemArray) + .ForCondition(subject => subject is not null) + .FailWith("but found .") + .Then + .ForCondition(subject => subject.Length == 0) + .FailWith("but found at least one item {0}.", singleItemArray)); return new AndConstraint((TAssertions)this); } @@ -355,7 +353,8 @@ public AndConstraint BeEquivalentTo(IEnumerable is . public AndConstraint BeEquivalentTo(IEnumerable expectation, Func, EquivalencyOptions> config, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(config); @@ -363,7 +362,8 @@ public AndConstraint BeEquivalentTo(IEnumerable()).AsCollection(); var context = - new EquivalencyValidationContext(Node.From>(() => AssertionScope.Current.CallerIdentity), + new EquivalencyValidationContext( + Node.From>(() => CallerIdentifier.DetermineCallerIdentity()), options) { Reason = new Reason(because, becauseArgs), @@ -425,8 +425,7 @@ public AndConstraint> BeInAscendingOrder /// is . public AndConstraint> BeInAscendingOrder( - IComparer comparer, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + IComparer comparer, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(comparer, nameof(comparer), "Cannot assert collection ordering without specifying a comparer."); @@ -457,7 +456,8 @@ public AndConstraint> BeInAscendingOrder( /// is . public AndConstraint> BeInAscendingOrder( Expression> propertyExpression, IComparer comparer, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(comparer, nameof(comparer), "Cannot assert collection ordering without specifying a comparer."); @@ -479,8 +479,7 @@ public AndConstraint> BeInAscendingOrder /// Empty and single element collections are considered to be ordered both in ascending and descending order at the same time. /// - public AndConstraint> BeInAscendingOrder( - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint> BeInAscendingOrder(string because = "", params object[] becauseArgs) { return BeInAscendingOrder(GetComparer(), because, becauseArgs); } @@ -503,7 +502,8 @@ public AndConstraint> BeInAscendingOrder( /// Empty and single element collections are considered to be ordered both in ascending and descending order at the same time. /// public AndConstraint> BeInAscendingOrder(Func comparison, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { return BeInOrder(Comparer.Create((x, y) => comparison(x, y)), SortOrder.Ascending, because, becauseArgs); } @@ -526,8 +526,8 @@ public AndConstraint> BeInAscendingOrder(Func public AndConstraint> BeInDescendingOrder( - Expression> propertyExpression, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + Expression> propertyExpression, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { return BeInDescendingOrder(propertyExpression, GetComparer(), because, becauseArgs); } @@ -551,8 +551,7 @@ public AndConstraint> BeInDescendingOrder /// is . public AndConstraint> BeInDescendingOrder( - IComparer comparer, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + IComparer comparer, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(comparer, nameof(comparer), "Cannot assert collection ordering without specifying a comparer."); @@ -583,7 +582,8 @@ public AndConstraint> BeInDescendingOrder( /// is . public AndConstraint> BeInDescendingOrder( Expression> propertyExpression, IComparer comparer, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(comparer, nameof(comparer), "Cannot assert collection ordering without specifying a comparer."); @@ -605,8 +605,7 @@ public AndConstraint> BeInDescendingOrder /// Empty and single element collections are considered to be ordered both in ascending and descending order at the same time. /// - public AndConstraint> BeInDescendingOrder( - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint> BeInDescendingOrder(string because = "", params object[] becauseArgs) { return BeInDescendingOrder(GetComparer(), because, becauseArgs); } @@ -629,7 +628,8 @@ public AndConstraint> BeInDescendingOrder( /// Empty and single element collections are considered to be ordered both in ascending and descending order at the same time. /// public AndConstraint> BeInDescendingOrder(Func comparison, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { return BeInOrder(Comparer.Create((x, y) => comparison(x, y)), SortOrder.Descending, because, becauseArgs); } @@ -644,12 +644,12 @@ public AndConstraint> BeInDescendingOrder(Func /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeNullOrEmpty([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint BeNullOrEmpty(string because = "", params object[] becauseArgs) { var singleItemArray = Subject?.Take(1).ToArray(); var nullOrEmpty = singleItemArray is null || singleItemArray.Length == 0; - Execute.Assertion.ForCondition(nullOrEmpty) + assertionChain.ForCondition(nullOrEmpty) .BecauseOf(because, becauseArgs) .FailWith( "Expected {context:collection} to be null or empty{reason}, but found at least one item {0}.", @@ -671,23 +671,22 @@ public AndConstraint BeNullOrEmpty([StringSyntax("CompositeFormat") /// /// is . public AndConstraint BeSubsetOf(IEnumerable expectedSuperset, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(expectedSuperset, nameof(expectedSuperset), "Cannot verify a subset against a collection."); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected {context:collection} to be a subset of {0}{reason}, ", expectedSuperset) - .Given(() => Subject) - .ForCondition(subject => subject is not null) - .FailWith("but found .") - .Then - .Given(subject => subject.Except(expectedSuperset)) - .ForCondition(excessItems => !excessItems.Any()) - .FailWith("but items {0} are not part of the superset.", excessItems => excessItems) - .Then - .ClearExpectation(); + .WithExpectation("Expected {context:collection} to be a subset of {0}{reason}, ", expectedSuperset, chain => chain + .Given(() => Subject) + .ForCondition(subject => subject is not null) + .FailWith("but found .") + .Then + .Given(subject => subject.Except(expectedSuperset)) + .ForCondition(excessItems => !excessItems.Any()) + .FailWith("but items {0} are not part of the superset.", excessItems => excessItems)); return new AndConstraint((TAssertions)this); } @@ -703,21 +702,21 @@ public AndConstraint BeSubsetOf(IEnumerable expectedSuperset, /// /// Zero or more objects to format using the placeholders in . /// - public AndWhichConstraint Contain(T expected, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndWhichConstraint Contain(T expected, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected {context:collection} to contain {0}{reason}, but found .", expected); IEnumerable matches = []; - if (success) + if (assertionChain.Succeeded) { ICollection collection = Subject.ConvertOrCastToCollection(); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(collection.Contains(expected)) .FailWith("Expected {context:collection} {0} to contain {1}{reason}.", collection, expected); @@ -746,25 +745,37 @@ public AndWhichConstraint Contain(Expression> pred { Guard.ThrowIfArgumentIsNull(predicate); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected {context:collection} to contain {0}{reason}, but found .", predicate.Body); IEnumerable matches = []; - if (success) + int? firstMatchingIndex = null; + if (assertionChain.Succeeded) { Func func = predicate.Compile(); - Execute.Assertion - .ForCondition(Subject!.Any(func)) + foreach (var (item, index) in Subject!.Select((item, index) => (item, index))) + { + if (func(item)) + { + firstMatchingIndex = index; + break; + } + } + + assertionChain + .ForCondition(firstMatchingIndex.HasValue) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:collection} {0} to have an item matching {1}{reason}.", Subject, predicate.Body); matches = Subject.Where(func); } + assertionChain.WithCallerPostfix($"[{firstMatchingIndex}]").ReuseOnce(); + return new AndWhichConstraint((TAssertions)this, matches); } @@ -782,20 +793,20 @@ public AndWhichConstraint Contain(Expression> pred /// /// is . /// is empty. - public AndConstraint Contain(IEnumerable expected, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint Contain(IEnumerable expected, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(expected, nameof(expected), "Cannot verify containment against a collection"); ICollection expectedObjects = expected.ConvertOrCastToCollection(); Guard.ThrowIfArgumentIsEmpty(expectedObjects, nameof(expected), "Cannot verify containment against an empty collection"); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected {context:collection} to contain {0}{reason}, but found .", expectedObjects); - if (success) + if (assertionChain.Succeeded) { IEnumerable missingItems = expectedObjects.Except(Subject!); @@ -803,14 +814,14 @@ public AndConstraint Contain(IEnumerable expected, { if (expectedObjects.Count > 1) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Expected {context:collection} {0} to contain {1}{reason}, but could not find {2}.", Subject, expectedObjects, missingItems); } else { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Expected {context:collection} {0} to contain {1}{reason}.", Subject, expectedObjects.Single()); @@ -844,7 +855,8 @@ public AndConstraint Contain(IEnumerable expected, /// Zero or more objects to format using the placeholders in . /// public AndWhichConstraint ContainEquivalentOf(TExpectation expectation, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { return ContainEquivalentOf(expectation, config => config, because, becauseArgs); } @@ -879,27 +891,28 @@ public AndWhichConstraint ContainEquivalentOf(TExp /// /// is . public AndWhichConstraint ContainEquivalentOf(TExpectation expectation, - Func, EquivalencyOptions> config, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + Func, + EquivalencyOptions> config, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(config); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected {context:collection} to contain equivalent of {0}{reason}, but found .", expectation); - if (success) + if (assertionChain.Succeeded) { EquivalencyOptions options = config(AssertionOptions.CloneDefaults()); using var scope = new AssertionScope(); - scope.AddReportable("configuration", () => options.ToString()); + assertionChain.AddReportable("configuration", () => options.ToString()); - foreach (T actualItem in Subject!) + foreach ((T actualItem, int index) in Subject!.Select((item, index) => (item, index))) { var context = - new EquivalencyValidationContext(Node.From(() => AssertionScope.Current.CallerIdentity), + new EquivalencyValidationContext(Node.From(() => CurrentAssertionChain.CallerIdentifier), options) { Reason = new Reason(because, becauseArgs), @@ -919,11 +932,11 @@ public AndWhichConstraint ContainEquivalentOf(TExp if (failures.Length == 0) { - return new AndWhichConstraint((TAssertions)this, actualItem); + return new AndWhichConstraint((TAssertions)this, actualItem, assertionChain, $"[{index}]"); } } - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Expected {context:collection} {0} to contain equivalent of {1}{reason}.", Subject, expectation); } @@ -957,16 +970,17 @@ public AndConstraint ContainInOrder(params T[] expected) /// /// is . public AndConstraint ContainInOrder(IEnumerable expected, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(expected, nameof(expected), "Cannot verify ordered containment against a collection."); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected {context:collection} to contain {0} in order{reason}, but found .", expected); - if (success) + if (assertionChain.Succeeded) { IList expectedItems = expected.ConvertOrCastToList(); IList actualItems = Subject.ConvertOrCastToList(); @@ -980,7 +994,7 @@ public AndConstraint ContainInOrder(IEnumerable expected, if (subjectIndex == -1) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith( "Expected {context:collection} {0} to contain items {1} in order{reason}" + @@ -1019,16 +1033,17 @@ public AndConstraint ContainInConsecutiveOrder(params T[] expected) /// /// is . public AndConstraint ContainInConsecutiveOrder(IEnumerable expected, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(expected, nameof(expected), "Cannot verify ordered containment against a collection."); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected {context:collection} to contain {0} in order{reason}, but found .", expected); - if (success) + if (assertionChain.Succeeded) { IList expectedItems = expected.ConvertOrCastToList(); @@ -1060,7 +1075,7 @@ public AndConstraint ContainInConsecutiveOrder(IEnumerable expec } } - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith( "Expected {context:collection} {0} to contain items {1} in order{reason}" + @@ -1081,21 +1096,18 @@ public AndConstraint ContainInConsecutiveOrder(IEnumerable expec /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint ContainItemsAssignableTo( - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint ContainItemsAssignableTo(string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .WithExpectation("Expected {context:collection} to contain at least one element assignable to type {0}{reason}, ", - typeof(TExpectation).FullName) - .ForCondition(Subject is not null) - .FailWith("but found .") - .Then - .Given(() => Subject.ConvertOrCastToCollection()) - .ForCondition(subject => subject.Any(x => typeof(TExpectation).IsAssignableFrom(GetType(x)))) - .FailWith("but found {0}.", subject => subject.Select(x => GetType(x))) - .Then - .ClearExpectation(); + typeof(TExpectation).FullName, chain => chain + .ForCondition(Subject is not null) + .FailWith("but found .") + .Then + .Given(() => Subject.ConvertOrCastToCollection()) + .ForCondition(subject => subject.Any(x => typeof(TExpectation).IsAssignableFrom(GetType(x)))) + .FailWith("but found {0}.", subject => subject.Select(x => GetType(x)))); return new AndConstraint((TAssertions)this); } @@ -1110,8 +1122,8 @@ public AndConstraint ContainItemsAssignableTo( /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotContainItemsAssignableTo( - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) => + public AndConstraint + NotContainItemsAssignableTo(string because = "", params object[] becauseArgs) => NotContainItemsAssignableTo(typeof(TExpectation), because, becauseArgs); /// @@ -1132,18 +1144,16 @@ public AndConstraint NotContainItemsAssignableTo(Type type, { Guard.ThrowIfArgumentIsNull(type); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .WithExpectation("Expected {context:collection} to not contain any elements assignable to type {0}{reason}, ", - type.FullName) - .ForCondition(Subject is not null) - .FailWith("but found .") - .Then - .Given(() => Subject.ConvertOrCastToCollection()) - .ForCondition(subject => subject.All(x => !type.IsAssignableFrom(GetType(x)))) - .FailWith("but found {0}.", subject => subject.Select(x => GetType(x))) - .Then - .ClearExpectation(); + type.FullName, chain => chain + .ForCondition(Subject is not null) + .FailWith("but found .") + .Then + .Given(() => Subject.ConvertOrCastToCollection()) + .ForCondition(subject => subject.All(x => !type.IsAssignableFrom(GetType(x)))) + .FailWith("but found {0}.", subject => subject.Select(x => GetType(x)))); return new AndConstraint((TAssertions)this); } @@ -1158,23 +1168,23 @@ public AndConstraint NotContainItemsAssignableTo(Type type, /// /// Zero or more objects to format using the placeholders in . /// - public AndWhichConstraint ContainSingle([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndWhichConstraint ContainSingle(string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected {context:collection} to contain a single item{reason}, but found ."); T match = default; - if (success) + if (assertionChain.Succeeded) { ICollection actualItems = Subject.ConvertOrCastToCollection(); switch (actualItems.Count) { case 0: // Fail, Collection is empty - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Expected {context:collection} to contain a single item{reason}, but the collection is empty."); @@ -1183,7 +1193,7 @@ public AndWhichConstraint ContainSingle([StringSyntax("Composite match = actualItems.Single(); break; default: // Fail, Collection contains more than a single item - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Expected {context:collection} to contain a single item{reason}, but found {0}.", Subject); @@ -1191,7 +1201,7 @@ public AndWhichConstraint ContainSingle([StringSyntax("Composite } } - return new AndWhichConstraint((TAssertions)this, match); + return new AndWhichConstraint((TAssertions)this, match, assertionChain, "[0]"); } /// @@ -1207,25 +1217,25 @@ public AndWhichConstraint ContainSingle([StringSyntax("Composite /// /// is . public AndWhichConstraint ContainSingle(Expression> predicate, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(predicate); const string expectationPrefix = "Expected {context:collection} to contain a single item matching {0}{reason}, "; - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith(expectationPrefix + "but found .", predicate); T[] matches = []; - if (success) + if (assertionChain.Succeeded) { ICollection actualItems = Subject.ConvertOrCastToCollection(); - Execute.Assertion + assertionChain .ForCondition(actualItems.Count > 0) .BecauseOf(because, becauseArgs) .FailWith(expectationPrefix + "but the collection is empty.", predicate); @@ -1235,13 +1245,13 @@ public AndWhichConstraint ContainSingle(Expression if (count == 0) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith(expectationPrefix + "but no such item was found.", predicate); } else if (count > 1) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith( expectationPrefix + "but " + count.ToString(CultureInfo.InvariantCulture) + " such items were found.", @@ -1253,7 +1263,7 @@ public AndWhichConstraint ContainSingle(Expression } } - return new AndWhichConstraint((TAssertions)this, matches); + return new AndWhichConstraint((TAssertions)this, matches, assertionChain, "[0]"); } /// @@ -1270,8 +1280,8 @@ public AndWhichConstraint ContainSingle(Expression /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint EndWith(IEnumerable expectation, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint EndWith(IEnumerable expectation, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { return EndWith(expectation, (a, b) => EqualityComparer.Default.Equals(a, b), because, becauseArgs); } @@ -1296,7 +1306,8 @@ public AndConstraint EndWith(IEnumerable expectation, /// is . public AndConstraint EndWith( IEnumerable expectation, Func equalityComparison, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(expectation, nameof(expectation), "Cannot compare collection with ."); @@ -1318,8 +1329,8 @@ public AndConstraint EndWith( /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint EndWith(T element, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint EndWith(T element, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { return EndWith([element], ObjectExtensions.GetComparer(), because, becauseArgs); } @@ -1355,7 +1366,8 @@ public AndConstraint Equal(params T[] elements) /// public AndConstraint Equal( IEnumerable expectation, Func equalityComparison, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { AssertSubjectEquality(expectation, equalityComparison, because, becauseArgs); @@ -1374,8 +1386,8 @@ public AndConstraint Equal( /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint Equal(IEnumerable expected, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint Equal(IEnumerable expected, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { AssertSubjectEquality(expected, ObjectExtensions.GetComparer(), because, becauseArgs); @@ -1393,19 +1405,19 @@ public AndConstraint Equal(IEnumerable expected, /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveCount(int expected, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint HaveCount(int expected, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected {context:collection} to contain {0} item(s){reason}, but found .", expected); - if (success) + if (assertionChain.Succeeded) { int actualCount = Subject!.Count(); - Execute.Assertion + assertionChain .ForCondition(actualCount == expected) .BecauseOf(because, becauseArgs) .FailWith( @@ -1429,17 +1441,18 @@ public AndConstraint HaveCount(int expected, /// /// is . public AndConstraint HaveCount(Expression> countPredicate, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(countPredicate, nameof(countPredicate), "Cannot compare collection count against a predicate."); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected {context:collection} to contain {0} items{reason}, but found .", countPredicate.Body); - if (success) + if (assertionChain.Succeeded) { Func compiledPredicate = countPredicate.Compile(); @@ -1447,7 +1460,7 @@ public AndConstraint HaveCount(Expression> countPre if (!compiledPredicate(actualCount)) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Expected {context:collection} to have a count {0}{reason}, but count is {1}: {2}.", countPredicate.Body, actualCount, Subject); @@ -1469,20 +1482,19 @@ public AndConstraint HaveCount(Expression> countPre /// Zero or more objects to format using the placeholders in . /// public AndConstraint HaveCountGreaterThanOrEqualTo(int expected, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected {context:collection} to contain at least {0} item(s){reason}, ", expected) - .Given(() => Subject) - .ForCondition(subject => subject is not null) - .FailWith("but found .") - .Then - .Given(subject => subject.Count()) - .ForCondition(actualCount => actualCount >= expected) - .FailWith("but found {0}: {1}.", actualCount => actualCount, _ => Subject) - .Then - .ClearExpectation(); + .WithExpectation("Expected {context:collection} to contain at least {0} item(s){reason}, ", expected, chain => chain + .Given(() => Subject) + .ForCondition(subject => subject is not null) + .FailWith("but found .") + .Then + .Given(subject => subject.Count()) + .ForCondition(actualCount => actualCount >= expected) + .FailWith("but found {0}: {1}.", actualCount => actualCount, _ => Subject)); return new AndConstraint((TAssertions)this); } @@ -1498,21 +1510,19 @@ public AndConstraint HaveCountGreaterThanOrEqualTo(int expected, /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveCountGreaterThan(int expected, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint HaveCountGreaterThan(int expected, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected {context:collection} to contain more than {0} item(s){reason}, ", expected) - .Given(() => Subject) - .ForCondition(subject => subject is not null) - .FailWith("but found .") - .Then - .Given(subject => subject.Count()) - .ForCondition(actualCount => actualCount > expected) - .FailWith("but found {0}: {1}.", actualCount => actualCount, _ => Subject) - .Then - .ClearExpectation(); + .WithExpectation("Expected {context:collection} to contain more than {0} item(s){reason}, ", expected, chain => chain + .Given(() => Subject) + .ForCondition(subject => subject is not null) + .FailWith("but found .") + .Then + .Given(subject => subject.Count()) + .ForCondition(actualCount => actualCount > expected) + .FailWith("but found {0}: {1}.", actualCount => actualCount, _ => Subject)); return new AndConstraint((TAssertions)this); } @@ -1531,18 +1541,16 @@ public AndConstraint HaveCountGreaterThan(int expected, public AndConstraint HaveCountLessThanOrEqualTo(int expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected {context:collection} to contain at most {0} item(s){reason}, ", expected) - .Given(() => Subject) - .ForCondition(subject => subject is not null) - .FailWith("but found .") - .Then - .Given(subject => subject.Count()) - .ForCondition(actualCount => actualCount <= expected) - .FailWith("but found {0}: {1}.", actualCount => actualCount, _ => Subject) - .Then - .ClearExpectation(); + .WithExpectation("Expected {context:collection} to contain at most {0} item(s){reason}, ", expected, chain => chain + .Given(() => Subject) + .ForCondition(subject => subject is not null) + .FailWith("but found .") + .Then + .Given(subject => subject.Count()) + .ForCondition(actualCount => actualCount <= expected) + .FailWith("but found {0}: {1}.", actualCount => actualCount, _ => Subject)); return new AndConstraint((TAssertions)this); } @@ -1558,21 +1566,19 @@ public AndConstraint HaveCountLessThanOrEqualTo(int expected, /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveCountLessThan(int expected, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint HaveCountLessThan(int expected, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected {context:collection} to contain fewer than {0} item(s){reason}, ", expected) - .Given(() => Subject) - .ForCondition(subject => subject is not null) - .FailWith("but found .") - .Then - .Given(subject => subject.Count()) - .ForCondition(actualCount => actualCount < expected) - .FailWith("but found {0}: {1}.", actualCount => actualCount, _ => Subject) - .Then - .ClearExpectation(); + .WithExpectation("Expected {context:collection} to contain fewer than {0} item(s){reason}, ", expected, chain => chain + .Given(() => Subject) + .ForCondition(subject => subject is not null) + .FailWith("but found .") + .Then + .Given(subject => subject.Count()) + .ForCondition(actualCount => actualCount < expected) + .FailWith("but found {0}: {1}.", actualCount => actualCount, _ => Subject)); return new AndConstraint((TAssertions)this); } @@ -1591,35 +1597,36 @@ public AndConstraint HaveCountLessThan(int expected, /// Zero or more objects to format using the placeholders in . /// public AndWhichConstraint HaveElementAt(int index, T element, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected {context:collection} to have element at index {0}{reason}, but found .", index); T actual = default; - if (success) + if (assertionChain.Succeeded) { if (index < Subject!.Count()) { actual = Subject.ElementAt(index); - Execute.Assertion + assertionChain .ForCondition(ObjectExtensions.GetComparer()(actual, element)) .BecauseOf(because, becauseArgs) .FailWith("Expected {0} at index {1}{reason}, but found {2}.", element, index, actual); } else { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Expected {0} at index {1}{reason}, but found no element.", element, index); } } - return new AndWhichConstraint((TAssertions)this, actual); + return new AndWhichConstraint((TAssertions)this, actual, assertionChain, $"[{index}]"); } /// @@ -1635,26 +1642,26 @@ public AndWhichConstraint HaveElementAt(int index, T element, /// Zero or more objects to format using the placeholders in . /// public AndConstraint HaveElementPreceding(T successor, T expectation, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected {context:collection} to have {0} precede {1}{reason}, ", expectation, successor) - .Given(() => Subject) - .ForCondition(subject => subject is not null) - .FailWith("but the collection is .") - .Then - .ForCondition(subject => subject.Any()) - .FailWith("but the collection is empty.") - .Then - .ForCondition(subject => HasPredecessor(successor, subject)) - .FailWith("but found nothing.") - .Then - .Given(subject => PredecessorOf(successor, subject)) - .ForCondition(predecessor => ObjectExtensions.GetComparer()(predecessor, expectation)) - .FailWith("but found {0}.", predecessor => predecessor) - .Then - .ClearExpectation(); + .WithExpectation("Expected {context:collection} to have {0} precede {1}{reason}, ", expectation, successor, chain => + chain + .Given(() => Subject) + .ForCondition(subject => subject is not null) + .FailWith("but the collection is .") + .Then + .ForCondition(subject => subject.Any()) + .FailWith("but the collection is empty.") + .Then + .ForCondition(subject => HasPredecessor(successor, subject)) + .FailWith("but found nothing.") + .Then + .Given(subject => PredecessorOf(successor, subject)) + .ForCondition(predecessor => ObjectExtensions.GetComparer()(predecessor, expectation)) + .FailWith("but found {0}.", predecessor => predecessor)); return new AndConstraint((TAssertions)this); } @@ -1672,26 +1679,26 @@ public AndConstraint HaveElementPreceding(T successor, T expectatio /// Zero or more objects to format using the placeholders in . /// public AndConstraint HaveElementSucceeding(T predecessor, T expectation, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected {context:collection} to have {0} succeed {1}{reason}, ", expectation, predecessor) - .Given(() => Subject) - .ForCondition(subject => subject is not null) - .FailWith("but the collection is .") - .Then - .ForCondition(subject => subject.Any()) - .FailWith("but the collection is empty.") - .Then - .ForCondition(subject => HasSuccessor(predecessor, subject)) - .FailWith("but found nothing.") - .Then - .Given(subject => SuccessorOf(predecessor, subject)) - .ForCondition(successor => ObjectExtensions.GetComparer()(successor, expectation)) - .FailWith("but found {0}.", successor => successor) - .Then - .ClearExpectation(); + .WithExpectation("Expected {context:collection} to have {0} succeed {1}{reason}, ", expectation, predecessor, + chain => chain + .Given(() => Subject) + .ForCondition(subject => subject is not null) + .FailWith("but the collection is .") + .Then + .ForCondition(subject => subject.Any()) + .FailWith("but the collection is empty.") + .Then + .ForCondition(subject => HasSuccessor(predecessor, subject)) + .FailWith("but found nothing.") + .Then + .Given(subject => SuccessorOf(predecessor, subject)) + .ForCondition(successor => ObjectExtensions.GetComparer()(successor, expectation)) + .FailWith("but found {0}.", successor => successor)); return new AndConstraint((TAssertions)this); } @@ -1709,22 +1716,21 @@ public AndConstraint HaveElementSucceeding(T predecessor, T expecta /// /// is . public AndConstraint HaveSameCount(IEnumerable otherCollection, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(otherCollection, nameof(otherCollection), "Cannot verify count against a collection."); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected {context:collection} to have ") - .Given(() => Subject) - .ForCondition(subject => subject is not null) - .FailWith("the same count as {0}{reason}, but found .", otherCollection) - .Then - .Given(subject => (actual: subject.Count(), expected: otherCollection.Count())) - .ForCondition(count => count.actual == count.expected) - .FailWith("{0} item(s){reason}, but found {1}.", count => count.expected, count => count.actual) - .Then - .ClearExpectation(); + .WithExpectation("Expected {context:collection} to have ", chain => chain + .Given(() => Subject) + .ForCondition(subject => subject is not null) + .FailWith("the same count as {0}{reason}, but found .", otherCollection) + .Then + .Given(subject => (actual: subject.Count(), expected: otherCollection.Count())) + .ForCondition(count => count.actual == count.expected) + .FailWith("{0} item(s){reason}, but found {1}.", count => count.expected, count => count.actual)); return new AndConstraint((TAssertions)this); } @@ -1742,21 +1748,22 @@ public AndConstraint HaveSameCount(IEnumerable /// is . public AndConstraint IntersectWith(IEnumerable otherCollection, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(otherCollection, nameof(otherCollection), "Cannot verify intersection against a collection."); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected {context:collection} to intersect with {0}{reason}, but found .", otherCollection); - if (success) + if (assertionChain.Succeeded) { IEnumerable sharedItems = Subject!.Intersect(otherCollection); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(sharedItems.Any()) .FailWith( @@ -1777,19 +1784,17 @@ public AndConstraint IntersectWith(IEnumerable otherCollection, /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeEmpty([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint NotBeEmpty(string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected {context:collection} not to be empty{reason}") - .Given(() => Subject) - .ForCondition(subject => subject is not null) - .FailWith(", but found .") - .Then - .ForCondition(subject => subject.Any()) - .FailWith(".") - .Then - .ClearExpectation(); + .WithExpectation("Expected {context:collection} not to be empty{reason}", chain => chain + .Given(() => Subject) + .ForCondition(subject => subject is not null) + .FailWith(", but found .") + .Then + .ForCondition(subject => subject.Any()) + .FailWith(".")); return new AndConstraint((TAssertions)this); } @@ -1808,20 +1813,21 @@ public AndConstraint NotBeEmpty([StringSyntax("CompositeFormat")] s /// /// is . public AndConstraint NotBeEquivalentTo(IEnumerable unexpected, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(unexpected, nameof(unexpected), "Cannot verify inequivalence against a collection."); if (Subject is null) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Expected {context:collection} not to be equivalent{reason}, but found ."); } if (ReferenceEquals(Subject, unexpected)) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith( "Expected {context:collection} {0} not to be equivalent with collection {1}{reason}, but they both reference the same object.", @@ -1852,13 +1858,13 @@ public AndConstraint NotBeEquivalentTo(IEnumerable public AndConstraint NotBeEquivalentTo(IEnumerable unexpected, Func, EquivalencyOptions> config, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(unexpected, nameof(unexpected), "Cannot verify inequivalence against a collection."); if (Subject is null) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Expected {context:collection} not to be equivalent{reason}, but found ."); } @@ -1872,7 +1878,7 @@ public AndConstraint NotBeEquivalentTo(IEnumerable 0) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:collection} {0} not to be equivalent to collection {1}{reason}.", Subject, @@ -1899,8 +1905,8 @@ public AndConstraint NotBeEquivalentTo(IEnumerable public AndConstraint NotBeInAscendingOrder( - Expression> propertyExpression, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + Expression> propertyExpression, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { return NotBeInAscendingOrder(propertyExpression, GetComparer(), because, becauseArgs); } @@ -1924,8 +1930,7 @@ public AndConstraint NotBeInAscendingOrder( /// /// is . public AndConstraint NotBeInAscendingOrder( - IComparer comparer, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + IComparer comparer, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(comparer, nameof(comparer), "Cannot assert collection ordering without specifying a comparer."); @@ -1956,7 +1961,8 @@ public AndConstraint NotBeInAscendingOrder( /// is . public AndConstraint NotBeInAscendingOrder( Expression> propertyExpression, IComparer comparer, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(comparer, nameof(comparer), "Cannot assert collection ordering without specifying a comparer."); @@ -1978,7 +1984,7 @@ public AndConstraint NotBeInAscendingOrder( /// /// Empty and single element collections are considered to be ordered both in ascending and descending order at the same time. /// - public AndConstraint NotBeInAscendingOrder([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint NotBeInAscendingOrder(string because = "", params object[] becauseArgs) { return NotBeInAscendingOrder(GetComparer(), because, becauseArgs); } @@ -2000,7 +2006,8 @@ public AndConstraint NotBeInAscendingOrder([StringSyntax("Composite /// Empty and single element collections are considered to be ordered both in ascending and descending order at the same time. /// public AndConstraint NotBeInAscendingOrder(Func comparison, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { return NotBeInOrder(Comparer.Create((x, y) => comparison(x, y)), SortOrder.Ascending, because, becauseArgs); } @@ -2023,8 +2030,8 @@ public AndConstraint NotBeInAscendingOrder(Func comparis /// Empty and single element collections are considered to be ordered both in ascending and descending order at the same time. /// public AndConstraint NotBeInDescendingOrder( - Expression> propertyExpression, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + Expression> propertyExpression, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { return NotBeInDescendingOrder(propertyExpression, GetComparer(), because, becauseArgs); } @@ -2079,7 +2086,8 @@ public AndConstraint NotBeInDescendingOrder( /// is . public AndConstraint NotBeInDescendingOrder( Expression> propertyExpression, IComparer comparer, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(comparer, nameof(comparer), "Cannot assert collection ordering without specifying a comparer."); @@ -2101,7 +2109,7 @@ public AndConstraint NotBeInDescendingOrder( /// /// Empty and single element collections are considered to be ordered both in ascending and descending order at the same time. /// - public AndConstraint NotBeInDescendingOrder([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint NotBeInDescendingOrder(string because = "", params object[] becauseArgs) { return NotBeInDescendingOrder(GetComparer(), because, becauseArgs); } @@ -2123,7 +2131,8 @@ public AndConstraint NotBeInDescendingOrder([StringSyntax("Composit /// Empty and single element collections are considered to be ordered both in ascending and descending order at the same time. /// public AndConstraint NotBeInDescendingOrder(Func comparison, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { return NotBeInOrder(Comparer.Create((x, y) => comparison(x, y)), SortOrder.Descending, because, becauseArgs); } @@ -2138,7 +2147,7 @@ public AndConstraint NotBeInDescendingOrder(Func compari /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeNullOrEmpty([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint NotBeNullOrEmpty(string because = "", params object[] becauseArgs) { return NotBeNull(because, becauseArgs) .And.NotBeEmpty(because, becauseArgs); @@ -2156,18 +2165,19 @@ public AndConstraint NotBeNullOrEmpty([StringSyntax("CompositeForma /// Zero or more objects to format using the placeholders in . /// public AndConstraint NotBeSubsetOf(IEnumerable unexpectedSuperset, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Cannot assert a collection against a subset."); - if (success) + if (assertionChain.Succeeded) { if (ReferenceEquals(Subject, unexpectedSuperset)) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith( "Did not expect {context:collection} {0} to be a subset of {1}{reason}, but they both reference the same object.", @@ -2179,7 +2189,7 @@ public AndConstraint NotBeSubsetOf(IEnumerable unexpectedSuperse if (actualItems.Intersect(unexpectedSuperset).Count() == actualItems.Count) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Did not expect {context:collection} {0} to be a subset of {1}{reason}.", actualItems, unexpectedSuperset); @@ -2200,31 +2210,27 @@ public AndConstraint NotBeSubsetOf(IEnumerable unexpectedSuperse /// /// Zero or more objects to format using the placeholders in . /// - public AndWhichConstraint NotContain(T unexpected, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint NotContain(T unexpected, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected {context:collection} to not contain {0}{reason}, but found .", unexpected); - IEnumerable matched = []; - - if (success) + if (assertionChain.Succeeded) { ICollection collection = Subject.ConvertOrCastToCollection(); if (collection.Contains(unexpected)) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Expected {context:collection} {0} to not contain {1}{reason}.", collection, unexpected); } - - matched = collection.Where(item => !EqualityComparer.Default.Equals(item, unexpected)); } - return new AndWhichConstraint((TAssertions)this, matched); + return new AndConstraint((TAssertions)this); } /// @@ -2240,21 +2246,22 @@ public AndWhichConstraint NotContain(T unexpected, /// /// is . public AndConstraint NotContain(Expression> predicate, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(predicate); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected {context:collection} not to contain {0}{reason}, but found .", predicate.Body); - if (success) + if (assertionChain.Succeeded) { Func compiledPredicate = predicate.Compile(); IEnumerable unexpectedItems = Subject!.Where(item => compiledPredicate(item)); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(!unexpectedItems.Any()) .FailWith("Expected {context:collection} {0} to not have any items matching {1}{reason}, but found {2}.", @@ -2278,8 +2285,8 @@ public AndConstraint NotContain(Expression> predicate /// /// is . /// is empty. - public AndConstraint NotContain(IEnumerable unexpected, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint NotContain(IEnumerable unexpected, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(unexpected, nameof(unexpected), "Cannot verify non-containment against a collection"); @@ -2288,12 +2295,12 @@ public AndConstraint NotContain(IEnumerable unexpected, Guard.ThrowIfArgumentIsEmpty(unexpectedObjects, nameof(unexpected), "Cannot verify non-containment against an empty collection"); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected {context:collection} to not contain {0}{reason}, but found .", unexpected); - if (success) + if (assertionChain.Succeeded) { IEnumerable foundItems = unexpectedObjects.Intersect(Subject!); @@ -2301,14 +2308,14 @@ public AndConstraint NotContain(IEnumerable unexpected, { if (unexpectedObjects.Count > 1) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Expected {context:collection} {0} to not contain {1}{reason}, but found {2}.", Subject, unexpected, foundItems); } else { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Expected {context:collection} {0} to not contain {1}{reason}.", Subject, unexpectedObjects.First()); @@ -2342,7 +2349,8 @@ public AndConstraint NotContain(IEnumerable unexpected, /// Zero or more objects to format using the placeholders in . /// public AndConstraint NotContainEquivalentOf(TExpectation unexpected, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { return NotContainEquivalentOf(unexpected, config => config, because, becauseArgs); } @@ -2378,18 +2386,19 @@ public AndConstraint NotContainEquivalentOf(TExpectat /// is . [SuppressMessage("Design", "MA0051:Method is too long", Justification = "Needs refactoring")] public AndConstraint NotContainEquivalentOf(TExpectation unexpected, - Func, EquivalencyOptions> config, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + Func, + EquivalencyOptions> config, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(config); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected {context:collection} not to contain equivalent of {0}{reason}, but collection is .", unexpected); - if (success) + if (assertionChain.Succeeded) { EquivalencyOptions options = config(AssertionOptions.CloneDefaults()); @@ -2402,7 +2411,7 @@ public AndConstraint NotContainEquivalentOf(TExpectat foreach (T actualItem in Subject!) { var context = - new EquivalencyValidationContext(Node.From(() => AssertionScope.Current.CallerIdentity), + new EquivalencyValidationContext(Node.From(() => CurrentAssertionChain.CallerIdentifier), options) { Reason = new Reason(because, becauseArgs), @@ -2433,22 +2442,21 @@ public AndConstraint NotContainEquivalentOf(TExpectat { using (new AssertionScope()) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) + .WithReportable("configuration", () => options.ToString()) .WithExpectation("Expected {context:collection} {0} not to contain equivalent of {1}{reason}, ", Subject, - unexpected) - .AddReportable("configuration", () => options.ToString()); - - if (foundIndices.Count == 1) - { - Execute.Assertion - .FailWith("but found one at index {0}.", foundIndices[0]); - } - else - { - Execute.Assertion - .FailWith("but found several at indices {0}.", foundIndices); - } + unexpected, chain => + { + if (foundIndices.Count == 1) + { + chain.FailWith("but found one at index {0}.", foundIndices[0]); + } + else + { + chain.FailWith("but found several at indices {0}.", foundIndices); + } + }); } } } @@ -2485,14 +2493,15 @@ public AndConstraint NotContainInOrder(params T[] unexpected) /// /// is . public AndConstraint NotContainInOrder(IEnumerable unexpected, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(unexpected, nameof(unexpected), "Cannot verify absence of ordered containment against a collection."); if (Subject is null) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Cannot verify absence of ordered containment in a collection."); @@ -2516,7 +2525,7 @@ public AndConstraint NotContainInOrder(IEnumerable unexpected, } } - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith( "Expected {context:collection} {0} to not contain items {1} in order{reason}, " + @@ -2556,14 +2565,15 @@ public AndConstraint NotContainInConsecutiveOrder(params T[] unexpe /// /// is . public AndConstraint NotContainInConsecutiveOrder(IEnumerable unexpected, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(unexpected, nameof(unexpected), "Cannot verify absence of ordered containment against a collection."); if (Subject is null) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Cannot verify absence of ordered containment in a collection."); @@ -2593,7 +2603,7 @@ public AndConstraint NotContainInConsecutiveOrder(IEnumerable un if (consecutiveItems == unexpectedItems.Count) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith( "Expected {context:collection} {0} to not contain items {1} in consecutive order{reason}, " + @@ -2622,17 +2632,18 @@ public AndConstraint NotContainInConsecutiveOrder(IEnumerable un /// /// is . public AndConstraint NotContainNulls(Expression> predicate, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) where TKey : class { Guard.ThrowIfArgumentIsNull(predicate); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected {context:collection} not to contain s{reason}, but collection is ."); - if (success) + if (assertionChain.Succeeded) { Func compiledPredicate = predicate.Compile(); @@ -2640,7 +2651,7 @@ public AndConstraint NotContainNulls(Expression .Where(e => compiledPredicate(e) is null) .ToArray(); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(values.Length == 0) .FailWith("Expected {context:collection} not to contain s on {0}{reason}, but found {1}.", @@ -2660,14 +2671,14 @@ public AndConstraint NotContainNulls(Expression /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotContainNulls([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint NotContainNulls(string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected {context:collection} not to contain s{reason}, but collection is ."); - if (success) + if (assertionChain.Succeeded) { int[] indices = Subject! .Select((item, index) => (Item: item, Index: index)) @@ -2679,7 +2690,7 @@ public AndConstraint NotContainNulls([StringSyntax("CompositeFormat { if (indices.Length > 1) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith( "Expected {context:collection} not to contain s{reason}, but found several at indices {0}.", @@ -2687,7 +2698,7 @@ public AndConstraint NotContainNulls([StringSyntax("CompositeFormat } else { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Expected {context:collection} not to contain s{reason}, but found one at index {0}.", indices[0]); @@ -2711,26 +2722,25 @@ public AndConstraint NotContainNulls([StringSyntax("CompositeFormat /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint NotEqual(IEnumerable unexpected, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint NotEqual(IEnumerable unexpected, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(unexpected, nameof(unexpected), "Cannot compare collection with ."); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected collections not to be equal{reason}, ") - .Given(() => Subject) - .ForCondition(subject => subject is not null) - .FailWith("but found .") - .Then - .ForCondition(subject => !ReferenceEquals(subject, unexpected)) - .FailWith("but they both reference the same object.") - .Then - .ClearExpectation() + .WithExpectation("Expected collections not to be equal{reason}, ", chain => chain + .Given(() => Subject) + .ForCondition(subject => subject is not null) + .FailWith("but found .") + .Then + .ForCondition(subject => !ReferenceEquals(subject, unexpected)) + .FailWith("but they both reference the same object.")) .Then - .Given(subject => subject.ConvertOrCastToCollection()) + .Given(() => Subject.ConvertOrCastToCollection()) .ForCondition(actualItems => !actualItems.SequenceEqual(unexpected)) - .FailWith("Did not expect collections {0} and {1} to be equal{reason}.", _ => unexpected, actualItems => actualItems); + .FailWith("Did not expect collections {0} and {1} to be equal{reason}.", _ => unexpected, + actualItems => actualItems); return new AndConstraint((TAssertions)this); } @@ -2746,21 +2756,19 @@ public AndConstraint NotEqual(IEnumerable unexpected, /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveCount(int unexpected, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint NotHaveCount(int unexpected, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected {context:collection} to not contain {0} item(s){reason}, ", unexpected) - .Given(() => Subject) - .ForCondition(subject => subject is not null) - .FailWith("but found .") - .Then - .Given(subject => subject.Count()) - .ForCondition(actualCount => actualCount != unexpected) - .FailWith("but found {0}.", actualCount => actualCount) - .Then - .ClearExpectation(); + .WithExpectation("Expected {context:collection} to not contain {0} item(s){reason}, ", unexpected, chain => chain + .Given(() => Subject) + .ForCondition(subject => subject is not null) + .FailWith("but found .") + .Then + .Given(subject => subject.Count()) + .ForCondition(actualCount => actualCount != unexpected) + .FailWith("but found {0}.", actualCount => actualCount)); return new AndConstraint((TAssertions)this); } @@ -2778,11 +2786,12 @@ public AndConstraint NotHaveCount(int unexpected, /// /// is . public AndConstraint NotHaveSameCount(IEnumerable otherCollection, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + string because = "", + params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(otherCollection, nameof(otherCollection), "Cannot verify count against a collection."); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .Given(() => Subject) .ForCondition(subject => subject is not null) @@ -2817,12 +2826,13 @@ public AndConstraint NotHaveSameCount(IEnumerable /// is . public AndConstraint NotIntersectWith(IEnumerable otherCollection, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(otherCollection, nameof(otherCollection), "Cannot verify intersection against a collection."); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .Given(() => Subject) .ForCondition(subject => subject is not null) @@ -2857,25 +2867,23 @@ public AndConstraint NotIntersectWith(IEnumerable otherCollectio /// /// is . public AndConstraint OnlyContain( - Expression> predicate, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + Expression> predicate, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(predicate); Func compiledPredicate = predicate.Compile(); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected {context:collection} to contain only items matching {0}{reason}, ", predicate.Body) - .Given(() => Subject) - .ForCondition(subject => subject is not null) - .FailWith("but the collection is .") - .Then - .Given(subject => subject.ConvertOrCastToCollection().Where(item => !compiledPredicate(item))) - .ForCondition(mismatchingItems => !mismatchingItems.Any()) - .FailWith("but {0} do(es) not match.", mismatchingItems => mismatchingItems) - .Then - .ClearExpectation(); + .WithExpectation("Expected {context:collection} to contain only items matching {0}{reason}, ", predicate.Body, + chain => chain + .Given(() => Subject) + .ForCondition(subject => subject is not null) + .FailWith("but the collection is .") + .Then + .Given(subject => subject.ConvertOrCastToCollection().Where(item => !compiledPredicate(item))) + .ForCondition(mismatchingItems => !mismatchingItems.Any()) + .FailWith("but {0} do(es) not match.", mismatchingItems => mismatchingItems)); return new AndConstraint((TAssertions)this); } @@ -2893,16 +2901,17 @@ public AndConstraint OnlyContain( /// /// is . public AndConstraint OnlyHaveUniqueItems(Expression> predicate, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(predicate); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected {context:collection} to only have unique items{reason}, but found ."); - if (success) + if (assertionChain.Succeeded) { Func compiledPredicate = predicate.Compile(); @@ -2915,7 +2924,7 @@ public AndConstraint OnlyHaveUniqueItems(Expression 1) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith( "Expected {context:collection} to only have unique items on {0}{reason}, but items {1} are not unique.", @@ -2924,7 +2933,7 @@ public AndConstraint OnlyHaveUniqueItems(Expression OnlyHaveUniqueItems(Expression /// Zero or more objects to format using the placeholders in . /// - public AndConstraint OnlyHaveUniqueItems([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint OnlyHaveUniqueItems(string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected {context:collection} to only have unique items{reason}, but found ."); - if (success) + if (assertionChain.Succeeded) { - IEnumerable groupWithMultipleItems = Subject! + T[] groupWithMultipleItems = Subject! .GroupBy(o => o) .Where(g => g.Count() > 1) - .Select(g => g.Key); + .Select(g => g.Key) + .ToArray(); - if (groupWithMultipleItems.Any()) + if (groupWithMultipleItems.Length > 0) { - if (groupWithMultipleItems.Count() > 1) + if (groupWithMultipleItems.Length > 1) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith( "Expected {context:collection} to only have unique items{reason}, but items {0} are not unique.", @@ -2973,10 +2983,10 @@ public AndConstraint OnlyHaveUniqueItems([StringSyntax("CompositeFo } else { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Expected {context:collection} to only have unique items{reason}, but item {0} is not unique.", - groupWithMultipleItems.First()); + groupWithMultipleItems[0]); } } } @@ -2999,21 +3009,20 @@ public AndConstraint OnlyHaveUniqueItems([StringSyntax("CompositeFo /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint AllSatisfy(Action expected, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint AllSatisfy(Action expected, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(expected, nameof(expected), "Cannot verify against a inspector"); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected {context:collection} to contain only items satisfying the inspector{reason}, ") - .Given(() => Subject) - .ForCondition(subject => subject is not null) - .FailWith("but collection is .") - .Then - .ClearExpectation(); + .WithExpectation("Expected {context:collection} to contain only items satisfying the inspector{reason}, ", + chain => chain + .Given(() => Subject) + .ForCondition(subject => subject is not null) + .FailWith("but collection is .")); - if (success) + if (assertionChain.Succeeded) { string[] failuresFromInspectors; @@ -3028,12 +3037,11 @@ public AndConstraint AllSatisfy(Action expected, string failureMessage = Environment.NewLine + string.Join(Environment.NewLine, failuresFromInspectors.Select(x => x.IndentLines())); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected {context:collection} to contain only items satisfying the inspector{reason}:") - .FailWithPreFormatted(failureMessage) - .Then - .ClearExpectation(); + .WithExpectation("Expected {context:collection} to contain only items satisfying the inspector{reason}:", + chain => chain + .FailWithPreFormatted(failureMessage)); } return new AndConstraint((TAssertions)this); @@ -3075,7 +3083,8 @@ public AndConstraint SatisfyRespectively(params Action[] element /// is . /// is empty. public AndConstraint SatisfyRespectively(IEnumerable> expected, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(expected, nameof(expected), "Cannot verify against a collection of inspectors"); @@ -3084,25 +3093,23 @@ public AndConstraint SatisfyRespectively(IEnumerable> exp Guard.ThrowIfArgumentIsEmpty(elementInspectors, nameof(expected), "Cannot verify against an empty collection of inspectors"); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected {context:collection} to satisfy all inspectors{reason}, ") - .Given(() => Subject) - .ForCondition(subject => subject is not null) - .FailWith("but collection is .") - .Then - .ForCondition(subject => subject.Any()) - .FailWith("but collection is empty.") - .Then - .ClearExpectation() + .WithExpectation("Expected {context:collection} to satisfy all inspectors{reason}, ", chain => chain + .Given(() => Subject) + .ForCondition(subject => subject is not null) + .FailWith("but collection is .") + .Then + .ForCondition(subject => subject.Any()) + .FailWith("but collection is empty.")) .Then - .Given(subject => (elements: subject.Count(), inspectors: elementInspectors.Count)) + .Given(() => (elements: Subject.Count(), inspectors: elementInspectors.Count)) .ForCondition(count => count.elements == count.inspectors) .FailWith( "Expected {context:collection} to contain exactly {0} items{reason}, but it contains {1} items", count => count.inspectors, count => count.elements); - if (success) + if (assertionChain.Succeeded) { string[] failuresFromInspectors; @@ -3116,13 +3123,12 @@ public AndConstraint SatisfyRespectively(IEnumerable> exp string failureMessage = Environment.NewLine + string.Join(Environment.NewLine, failuresFromInspectors.Select(x => x.IndentLines())); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .WithExpectation( - "Expected {context:collection} to satisfy all inspectors{reason}, but some inspectors are not satisfied:") - .FailWithPreFormatted(failureMessage) - .Then - .ClearExpectation(); + "Expected {context:collection} to satisfy all inspectors{reason}, but some inspectors are not satisfied:", + chain => chain + .FailWithPreFormatted(failureMessage)); } } @@ -3165,7 +3171,8 @@ public AndConstraint Satisfy(params Expression>[] pre /// is . /// is empty. public AndConstraint Satisfy(IEnumerable>> predicates, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(predicates, nameof(predicates), "Cannot verify against a collection of predicates"); @@ -3174,7 +3181,7 @@ public AndConstraint Satisfy(IEnumerable>> Guard.ThrowIfArgumentIsEmpty(predicatesList, nameof(predicates), "Cannot verify against an empty collection of predicates"); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .Given(() => Subject) .ForCondition(subject => subject is not null) @@ -3183,7 +3190,7 @@ public AndConstraint Satisfy(IEnumerable>> .ForCondition(subject => subject.Any()) .FailWith("Expected {context:collection} to satisfy all predicates{reason}, but collection is empty."); - if (success) + if (assertionChain.Succeeded) { MaximumMatchingSolution maximumMatchingSolution = new MaximumMatchingProblem(predicatesList, Subject).Solve(); @@ -3215,10 +3222,10 @@ public AndConstraint Satisfy(IEnumerable>> message += doubleNewLine + string.Join(doubleNewLine, elementDescriptions); } - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected {context:collection} to satisfy all predicates{reason}, but:") - .FailWithPreFormatted(message); + .WithExpectation("Expected {context:collection} to satisfy all predicates{reason}, but:", chain => chain + .FailWithPreFormatted(message)); } } @@ -3240,8 +3247,8 @@ public AndConstraint Satisfy(IEnumerable>> /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint StartWith(IEnumerable expectation, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint StartWith(IEnumerable expectation, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { return StartWith(expectation, (a, b) => EqualityComparer.Default.Equals(a, b), because, becauseArgs); } @@ -3266,7 +3273,8 @@ public AndConstraint StartWith(IEnumerable expectation, /// is . public AndConstraint StartWith( IEnumerable expectation, Func equalityComparison, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(expectation, nameof(expectation), "Cannot compare collection with ."); @@ -3288,8 +3296,8 @@ public AndConstraint StartWith( /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint StartWith(T element, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint StartWith(T element, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { return StartWith([element], ObjectExtensions.GetComparer(), because, becauseArgs); } @@ -3298,7 +3306,7 @@ internal AndConstraint> BeOrderedBy( Expression> propertyExpression, IComparer comparer, SortOrder direction, - [StringSyntax("CompositeFormat")] string because, + string because, object[] becauseArgs) { if (IsValidProperty(propertyExpression, because, becauseArgs)) @@ -3311,18 +3319,18 @@ internal AndConstraint> BeOrderedBy( direction, unordered); - Execute.Assertion + assertionChain .ForCondition(unordered.SequenceEqual(expectation)) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:collection} {0} to be ordered {1}{reason} and result in {2}.", () => Subject, () => GetExpressionOrderString(propertyExpression), () => expectation); return new AndConstraint>( - new SubsequentOrderingAssertions(Subject, expectation)); + new SubsequentOrderingAssertions(Subject, expectation, assertionChain)); } return new AndConstraint>( - new SubsequentOrderingAssertions(Subject, Enumerable.Empty().OrderBy(x => x))); + new SubsequentOrderingAssertions(Subject, Enumerable.Empty().OrderBy(x => x), assertionChain)); } internal virtual IOrderedEnumerable GetOrderedEnumerable( @@ -3352,50 +3360,49 @@ protected static IEnumerable RepeatAsManyAs(TExpecta protected void AssertCollectionEndsWith(IEnumerable actual, ICollection expected, Func equalityComparison, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(equalityComparison); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected {context:collection} to end with {0}{reason}, ", expected) - .Given(() => actual) - .AssertCollectionIsNotNull() - .Then - .AssertCollectionHasEnoughItems(expected.Count) - .Then - .AssertCollectionsHaveSameItems(expected, (a, e) => - { - int firstIndexToCompare = a.Count - e.Count; - int index = a.Skip(firstIndexToCompare).IndexOfFirstDifferenceWith(e, equalityComparison); - return index >= 0 ? index + firstIndexToCompare : index; - }) - .Then - .ClearExpectation(); + .WithExpectation("Expected {context:collection} to end with {0}{reason}, ", expected, chain => chain + .Given(() => actual) + .AssertCollectionIsNotNull() + .Then + .AssertCollectionHasEnoughItems(expected.Count) + .Then + .AssertCollectionsHaveSameItems(expected, (a, e) => + { + int firstIndexToCompare = a.Count - e.Count; + int index = a.Skip(firstIndexToCompare).IndexOfFirstDifferenceWith(e, equalityComparison); + return index >= 0 ? index + firstIndexToCompare : index; + })); } protected void AssertCollectionStartsWith(IEnumerable actualItems, ICollection expected, Func equalityComparison, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(equalityComparison); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected {context:collection} to start with {0}{reason}, ", expected) - .Given(() => actualItems) - .AssertCollectionIsNotNull() - .Then - .AssertCollectionHasEnoughItems(expected.Count) - .Then - .AssertCollectionsHaveSameItems(expected, (a, e) => a.Take(e.Count).IndexOfFirstDifferenceWith(e, equalityComparison)) - .Then - .ClearExpectation(); + .WithExpectation("Expected {context:collection} to start with {0}{reason}, ", expected, chain => chain + .Given(() => actualItems) + .AssertCollectionIsNotNull() + .Then + .AssertCollectionHasEnoughItems(expected.Count) + .Then + .AssertCollectionsHaveSameItems(expected, + (a, e) => a.Take(e.Count).IndexOfFirstDifferenceWith(e, equalityComparison))); } protected void AssertSubjectEquality(IEnumerable expectation, - Func equalityComparison, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + Func equalityComparison, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(equalityComparison); @@ -3411,21 +3418,16 @@ protected void AssertSubjectEquality(IEnumerable exp ICollection expectedItems = expectation.ConvertOrCastToCollection(); - AssertionScope assertion = Execute.Assertion.BecauseOf(because, becauseArgs); - - if (subjectIsNull) - { - assertion.FailWith("Expected {context:collection} to be equal to {0}{reason}, but found .", expectedItems); - } - - assertion - .WithExpectation("Expected {context:collection} to be equal to {0}{reason}, ", expectedItems) - .Given(() => Subject.ConvertOrCastToCollection()) - .AssertCollectionsHaveSameCount(expectedItems.Count) - .Then - .AssertCollectionsHaveSameItems(expectedItems, (a, e) => a.IndexOfFirstDifferenceWith(e, equalityComparison)) + assertionChain + .BecauseOf(because, becauseArgs) + .ForCondition(!subjectIsNull) + .FailWith("Expected {context:collection} to be equal to {0}{reason}, but found .", expectedItems) .Then - .ClearExpectation(); + .WithExpectation("Expected {context:collection} to be equal to {0}{reason}, ", expectedItems, chain => chain + .Given(() => Subject.ConvertOrCastToCollection()) + .AssertCollectionsHaveSameCount(expectedItems.Count) + .Then + .AssertCollectionsHaveSameItems(expectedItems, (a, e) => a.IndexOfFirstDifferenceWith(e, equalityComparison))); } private static string GetExpressionOrderString(Expression> propertyExpression) @@ -3512,25 +3514,28 @@ private string[] CollectFailuresFromInspectors(IEnumerable> elementIns } private bool IsValidProperty(Expression> propertyExpression, - [StringSyntax("CompositeFormat")] string because, object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because, + object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(propertyExpression, nameof(propertyExpression), "Cannot assert collection ordering without specifying a property."); propertyExpression.ValidateMemberPath(); - return Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected {context:collection} to be ordered by {0}{reason} but found .", () => propertyExpression.GetMemberPath()); + + return assertionChain.Succeeded; } private AndConstraint NotBeOrderedBy( Expression> propertyExpression, IComparer comparer, SortOrder direction, - [StringSyntax("CompositeFormat")] string because, + string because, object[] becauseArgs) { if (IsValidProperty(propertyExpression, because, becauseArgs)) @@ -3543,7 +3548,7 @@ private AndConstraint NotBeOrderedBy( direction, unordered); - Execute.Assertion + assertionChain .ForCondition(!unordered.SequenceEqual(expectation)) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:collection} {0} to not be ordered {1}{reason} and not result in {2}.", @@ -3558,19 +3563,19 @@ private AndConstraint NotBeOrderedBy( /// Elements are compared using their implementation. /// private AndConstraint> BeInOrder( - IComparer comparer, SortOrder expectedOrder, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + IComparer comparer, SortOrder expectedOrder, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { string sortOrder = expectedOrder == SortOrder.Ascending ? "ascending" : "descending"; - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith($"Expected {{context:collection}} to be in {sortOrder} order{{reason}}, but found ."); IOrderedEnumerable ordering = Array.Empty().OrderBy(x => x); - if (success) + if (assertionChain.Succeeded) { IList actualItems = Subject.ConvertOrCastToList(); @@ -3585,19 +3590,20 @@ private AndConstraint> BeInOrder( { if (!areSameOrEqual(actualItems[index], orderedItems[index])) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Expected {context:collection} to be in " + sortOrder + " order{reason}, but found {0} where item at index {1} is in wrong order.", actualItems, index); return new AndConstraint>( - new SubsequentOrderingAssertions(Subject, Enumerable.Empty().OrderBy(x => x))); + new SubsequentOrderingAssertions(Subject, Enumerable.Empty().OrderBy(x => x), assertionChain)); } } } - return new AndConstraint>(new SubsequentOrderingAssertions(Subject, ordering)); + return new AndConstraint>( + new SubsequentOrderingAssertions(Subject, ordering, assertionChain)); } /// @@ -3605,16 +3611,17 @@ private AndConstraint> BeInOrder( /// using their implementation. /// private AndConstraint NotBeInOrder(IComparer comparer, SortOrder order, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { string sortOrder = order == SortOrder.Ascending ? "ascending" : "descending"; - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith($"Did not expect {{context:collection}} to be in {sortOrder} order{{reason}}, but found ."); - if (success) + if (assertionChain.Succeeded) { IList actualItems = Subject.ConvertOrCastToList(); @@ -3628,7 +3635,7 @@ private AndConstraint NotBeInOrder(IComparer comparer, SortOrder .Where((actualItem, index) => !areSameOrEqual(actualItem, orderedItems[index])) .Any(); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(itemsAreUnordered) .FailWith( diff --git a/Src/FluentAssertions/Collections/GenericDictionaryAssertions.cs b/Src/FluentAssertions/Collections/GenericDictionaryAssertions.cs index 765a90f2ad..381e082a1b 100644 --- a/Src/FluentAssertions/Collections/GenericDictionaryAssertions.cs +++ b/Src/FluentAssertions/Collections/GenericDictionaryAssertions.cs @@ -17,8 +17,8 @@ public class GenericDictionaryAssertions : GenericDictionaryAssertions> where TCollection : IEnumerable> { - public GenericDictionaryAssertions(TCollection keyValuePairs) - : base(keyValuePairs) + public GenericDictionaryAssertions(TCollection keyValuePairs, AssertionChain assertionChain) + : base(keyValuePairs, assertionChain) { } } @@ -26,15 +26,17 @@ public GenericDictionaryAssertions(TCollection keyValuePairs) /// /// Contains a number of methods to assert that a is in the expected state. /// -[DebuggerNonUserCode] public class GenericDictionaryAssertions : GenericCollectionAssertions, TAssertions> where TCollection : IEnumerable> where TAssertions : GenericDictionaryAssertions { - public GenericDictionaryAssertions(TCollection keyValuePairs) - : base(keyValuePairs) + private readonly AssertionChain assertionChain; + + public GenericDictionaryAssertions(TCollection keyValuePairs, AssertionChain assertionChain) + : base(keyValuePairs, assertionChain) { + this.assertionChain = assertionChain; } #region Equal @@ -59,12 +61,12 @@ public AndConstraint Equal(T expected, { Guard.ThrowIfArgumentIsNull(expected, nameof(expected), "Cannot compare dictionary with ."); - bool success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:dictionary} to be equal to {0}{reason}, but found {1}.", expected, Subject); - if (success) + if (assertionChain.Succeeded) { IEnumerable subjectKeys = GetKeys(Subject); IEnumerable expectedKeys = GetKeys(expected); @@ -73,7 +75,7 @@ public AndConstraint Equal(T expected, if (missingKeys.Any()) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Expected {context:dictionary} to be equal to {0}{reason}, but could not find keys {1}.", expected, missingKeys); @@ -81,9 +83,10 @@ public AndConstraint Equal(T expected, if (additionalKeys.Any()) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .FailWith("Expected {context:dictionary} to be equal to {0}{reason}, but found additional keys {1}.", expected, + .FailWith("Expected {context:dictionary} to be equal to {0}{reason}, but found additional keys {1}.", + expected, additionalKeys); } @@ -91,7 +94,7 @@ public AndConstraint Equal(T expected, foreach (var key in expectedKeys) { - Execute.Assertion + assertionChain .ForCondition(areSameOrEqual(GetValue(Subject, key), GetValue(expected, key))) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:dictionary} to be equal to {0}{reason}, but {1} differs at key {2}.", @@ -122,16 +125,16 @@ public AndConstraint NotEqual(T unexpected, { Guard.ThrowIfArgumentIsNull(unexpected, nameof(unexpected), "Cannot compare dictionary with ."); - bool success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected dictionaries not to be equal{reason}, but found {0}.", Subject); - if (success) + if (assertionChain.Succeeded) { if (ReferenceEquals(Subject, unexpected)) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Expected dictionaries not to be equal{reason}, but they both reference the same object."); } @@ -149,7 +152,7 @@ public AndConstraint NotEqual(T unexpected, if (!foundDifference) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Did not expect dictionaries {0} and {1} to be equal{reason}.", unexpected, Subject); } @@ -220,7 +223,7 @@ public AndConstraint BeEquivalentTo(TExpectation expe EquivalencyOptions options = config(AssertionOptions.CloneDefaults()); var context = - new EquivalencyValidationContext(Node.From(() => AssertionScope.Current.CallerIdentity), options) + new EquivalencyValidationContext(Node.From(() => CurrentAssertionChain.CallerIdentifier), options) { Reason = new Reason(because, becauseArgs), TraceWriter = options.TraceWriter @@ -295,12 +298,12 @@ public AndConstraint ContainKeys(IEnumerable expected, ICollection expectedKeys = expected.ConvertOrCastToCollection(); Guard.ThrowIfArgumentIsEmpty(expectedKeys, nameof(expected), "Cannot verify key containment against an empty sequence"); - bool success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:dictionary} to contain keys {0}{reason}, but found .", expected); - if (success) + if (assertionChain.Succeeded) { IEnumerable missingKeys = expectedKeys.Where(key => !ContainsKey(Subject, key)); @@ -308,14 +311,15 @@ public AndConstraint ContainKeys(IEnumerable expected, { if (expectedKeys.Count > 1) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .FailWith("Expected {context:dictionary} {0} to contain keys {1}{reason}, but could not find {2}.", Subject, + .FailWith("Expected {context:dictionary} {0} to contain keys {1}{reason}, but could not find {2}.", + Subject, expected, missingKeys); } else { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Expected {context:dictionary} {0} to contain key {1}{reason}.", Subject, expected.First()); @@ -345,14 +349,14 @@ public AndConstraint ContainKeys(IEnumerable expected, public AndConstraint NotContainKey(TKey unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:dictionary} not to contain key {0}{reason}, but found .", unexpected); - if (success && ContainsKey(Subject, unexpected)) + if (assertionChain.Succeeded && ContainsKey(Subject, unexpected)) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Expected {context:dictionary} {0} not to contain key {1}{reason}, but found it anyhow.", Subject, unexpected); @@ -396,12 +400,12 @@ public AndConstraint NotContainKeys(IEnumerable unexpected, Guard.ThrowIfArgumentIsEmpty(unexpectedKeys, nameof(unexpected), "Cannot verify key containment against an empty sequence"); - bool success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:dictionary} to not contain keys {0}{reason}, but found .", unexpectedKeys); - if (success) + if (assertionChain.Succeeded) { IEnumerable foundKeys = unexpectedKeys.Where(key => ContainsKey(Subject, key)); @@ -409,14 +413,14 @@ public AndConstraint NotContainKeys(IEnumerable unexpected, { if (unexpectedKeys.Count > 1) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Expected {context:dictionary} {0} to not contain keys {1}{reason}, but found {2}.", Subject, unexpectedKeys, foundKeys); } else { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Expected {context:dictionary} {0} to not contain key {1}{reason}.", Subject, unexpectedKeys.First()); @@ -446,12 +450,10 @@ public AndConstraint NotContainKeys(IEnumerable unexpected, public AndWhichConstraint ContainValue(TValue expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - AndWhichConstraint> innerConstraint = - ContainValuesAndWhich([expected], because, becauseArgs); + AndWhichConstraint> result = + ContainValues([expected], because, becauseArgs); - return - new AndWhichConstraint( - innerConstraint.And, innerConstraint.Which); + return new AndWhichConstraint(result.And, result.Subject); } /// @@ -459,13 +461,13 @@ public AndWhichConstraint ContainValue(TValue expected, /// their implementation. /// /// The expected values - public AndConstraint ContainValues(params TValue[] expected) + public AndWhichConstraint> ContainValues(params TValue[] expected) { return ContainValues(expected, string.Empty); } /// - /// Asserts that the dictionary contains all of the specified values. Values are compared using + /// Asserts that the dictionary contains all the specified values. Values are compared using /// their implementation. /// /// The expected values @@ -478,68 +480,57 @@ public AndConstraint ContainValues(params TValue[] expected) /// /// is . /// is empty. - public AndConstraint ContainValues(IEnumerable expected, + public AndWhichConstraint> ContainValues(IEnumerable expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(expected, nameof(expected), "Cannot verify value containment against a collection of values"); - return ContainValuesAndWhich(expected, because, becauseArgs); - } - - private AndWhichConstraint> ContainValuesAndWhich(IEnumerable expected, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) - { ICollection expectedValues = expected.ConvertOrCastToCollection(); Guard.ThrowIfArgumentIsEmpty(expectedValues, nameof(expected), "Cannot verify value containment against an empty sequence"); - bool success = Execute.Assertion + var missingValues = new List(expectedValues); + + Dictionary matches = new(); + + assertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) - .FailWith("Expected {context:dictionary} to contain values {0}{reason}, but found {1}.", expected, Subject); - - IEnumerable matchedConstraint = null; + .FailWith("Expected {context:dictionary} to contain values {0}{reason}, but found .", expected); - if (success) + if (assertionChain.Succeeded) { - IEnumerable subjectValues = GetValues(Subject); - IEnumerable missingValues = expectedValues.Except(subjectValues); + foreach (var pair in Subject!) + { + if (missingValues.Contains(pair.Value)) + { + matches.Add(pair.Key, pair.Value); + missingValues.Remove(pair.Value); + } + } - if (missingValues.Any()) + if (missingValues.Count > 0) { - if (expectedValues.Count > 1) + if (expectedValues.Count == 1) { - Execute.Assertion - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context:dictionary} {0} to contain value {1}{reason}, but could not find {2}.", Subject, - expected, missingValues); + assertionChain.FailWith( + "Expected {context:dictionary} {0} to contain value {1}{reason}.", + Subject, expectedValues.Single()); } else { - Execute.Assertion - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context:dictionary} {0} to contain value {1}{reason}.", Subject, - expected.First()); + assertionChain.FailWith( + "Expected {context:dictionary} {0} to contain values {1}{reason}, but could not find {2}.", + Subject, expectedValues, missingValues.Count == 1 ? missingValues.Single() : missingValues); } } - - matchedConstraint = RepetitionPreservingIntersect(subjectValues, expectedValues); } - return new AndWhichConstraint>((TAssertions)this, matchedConstraint); - } + string postfix = matches.Count > 0 ? "[" + string.Join(" and ", matches.Keys) + "]" : ""; - /// - /// Returns an enumerable consisting of all items in the first collection also appearing in the second. - /// - /// Enumerable.Intersect is not suitable because it drops any repeated elements. - private static IEnumerable RepetitionPreservingIntersect( - IEnumerable first, IEnumerable second) - { - var secondSet = new HashSet(second); - return first.Where(e => secondSet.Contains(e)); + return new AndWhichConstraint>((TAssertions)this, matches.Values, assertionChain, postfix); } #endregion @@ -561,14 +552,14 @@ private static IEnumerable RepetitionPreservingIntersect( public AndConstraint NotContainValue(TValue unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:dictionary} not to contain value {0}{reason}, but found .", unexpected); - if (success && GetValues(Subject).Contains(unexpected)) + if (assertionChain.Succeeded && GetValues(Subject).Contains(unexpected)) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Expected {context:dictionary} {0} not to contain value {1}{reason}, but found it anyhow.", Subject, unexpected); @@ -612,12 +603,12 @@ public AndConstraint NotContainValues(IEnumerable unexpecte Guard.ThrowIfArgumentIsEmpty(unexpectedValues, nameof(unexpected), "Cannot verify value containment with an empty sequence"); - bool success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:dictionary} to not contain values {0}{reason}, but found .", unexpected); - if (success) + if (assertionChain.Succeeded) { IEnumerable foundValues = unexpectedValues.Intersect(GetValues(Subject)); @@ -625,14 +616,15 @@ public AndConstraint NotContainValues(IEnumerable unexpecte { if (unexpectedValues.Count > 1) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .FailWith("Expected {context:dictionary} {0} to not contain value {1}{reason}, but found {2}.", Subject, + .FailWith("Expected {context:dictionary} {0} to not contain value {1}{reason}, but found {2}.", + Subject, unexpected, foundValues); } else { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Expected {context:dictionary} {0} to not contain value {1}{reason}.", Subject, unexpected.First()); @@ -684,13 +676,13 @@ public AndConstraint Contain(params KeyValuePair[] ex Guard.ThrowIfArgumentIsEmpty(expectedKeyValuePairs, nameof(expected), "Cannot verify key containment against an empty collection of key/value pairs"); - bool success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:dictionary} to contain key/value pairs {0}{reason}, but dictionary is .", expected); - if (success) + if (assertionChain.Succeeded) { TKey[] expectedKeys = expectedKeyValuePairs.Select(keyValuePair => keyValuePair.Key).ToArray(); IEnumerable missingKeys = expectedKeys.Where(key => !ContainsKey(Subject, key)); @@ -699,7 +691,7 @@ public AndConstraint Contain(params KeyValuePair[] ex { if (expectedKeyValuePairs.Count > 1) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Expected {context:dictionary} {0} to contain key(s) {1}{reason}, but could not find keys {2}.", Subject, @@ -707,7 +699,7 @@ public AndConstraint Contain(params KeyValuePair[] ex } else { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Expected {context:dictionary} {0} to contain key {1}{reason}.", Subject, expectedKeys[0]); @@ -723,7 +715,7 @@ public AndConstraint Contain(params KeyValuePair[] ex { if (keyValuePairsNotSameOrEqualInSubject.Length > 1) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith( "Expected {context:dictionary} to contain {0}{reason}, but {context:dictionary} differs at keys {1}.", @@ -734,7 +726,7 @@ public AndConstraint Contain(params KeyValuePair[] ex KeyValuePair expectedKeyValuePair = keyValuePairsNotSameOrEqualInSubject[0]; TValue actual = GetValue(Subject, expectedKeyValuePair.Key); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Expected {context:dictionary} to contain value {0} at key {1}{reason}, but found {2}.", expectedKeyValuePair.Value, expectedKeyValuePair.Key, actual); @@ -782,19 +774,19 @@ public AndConstraint Contain(params KeyValuePair[] ex public AndConstraint Contain(TKey key, TValue value, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:dictionary} to contain value {0} at key {1}{reason}, but dictionary is .", value, key); - if (success) + if (assertionChain.Succeeded) { if (TryGetValue(Subject, key, out TValue actual)) { Func areSameOrEqual = ObjectExtensions.GetComparer(); - Execute.Assertion + assertionChain .ForCondition(areSameOrEqual(actual, value)) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:dictionary} to contain value {0} at key {1}{reason}, but found {2}.", value, key, @@ -802,7 +794,7 @@ public AndConstraint Contain(TKey key, TValue value, } else { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Expected {context:dictionary} to contain value {0} at key {1}{reason}, but the key was not found.", value, @@ -853,13 +845,13 @@ public AndConstraint NotContain(params KeyValuePair[] Guard.ThrowIfArgumentIsEmpty(keyValuePairs, nameof(items), "Cannot verify key containment against an empty collection of key/value pairs"); - bool success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:dictionary} to not contain key/value pairs {0}{reason}, but dictionary is .", items); - if (success) + if (assertionChain.Succeeded) { KeyValuePair[] keyValuePairsFound = keyValuePairs.Where(keyValuePair => ContainsKey(Subject, keyValuePair.Key)).ToArray(); @@ -875,7 +867,7 @@ public AndConstraint NotContain(params KeyValuePair[] { if (keyValuePairsSameOrEqualInSubject.Length > 1) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith( "Expected {context:dictionary} to not contain key/value pairs {0}{reason}, but found them anyhow.", @@ -885,7 +877,7 @@ public AndConstraint NotContain(params KeyValuePair[] { KeyValuePair keyValuePair = keyValuePairsSameOrEqualInSubject[0]; - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith( "Expected {context:dictionary} to not contain value {0} at key {1}{reason}, but found it anyhow.", @@ -935,15 +927,15 @@ public AndConstraint NotContain(params KeyValuePair[] public AndConstraint NotContain(TKey key, TValue value, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:dictionary} not to contain value {0} at key {1}{reason}, but dictionary is .", value, key); - if (success && TryGetValue(Subject, key, out TValue actual)) + if (assertionChain.Succeeded && TryGetValue(Subject, key, out TValue actual)) { - Execute.Assertion + assertionChain .ForCondition(!ObjectExtensions.GetComparer()(actual, value)) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:dictionary} not to contain value {0} at key {1}{reason}, but found it anyhow.", diff --git a/Src/FluentAssertions/Collections/StringCollectionAssertions.cs b/Src/FluentAssertions/Collections/StringCollectionAssertions.cs index 82e4498f3c..6d2fce15ca 100644 --- a/Src/FluentAssertions/Collections/StringCollectionAssertions.cs +++ b/Src/FluentAssertions/Collections/StringCollectionAssertions.cs @@ -13,8 +13,8 @@ public class StringCollectionAssertions : StringCollectionAssertions /// Initializes a new instance of the class. /// - public StringCollectionAssertions(IEnumerable actualValue) - : base(actualValue) + public StringCollectionAssertions(IEnumerable actualValue, AssertionChain assertionChain) + : base(actualValue, assertionChain) { } } @@ -26,8 +26,8 @@ public class StringCollectionAssertions /// /// Initializes a new instance of the class. /// - public StringCollectionAssertions(TCollection actualValue) - : base(actualValue) + public StringCollectionAssertions(TCollection actualValue, AssertionChain assertionChain) + : base(actualValue, assertionChain) { } } @@ -36,12 +36,15 @@ public class StringCollectionAssertions : GenericColle where TCollection : IEnumerable where TAssertions : StringCollectionAssertions { + private readonly AssertionChain assertionChain; + /// /// Initializes a new instance of the class. /// - public StringCollectionAssertions(TCollection actualValue) - : base(actualValue) + public StringCollectionAssertions(TCollection actualValue, AssertionChain assertionChain) + : base(actualValue, assertionChain) { + this.assertionChain = assertionChain; } /// @@ -131,7 +134,7 @@ public AndConstraint BeEquivalentTo(IEnumerable expectation options = config(AssertionOptions.CloneDefaults()).AsCollection(); var context = - new EquivalencyValidationContext(Node.From>(() => AssertionScope.Current.CallerIdentity), options) + new EquivalencyValidationContext(Node.From>(() => CurrentAssertionChain.CallerIdentifier), options) { Reason = new Reason(because, becauseArgs), TraceWriter = options.TraceWriter @@ -248,45 +251,48 @@ public AndWhichConstraint ContainMatch(string wildcardPatte Guard.ThrowIfArgumentIsEmpty(wildcardPattern, nameof(wildcardPattern), "Cannot match strings in collection against an empty string. Provide a wildcard pattern or use the Contain method."); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected {context:collection} to contain a match of {0}{reason}, but found .", wildcardPattern); - IEnumerable matched = []; + string[] matches = []; + + int? firstMatch = null; - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + (matches, firstMatch) = AllThatMatch(wildcardPattern); + + assertionChain .BecauseOf(because, becauseArgs) - .ForCondition(ContainsMatch(wildcardPattern)) + .ForCondition(matches.Length > 0) .FailWith("Expected {context:collection} {0} to contain a match of {1}{reason}.", Subject, wildcardPattern); - - matched = AllThatMatch(wildcardPattern); } - return new AndWhichConstraint((TAssertions)this, matched); + return new AndWhichConstraint((TAssertions)this, matches, assertionChain, "[" + firstMatch + "]"); } - private bool ContainsMatch(string wildcardPattern) + private (string[] MatchingItems, int? FirstMatchingIndex) AllThatMatch(string wildcardPattern) { - using var scope = new AssertionScope(); - - return Subject.Any(item => - { - item.Should().Match(wildcardPattern); - return scope.Discard().Length == 0; - }); - } + int? firstMatchingIndex = null; - private IEnumerable AllThatMatch(string wildcardPattern) - { - return Subject.Where(item => + var matches = Subject.Where((item, index) => { using var scope = new AssertionScope(); + item.Should().Match(wildcardPattern); - return scope.Discard().Length == 0; + + if (scope.Discard().Length == 0) + { + firstMatchingIndex ??= index; + return true; + } + + return false; }); + + return (matches.ToArray(), firstMatchingIndex); } /// @@ -333,15 +339,15 @@ public AndConstraint NotContainMatch(string wildcardPattern, Guard.ThrowIfArgumentIsEmpty(wildcardPattern, nameof(wildcardPattern), "Cannot match strings in collection against an empty string. Provide a wildcard pattern or use the NotContain method."); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Did not expect {context:collection} to contain a match of {0}{reason}, but found .", wildcardPattern); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(NotContainsMatch(wildcardPattern)) .FailWith("Did not expect {context:collection} {0} to contain a match of {1}{reason}.", Subject, wildcardPattern); diff --git a/Src/FluentAssertions/Collections/SubsequentOrderingAssertions.cs b/Src/FluentAssertions/Collections/SubsequentOrderingAssertions.cs index 8d1faebf3b..82634696b1 100644 --- a/Src/FluentAssertions/Collections/SubsequentOrderingAssertions.cs +++ b/Src/FluentAssertions/Collections/SubsequentOrderingAssertions.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using FluentAssertions.Execution; namespace FluentAssertions.Collections; @@ -8,8 +9,8 @@ namespace FluentAssertions.Collections; public class SubsequentOrderingAssertions : SubsequentOrderingGenericCollectionAssertions, T, SubsequentOrderingAssertions> { - public SubsequentOrderingAssertions(IEnumerable actualValue, IOrderedEnumerable previousOrderedEnumerable) - : base(actualValue, previousOrderedEnumerable) + public SubsequentOrderingAssertions(IEnumerable actualValue, IOrderedEnumerable previousOrderedEnumerable, AssertionChain assertionChain) + : base(actualValue, previousOrderedEnumerable, assertionChain) { } } diff --git a/Src/FluentAssertions/Collections/SubsequentOrderingGenericCollectionAssertions.cs b/Src/FluentAssertions/Collections/SubsequentOrderingGenericCollectionAssertions.cs index 104dedac65..789176f263 100644 --- a/Src/FluentAssertions/Collections/SubsequentOrderingGenericCollectionAssertions.cs +++ b/Src/FluentAssertions/Collections/SubsequentOrderingGenericCollectionAssertions.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Linq.Expressions; using FluentAssertions.Common; +using FluentAssertions.Execution; namespace FluentAssertions.Collections; @@ -17,8 +18,8 @@ public class SubsequentOrderingGenericCollectionAssertions previousOrderedEnumerable; private bool subsequentOrdering; - public SubsequentOrderingGenericCollectionAssertions(TCollection actualValue, IOrderedEnumerable previousOrderedEnumerable) - : base(actualValue) + public SubsequentOrderingGenericCollectionAssertions(TCollection actualValue, IOrderedEnumerable previousOrderedEnumerable, AssertionChain assertionChain) + : base(actualValue, assertionChain) { this.previousOrderedEnumerable = previousOrderedEnumerable; } @@ -170,8 +171,8 @@ public class SubsequentOrderingGenericCollectionAssertions : SubsequentOrderingGenericCollectionAssertions> where TCollection : IEnumerable { - public SubsequentOrderingGenericCollectionAssertions(TCollection actualValue, IOrderedEnumerable previousOrderedEnumerable) - : base(actualValue, previousOrderedEnumerable) + public SubsequentOrderingGenericCollectionAssertions(TCollection actualValue, IOrderedEnumerable previousOrderedEnumerable, AssertionChain assertionChain) + : base(actualValue, previousOrderedEnumerable, assertionChain) { } } diff --git a/Src/FluentAssertions/Common/MethodInfoExtensions.cs b/Src/FluentAssertions/Common/MethodInfoExtensions.cs index 7fcef498a5..5219f0570b 100644 --- a/Src/FluentAssertions/Common/MethodInfoExtensions.cs +++ b/Src/FluentAssertions/Common/MethodInfoExtensions.cs @@ -30,7 +30,6 @@ internal static IEnumerable GetMatchingAttributes(this M if (typeof(TAttribute) == typeof(MethodImplAttribute) && memberInfo is MethodBase methodBase) { (bool success, MethodImplAttribute methodImplAttribute) = RecreateMethodImplAttribute(methodBase); - if (success) { customAttributes.Add(methodImplAttribute as TAttribute); diff --git a/Src/FluentAssertions/Common/ObjectExtensions.cs b/Src/FluentAssertions/Common/ObjectExtensions.cs index 7ba5e3bdcc..e1bf0f0032 100644 --- a/Src/FluentAssertions/Common/ObjectExtensions.cs +++ b/Src/FluentAssertions/Common/ObjectExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using FluentAssertions.Formatting; namespace FluentAssertions.Common; @@ -77,4 +78,12 @@ ushort or uint or ulong); } + + /// + /// Convenience method to format an object to a string using the class. + /// + public static string ToFormattedString(this object source) + { + return Formatter.ToString(source); + } } diff --git a/Src/FluentAssertions/Common/StringExtensions.cs b/Src/FluentAssertions/Common/StringExtensions.cs index e83ab55b24..f1b047d02f 100644 --- a/Src/FluentAssertions/Common/StringExtensions.cs +++ b/Src/FluentAssertions/Common/StringExtensions.cs @@ -127,6 +127,12 @@ public static string RemoveNewlineStyle(this string @this) .Replace("\r", "\n", StringComparison.Ordinal); } + public static string RemoveTrailingWhitespaceFromLines(this string input) + { + // This regex matches whitespace characters (\s) that are followed by a line ending (\r?\n) + return Regex.Replace(input, @"[ \t]+(?=\r?\n)", string.Empty); + } + /// /// Counts the number of times the appears within a string by using the specified . /// @@ -156,4 +162,9 @@ public static bool IsLongOrMultiline(this string value) const int humanReadableLength = 8; return value.Length > humanReadableLength || value.Contains(Environment.NewLine, StringComparison.Ordinal); } + + public static bool IsNullOrEmpty(this string value) + { + return string.IsNullOrEmpty(value); + } } diff --git a/Src/FluentAssertions/CustomAssertionAttribute.cs b/Src/FluentAssertions/CustomAssertionAttribute.cs index 6078c1457f..87dfa929b1 100644 --- a/Src/FluentAssertions/CustomAssertionAttribute.cs +++ b/Src/FluentAssertions/CustomAssertionAttribute.cs @@ -4,7 +4,7 @@ namespace FluentAssertions; /// /// Marks a method as an extension to Fluent Assertions that either uses the built-in assertions -/// internally, or directly uses the Execute.Assertion. +/// internally, or directly uses AssertionChain. /// [AttributeUsage(AttributeTargets.Method)] #pragma warning disable CA1813 // Avoid unsealed attributes. This type has shipped. diff --git a/Src/FluentAssertions/CustomAssertionsAssemblyAttribute.cs b/Src/FluentAssertions/CustomAssertionsAssemblyAttribute.cs index faaff8e2b3..9e0cc90410 100644 --- a/Src/FluentAssertions/CustomAssertionsAssemblyAttribute.cs +++ b/Src/FluentAssertions/CustomAssertionsAssemblyAttribute.cs @@ -4,7 +4,7 @@ namespace FluentAssertions; /// /// Marks an assembly as containing extensions to Fluent Assertions that either uses the built-in assertions -/// internally, or directly uses the Execute.Assertion. +/// internally, or directly uses AssertionChain. /// [AttributeUsage(AttributeTargets.Assembly)] public sealed class CustomAssertionsAssemblyAttribute : Attribute; diff --git a/Src/FluentAssertions/EnumAssertionsExtensions.cs b/Src/FluentAssertions/EnumAssertionsExtensions.cs index 31688fb9cc..da15e34440 100644 --- a/Src/FluentAssertions/EnumAssertionsExtensions.cs +++ b/Src/FluentAssertions/EnumAssertionsExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.Diagnostics.Contracts; +using FluentAssertions.Execution; using FluentAssertions.Primitives; namespace FluentAssertions; @@ -19,7 +20,7 @@ public static class EnumAssertionsExtensions public static EnumAssertions Should(this TEnum @enum) where TEnum : struct, Enum { - return new EnumAssertions(@enum); + return new EnumAssertions(@enum, AssertionChain.GetOrCreate()); } /// @@ -30,6 +31,6 @@ public static EnumAssertions Should(this TEnum @enum) public static NullableEnumAssertions Should(this TEnum? @enum) where TEnum : struct, Enum { - return new NullableEnumAssertions(@enum); + return new NullableEnumAssertions(@enum, AssertionChain.GetOrCreate()); } } diff --git a/Src/FluentAssertions/Equivalency/AssertionChainExtensions.cs b/Src/FluentAssertions/Equivalency/AssertionChainExtensions.cs new file mode 100644 index 0000000000..95227db2cb --- /dev/null +++ b/Src/FluentAssertions/Equivalency/AssertionChainExtensions.cs @@ -0,0 +1,19 @@ +using FluentAssertions.Execution; + +namespace FluentAssertions.Equivalency; + +internal static class AssertionChainExtensions +{ + /// + /// Updates the with the relevant information from the current , including the correct + /// caller identification path. + /// + public static AssertionChain For(this AssertionChain chain, IEquivalencyValidationContext context) + { + chain.OverrideCallerIdentifier(() => context.CurrentNode.Description); + + return chain + .WithReportable("configuration", () => context.Options.ToString()) + .BecauseOf(context.Reason); + } +} diff --git a/Src/FluentAssertions/Equivalency/EquivalencyStep.cs b/Src/FluentAssertions/Equivalency/EquivalencyStep.cs index d50c77023a..58f689f771 100644 --- a/Src/FluentAssertions/Equivalency/EquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/EquivalencyStep.cs @@ -20,5 +20,5 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon /// Implements , but only gets called when the expected type matches . /// protected abstract EquivalencyResult OnHandle(Comparands comparands, IEquivalencyValidationContext context, - IValidateChildNodeEquivalency nested); + IValidateChildNodeEquivalency nestedValidator); } diff --git a/Src/FluentAssertions/Equivalency/EquivalencyValidationContext.cs b/Src/FluentAssertions/Equivalency/EquivalencyValidationContext.cs index 9d7215a971..2fbced998e 100644 --- a/Src/FluentAssertions/Equivalency/EquivalencyValidationContext.cs +++ b/Src/FluentAssertions/Equivalency/EquivalencyValidationContext.cs @@ -75,7 +75,7 @@ public bool IsCyclicReference(object expectation) is EqualityStrategy.Members or EqualityStrategy.ForceMembers; var reference = new ObjectReference(expectation, CurrentNode.PathAndName, compareByMembers); - return CyclicReferenceDetector.IsCyclicReference(reference, Options.CyclicReferenceHandling, Reason); + return CyclicReferenceDetector.IsCyclicReference(reference); } public ITraceWriter TraceWriter { get; set; } diff --git a/Src/FluentAssertions/Equivalency/EquivalencyValidator.cs b/Src/FluentAssertions/Equivalency/EquivalencyValidator.cs index 6d7de0a3fd..b856f6d22a 100644 --- a/Src/FluentAssertions/Equivalency/EquivalencyValidator.cs +++ b/Src/FluentAssertions/Equivalency/EquivalencyValidator.cs @@ -15,10 +15,6 @@ public void AssertEquality(Comparands comparands, EquivalencyValidationContext c { using var scope = new AssertionScope(); - scope.AssumeSingleCaller(); - scope.AddReportable("configuration", () => context.Options.ToString()); - scope.BecauseOf(context.Reason); - RecursivelyAssertEquivalencyOf(comparands, context); if (context.TraceWriter is not null) @@ -34,39 +30,40 @@ private void RecursivelyAssertEquivalencyOf(Comparands comparands, IEquivalencyV public void AssertEquivalencyOf(Comparands comparands, IEquivalencyValidationContext context) { - var scope = AssertionScope.Current; + var assertionChain = AssertionChain.GetOrCreate() + .For(context) + .BecauseOf(context.Reason); - if (ShouldContinueThisDeep(context.CurrentNode, context.Options, scope)) + if (ShouldContinueThisDeep(context.CurrentNode, context.Options, assertionChain)) { - TrackWhatIsNeededToProvideContextToFailures(scope, comparands, context.CurrentNode); - if (!context.IsCyclicReference(comparands.Expectation)) { TryToProveNodesAreEquivalent(comparands, context); } + else if (context.Options.CyclicReferenceHandling == CyclicReferenceHandling.ThrowException) + { + assertionChain.FailWith("Expected {context:subject} to be {expectation}{reason}, but it contains a cyclic reference."); + } + else + { + // If cyclic references are allowed, we consider the objects to be equivalent + } } } private static bool ShouldContinueThisDeep(INode currentNode, IEquivalencyOptions options, - AssertionScope assertionScope) + AssertionChain assertionChain) { bool shouldRecurse = options.AllowInfiniteRecursion || currentNode.Depth <= MaxDepth; if (!shouldRecurse) { // This will throw, unless we're inside an AssertionScope - assertionScope.FailWith($"The maximum recursion depth of {MaxDepth} was reached. "); + assertionChain.FailWith($"The maximum recursion depth of {MaxDepth} was reached. "); } return shouldRecurse; } - private static void TrackWhatIsNeededToProvideContextToFailures(AssertionScope scope, Comparands comparands, INode currentNode) - { - scope.Context = new Lazy(() => currentNode.Description); - - scope.TrackComparands(comparands.Subject, comparands.Expectation); - } - private void TryToProveNodesAreEquivalent(Comparands comparands, IEquivalencyValidationContext context) { using var _ = context.Tracer.WriteBlock(node => node.Description); diff --git a/Src/FluentAssertions/Equivalency/Execution/CyclicReferenceDetector.cs b/Src/FluentAssertions/Equivalency/Execution/CyclicReferenceDetector.cs index 600823aee9..4ca13ce49d 100644 --- a/Src/FluentAssertions/Equivalency/Execution/CyclicReferenceDetector.cs +++ b/Src/FluentAssertions/Equivalency/Execution/CyclicReferenceDetector.cs @@ -19,24 +19,13 @@ internal class CyclicReferenceDetector : ICloneable2 /// Determines whether the specified object reference is a cyclic reference to the same object earlier in the /// equivalency validation. /// - /// - /// The behavior of a cyclic reference is determined by the parameter. - /// - public bool IsCyclicReference(ObjectReference reference, CyclicReferenceHandling handling, Reason reason = null) + public bool IsCyclicReference(ObjectReference reference) { bool isCyclic = false; if (reference.CompareByMembers) { isCyclic = !observedReferences.Add(reference); - - if (isCyclic && handling == CyclicReferenceHandling.ThrowException) - { - AssertionScope.Current - .BecauseOf(reason) - .FailWith( - "Expected {context:subject} to be {expectation}{reason}, but it contains a cyclic reference."); - } } return isCyclic; diff --git a/Src/FluentAssertions/Equivalency/IEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/IEquivalencyStep.cs index d4f744b5ea..d0279009ae 100644 --- a/Src/FluentAssertions/Equivalency/IEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/IEquivalencyStep.cs @@ -15,5 +15,6 @@ public interface IEquivalencyStep /// /// May throw when preconditions are not met or if it detects mismatching data. /// - EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, IValidateChildNodeEquivalency valueChildNodes); + EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, + IValidateChildNodeEquivalency valueChildNodes); } diff --git a/Src/FluentAssertions/Equivalency/IMemberMatchingRule.cs b/Src/FluentAssertions/Equivalency/IMemberMatchingRule.cs index 00f1dbcdad..0a59d796eb 100644 --- a/Src/FluentAssertions/Equivalency/IMemberMatchingRule.cs +++ b/Src/FluentAssertions/Equivalency/IMemberMatchingRule.cs @@ -1,3 +1,5 @@ +using FluentAssertions.Execution; + namespace FluentAssertions.Equivalency; /// @@ -15,17 +17,18 @@ public interface IMemberMatchingRule /// simply return . /// /// - /// The of the subject's member for which a match must be found. Can never - /// be . + /// The of the subject's member for which a match must be found. Can never + /// be . /// /// - /// The subject object for which a matching member must be returned. Can never be . + /// The subject object for which a matching member must be returned. Can never be . /// /// /// + /// /// /// Returns the of the property with which to compare the subject with, or /// if no match was found. /// - IMember Match(IMember expectedMember, object subject, INode parent, IEquivalencyOptions options); + IMember Match(IMember expectedMember, object subject, INode parent, IEquivalencyOptions options, AssertionChain assertionChain); } diff --git a/Src/FluentAssertions/Equivalency/Matching/MappedMemberMatchingRule.cs b/Src/FluentAssertions/Equivalency/Matching/MappedMemberMatchingRule.cs index 51b6c43a2b..faa1aaaea2 100644 --- a/Src/FluentAssertions/Equivalency/Matching/MappedMemberMatchingRule.cs +++ b/Src/FluentAssertions/Equivalency/Matching/MappedMemberMatchingRule.cs @@ -1,6 +1,7 @@ using System; using System.Text.RegularExpressions; using FluentAssertions.Common; +using FluentAssertions.Execution; namespace FluentAssertions.Equivalency.Matching; @@ -29,7 +30,7 @@ public MappedMemberMatchingRule(string expectationMemberName, string subjectMemb this.subjectMemberName = subjectMemberName; } - public IMember Match(IMember expectedMember, object subject, INode parent, IEquivalencyOptions options) + public IMember Match(IMember expectedMember, object subject, INode parent, IEquivalencyOptions options, AssertionChain assertionChain) { if (parent.Type.IsSameOrInherits(typeof(TExpectation)) && subject is TSubject && expectedMember.Name == expectationMemberName) diff --git a/Src/FluentAssertions/Equivalency/Matching/MappedPathMatchingRule.cs b/Src/FluentAssertions/Equivalency/Matching/MappedPathMatchingRule.cs index ded3bcaa9f..88c44a8f4c 100644 --- a/Src/FluentAssertions/Equivalency/Matching/MappedPathMatchingRule.cs +++ b/Src/FluentAssertions/Equivalency/Matching/MappedPathMatchingRule.cs @@ -1,5 +1,6 @@ using System; using FluentAssertions.Common; +using FluentAssertions.Execution; namespace FluentAssertions.Equivalency.Matching; @@ -42,7 +43,7 @@ public MappedPathMatchingRule(string expectationMemberPath, string subjectMember } } - public IMember Match(IMember expectedMember, object subject, INode parent, IEquivalencyOptions options) + public IMember Match(IMember expectedMember, object subject, INode parent, IEquivalencyOptions options, AssertionChain assertionChain) { MemberPath path = expectationPath; diff --git a/Src/FluentAssertions/Equivalency/Matching/MustMatchByNameRule.cs b/Src/FluentAssertions/Equivalency/Matching/MustMatchByNameRule.cs index 36d220c195..dc5ac17c6e 100644 --- a/Src/FluentAssertions/Equivalency/Matching/MustMatchByNameRule.cs +++ b/Src/FluentAssertions/Equivalency/Matching/MustMatchByNameRule.cs @@ -9,7 +9,7 @@ namespace FluentAssertions.Equivalency.Matching; /// internal class MustMatchByNameRule : IMemberMatchingRule { - public IMember Match(IMember expectedMember, object subject, INode parent, IEquivalencyOptions options) + public IMember Match(IMember expectedMember, object subject, INode parent, IEquivalencyOptions options, AssertionChain assertionChain) { IMember subjectMember = null; @@ -33,12 +33,12 @@ public IMember Match(IMember expectedMember, object subject, INode parent, IEqui if (subjectMember is null) { - Execute.Assertion.FailWith( + assertionChain.FailWith( $"Expectation has {expectedMember.Description} that the other object does not have."); } else if (options.IgnoreNonBrowsableOnSubject && !subjectMember.IsBrowsable) { - Execute.Assertion.FailWith( + assertionChain.FailWith( $"Expectation has {expectedMember.Description} that is non-browsable in the other object, and non-browsable " + "members on the subject are ignored with the current configuration"); } diff --git a/Src/FluentAssertions/Equivalency/Matching/TryMatchByNameRule.cs b/Src/FluentAssertions/Equivalency/Matching/TryMatchByNameRule.cs index 06cabfdf60..9d6d7dc95c 100644 --- a/Src/FluentAssertions/Equivalency/Matching/TryMatchByNameRule.cs +++ b/Src/FluentAssertions/Equivalency/Matching/TryMatchByNameRule.cs @@ -1,5 +1,6 @@ using System.Reflection; using FluentAssertions.Common; +using FluentAssertions.Execution; namespace FluentAssertions.Equivalency.Matching; @@ -8,7 +9,7 @@ namespace FluentAssertions.Equivalency.Matching; /// internal class TryMatchByNameRule : IMemberMatchingRule { - public IMember Match(IMember expectedMember, object subject, INode parent, IEquivalencyOptions options) + public IMember Match(IMember expectedMember, object subject, INode parent, IEquivalencyOptions options, AssertionChain assertionChain) { if (options.IncludedProperties != MemberVisibility.None) { diff --git a/Src/FluentAssertions/Equivalency/MultiDimensionalArrayEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/MultiDimensionalArrayEquivalencyStep.cs index 016a566003..4962f36860 100644 --- a/Src/FluentAssertions/Equivalency/MultiDimensionalArrayEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/MultiDimensionalArrayEquivalencyStep.cs @@ -18,7 +18,7 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon return EquivalencyResult.ContinueWithNext; } - if (AreComparable(comparands, expectationAsArray)) + if (AreComparable(comparands, expectationAsArray, AssertionChain.GetOrCreate().For(context))) { if (expectationAsArray.Length == 0) { @@ -36,7 +36,8 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon IEquivalencyValidationContext itemContext = context.AsCollectionItem(listOfIndices); - valueChildNodes.AssertEquivalencyOf(new Comparands(subject, expectation, typeof(object)), itemContext); + valueChildNodes.AssertEquivalencyOf(new Comparands(subject, expectation, typeof(object)), + itemContext); } while (digit.Increment()); } @@ -52,25 +53,27 @@ private static Digit BuildDigitsRepresentingAllIndices(Array subjectAsArray) .Aggregate((Digit)null, (next, rank) => new Digit(subjectAsArray.GetLength(rank), next)); } - private static bool AreComparable(Comparands comparands, Array expectationAsArray) + private static bool AreComparable(Comparands comparands, Array expectationAsArray, AssertionChain assertionChain) { return - IsArray(comparands.Subject) && - HaveSameRank(comparands.Subject, expectationAsArray) && - HaveSameDimensions(comparands.Subject, expectationAsArray); + IsArray(comparands.Subject, assertionChain) && + HaveSameRank(comparands.Subject, expectationAsArray, assertionChain) && + HaveSameDimensions(comparands.Subject, expectationAsArray, assertionChain); } - private static bool IsArray(object type) + private static bool IsArray(object type, AssertionChain assertionChain) { - return AssertionScope.Current + assertionChain .ForCondition(type is not null) .FailWith("Cannot compare a multi-dimensional array to .") .Then .ForCondition(type is Array) .FailWith("Cannot compare a multi-dimensional array to something else."); + + return assertionChain.Succeeded; } - private static bool HaveSameDimensions(object subject, Array expectation) + private static bool HaveSameDimensions(object subject, Array expectation, AssertionChain assertionChain) { bool sameDimensions = true; @@ -79,22 +82,26 @@ private static bool HaveSameDimensions(object subject, Array expectation) int actualLength = ((Array)subject).GetLength(dimension); int expectedLength = expectation.GetLength(dimension); - sameDimensions &= AssertionScope.Current + assertionChain .ForCondition(expectedLength == actualLength) .FailWith("Expected dimension {0} to contain {1} item(s), but found {2}.", dimension, expectedLength, actualLength); + + sameDimensions &= assertionChain.Succeeded; } return sameDimensions; } - private static bool HaveSameRank(object subject, Array expectation) + private static bool HaveSameRank(object subject, Array expectation, AssertionChain assertionChain) { var subjectAsArray = (Array)subject; - return AssertionScope.Current + assertionChain .ForCondition(subjectAsArray.Rank == expectation.Rank) .FailWith("Expected {context:array} to have {0} dimension(s), but it has {1}.", expectation.Rank, subjectAsArray.Rank); + + return assertionChain.Succeeded; } } diff --git a/Src/FluentAssertions/Equivalency/Node.cs b/Src/FluentAssertions/Equivalency/Node.cs index 4f264a3d38..96ee51e87f 100644 --- a/Src/FluentAssertions/Equivalency/Node.cs +++ b/Src/FluentAssertions/Equivalency/Node.cs @@ -10,11 +10,18 @@ internal class Node : INode { private static readonly Regex MatchFirstIndex = new(@"^\[[0-9]+\]$"); + private GetSubjectId subjectIdProvider; + private string path; private string name; private string pathAndName; + private string cachedSubjectId; - public GetSubjectId GetSubjectId { get; protected set; } = () => string.Empty; + public GetSubjectId GetSubjectId + { + get => () => cachedSubjectId ??= subjectIdProvider(); + protected init => subjectIdProvider = value; + } public Type Type { get; protected set; } @@ -76,7 +83,7 @@ public static INode From(GetSubjectId getSubjectId) { return new Node { - GetSubjectId = () => getSubjectId() ?? "root", + subjectIdProvider = () => getSubjectId() ?? "root", Name = string.Empty, Path = string.Empty, Type = typeof(T), diff --git a/Src/FluentAssertions/Equivalency/Steps/AssertionResultSet.cs b/Src/FluentAssertions/Equivalency/Steps/AssertionResultSet.cs index b1b9b7cfe2..8bde0c02aa 100644 --- a/Src/FluentAssertions/Equivalency/Steps/AssertionResultSet.cs +++ b/Src/FluentAssertions/Equivalency/Steps/AssertionResultSet.cs @@ -22,7 +22,7 @@ public void AddSet(object key, string[] failures) } /// - /// Returns the closest match compared to the set identified by the provided or + /// Returns the closest match compared to the set identified by the provided or /// an empty array if one of the results represents a successful assertion. /// /// diff --git a/Src/FluentAssertions/Equivalency/Steps/AssertionRuleEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/AssertionRuleEquivalencyStep.cs index ce2116a0fe..2a1990786b 100644 --- a/Src/FluentAssertions/Equivalency/Steps/AssertionRuleEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/AssertionRuleEquivalencyStep.cs @@ -10,15 +10,15 @@ public class AssertionRuleEquivalencyStep : IEquivalencyStep { private readonly Func predicate; private readonly string description; - private readonly Action> assertion; + private readonly Action> assertionAction; private readonly AutoConversionStep converter = new(); public AssertionRuleEquivalencyStep( Expression> predicate, - Action> assertion) + Action> assertionAction) { this.predicate = predicate.Compile(); - this.assertion = assertion; + this.assertionAction = assertionAction; description = predicate.ToString(); } @@ -49,7 +49,6 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon { // Try again after conversion success = ExecuteAssertion(comparands, context); - if (success) { // If the assertion succeeded after conversion, discard the failures from @@ -67,30 +66,33 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon private bool ExecuteAssertion(Comparands comparands, IEquivalencyValidationContext context) { bool subjectIsNull = comparands.Subject is null; + bool expectationIsNull = comparands.Expectation is null; - bool subjectIsValidType = - AssertionScope.Current + var assertionChain = AssertionChain.GetOrCreate().For(context); + + assertionChain .ForCondition(subjectIsNull || comparands.Subject.GetType().IsSameOrInherits(typeof(TSubject))) .FailWith("Expected " + context.CurrentNode.Description + " from subject to be a {0}{reason}, but found a {1}.", - typeof(TSubject), comparands.Subject?.GetType()); - - bool expectationIsNull = comparands.Expectation is null; - - bool expectationIsValidType = - AssertionScope.Current + typeof(TSubject), comparands.Subject?.GetType()) + .Then .ForCondition(expectationIsNull || comparands.Expectation.GetType().IsSameOrInherits(typeof(TSubject))) .FailWith( "Expected " + context.CurrentNode.Description + " from expectation to be a {0}{reason}, but found a {1}.", typeof(TSubject), comparands.Expectation?.GetType()); - if (subjectIsValidType && expectationIsValidType) + if (assertionChain.Succeeded) { if ((subjectIsNull || expectationIsNull) && !CanBeNull()) { return false; } - assertion(AssertionContext.CreateFrom(comparands, context)); + // Caller identitification should not get confused about invoking a Should within the assertion action + string callerIdentifier = context.CurrentNode.Description; + assertionChain.OverrideCallerIdentifier(() => callerIdentifier); + assertionChain.ReuseOnce(); + + assertionAction(AssertionContext.CreateFrom(comparands, context)); return true; } diff --git a/Src/FluentAssertions/Equivalency/Steps/DictionaryEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/DictionaryEquivalencyStep.cs index 67dff5dbf2..2b9f777887 100644 --- a/Src/FluentAssertions/Equivalency/Steps/DictionaryEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/DictionaryEquivalencyStep.cs @@ -1,5 +1,6 @@ using System.Collections; using System.Diagnostics.CodeAnalysis; +using FluentAssertions.Common; using FluentAssertions.Execution; using static System.FormattableString; @@ -8,13 +9,16 @@ namespace FluentAssertions.Equivalency.Steps; public class DictionaryEquivalencyStep : EquivalencyStep { [SuppressMessage("ReSharper", "PossibleNullReferenceException")] - protected override EquivalencyResult OnHandle(Comparands comparands, IEquivalencyValidationContext context, - IValidateChildNodeEquivalency nested) + protected override EquivalencyResult OnHandle(Comparands comparands, + IEquivalencyValidationContext context, + IValidateChildNodeEquivalency nestedValidator) { var subject = comparands.Subject as IDictionary; var expectation = comparands.Expectation as IDictionary; - if (PreconditionsAreMet(expectation, subject) && expectation is not null) + var assertionChain = AssertionChain.GetOrCreate().For(context); + + if (PreconditionsAreMet(expectation, subject, assertionChain) && expectation is not null) { foreach (object key in expectation.Keys) { @@ -23,8 +27,7 @@ protected override EquivalencyResult OnHandle(Comparands comparands, IEquivalenc context.Tracer.WriteLine(member => Invariant($"Recursing into dictionary item {key} at {member.Description}")); - nested.AssertEquivalencyOf(new Comparands(subject[key], expectation[key], typeof(object)), - context.AsDictionaryItem(key)); + nestedValidator.AssertEquivalencyOf(new Comparands(subject[key], expectation[key], typeof(object)), context.AsDictionaryItem(key)); } else { @@ -32,6 +35,7 @@ protected override EquivalencyResult OnHandle(Comparands comparands, IEquivalenc Invariant( $"Comparing dictionary item {key} at {member.Description} between subject and expectation")); + assertionChain.WithCallerPostfix($"[{key.ToFormattedString()}]").ReuseOnce(); subject[key].Should().Be(expectation[key], context.Reason.FormattedMessage, context.Reason.Arguments); } } @@ -40,32 +44,38 @@ protected override EquivalencyResult OnHandle(Comparands comparands, IEquivalenc return EquivalencyResult.EquivalencyProven; } - private static bool PreconditionsAreMet(IDictionary expectation, IDictionary subject) + private static bool PreconditionsAreMet(IDictionary expectation, IDictionary subject, AssertionChain assertionChain) { - return AssertIsDictionary(subject) - && AssertEitherIsNotNull(expectation, subject) - && AssertSameLength(expectation, subject); + return AssertIsDictionary(subject, assertionChain) + && AssertEitherIsNotNull(expectation, subject, assertionChain) + && AssertSameLength(expectation, subject, assertionChain); } - private static bool AssertEitherIsNotNull(IDictionary expectation, IDictionary subject) + private static bool AssertEitherIsNotNull(IDictionary expectation, IDictionary subject, AssertionChain assertionChain) { - return AssertionScope.Current + assertionChain .ForCondition((expectation is null && subject is null) || expectation is not null) .FailWith("Expected {context:subject} to be {0}{reason}, but found {1}.", null, subject); + + return assertionChain.Succeeded; } - private static bool AssertIsDictionary(IDictionary subject) + private static bool AssertIsDictionary(IDictionary subject, AssertionChain assertionChain) { - return AssertionScope.Current + assertionChain .ForCondition(subject is not null) .FailWith("Expected {context:subject} to be a dictionary, but it is not."); + + return assertionChain.Succeeded; } - private static bool AssertSameLength(IDictionary expectation, IDictionary subject) + private static bool AssertSameLength(IDictionary expectation, IDictionary subject, AssertionChain assertionChain) { - return AssertionScope.Current + assertionChain .ForCondition(expectation is null || subject.Keys.Count == expectation.Keys.Count) .FailWith("Expected {context:subject} to be a dictionary with {0} item(s), but it only contains {1} item(s).", expectation?.Keys.Count, subject?.Keys.Count); + + return assertionChain.Succeeded; } } diff --git a/Src/FluentAssertions/Equivalency/Steps/DictionaryInterfaceInfo.cs b/Src/FluentAssertions/Equivalency/Steps/DictionaryInterfaceInfo.cs index f24ac8a726..e0ca76da14 100644 --- a/Src/FluentAssertions/Equivalency/Steps/DictionaryInterfaceInfo.cs +++ b/Src/FluentAssertions/Equivalency/Steps/DictionaryInterfaceInfo.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Reflection; using FluentAssertions.Common; -using FluentAssertions.Execution; namespace FluentAssertions.Equivalency.Steps; @@ -73,11 +72,8 @@ public static DictionaryInterfaceInfo FindFromWithKey(Type target, string role, if (suitableDictionaryInterfaces.Length > 1) { - // SMELL: Code could be written to handle this better, but is it really worth the effort? - AssertionScope.Current.FailWith( + throw new InvalidOperationException( $"The {role} implements multiple IDictionary interfaces taking a key of {key}. "); - - return null; } if (suitableDictionaryInterfaces.Length == 0) diff --git a/Src/FluentAssertions/Equivalency/Steps/EnumEqualityStep.cs b/Src/FluentAssertions/Equivalency/Steps/EnumEqualityStep.cs index a0251192ca..a089bdc445 100644 --- a/Src/FluentAssertions/Equivalency/Steps/EnumEqualityStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/EnumEqualityStep.cs @@ -18,7 +18,9 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon return EquivalencyResult.ContinueWithNext; } - bool succeeded = Execute.Assertion + var assertionChain = AssertionChain.GetOrCreate().For(context); + + assertionChain .ForCondition(comparands.Subject?.GetType().IsEnum == true) .BecauseOf(context.Reason) .FailWith(() => @@ -31,16 +33,16 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon comparands.Subject); }); - if (succeeded) + if (assertionChain.Succeeded) { switch (context.Options.EnumEquivalencyHandling) { case EnumEquivalencyHandling.ByValue: - HandleByValue(comparands, context.Reason); + HandleByValue(assertionChain, comparands, context.Reason); break; case EnumEquivalencyHandling.ByName: - HandleByName(comparands, context.Reason); + HandleByName(assertionChain, comparands, context.Reason); break; default: @@ -51,12 +53,12 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon return EquivalencyResult.EquivalencyProven; } - private static void HandleByValue(Comparands comparands, Reason reason) + private static void HandleByValue(AssertionChain assertionChain, Comparands comparands, Reason reason) { decimal? subjectsUnderlyingValue = ExtractDecimal(comparands.Subject); decimal? expectationsUnderlyingValue = ExtractDecimal(comparands.Expectation); - Execute.Assertion + assertionChain .ForCondition(subjectsUnderlyingValue == expectationsUnderlyingValue) .BecauseOf(reason) .FailWith(() => @@ -69,12 +71,12 @@ private static void HandleByValue(Comparands comparands, Reason reason) }); } - private static void HandleByName(Comparands comparands, Reason reason) + private static void HandleByName(AssertionChain assertionChain, Comparands comparands, Reason reason) { string subject = comparands.Subject.ToString(); string expected = comparands.Expectation.ToString(); - Execute.Assertion + assertionChain .ForCondition(subject == expected) .BecauseOf(reason) .FailWith(() => diff --git a/Src/FluentAssertions/Equivalency/Steps/EnumerableEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/EnumerableEquivalencyStep.cs index 45fc7d57e1..1b6906ba58 100644 --- a/Src/FluentAssertions/Equivalency/Steps/EnumerableEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/EnumerableEquivalencyStep.cs @@ -15,9 +15,11 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon return EquivalencyResult.ContinueWithNext; } - if (AssertSubjectIsCollection(comparands.Subject)) + var assertionChain = AssertionChain.GetOrCreate().For(context); + + if (AssertSubjectIsCollection(assertionChain, comparands.Subject)) { - var validator = new EnumerableEquivalencyValidator(valueChildNodes, context) + var validator = new EnumerableEquivalencyValidator(assertionChain, valueChildNodes, context) { Recursive = context.CurrentNode.IsRoot || context.Options.IsRecursive, OrderingRules = context.Options.OrderingRules @@ -29,20 +31,20 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon return EquivalencyResult.EquivalencyProven; } - private static bool AssertSubjectIsCollection(object subject) + private static bool AssertSubjectIsCollection(AssertionChain assertionChain, object subject) { - bool conditionMet = AssertionScope.Current + assertionChain .ForCondition(subject is not null) .FailWith("Expected a collection, but {context:Subject} is ."); - if (conditionMet) + if (assertionChain.Succeeded) { - conditionMet = AssertionScope.Current + assertionChain .ForCondition(IsCollection(subject.GetType())) .FailWith("Expected a collection, but {context:Subject} is of a non-collection type."); } - return conditionMet; + return assertionChain.Succeeded; } private static bool IsCollection(Type type) diff --git a/Src/FluentAssertions/Equivalency/Steps/EnumerableEquivalencyValidator.cs b/Src/FluentAssertions/Equivalency/Steps/EnumerableEquivalencyValidator.cs index 3a58151a4c..c2e7d5640f 100644 --- a/Src/FluentAssertions/Equivalency/Steps/EnumerableEquivalencyValidator.cs +++ b/Src/FluentAssertions/Equivalency/Steps/EnumerableEquivalencyValidator.cs @@ -16,13 +16,16 @@ internal class EnumerableEquivalencyValidator #region Private Definitions + private readonly AssertionChain assertionChain; private readonly IValidateChildNodeEquivalency parent; private readonly IEquivalencyValidationContext context; #endregion - public EnumerableEquivalencyValidator(IValidateChildNodeEquivalency parent, IEquivalencyValidationContext context) + public EnumerableEquivalencyValidator(AssertionChain assertionChain, IValidateChildNodeEquivalency parent, + IEquivalencyValidationContext context) { + this.assertionChain = assertionChain; this.parent = parent; this.context = context; Recursive = false; @@ -54,24 +57,25 @@ public void Execute(object[] subject, T[] expectation) } } - private static bool AssertIsNotNull(object expectation, object[] subject) + private bool AssertIsNotNull(object expectation, object[] subject) { - return AssertionScope.Current + assertionChain .ForCondition(expectation is not null) - .FailWith("Expected {context:subject} to be , but found {0}.", new object[] { subject }); + .FailWith("Expected {context:subject} to be , but found {0}.", [subject]); + + return assertionChain.Succeeded; } - private static Continuation AssertCollectionsHaveSameCount(ICollection subject, ICollection expectation) + private bool AssertCollectionsHaveSameCount(ICollection subject, ICollection expectation) { - return AssertionScope.Current - .WithExpectation("Expected {context:subject} to be a collection with {0} item(s){reason}", expectation.Count) + assertionChain .AssertEitherCollectionIsNotEmpty(subject, expectation) .Then .AssertCollectionHasEnoughItems(subject, expectation) .Then - .AssertCollectionHasNotTooManyItems(subject, expectation) - .Then - .ClearExpectation(); + .AssertCollectionHasNotTooManyItems(subject, expectation); + + return assertionChain.Succeeded; } private void AssertElementGraphEquivalency(object[] subjects, T[] expectations, INode currentNode) @@ -101,11 +105,9 @@ private void AssertElementGraphEquivalencyWithStrictOrdering(object[] subject $"Strictly comparing expectation {expectation} at {member.Description} to item with index {index} in {subjects}")); bool succeeded = StrictlyMatchAgainst(subjects, expectation, index); - if (!succeeded) { failedCount++; - if (failedCount >= FailedItemsFastFailThreshold) { context.Tracer.WriteLine(member => @@ -185,7 +187,7 @@ private bool LooselyMatchAgainst(IList subjects, T expectation, int e foreach (string failure in results.GetTheFailuresForTheSetWithTheFewestFailures(expectationIndex)) { - AssertionScope.Current.AddPreFormattedFailure(failure); + assertionChain.AddPreFormattedFailure(failure); } return indexToBeRemoved != -1; @@ -195,8 +197,7 @@ private string[] TryToMatch(object subject, T expectation, int expectationInd { using var scope = new AssertionScope(); - parent.AssertEquivalencyOf(new Comparands(subject, expectation, typeof(T)), - context.AsCollectionItem(expectationIndex)); + parent.AssertEquivalencyOf(new Comparands(subject, expectation, typeof(T)), context.AsCollectionItem(expectationIndex)); return scope.Discard(); } diff --git a/Src/FluentAssertions/Equivalency/Steps/EnumerableEquivalencyValidatorExtensions.cs b/Src/FluentAssertions/Equivalency/Steps/EnumerableEquivalencyValidatorExtensions.cs index 473811c746..f3f3d00e21 100644 --- a/Src/FluentAssertions/Equivalency/Steps/EnumerableEquivalencyValidatorExtensions.cs +++ b/Src/FluentAssertions/Equivalency/Steps/EnumerableEquivalencyValidatorExtensions.cs @@ -6,38 +6,46 @@ namespace FluentAssertions.Equivalency.Steps; internal static class EnumerableEquivalencyValidatorExtensions { - public static Continuation AssertEitherCollectionIsNotEmpty(this IAssertionScope scope, ICollection subject, + public static Continuation AssertEitherCollectionIsNotEmpty(this AssertionChain assertionChain, + ICollection subject, ICollection expectation) { - return scope - .ForCondition(subject.Count > 0 || expectation.Count == 0) - .FailWith(", but found an empty collection.") - .Then - .ForCondition(subject.Count == 0 || expectation.Count > 0) - .FailWith($", but {{0}}{Environment.NewLine}contains {{1}} item(s).", - subject, - subject.Count); + return assertionChain + .WithExpectation("Expected {context:subject} to be a collection with {0} item(s){reason}", expectation.Count, + chain => chain + .ForCondition(subject.Count > 0 || expectation.Count == 0) + .FailWith(", but found an empty collection.") + .Then + .ForCondition(subject.Count == 0 || expectation.Count > 0) + .FailWith($", but {{0}}{Environment.NewLine}contains {{1}} item(s).", + subject, + subject.Count)); } - public static Continuation AssertCollectionHasEnoughItems(this IAssertionScope scope, ICollection subject, + public static Continuation AssertCollectionHasEnoughItems(this AssertionChain assertionChain, ICollection subject, ICollection expectation) { - return scope - .ForCondition(subject.Count >= expectation.Count) - .FailWith($", but {{0}}{Environment.NewLine}contains {{1}} item(s) less than{Environment.NewLine}{{2}}.", - subject, - expectation.Count - subject.Count, - expectation); + return assertionChain + .WithExpectation("Expected {context:subject} to be a collection with {0} item(s){reason}", expectation.Count, + chain => chain + .ForCondition(subject.Count >= expectation.Count) + .FailWith($", but {{0}}{Environment.NewLine}contains {{1}} item(s) less than{Environment.NewLine}{{2}}.", + subject, + expectation.Count - subject.Count, + expectation)); } - public static Continuation AssertCollectionHasNotTooManyItems(this IAssertionScope scope, ICollection subject, + public static Continuation AssertCollectionHasNotTooManyItems(this AssertionChain assertionChain, + ICollection subject, ICollection expectation) { - return scope - .ForCondition(subject.Count <= expectation.Count) - .FailWith($", but {{0}}{Environment.NewLine}contains {{1}} item(s) more than{Environment.NewLine}{{2}}.", - subject, - subject.Count - expectation.Count, - expectation); + return assertionChain + .WithExpectation("Expected {context:subject} to be a collection with {0} item(s){reason}", expectation.Count, + chain => chain + .ForCondition(subject.Count <= expectation.Count) + .FailWith($", but {{0}}{Environment.NewLine}contains {{1}} item(s) more than{Environment.NewLine}{{2}}.", + subject, + subject.Count - expectation.Count, + expectation)); } } diff --git a/Src/FluentAssertions/Equivalency/Steps/EqualityComparerEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/EqualityComparerEquivalencyStep.cs index 2ba66b8c9e..6bd954d16b 100644 --- a/Src/FluentAssertions/Equivalency/Steps/EqualityComparerEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/EqualityComparerEquivalencyStep.cs @@ -29,7 +29,8 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon return EquivalencyResult.ContinueWithNext; } - Execute.Assertion + AssertionChain.GetOrCreate() + .For(context) .BecauseOf(context.Reason.FormattedMessage, context.Reason.Arguments) .ForCondition(comparands.Subject is T) .FailWith("Expected {context:object} to be of type {0}{because}, but found {1}", typeof(T), comparands.Subject) diff --git a/Src/FluentAssertions/Equivalency/Steps/GenericDictionaryEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/GenericDictionaryEquivalencyStep.cs index f10a166a19..01b58bb808 100644 --- a/Src/FluentAssertions/Equivalency/Steps/GenericDictionaryEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/GenericDictionaryEquivalencyStep.cs @@ -11,7 +11,7 @@ public class GenericDictionaryEquivalencyStep : IEquivalencyStep { #pragma warning disable SA1110 // Allow opening parenthesis on new line to reduce line length private static readonly MethodInfo AssertDictionaryEquivalenceMethod = - new Action, IDictionary> (AssertDictionaryEquivalence).GetMethodInfo().GetGenericMethodDefinition(); #pragma warning restore SA1110 @@ -37,10 +37,13 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon return EquivalencyResult.ContinueWithNext; } - if (IsNotNull(comparands.Subject) - && EnsureSubjectIsOfTheExpectedDictionaryType(comparands, expectedDictionary) is { } actualDictionary) + var assertionChain = AssertionChain.GetOrCreate().For(context); + + if (IsNotNull(assertionChain, comparands.Subject) + && EnsureSubjectIsOfTheExpectedDictionaryType(assertionChain, comparands, expectedDictionary) is { } actualDictionary) { - AssertDictionaryEquivalence(comparands, context, valueChildNodes, actualDictionary, expectedDictionary); + AssertDictionaryEquivalence(comparands, assertionChain, context, valueChildNodes, actualDictionary, + expectedDictionary); } return EquivalencyResult.EquivalencyProven; @@ -57,14 +60,17 @@ private static bool IsNonGenericDictionary(object subject) @interface.IsGenericType && @interface.GetGenericTypeDefinition() == typeof(IDictionary<,>)); } - private static bool IsNotNull(object subject) + private static bool IsNotNull(AssertionChain assertionChain, object subject) { - return AssertionScope.Current + assertionChain .ForCondition(subject is not null) .FailWith("Expected {context:Subject} not to be {0}{reason}.", new object[] { null }); + + return assertionChain.Succeeded; } - private static DictionaryInterfaceInfo EnsureSubjectIsOfTheExpectedDictionaryType(Comparands comparands, + private static DictionaryInterfaceInfo EnsureSubjectIsOfTheExpectedDictionaryType(AssertionChain assertionChain, + Comparands comparands, DictionaryInterfaceInfo expectedDictionary) { var actualDictionary = DictionaryInterfaceInfo.FindFromWithKey(comparands.Subject.GetType(), "subject", @@ -78,7 +84,7 @@ private static DictionaryInterfaceInfo EnsureSubjectIsOfTheExpectedDictionaryTyp if (actualDictionary is null) { - AssertionScope.Current.FailWith( + assertionChain.FailWith( "Expected {context:subject} to be a dictionary or collection of key-value pairs that is keyed to " + $"type {expectedDictionary.Key}."); } @@ -87,7 +93,9 @@ private static DictionaryInterfaceInfo EnsureSubjectIsOfTheExpectedDictionaryTyp } private static void FailWithLengthDifference( - IDictionary subject, IDictionary expectation) + IDictionary subject, + IDictionary expectation, + AssertionChain assertionChain) // Type constraint of TExpectedKey is asymmetric in regards to TSubjectKey // but it is valid. This constraint is implicitly enforced by the dictionary interface info which is called before @@ -99,19 +107,18 @@ private static void FailWithLengthDifference 0; bool hasAdditionalKeys = keyDifference.AdditionalKeys.Count > 0; - Execute.Assertion - .WithExpectation("Expected {context:subject} to be a dictionary with {0} item(s){reason}, ", expectation.Count) - .ForCondition(!hasMissingKeys || hasAdditionalKeys) - .FailWith("but it misses key(s) {0}", keyDifference.MissingKeys) - .Then - .ForCondition(hasMissingKeys || !hasAdditionalKeys) - .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) - .Then - .ClearExpectation(); + assertionChain + .WithExpectation("Expected {context:subject} to be a dictionary with {0} item(s){reason}, ", expectation.Count, + chain => chain + .ForCondition(!hasMissingKeys || hasAdditionalKeys) + .FailWith("but it misses key(s) {0}", keyDifference.MissingKeys) + .Then + .ForCondition(hasMissingKeys || !hasAdditionalKeys) + .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)); } private static KeyDifference CalculateKeyDifference(missingKeys, additionalKeys); } - private static void AssertDictionaryEquivalence(Comparands comparands, IEquivalencyValidationContext context, - IValidateChildNodeEquivalency parent, DictionaryInterfaceInfo actualDictionary, DictionaryInterfaceInfo expectedDictionary) + private static void AssertDictionaryEquivalence(Comparands comparands, AssertionChain assertionChain, + IEquivalencyValidationContext context, + IValidateChildNodeEquivalency parent, DictionaryInterfaceInfo actualDictionary, + DictionaryInterfaceInfo expectedDictionary) { AssertDictionaryEquivalenceMethod .MakeGenericMethod(actualDictionary.Key, actualDictionary.Value, expectedDictionary.Key, expectedDictionary.Value) - .Invoke(null, [context, parent, context.Options, comparands.Subject, comparands.Expectation]); + .Invoke(null, [assertionChain, context, parent, context.Options, comparands.Subject, comparands.Expectation]); } private static void AssertDictionaryEquivalence( + AssertionChain assertionChain, EquivalencyValidationContext context, IValidateChildNodeEquivalency parent, IEquivalencyOptions options, @@ -165,7 +175,7 @@ private static void AssertDictionaryEquivalence(key)); + parent.AssertEquivalencyOf(nestedComparands, context.AsDictionaryItem(key)); } } else { + assertionChain.ReuseOnce(); subjectValue.Should().Be(expectation[key], context.Reason.FormattedMessage, context.Reason.Arguments); } } else { - AssertionScope.Current + assertionChain .BecauseOf(context.Reason) .FailWith("Expected {context:subject} to contain key {0}{reason}.", key); } diff --git a/Src/FluentAssertions/Equivalency/Steps/GenericEnumerableEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/GenericEnumerableEquivalencyStep.cs index 1ff5fb4148..f7e4e7297f 100644 --- a/Src/FluentAssertions/Equivalency/Steps/GenericEnumerableEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/GenericEnumerableEquivalencyStep.cs @@ -27,15 +27,17 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon Type[] interfaceTypes = GetIEnumerableInterfaces(expectedType); - AssertionScope.Current + var assertionChain = AssertionChain.GetOrCreate().For(context); + + assertionChain .ForCondition(interfaceTypes.Length == 1) .FailWith(() => new FailReason("{context:Expectation} implements {0}, so cannot determine which one " + "to use for asserting the equivalency of the collection. ", interfaceTypes.Select(type => "IEnumerable<" + type.GetGenericArguments().Single() + ">"))); - if (AssertSubjectIsCollection(comparands.Subject)) + if (AssertSubjectIsCollection(assertionChain, comparands.Subject)) { - var validator = new EnumerableEquivalencyValidator(valueChildNodes, context) + var validator = new EnumerableEquivalencyValidator(assertionChain, valueChildNodes, context) { Recursive = context.CurrentNode.IsRoot || context.Options.IsRecursive, OrderingRules = context.Options.OrderingRules @@ -62,20 +64,20 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon private static void HandleImpl(EnumerableEquivalencyValidator validator, object[] subject, IEnumerable expectation) => validator.Execute(subject, ToArray(expectation)); - private static bool AssertSubjectIsCollection(object subject) + private static bool AssertSubjectIsCollection(AssertionChain assertionChain, object subject) { - bool conditionMet = AssertionScope.Current + assertionChain .ForCondition(subject is not null) .FailWith("Expected {context:subject} not to be {0}.", new object[] { null }); - if (conditionMet) + if (assertionChain.Succeeded) { - conditionMet = AssertionScope.Current + assertionChain .ForCondition(IsCollection(subject.GetType())) .FailWith("Expected {context:subject} to be a collection, but it was a {0}", subject.GetType()); } - return conditionMet; + return assertionChain.Succeeded; } private static bool IsCollection(Type type) diff --git a/Src/FluentAssertions/Equivalency/Steps/SimpleEqualityEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/SimpleEqualityEquivalencyStep.cs index 0e2b955cf4..af3598ec1d 100644 --- a/Src/FluentAssertions/Equivalency/Steps/SimpleEqualityEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/SimpleEqualityEquivalencyStep.cs @@ -1,3 +1,5 @@ +using FluentAssertions.Execution; + namespace FluentAssertions.Equivalency.Steps; public class SimpleEqualityEquivalencyStep : IEquivalencyStep @@ -7,6 +9,10 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon { if (!context.Options.IsRecursive && !context.CurrentNode.IsRoot) { + AssertionChain.GetOrCreate() + .For(context) + .ReuseOnce(); + comparands.Subject.Should().Be(comparands.Expectation, context.Reason.FormattedMessage, context.Reason.Arguments); return EquivalencyResult.EquivalencyProven; diff --git a/Src/FluentAssertions/Equivalency/Steps/StringEqualityEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/StringEqualityEquivalencyStep.cs index 00a736626c..ec0e70fd24 100644 --- a/Src/FluentAssertions/Equivalency/Steps/StringEqualityEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/StringEqualityEquivalencyStep.cs @@ -15,21 +15,23 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon return EquivalencyResult.ContinueWithNext; } - if (!ValidateAgainstNulls(comparands, context.CurrentNode)) + var assertionChain = AssertionChain.GetOrCreate().For(context); + + if (!ValidateAgainstNulls(assertionChain, comparands, context.CurrentNode)) { return EquivalencyResult.EquivalencyProven; } - bool subjectIsString = ValidateSubjectIsString(comparands, context.CurrentNode); + bool subjectIsString = ValidateSubjectIsString(assertionChain, comparands, context.CurrentNode); if (subjectIsString) { string subject = (string)comparands.Subject; string expectation = (string)comparands.Expectation; + assertionChain.ReuseOnce(); subject.Should() - .Be(expectation, CreateOptions(context.Options), - context.Reason.FormattedMessage, context.Reason.Arguments); + .Be(expectation, CreateOptions(context.Options), context.Reason.FormattedMessage, context.Reason.Arguments); } return EquivalencyResult.EquivalencyProven; @@ -67,7 +69,7 @@ private static Func, EquivalencyOptions> return o; }; - private static bool ValidateAgainstNulls(Comparands comparands, INode currentNode) + private static bool ValidateAgainstNulls(AssertionChain assertionChain, Comparands comparands, INode currentNode) { object expected = comparands.Expectation; object subject = comparands.Subject; @@ -76,7 +78,7 @@ private static bool ValidateAgainstNulls(Comparands comparands, INode currentNod if (onlyOneNull) { - AssertionScope.Current.FailWith( + assertionChain.FailWith( $"Expected {currentNode.Description} to be {{0}}{{reason}}, but found {{1}}.", expected, subject); return false; @@ -85,16 +87,17 @@ private static bool ValidateAgainstNulls(Comparands comparands, INode currentNod return true; } - private static bool ValidateSubjectIsString(Comparands comparands, INode currentNode) + private static bool ValidateSubjectIsString(AssertionChain assertionChain, Comparands comparands, INode currentNode) { if (comparands.Subject is string) { return true; } - return - AssertionScope.Current - .FailWith($"Expected {currentNode} to be {{0}}, but found {{1}}.", - comparands.RuntimeType, comparands.Subject.GetType()); + assertionChain.FailWith( + $"Expected {currentNode} to be {{0}}, but found {{1}}.", + comparands.RuntimeType, comparands.Subject.GetType()); + + return assertionChain.Succeeded; } } diff --git a/Src/FluentAssertions/Equivalency/Steps/StructuralEqualityEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/StructuralEqualityEquivalencyStep.cs index 23d022e952..0c43280b73 100644 --- a/Src/FluentAssertions/Equivalency/Steps/StructuralEqualityEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/StructuralEqualityEquivalencyStep.cs @@ -15,9 +15,11 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon return EquivalencyResult.ContinueWithNext; } + var assertionChain = AssertionChain.GetOrCreate().For(context); + if (comparands.Expectation is null) { - AssertionScope.Current + assertionChain .BecauseOf(context.Reason) .FailWith( "Expected {context:subject} to be {reason}, but found {0}.", @@ -25,7 +27,7 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon } else if (comparands.Subject is null) { - AssertionScope.Current + assertionChain .BecauseOf(context.Reason) .FailWith( "Expected {context:object} to be {0}{reason}, but found {1}.", @@ -55,7 +57,9 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon private static void AssertMemberEquality(Comparands comparands, IEquivalencyValidationContext context, IValidateChildNodeEquivalency parent, IMember selectedMember, IEquivalencyOptions options) { - IMember matchingMember = FindMatchFor(selectedMember, context.CurrentNode, comparands.Subject, options); + var assertionChain = AssertionChain.GetOrCreate().For(context); + + IMember matchingMember = FindMatchFor(selectedMember, context.CurrentNode, comparands.Subject, options, assertionChain); if (matchingMember is not null) { @@ -78,11 +82,11 @@ private static void AssertMemberEquality(Comparands comparands, IEquivalencyVali } private static IMember FindMatchFor(IMember selectedMember, INode currentNode, object subject, - IEquivalencyOptions config) + IEquivalencyOptions config, AssertionChain assertionChain) { IEnumerable query = from rule in config.MatchingRules - let match = rule.Match(selectedMember, subject, currentNode, config) + let match = rule.Match(selectedMember, subject, currentNode, config, assertionChain) where match is not null select match; diff --git a/Src/FluentAssertions/Equivalency/Steps/ValueTypeEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/ValueTypeEquivalencyStep.cs index c9270e421b..ee7d1b400d 100644 --- a/Src/FluentAssertions/Equivalency/Steps/ValueTypeEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/ValueTypeEquivalencyStep.cs @@ -1,4 +1,5 @@ using System; +using FluentAssertions.Execution; namespace FluentAssertions.Equivalency.Steps; @@ -26,6 +27,10 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon return $"Treating {member.Description} as a value type because {strategyName}."; }); + AssertionChain.GetOrCreate() + .For(context) + .ReuseOnce(); + comparands.Subject.Should().Be(comparands.Expectation, context.Reason.FormattedMessage, context.Reason.Arguments); return EquivalencyResult.EquivalencyProven; diff --git a/Src/FluentAssertions/Equivalency/Steps/XAttributeEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/XAttributeEquivalencyStep.cs index 1f960da7b9..c403ed74d9 100644 --- a/Src/FluentAssertions/Equivalency/Steps/XAttributeEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/XAttributeEquivalencyStep.cs @@ -1,15 +1,19 @@ using System.Xml.Linq; +using FluentAssertions.Execution; namespace FluentAssertions.Equivalency.Steps; public class XAttributeEquivalencyStep : EquivalencyStep { - protected override EquivalencyResult OnHandle(Comparands comparands, IEquivalencyValidationContext context, - IValidateChildNodeEquivalency nested) + protected override EquivalencyResult OnHandle(Comparands comparands, + IEquivalencyValidationContext context, + IValidateChildNodeEquivalency nestedValidator) { var subject = (XAttribute)comparands.Subject; var expectation = (XAttribute)comparands.Expectation; + AssertionChain.GetOrCreate().For(context).ReuseOnce(); + subject.Should().Be(expectation, context.Reason.FormattedMessage, context.Reason.Arguments); return EquivalencyResult.EquivalencyProven; diff --git a/Src/FluentAssertions/Equivalency/Steps/XDocumentEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/XDocumentEquivalencyStep.cs index b7556b4788..b88921f45b 100644 --- a/Src/FluentAssertions/Equivalency/Steps/XDocumentEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/XDocumentEquivalencyStep.cs @@ -1,15 +1,19 @@ using System.Xml.Linq; +using FluentAssertions.Execution; namespace FluentAssertions.Equivalency.Steps; public class XDocumentEquivalencyStep : EquivalencyStep { - protected override EquivalencyResult OnHandle(Comparands comparands, IEquivalencyValidationContext context, - IValidateChildNodeEquivalency nested) + protected override EquivalencyResult OnHandle(Comparands comparands, + IEquivalencyValidationContext context, + IValidateChildNodeEquivalency nestedValidator) { var subject = (XDocument)comparands.Subject; var expectation = (XDocument)comparands.Expectation; + AssertionChain.GetOrCreate().For(context).ReuseOnce(); + subject.Should().BeEquivalentTo(expectation, context.Reason.FormattedMessage, context.Reason.Arguments); return EquivalencyResult.EquivalencyProven; diff --git a/Src/FluentAssertions/Equivalency/Steps/XElementEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/XElementEquivalencyStep.cs index 8e01b45938..9ed4e2d503 100644 --- a/Src/FluentAssertions/Equivalency/Steps/XElementEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/XElementEquivalencyStep.cs @@ -1,15 +1,19 @@ using System.Xml.Linq; +using FluentAssertions.Execution; namespace FluentAssertions.Equivalency.Steps; public class XElementEquivalencyStep : EquivalencyStep { - protected override EquivalencyResult OnHandle(Comparands comparands, IEquivalencyValidationContext context, - IValidateChildNodeEquivalency nested) + protected override EquivalencyResult OnHandle(Comparands comparands, + IEquivalencyValidationContext context, + IValidateChildNodeEquivalency nestedValidator) { var subject = (XElement)comparands.Subject; var expectation = (XElement)comparands.Expectation; + AssertionChain.GetOrCreate().For(context).ReuseOnce(); + subject.Should().BeEquivalentTo(expectation, context.Reason.FormattedMessage, context.Reason.Arguments); return EquivalencyResult.EquivalencyProven; diff --git a/Src/FluentAssertions/EventRaisingExtensions.cs b/Src/FluentAssertions/EventRaisingExtensions.cs index 95545d234b..15798cba20 100644 --- a/Src/FluentAssertions/EventRaisingExtensions.cs +++ b/Src/FluentAssertions/EventRaisingExtensions.cs @@ -24,15 +24,16 @@ public static IEventRecording WithSender(this IEventRecording eventRecording, ob { var eventsForSender = new List(); var otherSenders = new List(); + var assertion = AssertionChain.GetOrCreate(); foreach (OccurredEvent @event in eventRecording) { - bool hasSender = Execute.Assertion + assertion .ForCondition(@event.Parameters.Length > 0) .FailWith("Expected event from sender {0}, " + $"but event {eventRecording.EventName} does not have any parameters", expectedSender); - if (hasSender) + if (assertion.Succeeded) { object sender = @event.Parameters[0]; @@ -47,7 +48,7 @@ public static IEventRecording WithSender(this IEventRecording eventRecording, ob } } - Execute.Assertion + assertion .ForCondition(eventsForSender.Count > 0) .FailWith("Expected sender {0}, but found {1}.", () => expectedSender, @@ -84,7 +85,8 @@ public static IEventRecording WithArgs(this IEventRecording eventRecording, E bool foundMatchingEvent = eventsWithMatchingPredicate.Count > 0; - Execute.Assertion + AssertionChain + .GetOrCreate() .ForCondition(foundMatchingEvent) .FailWith("Expected at least one event with some argument of type <{0}> that matches {1}, but found none.", typeof(T), @@ -137,7 +139,8 @@ public static IEventRecording WithArgs(this IEventRecording eventRecording, p if (!foundMatchingEvent) { - Execute.Assertion + AssertionChain + .GetOrCreate() .FailWith( "Expected at least one event with some arguments of type <{0}> that pairwise match {1}, but found none.", typeof(T), diff --git a/Src/FluentAssertions/Events/EventAssertions.cs b/Src/FluentAssertions/Events/EventAssertions.cs index f7e861deca..6d624bda02 100644 --- a/Src/FluentAssertions/Events/EventAssertions.cs +++ b/Src/FluentAssertions/Events/EventAssertions.cs @@ -16,10 +16,12 @@ namespace FluentAssertions.Events; public class EventAssertions : ReferenceTypeAssertions> { private const string PropertyChangedEventName = nameof(INotifyPropertyChanged.PropertyChanged); + private readonly AssertionChain assertionChain; - protected internal EventAssertions(IMonitor monitor) - : base(monitor.Subject) + protected internal EventAssertions(IMonitor monitor, AssertionChain assertionChain) + : base(monitor.Subject, assertionChain) { + this.assertionChain = assertionChain; Monitor = monitor; } @@ -47,7 +49,7 @@ public IEventRecording Raise(string eventName, [StringSyntax("CompositeFormat")] if (!recording.Any()) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Expected object {0} to raise event {1}{reason}, but it did not.", Monitor.Subject, eventName); } @@ -74,7 +76,7 @@ public void NotRaise(string eventName, [StringSyntax("CompositeFormat")] string if (events.Any()) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Expected object {0} to not raise event {1}{reason}, but it did.", Monitor.Subject, eventName); } @@ -104,14 +106,14 @@ public IEventRecording RaisePropertyChangeFor(Expression> proper IEventRecording recording = Monitor.GetRecordingFor(PropertyChangedEventName); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(recording.Any()) .FailWith( "Expected object {0} to raise event {1} for property {2}{reason}, but it did not raise that event at all.", Monitor.Subject, PropertyChangedEventName, propertyName); - if (success) + if (assertionChain.Succeeded) { var actualPropertyNames = recording .SelectMany(@event => @event.Parameters.OfType()) @@ -119,7 +121,7 @@ public IEventRecording RaisePropertyChangeFor(Expression> proper .Distinct() .ToArray(); - Execute.Assertion + assertionChain .ForCondition(actualPropertyNames.Contains(propertyName)) .BecauseOf(because, becauseArgs) .FailWith("Expected object {0} to raise event {1} for property {2}{reason}, but it was only raised for {3}.", @@ -151,7 +153,7 @@ public void NotRaisePropertyChangeFor(Expression> propertyExpres if (propertyName is null) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(!recording.Any()) .FailWith( @@ -160,7 +162,7 @@ public void NotRaisePropertyChangeFor(Expression> propertyExpres } else { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(!recording.Any(@event => @event.IsAffectingPropertyName(propertyName))) .FailWith( diff --git a/Src/FluentAssertions/Events/EventMonitor.cs b/Src/FluentAssertions/Events/EventMonitor.cs index 26ebe0ad29..bcb2efbec6 100644 --- a/Src/FluentAssertions/Events/EventMonitor.cs +++ b/Src/FluentAssertions/Events/EventMonitor.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Reflection; using FluentAssertions.Common; +using FluentAssertions.Execution; namespace FluentAssertions.Events; @@ -66,7 +67,7 @@ public void Clear() public EventAssertions Should() { - return new EventAssertions(this); + return new EventAssertions(this, AssertionChain.GetOrCreate()); } public IEventRecording GetRecordingFor(string eventName) diff --git a/Src/FluentAssertions/ExceptionAssertionsExtensions.cs b/Src/FluentAssertions/ExceptionAssertionsExtensions.cs index 900d1c9b4e..45de65a1a6 100644 --- a/Src/FluentAssertions/ExceptionAssertionsExtensions.cs +++ b/Src/FluentAssertions/ExceptionAssertionsExtensions.cs @@ -167,7 +167,8 @@ public static ExceptionAssertions WithParameterName( params object[] becauseArgs) where TException : ArgumentException { - Execute.Assertion + AssertionChain + .GetOrCreate() .ForCondition(parent.Which.ParamName == paramName) .BecauseOf(because, becauseArgs) .FailWith("Expected exception with parameter name {0}{reason}, but found {1}.", paramName, parent.Which.ParamName); diff --git a/Src/FluentAssertions/Execution/AssertionChain.cs b/Src/FluentAssertions/Execution/AssertionChain.cs new file mode 100644 index 0000000000..970690593d --- /dev/null +++ b/Src/FluentAssertions/Execution/AssertionChain.cs @@ -0,0 +1,355 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Threading; +using FluentAssertions.Common; + +namespace FluentAssertions.Execution; + +public sealed class AssertionChain +{ + private readonly Func getCurrentScope; + private readonly ContextDataDictionary contextData = new(); + private string fallbackIdentifier = "object"; + private Func getCallerIdentifier; + private Func reason; + private bool? succeeded; + + // We need to keep track of this because we don't want the second successful assertion hide the first unsuccessful assertion + private Func expectation; + private string callerPostfix = string.Empty; + + private static readonly AsyncLocal Instance = new(); + + /// + /// Ensures that the next call to will reuse the current instance. + /// + public void ReuseOnce() + { + Instance.Value = this; + } + + /// + /// Indicates whether the previous assertion in the chain was successful. + /// + /// + /// This property is used internally to determine if subsequent assertions + /// should be evaluated based on the result of the previous assertion. + /// + internal bool PreviousAssertionSucceeded { get; private set; } = true; + + /// + /// Indicates whether the caller identifier has been manually overridden. + /// + /// + /// This property is used to track if the caller identifier has been customized using the + /// method or similar methods that modify the identifier. + /// + public bool HasOverriddenCallerIdentifier { get; private set; } + + /// + /// Either starts a new assertion chain, or, when was called, for once, will return + /// an existing instance. + /// + public static AssertionChain GetOrCreate() + { + if (Instance.Value != null) + { + AssertionChain assertionChain = Instance.Value; + Instance.Value = null; + return assertionChain; + } + + return new AssertionChain(() => AssertionScope.Current, + () => FluentAssertions.CallerIdentifier.DetermineCallerIdentity()); + } + + private AssertionChain(Func getCurrentScope, Func getCallerIdentifier) + { + this.getCurrentScope = getCurrentScope; + + this.getCallerIdentifier = () => + { + var scopeName = getCurrentScope().Name(); + var callerIdentifier = getCallerIdentifier(); + + if (scopeName is null) + { + return callerIdentifier; + } + else if (callerIdentifier is null) + { + return scopeName; + } + else + { + return $"{scopeName}/{callerIdentifier}"; + } + }; + } + + /// + /// The effective caller identifier including any prefixes and postfixes configured through + /// . + /// + /// + /// Can be overridden with . + /// + public string CallerIdentifier => getCallerIdentifier() + callerPostfix; + + /// + /// Adds an explanation of why the assertion is supposed to succeed to the scope. + /// + /// + /// An object containing a formatted phrase as is supported by explaining why the assertion + /// is needed, as well as zero or more objects to format the placeholders. + /// If the phrase does not start with the word because, it is prepended automatically.explaining why the assertion is needed. + /// + public AssertionChain BecauseOf(Reason reason) + { + return BecauseOf(reason.FormattedMessage, reason.Arguments); + } + + /// + /// Adds an explanation of why the assertion is supposed to succeed to the scope. + /// + /// + /// A 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 AssertionChain BecauseOf(string because, params object[] becauseArgs) + { + reason = () => + { + try + { + string becauseOrEmpty = because ?? string.Empty; + + return becauseArgs?.Length > 0 + ? string.Format(CultureInfo.InvariantCulture, becauseOrEmpty, becauseArgs) + : becauseOrEmpty; + } + catch (FormatException formatException) + { + return + $"**WARNING** because message '{because}' could not be formatted with string.Format{Environment.NewLine}{formatException.StackTrace}"; + } + }; + + return this; + } + + public AssertionChain ForCondition(bool condition) + { + if (PreviousAssertionSucceeded) + { + succeeded = condition; + } + + return this; + } + + public AssertionChain ForConstraint(OccurrenceConstraint constraint, int actualOccurrences) + { + if (PreviousAssertionSucceeded) + { + constraint.RegisterContextData((key, value) => contextData.Add( + new ContextDataDictionary.DataItem(key, value, reportable: false, requiresFormatting: false))); + + succeeded = constraint.Assert(actualOccurrences); + } + + return this; + } + + public Continuation WithExpectation(string message, object arg1, Action chain) + { + return WithExpectation(message, chain, arg1); + } + + public Continuation WithExpectation(string message, object arg1, object arg2, Action chain) + { + return WithExpectation(message, chain, arg1, arg2); + } + + public Continuation WithExpectation(string message, Action chain) + { + return WithExpectation(message, chain, []); + } + + private Continuation WithExpectation(string message, Action chain, params object[] args) + { + if (PreviousAssertionSucceeded) + { + expectation = () => + { + var formatter = new FailureMessageFormatter(getCurrentScope().FormattingOptions) + .WithReason(reason?.Invoke() ?? string.Empty) + .WithContext(contextData) + .WithIdentifier(CallerIdentifier) + .WithFallbackIdentifier(fallbackIdentifier); + + return formatter.Format(message, args); + }; + + chain(this); + + expectation = null; + } + + return new Continuation(this); + } + + public AssertionChain WithDefaultIdentifier(string identifier) + { + fallbackIdentifier = identifier; + return this; + } + + public GivenSelector Given(Func selector) + { + return new GivenSelector(selector, this); + } + + internal Continuation FailWithPreFormatted(string formattedFailReason) + { + return FailWith(() => formattedFailReason); + } + + public Continuation FailWith(string message) + { + return FailWith(() => new FailReason(message)); + } + + public Continuation FailWith(string message, params object[] args) + { + return FailWith(() => new FailReason(message, args)); + } + + public Continuation FailWith(string message, params Func[] argProviders) + { + return FailWith( + () => new FailReason( + message, + argProviders.Select(a => a()).ToArray())); + } + + public Continuation FailWith(Func getFailureReason) + { + return FailWith(() => + { + var formatter = new FailureMessageFormatter(getCurrentScope().FormattingOptions) + .WithReason(reason?.Invoke() ?? string.Empty) + .WithContext(contextData) + .WithIdentifier(CallerIdentifier) + .WithFallbackIdentifier(fallbackIdentifier); + + FailReason failReason = getFailureReason(); + + return formatter.Format(failReason.Message, failReason.Args); + }); + } + + private Continuation FailWith(Func getFailureReason) + { + if (PreviousAssertionSucceeded) + { + PreviousAssertionSucceeded = succeeded is true; + + if (succeeded is not true) + { + string failure = getFailureReason(); + + if (expectation is not null) + { + failure = expectation() + failure; + } + + getCurrentScope().AddPreFormattedFailure(failure.Capitalize().RemoveTrailingWhitespaceFromLines()); + } + } + + // Reset the state for successive assertions on this object + succeeded = null; + + return new Continuation(this); + } + + /// + /// Allows overriding the caller identifier for the next call to one of the `FailWith` overloads instead + /// of relying on the automatic behavior that extracts the variable names from the C# code. + /// + public void OverrideCallerIdentifier(Func getCallerIdentifier) + { + this.getCallerIdentifier = getCallerIdentifier; + HasOverriddenCallerIdentifier = true; + } + + /// + /// Adds a postfix such as [0] to the caller identifier detected by the library. + /// + /// + /// Can be used by an assertion that uses to return an object or + /// collection on which another assertion is executed, and which wants to amend the automatically detected caller + /// identifier with a postfix. + /// + public AssertionChain WithCallerPostfix(string postfix) + { + callerPostfix = postfix; + HasOverriddenCallerIdentifier = true; + + return this; + } + + /// + /// Adds some information to the assertion that will be included in the message + /// that is emitted if an assertion fails. + /// + public void AddReportable(string key, string value) + { + getCurrentScope().AddReportable(key, value); + } + + /// + /// Adds some information to the assertion that will be included in the message + /// that is emitted if an assertion fails. The value is only calculated on failure. + /// + public void AddReportable(string key, Func getValue) + { + getCurrentScope().AddReportable(key, getValue); + } + + /// + /// Fluent alternative for + /// + public AssertionChain WithReportable(string name, Func content) + { + getCurrentScope().AddReportable(name, content); + return this; + } + + /// + /// Registers a failure in the chain that doesn't need any parsing or formatting anymore. + /// + internal void AddPreFormattedFailure(string failure) + { + getCurrentScope().AddPreFormattedFailure(failure); + } + + /// + /// Gets a value indicating whether all assertions in the have succeeded. + /// + public bool Succeeded => PreviousAssertionSucceeded && succeeded is null or true; + + public AssertionChain UsingLineBreaks + { + get + { + getCurrentScope().FormattingOptions.UseLineBreaks = true; + return this; + } + } +} diff --git a/Src/FluentAssertions/Execution/AssertionScope.cs b/Src/FluentAssertions/Execution/AssertionScope.cs index 2b70d559c7..a6705b60a6 100644 --- a/Src/FluentAssertions/Execution/AssertionScope.cs +++ b/Src/FluentAssertions/Execution/AssertionScope.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; using System.Linq; using System.Text; using System.Threading; @@ -14,60 +11,36 @@ namespace FluentAssertions.Execution; /// Represents an implicit or explicit scope within which multiple assertions can be collected. /// /// -/// This class is supposed to have a very short lifetime and is not safe to be used in assertions that cross thread-boundaries +/// This class is supposed to have a very short lifetime and is not safe to be used in assertion that cross thread-boundaries /// such as when using or . /// -public sealed class AssertionScope : IAssertionScope +// Remove all assertion logic from this class since it is superseded by the Assertion class +public sealed class AssertionScope : IDisposable { - #region Private Definitions - private readonly IAssertionStrategy assertionStrategy; - private readonly ContextDataDictionary contextData = new(); + private static readonly AsyncLocal CurrentScope = new(); + private readonly Func callerIdentityProvider = () => CallerIdentifier.DetermineCallerIdentity(); + private readonly ContextDataDictionary reportableData = new(); private readonly StringBuilder tracing = new(); - private Func reason; - - private static readonly AsyncLocal CurrentScope = new(); - private Func callerIdentityProvider = () => CallerIdentifier.DetermineCallerIdentity(); -#pragma warning disable CA2213 private AssertionScope parent; -#pragma warning restore CA2213 - private Func expectation; - private string fallbackIdentifier = "object"; - private bool? succeeded; - - private sealed class DeferredReportable - { - private readonly Lazy lazyValue; - - public DeferredReportable(Func valueFunc) - { - lazyValue = new Lazy(valueFunc); - } - - public override string ToString() => lazyValue.Value; - } - - #endregion /// /// Starts an unnamed scope within which multiple assertions can be executed /// and which will not throw until the scope is disposed. /// public AssertionScope() - : this(new CollectingAssertionStrategy(), context: null) + : this(() => null, new CollectingAssertionStrategy()) { - SetCurrentAssertionScope(this); } /// /// Starts a named scope within which multiple assertions can be executed /// and which will not throw until the scope is disposed. /// - public AssertionScope(string context) - : this(new CollectingAssertionStrategy(), new Lazy(() => context)) + public AssertionScope(string name) + : this(() => name, new CollectingAssertionStrategy()) { - SetCurrentAssertionScope(this); } /// @@ -76,19 +49,17 @@ public AssertionScope(string context) /// The assertion strategy for this scope. /// is . public AssertionScope(IAssertionStrategy assertionStrategy) - : this(assertionStrategy, context: null) + : this(() => null, assertionStrategy) { - SetCurrentAssertionScope(this); } /// /// Starts a named scope within which multiple assertions can be executed /// and which will not throw until the scope is disposed. /// - public AssertionScope(Lazy context) - : this(new CollectingAssertionStrategy(), context) + public AssertionScope(Func name) + : this(name, new CollectingAssertionStrategy()) { - SetCurrentAssertionScope(this); } /// @@ -96,47 +67,52 @@ public AssertionScope(Lazy context) /// /// The assertion strategy for this scope. /// is . - private AssertionScope(IAssertionStrategy assertionStrategy, Lazy context) + private AssertionScope(Func name, IAssertionStrategy assertionStrategy) { + parent = CurrentScope.Value; + CurrentScope.Value = this; + this.assertionStrategy = assertionStrategy ?? throw new ArgumentNullException(nameof(assertionStrategy)); - parent = GetCurrentAssertionScope(); - if (parent is not null) { - contextData.Add(parent.contextData); - reason = parent.reason; + // Combine the existing Name with the parent.Name if it exists. + Name = () => + { + var parentName = parent.Name(); + if (parentName.IsNullOrEmpty()) + { + return name(); + } + else if (name().IsNullOrEmpty()) + { + return parentName; + } + else + { + return parentName + "/" + name(); + } + }; + callerIdentityProvider = parent.callerIdentityProvider; FormattingOptions = parent.FormattingOptions.Clone(); - Context = JoinContexts(parent.Context, context); } else { - Context = context; + Name = name; } } - private static Lazy JoinContexts(Lazy outer, Lazy inner) - { - return (outer, inner) switch - { - (null, null) => null, - ({ } a, null) => a, - (null, { } b) => b, - ({ } a, { } b) => Join(a, b) - }; - - static Lazy Join(Lazy outer, Lazy inner) => - new(() => outer.Value + "/" + inner.Value); - } - /// - /// Gets or sets the context of the current assertion scope, e.g. the path of the object graph - /// that is being asserted on. The context is provided by a which - /// only gets evaluated when its value is actually needed (in most cases during a failure). + /// Gets or sets the name of the current assertion scope, e.g. the path of the object graph + /// that is being asserted on. /// - public Lazy Context { get; set; } + /// + /// The context is provided by a which + /// only gets evaluated when its value is actually needed (in most cases during a failure). + /// + public Func Name { get; } /// /// Gets the current thread-specific assertion scope. @@ -146,20 +122,10 @@ public static AssertionScope Current #pragma warning disable CA2000 // AssertionScope should not be disposed here get { - return GetCurrentAssertionScope() ?? new AssertionScope(new DefaultAssertionStrategy(), context: null); + return CurrentScope.Value ?? new AssertionScope(() => null, new DefaultAssertionStrategy()); } #pragma warning restore CA2000 - private set => SetCurrentAssertionScope(value); - } - - /// - public AssertionScope UsingLineBreaks - { - get - { - FormattingOptions.UseLineBreaks = true; - return this; - } + private set => CurrentScope.Value = value; } /// @@ -167,177 +133,6 @@ public AssertionScope UsingLineBreaks /// public FormattingOptions FormattingOptions { get; } = AssertionOptions.FormattingOptions.Clone(); - internal bool Succeeded => succeeded == true; - - /// - /// Adds an explanation of why the assertion is supposed to succeed to the scope. - /// - public AssertionScope BecauseOf(Reason reason) - { - return BecauseOf(reason.FormattedMessage, reason.Arguments); - } - - /// - public AssertionScope BecauseOf([StringSyntax("CompositeFormat")] string because, params object[] becauseArgs) - { - reason = () => - { - try - { - string becauseOrEmpty = because ?? string.Empty; - - return becauseArgs?.Length > 0 - ? string.Format(CultureInfo.InvariantCulture, becauseOrEmpty, becauseArgs) - : becauseOrEmpty; - } - catch (FormatException formatException) - { - return - $"**WARNING** because message '{because}' could not be formatted with string.Format{Environment.NewLine}{formatException.StackTrace}"; - } - }; - - return this; - } - - /// - public AssertionScope WithExpectation(string message, params object[] args) - { - Func localReason = reason; - - expectation = () => - { - var messageBuilder = new MessageBuilder(FormattingOptions); - string actualReason = localReason?.Invoke() ?? string.Empty; - string identifier = GetIdentifier(); - - return messageBuilder.Build(message, args, actualReason, contextData, identifier, fallbackIdentifier); - }; - - return this; - } - - internal void TrackComparands(object subject, object expectation) - { - contextData.Add(new ContextDataDictionary.DataItem("subject", subject, reportable: false, requiresFormatting: true)); - contextData.Add(new ContextDataDictionary.DataItem("expectation", expectation, reportable: false, requiresFormatting: true)); - } - - /// - public Continuation ClearExpectation() - { - expectation = null; - - // SMELL: Isn't this always going to return null? Or this method also called without FailWith (which sets the success state to null) - return new Continuation(this, Succeeded); - } - - public GivenSelector Given(Func selector) - { - return new GivenSelector(selector, this, continueAsserting: succeeded != false); - } - - /// - public AssertionScope ForCondition(bool condition) - { - succeeded = condition; - - return this; - } - - /// - public AssertionScope ForConstraint(OccurrenceConstraint constraint, int actualOccurrences) - { - constraint.RegisterReportables(this); - succeeded = constraint.Assert(actualOccurrences); - - return this; - } - - /// - public Continuation FailWith(Func failReasonFunc) - { - return FailWith(() => - { - string localReason = reason?.Invoke() ?? string.Empty; - var messageBuilder = new MessageBuilder(FormattingOptions); - string identifier = GetIdentifier(); - FailReason failReason = failReasonFunc(); - - string result = messageBuilder.Build(failReason.Message, failReason.Args, localReason, contextData, identifier, - fallbackIdentifier); - - return result; - }); - } - - internal Continuation FailWithPreFormatted(string formattedFailReason) => - FailWith(() => formattedFailReason); - - private Continuation FailWith(Func failReasonFunc) - { - try - { - bool failed = succeeded != true; - - if (failed) - { - string result = failReasonFunc(); - - if (expectation is not null) - { - result = expectation() + result; - } - - assertionStrategy.HandleFailure(result.Capitalize()); - - succeeded = false; - } - - return new Continuation(this, continueAsserting: !failed); - } - finally - { - succeeded = null; - } - } - - /// - public Continuation FailWith(string message) - { - return FailWith(() => new FailReason(message)); - } - - /// - public Continuation FailWith(string message, params object[] args) - { - return FailWith(() => new FailReason(message, args)); - } - - /// - public Continuation FailWith(string message, params Func[] argProviders) - { - return FailWith(() => new FailReason(message, - argProviders.Select(a => a()).ToArray())); - } - - private string GetIdentifier() - { - var identifier = Context?.Value; - - if (string.IsNullOrEmpty(identifier)) - { - identifier = CallerIdentity; - } - - return identifier; - } - - /// - /// Gets the identity of the caller associated with the current scope. - /// - public string CallerIdentity => callerIdentityProvider(); - /// /// Adds a pre-formatted failure message to the current scope. /// @@ -347,38 +142,30 @@ public void AddPreFormattedFailure(string formattedFailureMessage) } /// - /// Adds a block of tracing to the scope for reporting when an assertion fails. - /// - public void AppendTracing(string tracingBlock) - { - tracing.Append(tracingBlock); - } - - /// - /// Tracks a keyed object in the current scope that is excluded from the failure message in case an assertion fails. + /// Adds some information to the assertion scope that will be included in the message + /// that is emitted if an assertion fails. /// - public void AddNonReportable(string key, object value) + internal void AddReportable(string key, string value) { - contextData.Add(new ContextDataDictionary.DataItem(key, value, reportable: false, requiresFormatting: false)); + reportableData.Add(new ContextDataDictionary.DataItem(key, value, reportable: true, requiresFormatting: false)); } /// /// Adds some information to the assertion scope that will be included in the message - /// that is emitted if an assertion fails. + /// that is emitted if an assertion fails. The value is only calculated on failure. /// - public void AddReportable(string key, string value) + internal void AddReportable(string key, Func valueFunc) { - contextData.Add(new ContextDataDictionary.DataItem(key, value, reportable: true, requiresFormatting: false)); + reportableData.Add(new ContextDataDictionary.DataItem(key, new DeferredReportable(valueFunc), reportable: true, + requiresFormatting: false)); } /// - /// Adds some information to the assertion scope that will be included in the message - /// that is emitted if an assertion fails. The value is only calculated on failure. + /// Adds a block of tracing to the scope for reporting when an assertion fails. /// - public void AddReportable(string key, Func valueFunc) + public void AppendTracing(string tracingBlock) { - contextData.Add(new ContextDataDictionary.DataItem(key, new DeferredReportable(valueFunc), reportable: true, - requiresFormatting: false)); + tracing.Append(tracingBlock); } /// @@ -395,18 +182,10 @@ public bool HasFailures() return assertionStrategy.FailureMessages.Any(); } - /// - /// Gets data associated with the current scope and identified by . - /// - public T Get(string key) - { - return contextData.Get(key); - } - /// public void Dispose() { - SetCurrentAssertionScope(parent); + CurrentScope.Value = parent; if (parent is not null) { @@ -415,67 +194,26 @@ public void Dispose() parent.assertionStrategy.HandleFailure(failureMessage); } - parent.contextData.Add(contextData); + parent.reportableData.Add(reportableData); parent.AppendTracing(tracing.ToString()); parent = null; } else { - IDictionary reportable = contextData.GetReportable(); - if (tracing.Length > 0) { - reportable.Add("trace", tracing.ToString()); + reportableData.Add(new ContextDataDictionary.DataItem("trace", tracing.ToString(), reportable: true, requiresFormatting: false)); } - assertionStrategy.ThrowIfAny(reportable); + assertionStrategy.ThrowIfAny(reportableData.GetReportable()); } } - /// - public AssertionScope WithDefaultIdentifier(string identifier) + private sealed class DeferredReportable(Func valueFunc) { - fallbackIdentifier = identifier; - return this; - } - - /// - /// Allows the scope to assume that all assertions that happen within this scope are going to - /// be initiated by the same caller. - /// - public void AssumeSingleCaller() - { - // Since we know there's only one caller, we don't have to have every assertion determine the caller identity again - var provider = new Lazy(() => CallerIdentifier.DetermineCallerIdentity()); - callerIdentityProvider = () => provider.Value; - } - - private static AssertionScope GetCurrentAssertionScope() - { - return CurrentScope.Value; - } + private readonly Lazy lazyValue = new(valueFunc); - private static void SetCurrentAssertionScope(AssertionScope scope) - { - CurrentScope.Value = scope; + public override string ToString() => lazyValue.Value; } - - #region Explicit Implementation to support the interface - - IAssertionScope IAssertionScope.ForCondition(bool condition) => ForCondition(condition); - - IAssertionScope IAssertionScope.ForConstraint(OccurrenceConstraint constraint, int actualOccurrences) => - ForConstraint(constraint, actualOccurrences); - - IAssertionScope IAssertionScope.BecauseOf([StringSyntax("CompositeFormat")] string because, params object[] becauseArgs) => - BecauseOf(because, becauseArgs); - - IAssertionScope IAssertionScope.WithExpectation(string message, params object[] args) => WithExpectation(message, args); - - IAssertionScope IAssertionScope.WithDefaultIdentifier(string identifier) => WithDefaultIdentifier(identifier); - - IAssertionScope IAssertionScope.UsingLineBreaks => UsingLineBreaks; - - #endregion } diff --git a/Src/FluentAssertions/Execution/ContextDataDictionary.cs b/Src/FluentAssertions/Execution/ContextDataDictionary.cs index 8a079f6240..23ebd90548 100644 --- a/Src/FluentAssertions/Execution/ContextDataDictionary.cs +++ b/Src/FluentAssertions/Execution/ContextDataDictionary.cs @@ -44,7 +44,6 @@ public void Add(ContextDataDictionary contextDataDictionary) public void Add(DataItem item) { int existingItemIndex = items.FindIndex(i => i.Key == item.Key); - if (existingItemIndex >= 0) { items[existingItemIndex] = item; @@ -55,13 +54,7 @@ public void Add(DataItem item) } } - public T Get(string key) - { - DataItem item = items.SingleOrDefault(i => i.Key == key); - return (T)(item?.Value ?? default(T)); - } - - internal class DataItem(string key, object value, bool reportable, bool requiresFormatting) + public class DataItem(string key, object value, bool reportable, bool requiresFormatting) { public string Key { get; } = key; diff --git a/Src/FluentAssertions/Execution/Continuation.cs b/Src/FluentAssertions/Execution/Continuation.cs index b59ad7f4e7..b2f65e62a3 100644 --- a/Src/FluentAssertions/Execution/Continuation.cs +++ b/Src/FluentAssertions/Execution/Continuation.cs @@ -1,35 +1,17 @@ -namespace FluentAssertions.Execution; +namespace FluentAssertions.Execution; /// /// Enables chaining multiple assertions on an . /// public class Continuation { - private readonly AssertionScope sourceScope; - private readonly bool continueAsserting; - - internal Continuation(AssertionScope sourceScope, bool continueAsserting) + internal Continuation(AssertionChain parent) { - this.sourceScope = sourceScope; - this.continueAsserting = continueAsserting; + Then = parent; } /// /// Continues the assertion chain if the previous assertion was successful. /// - public IAssertionScope Then - { - get - { - return new ContinuedAssertionScope(sourceScope, continueAsserting); - } - } - - /// - /// Provides back-wards compatibility for code that expects to return a boolean. - /// - public static implicit operator bool(Continuation continuation) - { - return continuation.continueAsserting; - } + public AssertionChain Then { get; } } diff --git a/Src/FluentAssertions/Execution/ContinuationOfGiven.cs b/Src/FluentAssertions/Execution/ContinuationOfGiven.cs index 47ca54a889..ef42263f3f 100644 --- a/Src/FluentAssertions/Execution/ContinuationOfGiven.cs +++ b/Src/FluentAssertions/Execution/ContinuationOfGiven.cs @@ -1,15 +1,12 @@ -namespace FluentAssertions.Execution; +namespace FluentAssertions.Execution; /// -/// Enables chaining multiple assertions from a call. +/// Enables chaining multiple assertions from a call. /// public class ContinuationOfGiven { - private readonly bool succeeded; - - internal ContinuationOfGiven(GivenSelector parent, bool succeeded) + internal ContinuationOfGiven(GivenSelector parent) { - this.succeeded = succeeded; Then = parent; } @@ -18,11 +15,5 @@ internal ContinuationOfGiven(GivenSelector parent, bool succeeded) /// public GivenSelector Then { get; } - /// - /// Provides back-wards compatibility for code that expects to return a boolean. - /// - public static implicit operator bool(ContinuationOfGiven continuationOfGiven) - { - return continuationOfGiven.succeeded; - } + public bool Succeeded => Then.Succeeded; } diff --git a/Src/FluentAssertions/Execution/ContinuedAssertionScope.cs b/Src/FluentAssertions/Execution/ContinuedAssertionScope.cs deleted file mode 100644 index 1cc2d6dba9..0000000000 --- a/Src/FluentAssertions/Execution/ContinuedAssertionScope.cs +++ /dev/null @@ -1,156 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; - -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 sealed class ContinuedAssertionScope : IAssertionScope -{ - private readonly AssertionScope predecessor; - private readonly bool continueAsserting; - - internal ContinuedAssertionScope(AssertionScope predecessor, bool continueAsserting) - { - this.predecessor = predecessor; - this.continueAsserting = continueAsserting; - } - - /// - public GivenSelector Given(Func selector) - { - if (continueAsserting) - { - return predecessor.Given(selector); - } - - return new GivenSelector(() => default, predecessor, continueAsserting: false); - } - - /// - public IAssertionScope ForCondition(bool condition) - { - if (continueAsserting) - { - return predecessor.ForCondition(condition); - } - - return this; - } - - /// - public IAssertionScope ForConstraint(OccurrenceConstraint constraint, int actualOccurrences) - { - if (continueAsserting) - { - return predecessor.ForConstraint(constraint, actualOccurrences); - } - - return this; - } - - /// - public Continuation FailWith(string message) - { - if (continueAsserting) - { - return predecessor.FailWith(message); - } - - return new Continuation(predecessor, continueAsserting: false); - } - - /// - public Continuation FailWith(string message, params Func[] argProviders) - { - if (continueAsserting) - { - return predecessor.FailWith(message, argProviders); - } - - return new Continuation(predecessor, continueAsserting: false); - } - - /// - public Continuation FailWith(Func failReasonFunc) - { - if (continueAsserting) - { - return predecessor.FailWith(failReasonFunc); - } - - return new Continuation(predecessor, continueAsserting: false); - } - - /// - public Continuation FailWith(string message, params object[] args) - { - if (continueAsserting) - { - return predecessor.FailWith(message, args); - } - - return new Continuation(predecessor, continueAsserting: false); - } - - /// - public IAssertionScope BecauseOf([StringSyntax("CompositeFormat")] string because, params object[] becauseArgs) - { - if (continueAsserting) - { - return predecessor.BecauseOf(because, becauseArgs); - } - - return this; - } - - /// - public Continuation ClearExpectation() - { - predecessor.ClearExpectation(); - - return new Continuation(predecessor, continueAsserting); - } - - /// - public IAssertionScope WithExpectation(string message, params object[] args) - { - if (continueAsserting) - { - return predecessor.WithExpectation(message, args); - } - - return this; - } - - /// - public IAssertionScope WithDefaultIdentifier(string identifier) - { - if (continueAsserting) - { - return predecessor.WithDefaultIdentifier(identifier); - } - - return this; - } - - /// - public IAssertionScope UsingLineBreaks => predecessor.UsingLineBreaks; - - /// - public string[] Discard() - { - return predecessor.Discard(); - } - - /// - public void Dispose() - { - predecessor.Dispose(); - } -} diff --git a/Src/FluentAssertions/Execution/Execute.cs b/Src/FluentAssertions/Execution/Execute.cs deleted file mode 100644 index 9533780cb4..0000000000 --- a/Src/FluentAssertions/Execution/Execute.cs +++ /dev/null @@ -1,21 +0,0 @@ -using FluentAssertions.Common; - -namespace FluentAssertions.Execution; - -/// -/// Helper class for verifying a condition and/or throwing a test harness specific exception representing an assertion failure. -/// -public static class Execute -{ - /// - /// Gets an object that wraps and executes a conditional or unconditional assertion. - /// - public static AssertionScope Assertion - { - get - { - Services.EnsureInitialized(); - return AssertionScope.Current; - } - } -} diff --git a/Src/FluentAssertions/Execution/FailReason.cs b/Src/FluentAssertions/Execution/FailReason.cs index 420f3a2e47..27bab552db 100644 --- a/Src/FluentAssertions/Execution/FailReason.cs +++ b/Src/FluentAssertions/Execution/FailReason.cs @@ -1,15 +1,15 @@ namespace FluentAssertions.Execution; /// -/// Represents assertion fail reason. Contains the message and arguments for message's numbered placeholders. +/// Represents the assertion fail reason. Contains the message and arguments for message's numbered placeholders. /// /// /// 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 . +/// 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. diff --git a/Src/FluentAssertions/Execution/MessageBuilder.cs b/Src/FluentAssertions/Execution/FailureMessageFormatter.cs similarity index 68% rename from Src/FluentAssertions/Execution/MessageBuilder.cs rename to Src/FluentAssertions/Execution/FailureMessageFormatter.cs index 65460111f5..3559ea97b6 100644 --- a/Src/FluentAssertions/Execution/MessageBuilder.cs +++ b/Src/FluentAssertions/Execution/FailureMessageFormatter.cs @@ -1,39 +1,87 @@ -#region - -using System; +using System; using System.Globalization; using System.Linq; using System.Text.RegularExpressions; using FluentAssertions.Common; using FluentAssertions.Formatting; -#endregion - namespace FluentAssertions.Execution; /// /// Encapsulates expanding the various placeholders supported in a failure message. /// -internal class MessageBuilder +internal class FailureMessageFormatter(FormattingOptions formattingOptions) { - private readonly FormattingOptions formattingOptions; + private static readonly char[] Blanks = ['\r', '\n', ' ', '\t']; + private string reason; + private ContextDataDictionary contextData; + private string identifier; + private string fallbackIdentifier; + + public FailureMessageFormatter WithReason(string reason) + { + this.reason = SanitizeReason(reason ?? string.Empty); + return this; + } + + private static string SanitizeReason(string reason) + { + if (!string.IsNullOrEmpty(reason)) + { + reason = EnsurePrefix("because", reason); + reason = reason.EscapePlaceholders(); + + return StartsWithBlank(reason) ? reason : " " + reason; + } - #region Private Definitions + return string.Empty; + } + + // SMELL: looks way too complex just to retain the leading whitespace + private static string EnsurePrefix(string prefix, string text) + { + string leadingBlanks = ExtractLeadingBlanksFrom(text); + string textWithoutLeadingBlanks = text.Substring(leadingBlanks.Length); + + return !textWithoutLeadingBlanks.StartsWith(prefix, StringComparison.OrdinalIgnoreCase) + ? leadingBlanks + prefix + " " + textWithoutLeadingBlanks + : text; + } + + private static string ExtractLeadingBlanksFrom(string text) + { + string trimmedText = text.TrimStart(Blanks); + int leadingBlanksCount = text.Length - trimmedText.Length; + + return text.Substring(0, leadingBlanksCount); + } + + private static bool StartsWithBlank(string text) + { + return text.Length > 0 && Blanks.Contains(text[0]); + } - private readonly char[] blanks = ['\r', '\n', ' ', '\t']; + public FailureMessageFormatter WithContext(ContextDataDictionary contextData) + { + this.contextData = contextData; + return this; + } - #endregion + public FailureMessageFormatter WithIdentifier(string identifier) + { + this.identifier = identifier; + return this; + } - public MessageBuilder(FormattingOptions formattingOptions) + public FailureMessageFormatter WithFallbackIdentifier(string fallbackIdentifier) { - this.formattingOptions = formattingOptions; + this.fallbackIdentifier = fallbackIdentifier; + return this; } - // SMELL: Too many parameters. - public string Build(string message, object[] messageArgs, string reason, ContextDataDictionary contextData, string identifier, - string fallbackIdentifier) + public string Format(string message, object[] messageArgs) { - message = message.Replace("{reason}", SanitizeReason(reason), StringComparison.Ordinal); + message = message.Replace("{reason}", reason, StringComparison.Ordinal); message = SubstituteIdentifier(message, identifier?.EscapePlaceholders(), fallbackIdentifier); @@ -91,7 +139,7 @@ private static string SubstituteContextualTags(string message, ContextDataDictio private string FormatArgumentPlaceholders(string failureMessage, object[] failureArgs) { - string[] values = failureArgs.Select(a => Formatter.ToString(a, formattingOptions)).ToArray(); + object[] values = failureArgs.Select(object (a) => Formatter.ToString(a, formattingOptions)).ToArray(); try { @@ -103,41 +151,4 @@ private string FormatArgumentPlaceholders(string failureMessage, object[] failur $"**WARNING** failure message '{failureMessage}' could not be formatted with string.Format{Environment.NewLine}{formatException.StackTrace}"; } } - - private string SanitizeReason(string reason) - { - if (!string.IsNullOrEmpty(reason)) - { - reason = EnsurePrefix("because", reason); - reason = reason.EscapePlaceholders(); - - return StartsWithBlank(reason) ? reason : " " + reason; - } - - return string.Empty; - } - - // SMELL: looks way too complex just to retain the leading whitespace - private string EnsurePrefix(string prefix, string text) - { - string leadingBlanks = ExtractLeadingBlanksFrom(text); - string textWithoutLeadingBlanks = text.Substring(leadingBlanks.Length); - - return !textWithoutLeadingBlanks.StartsWith(prefix, StringComparison.OrdinalIgnoreCase) - ? leadingBlanks + prefix + " " + textWithoutLeadingBlanks - : text; - } - - private string ExtractLeadingBlanksFrom(string text) - { - string trimmedText = text.TrimStart(blanks); - int leadingBlanksCount = text.Length - trimmedText.Length; - - return text.Substring(0, leadingBlanksCount); - } - - private bool StartsWithBlank(string text) - { - return text.Length > 0 && blanks.Contains(text[0]); - } } diff --git a/Src/FluentAssertions/Execution/GivenSelector.cs b/Src/FluentAssertions/Execution/GivenSelector.cs index b2d776fb26..0c2e42798f 100644 --- a/Src/FluentAssertions/Execution/GivenSelector.cs +++ b/Src/FluentAssertions/Execution/GivenSelector.cs @@ -1,28 +1,27 @@ -using System; +using System; using System.Linq; using FluentAssertions.Common; namespace FluentAssertions.Execution; /// -/// Represents a chaining object returned from to continue the assertion using +/// Represents a chaining object returned from to continue the assertion using /// an object returned by a selector. /// public class GivenSelector { - private readonly AssertionScope predecessor; - private readonly T subject; + private readonly AssertionChain assertionChain; + private readonly T selector; - private bool continueAsserting; - - internal GivenSelector(Func selector, AssertionScope predecessor, bool continueAsserting) + internal GivenSelector(Func selector, AssertionChain assertionChain) { - this.predecessor = predecessor; - this.continueAsserting = continueAsserting; + this.assertionChain = assertionChain; - subject = continueAsserting ? selector() : default; + this.selector = assertionChain.Succeeded ? selector() : default; } + public bool Succeeded => assertionChain.Succeeded; + /// /// Specify the condition that must be satisfied upon the subject selected through a prior selector. /// @@ -31,78 +30,47 @@ internal GivenSelector(Func selector, AssertionScope predecessor, bool contin /// /// /// The condition will not be evaluated if the prior assertion failed, - /// nor will throw any exceptions. + /// nor will throw any exceptions. /// /// is . public GivenSelector ForCondition(Func predicate) { Guard.ThrowIfArgumentIsNull(predicate); - if (continueAsserting) + if (assertionChain.Succeeded) { - predecessor.ForCondition(predicate(subject)); + assertionChain.ForCondition(predicate(selector)); } return this; } - /// - /// The will not be invoked if the prior assertion failed, - /// nor will throw any exceptions. - /// - /// - /// is . public GivenSelector Given(Func selector) { Guard.ThrowIfArgumentIsNull(selector); - return new GivenSelector(() => selector(subject), predecessor, continueAsserting); + return new GivenSelector(() => selector(this.selector), assertionChain); } - /// public ContinuationOfGiven FailWith(string message) { return FailWith(message, Array.Empty()); } - /// - /// - /// The will not be invoked if the prior assertion failed, - /// nor will throw any exceptions. - /// - /// public ContinuationOfGiven FailWith(string message, params Func[] args) { - if (continueAsserting) + if (assertionChain.PreviousAssertionSucceeded) { - object[] mappedArguments = args.Select(a => a(subject)).ToArray(); + object[] mappedArguments = args.Select(a => a(selector)).ToArray(); return FailWith(message, mappedArguments); } - return new ContinuationOfGiven(this, succeeded: false); + return new ContinuationOfGiven(this); } - /// - /// - /// The will not be invoked if the prior assertion failed, - /// nor will throw any exceptions. - /// - /// public ContinuationOfGiven FailWith(string message, params object[] args) { - if (continueAsserting) - { - continueAsserting = predecessor.FailWith(message, args); - return new ContinuationOfGiven(this, continueAsserting); - } - - return new ContinuationOfGiven(this, succeeded: false); - } - - /// - public ContinuationOfGiven ClearExpectation() - { - predecessor.ClearExpectation(); - return new ContinuationOfGiven(this, continueAsserting); + assertionChain.FailWith(message, args); + return new ContinuationOfGiven(this); } } diff --git a/Src/FluentAssertions/Execution/IAssertionScope.cs b/Src/FluentAssertions/Execution/IAssertionScope.cs deleted file mode 100644 index d114564ac5..0000000000 --- a/Src/FluentAssertions/Execution/IAssertionScope.cs +++ /dev/null @@ -1,186 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; - -namespace FluentAssertions.Execution; - -public interface IAssertionScope : IDisposable -{ - /// - /// Allows to safely select the subject for successive assertions. - /// - /// - /// Selector which result is passed to successive calls to . - /// - GivenSelector Given(Func selector); - - /// - /// Specify the condition that must be satisfied. - /// - /// - /// If the assertion will be treated as successful and no exceptions will be thrown. - /// - IAssertionScope ForCondition(bool condition); - - /// - /// Makes assertion fail when does not match . - /// - /// The occurrence description in natural language could then be inserted in failure message by using - /// {expectedOccurrence} placeholder in message parameters of and its - /// overloaded versions. - /// - /// - /// defining the number of expected occurrences. - /// The number of actual occurrences. - IAssertionScope ForConstraint(OccurrenceConstraint constraint, int actualOccurrences); - - /// - /// Sets the failure message when the assertion is not met, or completes the failure message set to a prior call to - /// . - /// - /// - /// Messages may contain a few specialized placeholders. 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. - /// - /// - /// 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. - Continuation FailWith(string message); - - /// - /// 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); - - /// - /// Sets the failure message when the assertion is not met, or completes the failure message set to a prior call to - /// , - /// but postpones evaluation of the formatting arguments until the assertion really fails. - /// - /// - /// 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 lazily evaluated arguments to any numbered placeholders - public Continuation FailWith(string message, params Func[] argProviders); - - /// - /// Specify the reason why you expect the condition to be . - /// - /// - /// 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([StringSyntax("CompositeFormat")] 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}. - /// - /// - /// 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); - - /// - /// Forces the formatters, that support it, to add the necessary line breaks. - /// - /// - /// This is just shorthand for modifying the property. - /// - IAssertionScope UsingLineBreaks { get; } - - /// - /// Discards and returns the failures that happened up to now. - /// - string[] Discard(); -} diff --git a/Src/FluentAssertions/Formatting/FormattedObjectGraph.cs b/Src/FluentAssertions/Formatting/FormattedObjectGraph.cs index 3f5ba3550e..e86f2185b4 100644 --- a/Src/FluentAssertions/Formatting/FormattedObjectGraph.cs +++ b/Src/FluentAssertions/Formatting/FormattedObjectGraph.cs @@ -15,13 +15,19 @@ namespace FluentAssertions.Formatting; /// to the maximum number of lines provided through its constructor. It will throw /// a if the number of lines exceeds the maximum. /// -public class FormattedObjectGraph(int maxLines) +public class FormattedObjectGraph { + private readonly int maxLines; private readonly List lines = []; private readonly StringBuilder lineBuilder = new(); private int indentation; private string lineBuilderWhitespace = string.Empty; + public FormattedObjectGraph(int maxLines) + { + this.maxLines = maxLines; + } + /// /// The number of spaces that should be used by every indentation level. /// @@ -93,13 +99,14 @@ private void InsertInitialNewLine() private void FlushCurrentLine() { - if (lineBuilder.Length > 0) + string line = lineBuilder.ToString().TrimEnd(); + if (line.Length > 0) { - AppendWithoutExceedingMaximumLines($"{lineBuilderWhitespace}{lineBuilder}"); - - lineBuilder.Clear(); - lineBuilderWhitespace = Whitespace; + AppendWithoutExceedingMaximumLines(lineBuilderWhitespace + line); } + + lineBuilder.Clear(); + lineBuilderWhitespace = Whitespace; } private void AppendWithoutExceedingMaximumLines(string line) diff --git a/Src/FluentAssertions/Formatting/Formatter.cs b/Src/FluentAssertions/Formatting/Formatter.cs index d61ea84f9f..4ac3f13bfd 100644 --- a/Src/FluentAssertions/Formatting/Formatter.cs +++ b/Src/FluentAssertions/Formatting/Formatter.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using FluentAssertions.Common; -using FluentAssertions.Equivalency; using FluentAssertions.Equivalency.Execution; using FluentAssertions.Execution; using FluentAssertions.Xml; @@ -27,6 +26,7 @@ public static class Formatter new XElementValueFormatter(), new XAttributeValueFormatter(), new PropertyInfoFormatter(), + new MethodInfoFormatter(), new NullValueFormatter(), new GuidValueFormatter(), new DateTimeOffsetValueFormatter(), @@ -214,7 +214,7 @@ public bool TryPush(string path, object value) string fullPath = GetFullPath(); var reference = new ObjectReference(value, fullPath); - return !tracker.IsCyclicReference(reference, CyclicReferenceHandling.Ignore); + return !tracker.IsCyclicReference(reference); } private string GetFullPath() => string.Join(".", pathStack.Reverse()); diff --git a/Src/FluentAssertions/Formatting/MethodInfoFormatter.cs b/Src/FluentAssertions/Formatting/MethodInfoFormatter.cs new file mode 100644 index 0000000000..6e40cfdf8e --- /dev/null +++ b/Src/FluentAssertions/Formatting/MethodInfoFormatter.cs @@ -0,0 +1,40 @@ +using System.Reflection; + +namespace FluentAssertions.Formatting; + +public class MethodInfoFormatter : IValueFormatter +{ + /// + /// Indicates whether the current can handle the specified . + /// + /// The value for which to create a . + /// + /// if the current can handle the specified value; otherwise, . + /// + public bool CanHandle(object value) + { + return value is MethodInfo; + } + + public void Format(object value, FormattedObjectGraph formattedGraph, FormattingContext context, FormatChild formatChild) + { + var method = (MethodInfo)value; + if (method is null) + { + formattedGraph.AddFragment(""); + } + else if (method.IsSpecialName && method.Name == "op_Implicit") + { + formattedGraph.AddFragment($"implicit operator {method.ReturnType.Name}({method.GetParameters()[0].ParameterType.Name})"); + } + else if (method.IsSpecialName && method.Name == "op_Explicit") + { + formattedGraph.AddFragment( + $"explicit operator {method.ReturnType.Name}({method.GetParameters()[0].ParameterType.Name})"); + } + else + { + formattedGraph.AddFragment($"{method!.DeclaringType!.Name + "." + method.Name}"); + } + } +} diff --git a/Src/FluentAssertions/Formatting/PropertyInfoFormatter.cs b/Src/FluentAssertions/Formatting/PropertyInfoFormatter.cs index c9746c2901..4ec7de86e0 100644 --- a/Src/FluentAssertions/Formatting/PropertyInfoFormatter.cs +++ b/Src/FluentAssertions/Formatting/PropertyInfoFormatter.cs @@ -18,6 +18,13 @@ public bool CanHandle(object value) public void Format(object value, FormattedObjectGraph formattedGraph, FormattingContext context, FormatChild formatChild) { - formattedGraph.AddFragment(((PropertyInfo)value).Name); + if (value is not PropertyInfo property) + { + formattedGraph.AddFragment(""); + } + else + { + formattedGraph.AddFragment($"{property.DeclaringType?.Name ?? string.Empty}.{property.Name}"); + } } } diff --git a/Src/FluentAssertions/Numeric/ByteAssertions.cs b/Src/FluentAssertions/Numeric/ByteAssertions.cs index 6467e86f5d..ccd7399bcf 100644 --- a/Src/FluentAssertions/Numeric/ByteAssertions.cs +++ b/Src/FluentAssertions/Numeric/ByteAssertions.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Globalization; +using FluentAssertions.Execution; namespace FluentAssertions.Numeric; @@ -9,8 +10,8 @@ namespace FluentAssertions.Numeric; [DebuggerNonUserCode] internal class ByteAssertions : NumericAssertions { - internal ByteAssertions(byte value) - : base(value) + internal ByteAssertions(byte value, AssertionChain assertionChain) + : base(value, assertionChain) { } diff --git a/Src/FluentAssertions/Numeric/ComparableTypeAssertions.cs b/Src/FluentAssertions/Numeric/ComparableTypeAssertions.cs index 805812df65..3a40a75be1 100644 --- a/Src/FluentAssertions/Numeric/ComparableTypeAssertions.cs +++ b/Src/FluentAssertions/Numeric/ComparableTypeAssertions.cs @@ -16,8 +16,8 @@ namespace FluentAssertions.Numeric; [DebuggerNonUserCode] public class ComparableTypeAssertions : ComparableTypeAssertions> { - public ComparableTypeAssertions(IComparable value) - : base(value) + public ComparableTypeAssertions(IComparable value, AssertionChain assertionChain) + : base(value, assertionChain) { } } @@ -30,10 +30,12 @@ public class ComparableTypeAssertions : ReferenceTypeAssertions< where TAssertions : ComparableTypeAssertions { private const int Equal = 0; + private readonly AssertionChain assertionChain; - public ComparableTypeAssertions(IComparable value) - : base(value) + public ComparableTypeAssertions(IComparable value, AssertionChain assertionChain) + : base(value, assertionChain) { + this.assertionChain = assertionChain; } /// @@ -51,7 +53,7 @@ public ComparableTypeAssertions(IComparable value) /// public AndConstraint Be(T expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Equals(Subject, expected)) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:object} to be equal to {0}{reason}, but found {1}.", expected, Subject); @@ -112,7 +114,7 @@ public AndConstraint BeEquivalentTo(TExpectation expe EquivalencyOptions options = config(AssertionOptions.CloneDefaults()); var context = new EquivalencyValidationContext( - Node.From(() => AssertionScope.Current.CallerIdentity), options) + Node.From(() => CurrentAssertionChain.CallerIdentifier), options) { Reason = new Reason(because, becauseArgs), TraceWriter = options.TraceWriter @@ -145,7 +147,7 @@ public AndConstraint BeEquivalentTo(TExpectation expe /// public AndConstraint NotBe(T unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(!Equals(Subject, unexpected)) .BecauseOf(because, becauseArgs) .FailWith("Did not expect {context:object} to be equal to {0}{reason}.", unexpected); @@ -169,7 +171,7 @@ public AndConstraint NotBe(T unexpected, [StringSyntax("CompositeFo /// public AndConstraint BeRankedEquallyTo(T expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject.CompareTo(expected) == Equal) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:object} {0} to be ranked as equal to {1}{reason}.", Subject, expected); @@ -193,7 +195,7 @@ public AndConstraint BeRankedEquallyTo(T expected, [StringSyntax("C /// public AndConstraint NotBeRankedEquallyTo(T unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject.CompareTo(unexpected) != Equal) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:object} {0} not to be ranked as equal to {1}{reason}.", Subject, unexpected); @@ -216,7 +218,7 @@ public AndConstraint NotBeRankedEquallyTo(T unexpected, [StringSynt /// public AndConstraint BeLessThan(T expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject.CompareTo(expected) < Equal) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:object} {0} to be less than {1}{reason}.", Subject, expected); @@ -239,7 +241,7 @@ public AndConstraint BeLessThan(T expected, [StringSyntax("Composit /// public AndConstraint BeLessThanOrEqualTo(T expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject.CompareTo(expected) <= Equal) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:object} {0} to be less than or equal to {1}{reason}.", Subject, expected); @@ -262,7 +264,7 @@ public AndConstraint BeLessThanOrEqualTo(T expected, [StringSyntax( /// public AndConstraint BeGreaterThan(T expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject.CompareTo(expected) > Equal) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:object} {0} to be greater than {1}{reason}.", Subject, expected); @@ -285,7 +287,7 @@ public AndConstraint BeGreaterThan(T expected, [StringSyntax("Compo /// public AndConstraint BeGreaterThanOrEqualTo(T expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject.CompareTo(expected) >= Equal) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:object} {0} to be greater than or equal to {1}{reason}.", Subject, expected); @@ -315,7 +317,7 @@ public AndConstraint BeGreaterThanOrEqualTo(T expected, [StringSynt public AndConstraint BeInRange(T minimumValue, T maximumValue, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject.CompareTo(minimumValue) >= Equal && Subject.CompareTo(maximumValue) <= Equal) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:object} to be between {0} and {1}{reason}, but found {2}.", @@ -346,7 +348,7 @@ public AndConstraint BeInRange(T minimumValue, T maximumValue, public AndConstraint NotBeInRange(T minimumValue, T maximumValue, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(!(Subject.CompareTo(minimumValue) >= Equal && Subject.CompareTo(maximumValue) <= Equal)) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:object} to not be between {0} and {1}{reason}, but found {2}.", @@ -382,7 +384,7 @@ public AndConstraint BeOneOf(params T[] validValues) public AndConstraint BeOneOf(IEnumerable validValues, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(validValues.Any(val => Equals(Subject, val))) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:object} to be one of {0}{reason}, but found {1}.", validValues, Subject); diff --git a/Src/FluentAssertions/Numeric/DecimalAssertions.cs b/Src/FluentAssertions/Numeric/DecimalAssertions.cs index 0b0d91bf00..aa31679f46 100644 --- a/Src/FluentAssertions/Numeric/DecimalAssertions.cs +++ b/Src/FluentAssertions/Numeric/DecimalAssertions.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.Globalization; +using FluentAssertions.Execution; namespace FluentAssertions.Numeric; @@ -10,8 +11,8 @@ namespace FluentAssertions.Numeric; [DebuggerNonUserCode] internal class DecimalAssertions : NumericAssertions { - internal DecimalAssertions(decimal value) - : base(value) + internal DecimalAssertions(decimal value, AssertionChain assertionChain) + : base(value, assertionChain) { } diff --git a/Src/FluentAssertions/Numeric/DoubleAssertions.cs b/Src/FluentAssertions/Numeric/DoubleAssertions.cs index d8395924b5..0596191408 100644 --- a/Src/FluentAssertions/Numeric/DoubleAssertions.cs +++ b/Src/FluentAssertions/Numeric/DoubleAssertions.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Globalization; +using FluentAssertions.Execution; namespace FluentAssertions.Numeric; @@ -9,8 +10,8 @@ namespace FluentAssertions.Numeric; [DebuggerNonUserCode] internal class DoubleAssertions : NumericAssertions { - internal DoubleAssertions(double value) - : base(value) + internal DoubleAssertions(double value, AssertionChain assertionChain) + : base(value, assertionChain) { } diff --git a/Src/FluentAssertions/Numeric/Int16Assertions.cs b/Src/FluentAssertions/Numeric/Int16Assertions.cs index d4391d4115..7d7b9c2102 100644 --- a/Src/FluentAssertions/Numeric/Int16Assertions.cs +++ b/Src/FluentAssertions/Numeric/Int16Assertions.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Globalization; +using FluentAssertions.Execution; namespace FluentAssertions.Numeric; @@ -9,8 +10,8 @@ namespace FluentAssertions.Numeric; [DebuggerNonUserCode] internal class Int16Assertions : NumericAssertions { - internal Int16Assertions(short value) - : base(value) + internal Int16Assertions(short value, AssertionChain assertionChain) + : base(value, assertionChain) { } diff --git a/Src/FluentAssertions/Numeric/Int32Assertions.cs b/Src/FluentAssertions/Numeric/Int32Assertions.cs index 44baf64bd6..cfcb25f472 100644 --- a/Src/FluentAssertions/Numeric/Int32Assertions.cs +++ b/Src/FluentAssertions/Numeric/Int32Assertions.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Globalization; +using FluentAssertions.Execution; namespace FluentAssertions.Numeric; @@ -9,8 +10,8 @@ namespace FluentAssertions.Numeric; [DebuggerNonUserCode] internal class Int32Assertions : NumericAssertions { - internal Int32Assertions(int value) - : base(value) + internal Int32Assertions(int value, AssertionChain assertionChain) + : base(value, assertionChain) { } diff --git a/Src/FluentAssertions/Numeric/Int64Assertions.cs b/Src/FluentAssertions/Numeric/Int64Assertions.cs index b3f4cd8650..c733dd294e 100644 --- a/Src/FluentAssertions/Numeric/Int64Assertions.cs +++ b/Src/FluentAssertions/Numeric/Int64Assertions.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Globalization; +using FluentAssertions.Execution; namespace FluentAssertions.Numeric; @@ -9,8 +10,8 @@ namespace FluentAssertions.Numeric; [DebuggerNonUserCode] internal class Int64Assertions : NumericAssertions { - internal Int64Assertions(long value) - : base(value) + internal Int64Assertions(long value, AssertionChain assertionChain) + : base(value, assertionChain) { } diff --git a/Src/FluentAssertions/Numeric/NullableByteAssertions.cs b/Src/FluentAssertions/Numeric/NullableByteAssertions.cs index 42001910fc..9fa6a4c4f0 100644 --- a/Src/FluentAssertions/Numeric/NullableByteAssertions.cs +++ b/Src/FluentAssertions/Numeric/NullableByteAssertions.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Globalization; +using FluentAssertions.Execution; namespace FluentAssertions.Numeric; @@ -9,8 +10,8 @@ namespace FluentAssertions.Numeric; [DebuggerNonUserCode] internal class NullableByteAssertions : NullableNumericAssertions { - internal NullableByteAssertions(byte? value) - : base(value) + internal NullableByteAssertions(byte? value, AssertionChain assertionChain) + : base(value, assertionChain) { } diff --git a/Src/FluentAssertions/Numeric/NullableDecimalAssertions.cs b/Src/FluentAssertions/Numeric/NullableDecimalAssertions.cs index 3a2897eb16..d5b27880c6 100644 --- a/Src/FluentAssertions/Numeric/NullableDecimalAssertions.cs +++ b/Src/FluentAssertions/Numeric/NullableDecimalAssertions.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.Globalization; +using FluentAssertions.Execution; namespace FluentAssertions.Numeric; @@ -10,8 +11,8 @@ namespace FluentAssertions.Numeric; [DebuggerNonUserCode] internal class NullableDecimalAssertions : NullableNumericAssertions { - internal NullableDecimalAssertions(decimal? value) - : base(value) + internal NullableDecimalAssertions(decimal? value, AssertionChain assertionChain) + : base(value, assertionChain) { } diff --git a/Src/FluentAssertions/Numeric/NullableDoubleAssertions.cs b/Src/FluentAssertions/Numeric/NullableDoubleAssertions.cs index 62c2c34a42..97af5f97bc 100644 --- a/Src/FluentAssertions/Numeric/NullableDoubleAssertions.cs +++ b/Src/FluentAssertions/Numeric/NullableDoubleAssertions.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Globalization; +using FluentAssertions.Execution; namespace FluentAssertions.Numeric; @@ -9,8 +10,8 @@ namespace FluentAssertions.Numeric; [DebuggerNonUserCode] internal class NullableDoubleAssertions : NullableNumericAssertions { - internal NullableDoubleAssertions(double? value) - : base(value) + internal NullableDoubleAssertions(double? value, AssertionChain assertionChain) + : base(value, assertionChain) { } diff --git a/Src/FluentAssertions/Numeric/NullableInt16Assertions.cs b/Src/FluentAssertions/Numeric/NullableInt16Assertions.cs index 89c5cc65d2..802d0c864d 100644 --- a/Src/FluentAssertions/Numeric/NullableInt16Assertions.cs +++ b/Src/FluentAssertions/Numeric/NullableInt16Assertions.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Globalization; +using FluentAssertions.Execution; namespace FluentAssertions.Numeric; @@ -9,8 +10,8 @@ namespace FluentAssertions.Numeric; [DebuggerNonUserCode] internal class NullableInt16Assertions : NullableNumericAssertions { - internal NullableInt16Assertions(short? value) - : base(value) + internal NullableInt16Assertions(short? value, AssertionChain assertionChain) + : base(value, assertionChain) { } diff --git a/Src/FluentAssertions/Numeric/NullableInt32Assertions.cs b/Src/FluentAssertions/Numeric/NullableInt32Assertions.cs index 67636c9ddf..7b2400568f 100644 --- a/Src/FluentAssertions/Numeric/NullableInt32Assertions.cs +++ b/Src/FluentAssertions/Numeric/NullableInt32Assertions.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Globalization; +using FluentAssertions.Execution; namespace FluentAssertions.Numeric; @@ -9,8 +10,8 @@ namespace FluentAssertions.Numeric; [DebuggerNonUserCode] internal class NullableInt32Assertions : NullableNumericAssertions { - internal NullableInt32Assertions(int? value) - : base(value) + internal NullableInt32Assertions(int? value, AssertionChain assertionChain) + : base(value, assertionChain) { } diff --git a/Src/FluentAssertions/Numeric/NullableInt64Assertions.cs b/Src/FluentAssertions/Numeric/NullableInt64Assertions.cs index 4630361178..1e5e04d386 100644 --- a/Src/FluentAssertions/Numeric/NullableInt64Assertions.cs +++ b/Src/FluentAssertions/Numeric/NullableInt64Assertions.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Globalization; +using FluentAssertions.Execution; namespace FluentAssertions.Numeric; @@ -9,8 +10,8 @@ namespace FluentAssertions.Numeric; [DebuggerNonUserCode] internal class NullableInt64Assertions : NullableNumericAssertions { - internal NullableInt64Assertions(long? value) - : base(value) + internal NullableInt64Assertions(long? value, AssertionChain assertionChain) + : base(value, assertionChain) { } diff --git a/Src/FluentAssertions/Numeric/NullableNumericAssertions.cs b/Src/FluentAssertions/Numeric/NullableNumericAssertions.cs index 5443d7f0a3..88af8fc2b3 100644 --- a/Src/FluentAssertions/Numeric/NullableNumericAssertions.cs +++ b/Src/FluentAssertions/Numeric/NullableNumericAssertions.cs @@ -11,8 +11,8 @@ namespace FluentAssertions.Numeric; public class NullableNumericAssertions : NullableNumericAssertions> where T : struct, IComparable { - public NullableNumericAssertions(T? value) - : base(value) + public NullableNumericAssertions(T? value, AssertionChain assertionChain) + : base(value, assertionChain) { } } @@ -22,9 +22,12 @@ public class NullableNumericAssertions : NumericAssertions where TAssertions : NullableNumericAssertions { - public NullableNumericAssertions(T? value) - : base(value) + private readonly AssertionChain assertionChain; + + public NullableNumericAssertions(T? value, AssertionChain assertionChain) + : base(value, assertionChain) { + this.assertionChain = assertionChain; } /// @@ -39,7 +42,7 @@ public NullableNumericAssertions(T? value) /// public AndConstraint HaveValue([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject.HasValue) .BecauseOf(because, becauseArgs) .FailWith("Expected a value{reason}."); @@ -74,7 +77,7 @@ public AndConstraint NotBeNull([StringSyntax("CompositeFormat")] st /// public AndConstraint NotHaveValue([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(!Subject.HasValue) .BecauseOf(because, becauseArgs) .FailWith("Did not expect a value{reason}, but found {0}.", Subject); @@ -116,7 +119,7 @@ public AndConstraint Match(Expression> predicate, { Guard.ThrowIfArgumentIsNull(predicate); - Execute.Assertion + assertionChain .ForCondition(predicate.Compile()(Subject)) .BecauseOf(because, becauseArgs) .FailWith("Expected value to match {0}{reason}, but found {1}.", predicate, Subject); diff --git a/Src/FluentAssertions/Numeric/NullableSByteAssertions.cs b/Src/FluentAssertions/Numeric/NullableSByteAssertions.cs index 76967bc17e..e83e756404 100644 --- a/Src/FluentAssertions/Numeric/NullableSByteAssertions.cs +++ b/Src/FluentAssertions/Numeric/NullableSByteAssertions.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Globalization; +using FluentAssertions.Execution; namespace FluentAssertions.Numeric; @@ -9,8 +10,8 @@ namespace FluentAssertions.Numeric; [DebuggerNonUserCode] internal class NullableSByteAssertions : NullableNumericAssertions { - internal NullableSByteAssertions(sbyte? value) - : base(value) + internal NullableSByteAssertions(sbyte? value, AssertionChain assertionChain) + : base(value, assertionChain) { } diff --git a/Src/FluentAssertions/Numeric/NullableSingleAssertions.cs b/Src/FluentAssertions/Numeric/NullableSingleAssertions.cs index fbcdaa919d..3a82df8969 100644 --- a/Src/FluentAssertions/Numeric/NullableSingleAssertions.cs +++ b/Src/FluentAssertions/Numeric/NullableSingleAssertions.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Globalization; +using FluentAssertions.Execution; namespace FluentAssertions.Numeric; @@ -9,8 +10,8 @@ namespace FluentAssertions.Numeric; [DebuggerNonUserCode] internal class NullableSingleAssertions : NullableNumericAssertions { - internal NullableSingleAssertions(float? value) - : base(value) + internal NullableSingleAssertions(float? value, AssertionChain assertionChain) + : base(value, assertionChain) { } diff --git a/Src/FluentAssertions/Numeric/NullableUInt16Assertions.cs b/Src/FluentAssertions/Numeric/NullableUInt16Assertions.cs index 97aad47a07..0df417d5e2 100644 --- a/Src/FluentAssertions/Numeric/NullableUInt16Assertions.cs +++ b/Src/FluentAssertions/Numeric/NullableUInt16Assertions.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Globalization; +using FluentAssertions.Execution; namespace FluentAssertions.Numeric; @@ -9,8 +10,8 @@ namespace FluentAssertions.Numeric; [DebuggerNonUserCode] internal class NullableUInt16Assertions : NullableNumericAssertions { - internal NullableUInt16Assertions(ushort? value) - : base(value) + internal NullableUInt16Assertions(ushort? value, AssertionChain assertionChain) + : base(value, assertionChain) { } diff --git a/Src/FluentAssertions/Numeric/NullableUInt32Assertions.cs b/Src/FluentAssertions/Numeric/NullableUInt32Assertions.cs index abf8f69801..69b94a8086 100644 --- a/Src/FluentAssertions/Numeric/NullableUInt32Assertions.cs +++ b/Src/FluentAssertions/Numeric/NullableUInt32Assertions.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Globalization; +using FluentAssertions.Execution; namespace FluentAssertions.Numeric; @@ -9,8 +10,8 @@ namespace FluentAssertions.Numeric; [DebuggerNonUserCode] internal class NullableUInt32Assertions : NullableNumericAssertions { - internal NullableUInt32Assertions(uint? value) - : base(value) + internal NullableUInt32Assertions(uint? value, AssertionChain assertionChain) + : base(value, assertionChain) { } diff --git a/Src/FluentAssertions/Numeric/NullableUInt64Assertions.cs b/Src/FluentAssertions/Numeric/NullableUInt64Assertions.cs index 1a2e44c3a7..6c36e3b703 100644 --- a/Src/FluentAssertions/Numeric/NullableUInt64Assertions.cs +++ b/Src/FluentAssertions/Numeric/NullableUInt64Assertions.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Globalization; +using FluentAssertions.Execution; namespace FluentAssertions.Numeric; @@ -9,8 +10,8 @@ namespace FluentAssertions.Numeric; [DebuggerNonUserCode] internal class NullableUInt64Assertions : NullableNumericAssertions { - internal NullableUInt64Assertions(ulong? value) - : base(value) + internal NullableUInt64Assertions(ulong? value, AssertionChain assertionChain) + : base(value, assertionChain) { } diff --git a/Src/FluentAssertions/Numeric/NumericAssertions.cs b/Src/FluentAssertions/Numeric/NumericAssertions.cs index 81cf130b6f..ea1ffac010 100644 --- a/Src/FluentAssertions/Numeric/NumericAssertions.cs +++ b/Src/FluentAssertions/Numeric/NumericAssertions.cs @@ -16,8 +16,8 @@ namespace FluentAssertions.Numeric; public class NumericAssertions : NumericAssertions> where T : struct, IComparable { - public NumericAssertions(T value) - : base(value) + public NumericAssertions(T value, AssertionChain assertionChain) + : base(value, assertionChain) { } } @@ -32,14 +32,15 @@ public class NumericAssertions where T : struct, IComparable where TAssertions : NumericAssertions { - public NumericAssertions(T value) - : this((T?)value) + public NumericAssertions(T value, AssertionChain assertionChain) + : this((T?)value, assertionChain) { } - private protected NumericAssertions(T? value) + private protected NumericAssertions(T? value, AssertionChain assertionChain) { Subject = value; + CurrentAssertionChain = assertionChain; } public T? Subject { get; } @@ -57,7 +58,7 @@ private protected NumericAssertions(T? value) /// public AndConstraint Be(T expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + CurrentAssertionChain .ForCondition(Subject?.CompareTo(expected) == 0) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:value} to be {0}{reason}, but found {1}" + GenerateDifferenceMessage(expected), expected, @@ -79,7 +80,7 @@ public AndConstraint Be(T expected, [StringSyntax("CompositeFormat" /// public AndConstraint Be(T? expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + CurrentAssertionChain .ForCondition(expected is { } value ? Subject?.CompareTo(value) == 0 : !Subject.HasValue) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:value} to be {0}{reason}, but found {1}" + GenerateDifferenceMessage(expected), expected, @@ -101,7 +102,7 @@ public AndConstraint Be(T? expected, [StringSyntax("CompositeFormat /// public AndConstraint NotBe(T unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + CurrentAssertionChain .ForCondition(Subject?.CompareTo(unexpected) != 0) .BecauseOf(because, becauseArgs) .FailWith("Did not expect {context:value} to be {0}{reason}.", unexpected); @@ -122,7 +123,7 @@ public AndConstraint NotBe(T unexpected, [StringSyntax("CompositeFo /// public AndConstraint NotBe(T? unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + CurrentAssertionChain .ForCondition(unexpected is { } value ? Subject?.CompareTo(value) != 0 : Subject.HasValue) .BecauseOf(because, becauseArgs) .FailWith("Did not expect {context:value} to be {0}{reason}.", unexpected); @@ -142,7 +143,7 @@ public AndConstraint NotBe(T? unexpected, [StringSyntax("CompositeF /// public AndConstraint BePositive([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + CurrentAssertionChain .ForCondition(Subject?.CompareTo(default) > 0) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:value} to be positive{reason}, but found {0}.", Subject); @@ -162,7 +163,7 @@ public AndConstraint BePositive([StringSyntax("CompositeFormat")] s /// public AndConstraint BeNegative([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + CurrentAssertionChain .ForCondition(Subject is { } value && !IsNaN(value) && value.CompareTo(default) < 0) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:value} to be negative{reason}, but found {0}.", Subject); @@ -188,7 +189,7 @@ public AndConstraint BeLessThan(T expected, [StringSyntax("Composit throw new ArgumentException("A value can never be less than NaN", nameof(expected)); } - Execute.Assertion + CurrentAssertionChain .ForCondition(Subject is { } value && !IsNaN(value) && value.CompareTo(expected) < 0) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:value} to be less than {0}{reason}, but found {1}" + GenerateDifferenceMessage(expected), @@ -216,7 +217,7 @@ public AndConstraint BeLessThanOrEqualTo(T expected, throw new ArgumentException("A value can never be less than or equal to NaN", nameof(expected)); } - Execute.Assertion + CurrentAssertionChain .ForCondition(Subject is { } value && !IsNaN(value) && value.CompareTo(expected) <= 0) .BecauseOf(because, becauseArgs) .FailWith( @@ -245,7 +246,7 @@ public AndConstraint BeGreaterThan(T expected, throw new ArgumentException("A value can never be greater than NaN", nameof(expected)); } - Execute.Assertion + CurrentAssertionChain .ForCondition(Subject?.CompareTo(expected) > 0) .BecauseOf(because, becauseArgs) .FailWith( @@ -274,7 +275,7 @@ public AndConstraint BeGreaterThanOrEqualTo(T expected, throw new ArgumentException("A value can never be greater than or equal to a NaN", nameof(expected)); } - Execute.Assertion + CurrentAssertionChain .ForCondition(Subject?.CompareTo(expected) >= 0) .BecauseOf(because, becauseArgs) .FailWith( @@ -311,7 +312,7 @@ public AndConstraint BeInRange(T minimumValue, T maximumValue, throw new ArgumentException("A range cannot begin or end with NaN"); } - Execute.Assertion + CurrentAssertionChain .ForCondition(Subject is { } value && value.CompareTo(minimumValue) >= 0 && value.CompareTo(maximumValue) <= 0) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:value} to be between {0} and {1}{reason}, but found {2}.", @@ -347,7 +348,7 @@ public AndConstraint NotBeInRange(T minimumValue, T maximumValue, throw new ArgumentException("A range cannot begin or end with NaN"); } - Execute.Assertion + CurrentAssertionChain .ForCondition(Subject is { } value && !(value.CompareTo(minimumValue) >= 0 && value.CompareTo(maximumValue) <= 0)) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:value} to not be between {0} and {1}{reason}, but found {2}.", @@ -383,7 +384,7 @@ public AndConstraint BeOneOf(params T[] validValues) public AndConstraint BeOneOf(IEnumerable validValues, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + CurrentAssertionChain .ForCondition(Subject is { } value && validValues.Contains(value)) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:value} to be one of {0}{reason}, but found {1}.", validValues, Subject); @@ -441,12 +442,12 @@ public AndConstraint NotBeOfType(Type unexpectedType, [StringSyntax { Guard.ThrowIfArgumentIsNull(unexpectedType); - bool success = Execute.Assertion + CurrentAssertionChain .ForCondition(Subject.HasValue) .BecauseOf(because, becauseArgs) .FailWith("Expected type not to be " + unexpectedType + "{reason}, but found ."); - if (success) + if (CurrentAssertionChain.Succeeded) { Subject.GetType().Should().NotBe(unexpectedType, because, becauseArgs); } @@ -473,7 +474,7 @@ public AndConstraint Match(Expression> predicate, { Guard.ThrowIfArgumentIsNull(predicate); - Execute.Assertion + CurrentAssertionChain .ForCondition(predicate.Compile()((T)Subject)) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:value} to match {0}{reason}, but found {1}.", predicate.Body, Subject); @@ -510,4 +511,6 @@ private string GenerateDifferenceMessage(T? expected) var difference = CalculateDifferenceForFailureMessage(subject, expectedValue); return difference is null ? noDifferenceMessage : $" (difference of {difference})."; } + + public AssertionChain CurrentAssertionChain { get; } } diff --git a/Src/FluentAssertions/Numeric/SByteAssertions.cs b/Src/FluentAssertions/Numeric/SByteAssertions.cs index f0f19c4cda..7ac217e8a3 100644 --- a/Src/FluentAssertions/Numeric/SByteAssertions.cs +++ b/Src/FluentAssertions/Numeric/SByteAssertions.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Globalization; +using FluentAssertions.Execution; namespace FluentAssertions.Numeric; @@ -9,8 +10,8 @@ namespace FluentAssertions.Numeric; [DebuggerNonUserCode] internal class SByteAssertions : NumericAssertions { - internal SByteAssertions(sbyte value) - : base(value) + internal SByteAssertions(sbyte value, AssertionChain assertionChain) + : base(value, assertionChain) { } diff --git a/Src/FluentAssertions/Numeric/SingleAssertions.cs b/Src/FluentAssertions/Numeric/SingleAssertions.cs index 6a11220314..2276b01334 100644 --- a/Src/FluentAssertions/Numeric/SingleAssertions.cs +++ b/Src/FluentAssertions/Numeric/SingleAssertions.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Globalization; +using FluentAssertions.Execution; namespace FluentAssertions.Numeric; @@ -9,8 +10,8 @@ namespace FluentAssertions.Numeric; [DebuggerNonUserCode] internal class SingleAssertions : NumericAssertions { - internal SingleAssertions(float value) - : base(value) + internal SingleAssertions(float value, AssertionChain assertionChain) + : base(value, assertionChain) { } diff --git a/Src/FluentAssertions/Numeric/UInt16Assertions.cs b/Src/FluentAssertions/Numeric/UInt16Assertions.cs index b067a2a085..c1672f1482 100644 --- a/Src/FluentAssertions/Numeric/UInt16Assertions.cs +++ b/Src/FluentAssertions/Numeric/UInt16Assertions.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Globalization; +using FluentAssertions.Execution; namespace FluentAssertions.Numeric; @@ -9,8 +10,8 @@ namespace FluentAssertions.Numeric; [DebuggerNonUserCode] internal class UInt16Assertions : NumericAssertions { - internal UInt16Assertions(ushort value) - : base(value) + internal UInt16Assertions(ushort value, AssertionChain assertionChain) + : base(value, assertionChain) { } diff --git a/Src/FluentAssertions/Numeric/UInt32Assertions.cs b/Src/FluentAssertions/Numeric/UInt32Assertions.cs index ecd089901d..49bcaad48f 100644 --- a/Src/FluentAssertions/Numeric/UInt32Assertions.cs +++ b/Src/FluentAssertions/Numeric/UInt32Assertions.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Globalization; +using FluentAssertions.Execution; namespace FluentAssertions.Numeric; @@ -9,8 +10,8 @@ namespace FluentAssertions.Numeric; [DebuggerNonUserCode] internal class UInt32Assertions : NumericAssertions { - internal UInt32Assertions(uint value) - : base(value) + internal UInt32Assertions(uint value, AssertionChain assertionChain) + : base(value, assertionChain) { } diff --git a/Src/FluentAssertions/Numeric/UInt64Assertions.cs b/Src/FluentAssertions/Numeric/UInt64Assertions.cs index f45d9f35d4..3060a1c012 100644 --- a/Src/FluentAssertions/Numeric/UInt64Assertions.cs +++ b/Src/FluentAssertions/Numeric/UInt64Assertions.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Globalization; +using FluentAssertions.Execution; namespace FluentAssertions.Numeric; @@ -9,8 +10,8 @@ namespace FluentAssertions.Numeric; [DebuggerNonUserCode] internal class UInt64Assertions : NumericAssertions { - internal UInt64Assertions(ulong value) - : base(value) + internal UInt64Assertions(ulong value, AssertionChain assertionChain) + : base(value, assertionChain) { } diff --git a/Src/FluentAssertions/NumericAssertionsExtensions.cs b/Src/FluentAssertions/NumericAssertionsExtensions.cs index a2ab750506..20d3b9e6fe 100644 --- a/Src/FluentAssertions/NumericAssertionsExtensions.cs +++ b/Src/FluentAssertions/NumericAssertionsExtensions.cs @@ -49,8 +49,9 @@ public static AndConstraint> BeCloseTo(this NumericAsse maxValue = sbyte.MaxValue; } - FailIfValueOutsideBounds(minValue <= actualValue && actualValue <= maxValue, nearbyValue, delta, actualValue, because, - becauseArgs); + FailIfValueOutsideBounds(parent.CurrentAssertionChain, + minValue <= actualValue && actualValue <= maxValue, + nearbyValue, delta, actualValue, because, becauseArgs); return new AndConstraint>(parent); } @@ -91,8 +92,9 @@ public static AndConstraint> BeCloseTo(this NumericAsser maxValue = byte.MaxValue; } - FailIfValueOutsideBounds(minValue <= actualValue && actualValue <= maxValue, nearbyValue, delta, actualValue, because, - becauseArgs); + FailIfValueOutsideBounds(parent.CurrentAssertionChain, + minValue <= actualValue && actualValue <= maxValue, nearbyValue, delta, actualValue, + because, becauseArgs); return new AndConstraint>(parent); } @@ -133,8 +135,10 @@ public static AndConstraint> BeCloseTo(this NumericAsse maxValue = short.MaxValue; } - FailIfValueOutsideBounds(minValue <= actualValue && actualValue <= maxValue, nearbyValue, delta, actualValue, because, - becauseArgs); + FailIfValueOutsideBounds(parent.CurrentAssertionChain, + minValue <= actualValue && actualValue <= maxValue, + nearbyValue, delta, actualValue, + because, becauseArgs); return new AndConstraint>(parent); } @@ -175,8 +179,10 @@ public static AndConstraint> BeCloseTo(this NumericAss maxValue = ushort.MaxValue; } - FailIfValueOutsideBounds(minValue <= actualValue && actualValue <= maxValue, nearbyValue, delta, actualValue, because, - becauseArgs); + FailIfValueOutsideBounds(parent.CurrentAssertionChain, + minValue <= actualValue && actualValue <= maxValue, + nearbyValue, delta, actualValue, + because, becauseArgs); return new AndConstraint>(parent); } @@ -217,7 +223,10 @@ public static AndConstraint> BeCloseTo(this NumericAssert maxValue = int.MaxValue; } - FailIfValueOutsideBounds(minValue <= actualValue && actualValue <= maxValue, nearbyValue, delta, actualValue, because, + FailIfValueOutsideBounds(parent.CurrentAssertionChain, + minValue <= actualValue && actualValue <= maxValue, + nearbyValue, delta, actualValue, + because, becauseArgs); return new AndConstraint>(parent); @@ -259,8 +268,11 @@ public static AndConstraint> BeCloseTo(this NumericAsser maxValue = uint.MaxValue; } - FailIfValueOutsideBounds(minValue <= actualValue && actualValue <= maxValue, nearbyValue, delta, actualValue, because, - becauseArgs); + FailIfValueOutsideBounds( + parent.CurrentAssertionChain, + minValue <= actualValue && actualValue <= maxValue, + nearbyValue, delta, actualValue, + because, becauseArgs); return new AndConstraint>(parent); } @@ -290,8 +302,10 @@ public static AndConstraint> BeCloseTo(this NumericAsser long minValue = GetMinValue(nearbyValue, delta); long maxValue = GetMaxValue(nearbyValue, delta); - FailIfValueOutsideBounds(minValue <= actualValue && actualValue <= maxValue, nearbyValue, delta, actualValue, because, - becauseArgs); + FailIfValueOutsideBounds(parent.CurrentAssertionChain, + minValue <= actualValue && actualValue <= maxValue, + nearbyValue, delta, actualValue, + because, becauseArgs); return new AndConstraint>(parent); } @@ -332,17 +346,19 @@ public static AndConstraint> BeCloseTo(this NumericAsse maxValue = ulong.MaxValue; } - FailIfValueOutsideBounds(minValue <= actualValue && actualValue <= maxValue, nearbyValue, delta, actualValue, because, - becauseArgs); + FailIfValueOutsideBounds(parent.CurrentAssertionChain, + minValue <= actualValue && actualValue <= maxValue, + nearbyValue, delta, actualValue, + because, becauseArgs); return new AndConstraint>(parent); } - private static void FailIfValueOutsideBounds(bool valueWithinBounds, + private static void FailIfValueOutsideBounds(AssertionChain assertionChain, bool valueWithinBounds, TValue nearbyValue, TDelta delta, TValue actualValue, [StringSyntax("CompositeFormat")] string because, object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(valueWithinBounds) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:value} to be within {0} from {1}{reason}, but found {2}.", @@ -389,8 +405,10 @@ public static AndConstraint> NotBeCloseTo(this NumericA maxValue = sbyte.MaxValue; } - FailIfValueInsideBounds(!(minValue <= actualValue && actualValue <= maxValue), distantValue, delta, actualValue, because, - becauseArgs); + FailIfValueInsideBounds(parent.CurrentAssertionChain, + !(minValue <= actualValue && actualValue <= maxValue), + distantValue, delta, actualValue, + because, becauseArgs); return new AndConstraint>(parent); } @@ -431,8 +449,11 @@ public static AndConstraint> NotBeCloseTo(this NumericAs maxValue = byte.MaxValue; } - FailIfValueInsideBounds(!(minValue <= actualValue && actualValue <= maxValue), distantValue, delta, actualValue, because, - becauseArgs); + FailIfValueInsideBounds( + parent.CurrentAssertionChain, + !(minValue <= actualValue && actualValue <= maxValue), + distantValue, delta, actualValue, + because, becauseArgs); return new AndConstraint>(parent); } @@ -473,8 +494,11 @@ public static AndConstraint> NotBeCloseTo(this NumericA maxValue = short.MaxValue; } - FailIfValueInsideBounds(!(minValue <= actualValue && actualValue <= maxValue), distantValue, delta, actualValue, because, - becauseArgs); + FailIfValueInsideBounds( + parent.CurrentAssertionChain, + !(minValue <= actualValue && actualValue <= maxValue), + distantValue, delta, actualValue, + because, becauseArgs); return new AndConstraint>(parent); } @@ -515,8 +539,10 @@ public static AndConstraint> NotBeCloseTo(this Numeric maxValue = ushort.MaxValue; } - FailIfValueInsideBounds(!(minValue <= actualValue && actualValue <= maxValue), distantValue, delta, actualValue, because, - becauseArgs); + FailIfValueInsideBounds(parent.CurrentAssertionChain, + !(minValue <= actualValue && actualValue <= maxValue), + distantValue, delta, actualValue, + because, becauseArgs); return new AndConstraint>(parent); } @@ -557,8 +583,11 @@ public static AndConstraint> NotBeCloseTo(this NumericAss maxValue = int.MaxValue; } - FailIfValueInsideBounds(!(minValue <= actualValue && actualValue <= maxValue), distantValue, delta, actualValue, because, - becauseArgs); + FailIfValueInsideBounds( + parent.CurrentAssertionChain, + !(minValue <= actualValue && actualValue <= maxValue), + distantValue, delta, actualValue, + because, becauseArgs); return new AndConstraint>(parent); } @@ -599,8 +628,10 @@ public static AndConstraint> NotBeCloseTo(this NumericAs maxValue = uint.MaxValue; } - FailIfValueInsideBounds(!(minValue <= actualValue && actualValue <= maxValue), distantValue, delta, actualValue, because, - becauseArgs); + FailIfValueInsideBounds(parent.CurrentAssertionChain, + !(minValue <= actualValue && actualValue <= maxValue), + distantValue, delta, actualValue, + because, becauseArgs); return new AndConstraint>(parent); } @@ -630,8 +661,10 @@ public static AndConstraint> NotBeCloseTo(this NumericAs long minValue = GetMinValue(distantValue, delta); long maxValue = GetMaxValue(distantValue, delta); - FailIfValueInsideBounds(!(minValue <= actualValue && actualValue <= maxValue), distantValue, delta, actualValue, because, - becauseArgs); + FailIfValueInsideBounds(parent.CurrentAssertionChain, + !(minValue <= actualValue && actualValue <= maxValue), + distantValue, delta, actualValue, + because, becauseArgs); return new AndConstraint>(parent); } @@ -672,18 +705,23 @@ public static AndConstraint> NotBeCloseTo(this NumericA maxValue = ulong.MaxValue; } - FailIfValueInsideBounds(!(minValue <= actualValue && actualValue <= maxValue), distantValue, delta, actualValue, because, + FailIfValueInsideBounds(parent.CurrentAssertionChain, + !(minValue <= actualValue && actualValue <= maxValue), distantValue, + delta, + actualValue, + because, becauseArgs); return new AndConstraint>(parent); } private static void FailIfValueInsideBounds( + AssertionChain assertionChain, bool valueOutsideBounds, TValue distantValue, TDelta delta, TValue actualValue, [StringSyntax("CompositeFormat")] string because, object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(valueOutsideBounds) .BecauseOf(because, becauseArgs) .FailWith("Did not expect {context:value} to be within {0} from {1}{reason}, but found {2}.", @@ -718,15 +756,17 @@ public static AndConstraint> BeApproximately(th { Guard.ThrowIfArgumentIsNegative(precision); - bool success = Execute.Assertion + var assertion = parent.CurrentAssertionChain; + + assertion .ForCondition(parent.Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:value} to approximate {0} +/- {1}{reason}, but it was .", expectedValue, precision); - if (success) + if (assertion.Succeeded) { - var nonNullableAssertions = new SingleAssertions(parent.Subject.Value); + var nonNullableAssertions = new SingleAssertions(parent.Subject.Value, assertion); nonNullableAssertions.BeApproximately(expectedValue, precision, because, becauseArgs); } @@ -763,13 +803,15 @@ public static AndConstraint> BeApproximately(th return new AndConstraint>(parent); } - bool succeeded = Execute.Assertion + var assertion = parent.CurrentAssertionChain; + + assertion .ForCondition(expectedValue is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:value} to approximate {0} +/- {1}{reason}, but it was {2}.", expectedValue, precision, parent.Subject); - if (succeeded) + if (assertion.Succeeded) { // ReSharper disable once PossibleInvalidOperationException parent.BeApproximately(expectedValue.Value, precision, because, becauseArgs); @@ -852,15 +894,17 @@ public static AndConstraint> BeApproximately(t { Guard.ThrowIfArgumentIsNegative(precision); - bool success = Execute.Assertion + var assertion = parent.CurrentAssertionChain; + + assertion .ForCondition(parent.Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:value} to approximate {0} +/- {1}{reason}, but it was .", expectedValue, precision); - if (success) + if (assertion.Succeeded) { - var nonNullableAssertions = new DoubleAssertions(parent.Subject.Value); + var nonNullableAssertions = new DoubleAssertions(parent.Subject.Value, assertion); BeApproximately(nonNullableAssertions, expectedValue, precision, because, becauseArgs); } @@ -897,13 +941,15 @@ public static AndConstraint> BeApproximately(t return new AndConstraint>(parent); } - bool succeeded = Execute.Assertion + var assertion = parent.CurrentAssertionChain; + + assertion .ForCondition(expectedValue is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:value} to approximate {0} +/- {1}{reason}, but it was {2}.", expectedValue, precision, parent.Subject); - if (succeeded) + if (assertion.Succeeded) { // ReSharper disable once PossibleInvalidOperationException parent.BeApproximately(expectedValue.Value, precision, because, becauseArgs); @@ -987,15 +1033,17 @@ public static AndConstraint> BeApproximately( { Guard.ThrowIfArgumentIsNegative(precision); - bool success = Execute.Assertion + var assertion = parent.CurrentAssertionChain; + + assertion .ForCondition(parent.Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:value} to approximate {0} +/- {1}{reason}, but it was .", expectedValue, precision); - if (success) + if (assertion.Succeeded) { - var nonNullableAssertions = new DecimalAssertions(parent.Subject.Value); + var nonNullableAssertions = new DecimalAssertions(parent.Subject.Value, assertion); BeApproximately(nonNullableAssertions, expectedValue, precision, because, becauseArgs); } @@ -1033,13 +1081,15 @@ public static AndConstraint> BeApproximately( return new AndConstraint>(parent); } - bool succeeded = Execute.Assertion + var assertion = parent.CurrentAssertionChain; + + assertion .ForCondition(expectedValue is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:value} to approximate {0} +/- {1}{reason}, but it was {2}.", expectedValue, precision, parent.Subject); - if (succeeded) + if (assertion.Succeeded) { // ReSharper disable once PossibleInvalidOperationException parent.BeApproximately(expectedValue.Value, precision, because, becauseArgs); @@ -1086,7 +1136,9 @@ private static void FailIfDifferenceOutsidePrecision( [StringSyntax("CompositeFormat")] string because, object[] becauseArgs) where T : struct, IComparable { - Execute.Assertion + var assertion = parent.CurrentAssertionChain; + + assertion .ForCondition(differenceWithinPrecision) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:value} to approximate {1} +/- {2}{reason}, but {0} differed by {3}.", @@ -1123,7 +1175,7 @@ public static AndConstraint> NotBeApproximately if (parent.Subject is not null) { - var nonNullableAssertions = new SingleAssertions(parent.Subject.Value); + var nonNullableAssertions = new SingleAssertions(parent.Subject.Value, parent.CurrentAssertionChain); nonNullableAssertions.NotBeApproximately(unexpectedValue, precision, because, becauseArgs); } @@ -1160,13 +1212,15 @@ public static AndConstraint> NotBeApproximately return new AndConstraint>(parent); } - bool succeeded = Execute.Assertion + var assertion = parent.CurrentAssertionChain; + + assertion .ForCondition(parent.Subject is not null && unexpectedValue is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:value} to not approximate {0} +/- {1}{reason}, but it was {2}.", unexpectedValue, precision, parent.Subject); - if (succeeded) + if (assertion.Succeeded) { // ReSharper disable once PossibleInvalidOperationException parent.NotBeApproximately(unexpectedValue.Value, precision, because, becauseArgs); @@ -1252,7 +1306,7 @@ public static AndConstraint> NotBeApproximatel if (parent.Subject is not null) { - var nonNullableAssertions = new DoubleAssertions(parent.Subject.Value); + var nonNullableAssertions = new DoubleAssertions(parent.Subject.Value, parent.CurrentAssertionChain); nonNullableAssertions.NotBeApproximately(unexpectedValue, precision, because, becauseArgs); } @@ -1290,13 +1344,15 @@ public static AndConstraint> NotBeApproximatel return new AndConstraint>(parent); } - bool succeeded = Execute.Assertion + AssertionChain assertionChain = parent.CurrentAssertionChain; + + assertionChain .ForCondition(parent.Subject is not null && unexpectedValue is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:value} to not approximate {0} +/- {1}{reason}, but it was {2}.", unexpectedValue, precision, parent.Subject); - if (succeeded) + if (assertionChain.Succeeded) { // ReSharper disable once PossibleInvalidOperationException parent.NotBeApproximately(unexpectedValue.Value, precision, because, becauseArgs); @@ -1382,7 +1438,7 @@ public static AndConstraint> NotBeApproximate if (parent.Subject is not null) { - var nonNullableAssertions = new DecimalAssertions(parent.Subject.Value); + var nonNullableAssertions = new DecimalAssertions(parent.Subject.Value, parent.CurrentAssertionChain); NotBeApproximately(nonNullableAssertions, unexpectedValue, precision, because, becauseArgs); } @@ -1420,13 +1476,15 @@ public static AndConstraint> NotBeApproximate return new AndConstraint>(parent); } - bool succeeded = Execute.Assertion + var assertion = parent.CurrentAssertionChain; + + assertion .ForCondition(parent.Subject is not null && unexpectedValue is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:value} to not approximate {0} +/- {1}{reason}, but it was {2}.", unexpectedValue, precision, parent.Subject); - if (succeeded) + if (assertion.Succeeded) { // ReSharper disable once PossibleInvalidOperationException parent.NotBeApproximately(unexpectedValue.Value, precision, because, becauseArgs); @@ -1473,7 +1531,7 @@ private static void FailIfDifferenceWithinPrecision( [StringSyntax("CompositeFormat")] string because, object[] becauseArgs) where T : struct, IComparable { - Execute.Assertion + parent.CurrentAssertionChain .ForCondition(differenceOutsidePrecision) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:value} to not approximate {1} +/- {2}{reason}, but {0} only differed by {3}.", @@ -1500,7 +1558,7 @@ public static AndConstraint> BeNaN(this NumericAssertio { float actualValue = parent.Subject.Value; - Execute.Assertion + parent.CurrentAssertionChain .ForCondition(float.IsNaN(actualValue)) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:value} to be NaN{reason}, but found {0}.", actualValue); @@ -1524,7 +1582,7 @@ public static AndConstraint> BeNaN(this NumericAsserti { double actualValue = parent.Subject.Value; - Execute.Assertion + parent.CurrentAssertionChain .ForCondition(double.IsNaN(actualValue)) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:value} to be NaN{reason}, but found {0}.", actualValue); @@ -1548,7 +1606,7 @@ public static AndConstraint> BeNaN(this Nullabl { float? actualValue = parent.Subject; - Execute.Assertion + parent.CurrentAssertionChain .ForCondition(actualValue is { } value && float.IsNaN(value)) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:value} to be NaN{reason}, but found {0}.", actualValue); @@ -1572,7 +1630,7 @@ public static AndConstraint> BeNaN(this Nullab { double? actualValue = parent.Subject; - Execute.Assertion + parent.CurrentAssertionChain .ForCondition(actualValue is { } value && double.IsNaN(value)) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:value} to be NaN{reason}, but found {0}.", actualValue); @@ -1600,7 +1658,7 @@ public static AndConstraint> NotBeNaN(this NumericAsser { float actualValue = parent.Subject.Value; - Execute.Assertion + parent.CurrentAssertionChain .ForCondition(!float.IsNaN(actualValue)) .BecauseOf(because, becauseArgs) .FailWith("Did not expect {context:value} to be NaN{reason}."); @@ -1624,7 +1682,7 @@ public static AndConstraint> NotBeNaN(this NumericAsse { double actualValue = parent.Subject.Value; - Execute.Assertion + parent.CurrentAssertionChain .ForCondition(!double.IsNaN(actualValue)) .BecauseOf(because, becauseArgs) .FailWith("Did not expect {context:value} to be NaN{reason}."); @@ -1649,7 +1707,7 @@ public static AndConstraint> NotBeNaN(this Null float? actualValue = parent.Subject; bool actualValueIsNaN = actualValue is { } value && float.IsNaN(value); - Execute.Assertion + parent.CurrentAssertionChain .ForCondition(!actualValueIsNaN) .BecauseOf(because, becauseArgs) .FailWith("Did not expect {context:value} to be NaN{reason}."); @@ -1674,7 +1732,7 @@ public static AndConstraint> NotBeNaN(this Nul double? actualValue = parent.Subject; bool actualValueIsNaN = actualValue is { } value && double.IsNaN(value); - Execute.Assertion + parent.CurrentAssertionChain .ForCondition(!actualValueIsNaN) .BecauseOf(because, becauseArgs) .FailWith("Did not expect {context:value} to be NaN{reason}."); @@ -1687,6 +1745,7 @@ public static AndConstraint> NotBeNaN(this Nul private static long GetMinValue(long value, ulong delta) { long minValue; + if (delta <= (ulong.MaxValue / 2)) { minValue = value - (long)delta; @@ -1711,6 +1770,7 @@ private static long GetMinValue(long value, ulong delta) private static long GetMaxValue(long value, ulong delta) { long maxValue; + if (delta <= (ulong.MaxValue / 2)) { maxValue = value + (long)delta; diff --git a/Src/FluentAssertions/ObjectAssertionsExtensions.cs b/Src/FluentAssertions/ObjectAssertionsExtensions.cs index 7819357665..5f93f280fa 100644 --- a/Src/FluentAssertions/ObjectAssertionsExtensions.cs +++ b/Src/FluentAssertions/ObjectAssertionsExtensions.cs @@ -5,7 +5,6 @@ using System.Xml.Serialization; using FluentAssertions.Common; using FluentAssertions.Equivalency; -using FluentAssertions.Execution; using FluentAssertions.Primitives; namespace FluentAssertions; @@ -66,7 +65,7 @@ public static AndConstraint BeDataContractSerializable(this } catch (Exception exc) { - Execute.Assertion + assertions.CurrentAssertionChain .BecauseOf(because, becauseArgs) .FailWith("Expected {0} to be serializable{reason}, but serialization failed with:" + Environment.NewLine + Environment.NewLine + "{1}.", @@ -110,7 +109,7 @@ public static AndConstraint BeXmlSerializable(this ObjectAsser } catch (Exception exc) { - Execute.Assertion + assertions.CurrentAssertionChain .BecauseOf(because, becauseArgs) .FailWith("Expected {0} to be serializable{reason}, but serialization failed with:" + Environment.NewLine + Environment.NewLine + "{1}.", diff --git a/Src/FluentAssertions/OccurrenceConstraint.cs b/Src/FluentAssertions/OccurrenceConstraint.cs index 1f93dd0f13..347df0b750 100644 --- a/Src/FluentAssertions/OccurrenceConstraint.cs +++ b/Src/FluentAssertions/OccurrenceConstraint.cs @@ -1,6 +1,5 @@ using System; using FluentAssertions.Common; -using FluentAssertions.Execution; namespace FluentAssertions; @@ -22,8 +21,8 @@ protected OccurrenceConstraint(int expectedCount) internal abstract bool Assert(int actual); - internal void RegisterReportables(AssertionScope scope) + internal void RegisterContextData(Action register) { - scope.AddReportable("expectedOccurrence", $"{Mode} {ExpectedCount.Times()}"); + register("expectedOccurrence", $"{Mode} {ExpectedCount.Times()}"); } } diff --git a/Src/FluentAssertions/Primitives/BooleanAssertions.cs b/Src/FluentAssertions/Primitives/BooleanAssertions.cs index 1adcba679f..4932df3e13 100644 --- a/Src/FluentAssertions/Primitives/BooleanAssertions.cs +++ b/Src/FluentAssertions/Primitives/BooleanAssertions.cs @@ -12,8 +12,8 @@ namespace FluentAssertions.Primitives; public class BooleanAssertions : BooleanAssertions { - public BooleanAssertions(bool? value) - : base(value) + public BooleanAssertions(bool? value, AssertionChain assertionChain) + : base(value, assertionChain) { } } @@ -27,8 +27,11 @@ public BooleanAssertions(bool? value) public class BooleanAssertions where TAssertions : BooleanAssertions { - public BooleanAssertions(bool? value) + private readonly AssertionChain assertionChain; + + public BooleanAssertions(bool? value, AssertionChain assertionChain) { + this.assertionChain = assertionChain; Subject = value; } @@ -49,7 +52,7 @@ public BooleanAssertions(bool? value) /// public AndConstraint BeFalse([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject == false) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:boolean} to be {0}{reason}, but found {1}.", false, Subject); @@ -69,7 +72,7 @@ public AndConstraint BeFalse([StringSyntax("CompositeFormat")] stri /// public AndConstraint BeTrue([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject == true) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:boolean} to be {0}{reason}, but found {1}.", true, Subject); @@ -88,9 +91,10 @@ public AndConstraint BeTrue([StringSyntax("CompositeFormat")] strin /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint Be(bool expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint Be(bool expected, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject == expected) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:boolean} to be {0}{reason}, but found {1}.", expected, Subject); @@ -109,9 +113,10 @@ public AndConstraint Be(bool expected, [StringSyntax("CompositeForm /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBe(bool unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint NotBe(bool unexpected, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject != unexpected) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:boolean} not to be {0}{reason}, but found {1}.", unexpected, Subject); @@ -136,16 +141,15 @@ public AndConstraint Imply(bool consequent, { bool? antecedent = Subject; - Execute.Assertion + assertionChain .ForCondition(antecedent is not null) .BecauseOf(because, becauseArgs) - .WithExpectation("Expected {context:antecedent} ({0}) to imply consequent ({1}){reason}, ", antecedent, consequent) - .FailWith("but found null.") - .Then - .ForCondition(!antecedent.Value || consequent) - .FailWith("but it did not.") - .Then - .ClearExpectation(); + .WithExpectation("Expected {context:antecedent} ({0}) to imply consequent ({1}){reason}, ", antecedent, consequent, + chain => chain + .FailWith("but found null.") + .Then + .ForCondition(!antecedent.Value || consequent) + .FailWith("but it did not.")); return new AndConstraint((TAssertions)this); } diff --git a/Src/FluentAssertions/Primitives/DateOnlyAssertions.cs b/Src/FluentAssertions/Primitives/DateOnlyAssertions.cs index 5adc26841c..c5e57b5d73 100644 --- a/Src/FluentAssertions/Primitives/DateOnlyAssertions.cs +++ b/Src/FluentAssertions/Primitives/DateOnlyAssertions.cs @@ -15,8 +15,8 @@ namespace FluentAssertions.Primitives; [DebuggerNonUserCode] public class DateOnlyAssertions : DateOnlyAssertions { - public DateOnlyAssertions(DateOnly? value) - : base(value) + public DateOnlyAssertions(DateOnly? value, AssertionChain assertionChain) + : base(value, assertionChain) { } } @@ -30,8 +30,11 @@ public DateOnlyAssertions(DateOnly? value) public class DateOnlyAssertions where TAssertions : DateOnlyAssertions { - public DateOnlyAssertions(DateOnly? value) + private readonly AssertionChain assertionChain; + + public DateOnlyAssertions(DateOnly? value, AssertionChain assertionChain) { + this.assertionChain = assertionChain; Subject = value; } @@ -51,9 +54,10 @@ public DateOnlyAssertions(DateOnly? value) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint Be(DateOnly expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint Be(DateOnly expected, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject == expected) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:date} to be {0}{reason}, but found {1}.", @@ -73,9 +77,10 @@ public AndConstraint Be(DateOnly expected, [StringSyntax("Composite /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint Be(DateOnly? expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint Be(DateOnly? expected, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject == expected) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:date} to be {0}{reason}, but found {1}.", @@ -98,7 +103,7 @@ public AndConstraint Be(DateOnly? expected, [StringSyntax("Composit public AndConstraint NotBe(DateOnly unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject != unexpected) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:date} not to be {0}{reason}, but it is.", unexpected); @@ -120,7 +125,7 @@ public AndConstraint NotBe(DateOnly unexpected, [StringSyntax("Comp public AndConstraint NotBe(DateOnly? unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject != unexpected) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:date} not to be {0}{reason}, but it is.", unexpected); @@ -142,7 +147,7 @@ public AndConstraint NotBe(DateOnly? unexpected, public AndConstraint BeBefore(DateOnly expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject < expected) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:date} to be before {0}{reason}, but found {1}.", expected, @@ -182,7 +187,7 @@ public AndConstraint NotBeBefore(DateOnly unexpected, public AndConstraint BeOnOrBefore(DateOnly expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject <= expected) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:date} to be on or before {0}{reason}, but found {1}.", expected, @@ -222,7 +227,7 @@ public AndConstraint NotBeOnOrBefore(DateOnly unexpected, public AndConstraint BeAfter(DateOnly expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject > expected) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:date} to be after {0}{reason}, but found {1}.", expected, @@ -262,7 +267,7 @@ public AndConstraint NotBeAfter(DateOnly unexpected, public AndConstraint BeOnOrAfter(DateOnly expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject >= expected) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:date} to be on or after {0}{reason}, but found {1}.", expected, @@ -299,18 +304,17 @@ public AndConstraint NotBeOnOrAfter(DateOnly unexpected, /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveYear(int expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint HaveYear(int expected, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected the year part of {context:the date} to be {0}{reason}", expected) - .ForCondition(Subject.HasValue) - .FailWith(", but found .") - .Then - .ForCondition(Subject.Value.Year == expected) - .FailWith(", but found {0}.", Subject.Value.Year) - .Then - .ClearExpectation(); + .WithExpectation("Expected the year part of {context:the date} to be {0}{reason}", expected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith(", but found .") + .Then + .ForCondition(Subject.Value.Year == expected) + .FailWith(", but found {0}.", Subject.Value.Year)); return new AndConstraint((TAssertions)this); } @@ -326,9 +330,10 @@ public AndConstraint HaveYear(int expected, [StringSyntax("Composit /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveYear(int unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint NotHaveYear(int unexpected, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject.HasValue) .FailWith("Did not expect the year part of {context:the date} to be {0}{reason}, but found a DateOnly.", @@ -352,18 +357,17 @@ public AndConstraint NotHaveYear(int unexpected, [StringSyntax("Com /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveMonth(int expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint HaveMonth(int expected, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected the month part of {context:the date} to be {0}{reason}", expected) - .ForCondition(Subject.HasValue) - .FailWith(", but found a DateOnly.") - .Then - .ForCondition(Subject.Value.Month == expected) - .FailWith(", but found {0}.", Subject.Value.Month) - .Then - .ClearExpectation(); + .WithExpectation("Expected the month part of {context:the date} to be {0}{reason}", expected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith(", but found a DateOnly.") + .Then + .ForCondition(Subject.Value.Month == expected) + .FailWith(", but found {0}.", Subject.Value.Month)); return new AndConstraint((TAssertions)this); } @@ -379,18 +383,17 @@ public AndConstraint HaveMonth(int expected, [StringSyntax("Composi /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveMonth(int unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint NotHaveMonth(int unexpected, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Did not expect the month part of {context:the date} to be {0}{reason}", unexpected) - .ForCondition(Subject.HasValue) - .FailWith(", but found a DateOnly.") - .Then - .ForCondition(Subject.Value.Month != unexpected) - .FailWith(", but it was.") - .Then - .ClearExpectation(); + .WithExpectation("Did not expect the month part of {context:the date} to be {0}{reason}", unexpected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith(", but found a DateOnly.") + .Then + .ForCondition(Subject.Value.Month != unexpected) + .FailWith(", but it was.")); return new AndConstraint((TAssertions)this); } @@ -406,18 +409,17 @@ public AndConstraint NotHaveMonth(int unexpected, [StringSyntax("Co /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveDay(int expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint HaveDay(int expected, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected the day part of {context:the date} to be {0}{reason}", expected) - .ForCondition(Subject.HasValue) - .FailWith(", but found a DateOnly.") - .Then - .ForCondition(Subject.Value.Day == expected) - .FailWith(", but found {0}.", Subject.Value.Day) - .Then - .ClearExpectation(); + .WithExpectation("Expected the day part of {context:the date} to be {0}{reason}", expected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith(", but found a DateOnly.") + .Then + .ForCondition(Subject.Value.Day == expected) + .FailWith(", but found {0}.", Subject.Value.Day)); return new AndConstraint((TAssertions)this); } @@ -433,18 +435,17 @@ public AndConstraint HaveDay(int expected, [StringSyntax("Composite /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveDay(int unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint NotHaveDay(int unexpected, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Did not expect the day part of {context:the date} to be {0}{reason}", unexpected) - .ForCondition(Subject.HasValue) - .FailWith(", but found a DateOnly.") - .Then - .ForCondition(Subject.Value.Day != unexpected) - .FailWith(", but it was.") - .Then - .ClearExpectation(); + .WithExpectation("Did not expect the day part of {context:the date} to be {0}{reason}", unexpected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith(", but found a DateOnly.") + .Then + .ForCondition(Subject.Value.Day != unexpected) + .FailWith(", but it was.")); return new AndConstraint((TAssertions)this); } @@ -484,7 +485,8 @@ public AndConstraint BeOneOf(params DateOnly[] validValues) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeOneOf(IEnumerable validValues, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint BeOneOf(IEnumerable validValues, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return BeOneOf(validValues.Cast(), because, becauseArgs); } @@ -505,7 +507,7 @@ public AndConstraint BeOneOf(IEnumerable validValues, [St public AndConstraint BeOneOf(IEnumerable validValues, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(validValues.Contains(Subject)) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:date} to be one of {0}{reason}, but found {1}.", validValues, Subject); diff --git a/Src/FluentAssertions/Primitives/DateTimeAssertions.cs b/Src/FluentAssertions/Primitives/DateTimeAssertions.cs index 63effadf47..b1f7ffa01a 100644 --- a/Src/FluentAssertions/Primitives/DateTimeAssertions.cs +++ b/Src/FluentAssertions/Primitives/DateTimeAssertions.cs @@ -18,8 +18,8 @@ namespace FluentAssertions.Primitives; [DebuggerNonUserCode] public class DateTimeAssertions : DateTimeAssertions { - public DateTimeAssertions(DateTime? value) - : base(value) + public DateTimeAssertions(DateTime? value, AssertionChain assertionChain) + : base(value, assertionChain) { } } @@ -37,8 +37,11 @@ public DateTimeAssertions(DateTime? value) public class DateTimeAssertions where TAssertions : DateTimeAssertions { - public DateTimeAssertions(DateTime? value) + private readonly AssertionChain assertionChain; + + public DateTimeAssertions(DateTime? value, AssertionChain assertionChain) { + this.assertionChain = assertionChain; Subject = value; } @@ -58,9 +61,10 @@ public DateTimeAssertions(DateTime? value) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint Be(DateTime expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint Be(DateTime expected, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject == expected) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:date and time} to be {0}{reason}, but found {1}.", @@ -80,9 +84,10 @@ public AndConstraint Be(DateTime expected, [StringSyntax("Composite /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint Be(DateTime? expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint Be(DateTime? expected, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject == expected) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:date and time} to be {0}{reason}, but found {1}.", @@ -105,7 +110,7 @@ public AndConstraint Be(DateTime? expected, [StringSyntax("Composit public AndConstraint NotBe(DateTime unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject != unexpected) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:date and time} not to be {0}{reason}, but it is.", unexpected); @@ -127,7 +132,7 @@ public AndConstraint NotBe(DateTime unexpected, public AndConstraint NotBe(DateTime? unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject != unexpected) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:date and time} not to be {0}{reason}, but it is.", unexpected); @@ -170,16 +175,15 @@ public AndConstraint BeCloseTo(DateTime nearbyTime, TimeSpan precis TimeSpan? difference = (Subject - nearbyTime)?.Duration(); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected {context:the date and time} to be within {0} from {1}{reason}", precision, nearbyTime) - .ForCondition(Subject is not null) - .FailWith(", but found .") - .Then - .ForCondition(Subject >= minimumValue && Subject <= maximumValue) - .FailWith(", but {0} was off by {1}.", Subject, difference) - .Then - .ClearExpectation(); + .WithExpectation("Expected {context:the date and time} to be within {0} from {1}{reason}", precision, nearbyTime, + chain => chain + .ForCondition(Subject is not null) + .FailWith(", but found .") + .Then + .ForCondition(Subject >= minimumValue && Subject <= maximumValue) + .FailWith(", but {0} was off by {1}.", Subject, difference)); return new AndConstraint((TAssertions)this); } @@ -217,7 +221,7 @@ public AndConstraint NotBeCloseTo(DateTime distantTime, TimeSpan pr long distanceToMaxInTicks = (DateTime.MaxValue - distantTime).Ticks; DateTime maximumValue = distantTime.AddTicks(Math.Min(precision.Ticks, distanceToMaxInTicks)); - Execute.Assertion + assertionChain .ForCondition(Subject < minimumValue || Subject > maximumValue) .BecauseOf(because, becauseArgs) .FailWith( @@ -242,7 +246,7 @@ public AndConstraint NotBeCloseTo(DateTime distantTime, TimeSpan pr public AndConstraint BeBefore(DateTime expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject < expected) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:the date and time} to be before {0}{reason}, but found {1}.", expected, @@ -282,7 +286,7 @@ public AndConstraint NotBeBefore(DateTime unexpected, public AndConstraint BeOnOrBefore(DateTime expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject <= expected) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:the date and time} to be on or before {0}{reason}, but found {1}.", expected, @@ -322,7 +326,7 @@ public AndConstraint NotBeOnOrBefore(DateTime unexpected, public AndConstraint BeAfter(DateTime expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject > expected) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:the date and time} to be after {0}{reason}, but found {1}.", expected, @@ -362,7 +366,7 @@ public AndConstraint NotBeAfter(DateTime unexpected, public AndConstraint BeOnOrAfter(DateTime expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject >= expected) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:the date and time} to be on or after {0}{reason}, but found {1}.", expected, @@ -399,18 +403,17 @@ public AndConstraint NotBeOnOrAfter(DateTime unexpected, /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveYear(int expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint HaveYear(int expected, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected the year part of {context:the date} to be {0}{reason}", expected) - .ForCondition(Subject.HasValue) - .FailWith(", but found .") - .Then - .ForCondition(Subject.Value.Year == expected) - .FailWith(", but found {0}.", Subject.Value.Year) - .Then - .ClearExpectation(); + .WithExpectation("Expected the year part of {context:the date} to be {0}{reason}", expected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith(", but found .") + .Then + .ForCondition(Subject.Value.Year == expected) + .FailWith(", but found {0}.", Subject.Value.Year)); return new AndConstraint((TAssertions)this); } @@ -426,9 +429,10 @@ public AndConstraint HaveYear(int expected, [StringSyntax("Composit /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveYear(int unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint NotHaveYear(int unexpected, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject.HasValue) .FailWith("Did not expect the year part of {context:the date} to be {0}{reason}, but found a DateTime.", @@ -452,18 +456,17 @@ public AndConstraint NotHaveYear(int unexpected, [StringSyntax("Com /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveMonth(int expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint HaveMonth(int expected, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected the month part of {context:the date} to be {0}{reason}", expected) - .ForCondition(Subject.HasValue) - .FailWith(", but found a DateTime.") - .Then - .ForCondition(Subject.Value.Month == expected) - .FailWith(", but found {0}.", Subject.Value.Month) - .Then - .ClearExpectation(); + .WithExpectation("Expected the month part of {context:the date} to be {0}{reason}", expected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith(", but found a DateTime.") + .Then + .ForCondition(Subject.Value.Month == expected) + .FailWith(", but found {0}.", Subject.Value.Month)); return new AndConstraint((TAssertions)this); } @@ -479,18 +482,17 @@ public AndConstraint HaveMonth(int expected, [StringSyntax("Composi /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveMonth(int unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint NotHaveMonth(int unexpected, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Did not expect the month part of {context:the date} to be {0}{reason}", unexpected) - .ForCondition(Subject.HasValue) - .FailWith(", but found a DateTime.") - .Then - .ForCondition(Subject.Value.Month != unexpected) - .FailWith(", but it was.") - .Then - .ClearExpectation(); + .WithExpectation("Did not expect the month part of {context:the date} to be {0}{reason}", unexpected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith(", but found a DateTime.") + .Then + .ForCondition(Subject.Value.Month != unexpected) + .FailWith(", but it was.")); return new AndConstraint((TAssertions)this); } @@ -506,18 +508,17 @@ public AndConstraint NotHaveMonth(int unexpected, [StringSyntax("Co /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveDay(int expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint HaveDay(int expected, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected the day part of {context:the date} to be {0}{reason}", expected) - .ForCondition(Subject.HasValue) - .FailWith(", but found a DateTime.") - .Then - .ForCondition(Subject.Value.Day == expected) - .FailWith(", but found {0}.", Subject.Value.Day) - .Then - .ClearExpectation(); + .WithExpectation("Expected the day part of {context:the date} to be {0}{reason}", expected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith(", but found a DateTime.") + .Then + .ForCondition(Subject.Value.Day == expected) + .FailWith(", but found {0}.", Subject.Value.Day)); return new AndConstraint((TAssertions)this); } @@ -533,18 +534,17 @@ public AndConstraint HaveDay(int expected, [StringSyntax("Composite /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveDay(int unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint NotHaveDay(int unexpected, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Did not expect the day part of {context:the date} to be {0}{reason}", unexpected) - .ForCondition(Subject.HasValue) - .FailWith(", but found a DateTime.") - .Then - .ForCondition(Subject.Value.Day != unexpected) - .FailWith(", but it was.") - .Then - .ClearExpectation(); + .WithExpectation("Did not expect the day part of {context:the date} to be {0}{reason}", unexpected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith(", but found a DateTime.") + .Then + .ForCondition(Subject.Value.Day != unexpected) + .FailWith(", but it was.")); return new AndConstraint((TAssertions)this); } @@ -560,18 +560,17 @@ public AndConstraint NotHaveDay(int unexpected, [StringSyntax("Comp /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveHour(int expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint HaveHour(int expected, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected the hour part of {context:the time} to be {0}{reason}", expected) - .ForCondition(Subject.HasValue) - .FailWith(", but found a DateTime.") - .Then - .ForCondition(Subject.Value.Hour == expected) - .FailWith(", but found {0}.", Subject.Value.Hour) - .Then - .ClearExpectation(); + .WithExpectation("Expected the hour part of {context:the time} to be {0}{reason}", expected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith(", but found a DateTime.") + .Then + .ForCondition(Subject.Value.Hour == expected) + .FailWith(", but found {0}.", Subject.Value.Hour)); return new AndConstraint((TAssertions)this); } @@ -587,18 +586,17 @@ public AndConstraint HaveHour(int expected, [StringSyntax("Composit /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveHour(int unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint NotHaveHour(int unexpected, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Did not expect the hour part of {context:the time} to be {0}{reason}", unexpected) - .ForCondition(Subject.HasValue) - .FailWith(", but found a DateTime.", unexpected) - .Then - .ForCondition(Subject.Value.Hour != unexpected) - .FailWith(", but it was.", unexpected, Subject.Value.Hour) - .Then - .ClearExpectation(); + .WithExpectation("Did not expect the hour part of {context:the time} to be {0}{reason}", unexpected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith(", but found a DateTime.", unexpected) + .Then + .ForCondition(Subject.Value.Hour != unexpected) + .FailWith(", but it was.", unexpected, Subject.Value.Hour)); return new AndConstraint((TAssertions)this); } @@ -617,16 +615,14 @@ public AndConstraint NotHaveHour(int unexpected, [StringSyntax("Com public AndConstraint HaveMinute(int expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected the minute part of {context:the time} to be {0}{reason}", expected) - .ForCondition(Subject.HasValue) - .FailWith(", but found a DateTime.") - .Then - .ForCondition(Subject.Value.Minute == expected) - .FailWith(", but found {0}.", Subject.Value.Minute) - .Then - .ClearExpectation(); + .WithExpectation("Expected the minute part of {context:the time} to be {0}{reason}", expected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith(", but found a DateTime.") + .Then + .ForCondition(Subject.Value.Minute == expected) + .FailWith(", but found {0}.", Subject.Value.Minute)); return new AndConstraint((TAssertions)this); } @@ -645,16 +641,14 @@ public AndConstraint HaveMinute(int expected, public AndConstraint NotHaveMinute(int unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Did not expect the minute part of {context:the time} to be {0}{reason}", unexpected) - .ForCondition(Subject.HasValue) - .FailWith(", but found a DateTime.", unexpected) - .Then - .ForCondition(Subject.Value.Minute != unexpected) - .FailWith(", but it was.", unexpected, Subject.Value.Minute) - .Then - .ClearExpectation(); + .WithExpectation("Did not expect the minute part of {context:the time} to be {0}{reason}", unexpected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith(", but found a DateTime.", unexpected) + .Then + .ForCondition(Subject.Value.Minute != unexpected) + .FailWith(", but it was.", unexpected, Subject.Value.Minute)); return new AndConstraint((TAssertions)this); } @@ -673,16 +667,14 @@ public AndConstraint NotHaveMinute(int unexpected, public AndConstraint HaveSecond(int expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected the seconds part of {context:the time} to be {0}{reason}", expected) - .ForCondition(Subject.HasValue) - .FailWith(", but found a DateTime.") - .Then - .ForCondition(Subject.Value.Second == expected) - .FailWith(", but found {0}.", Subject.Value.Second) - .Then - .ClearExpectation(); + .WithExpectation("Expected the seconds part of {context:the time} to be {0}{reason}", expected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith(", but found a DateTime.") + .Then + .ForCondition(Subject.Value.Second == expected) + .FailWith(", but found {0}.", Subject.Value.Second)); return new AndConstraint((TAssertions)this); } @@ -701,16 +693,14 @@ public AndConstraint HaveSecond(int expected, public AndConstraint NotHaveSecond(int unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Did not expect the seconds part of {context:the time} to be {0}{reason}", unexpected) - .ForCondition(Subject.HasValue) - .FailWith(", but found a DateTime.") - .Then - .ForCondition(Subject.Value.Second != unexpected) - .FailWith(", but it was.") - .Then - .ClearExpectation(); + .WithExpectation("Did not expect the seconds part of {context:the time} to be {0}{reason}", unexpected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith(", but found a DateTime.") + .Then + .ForCondition(Subject.Value.Second != unexpected) + .FailWith(", but it was.")); return new AndConstraint((TAssertions)this); } @@ -724,7 +714,8 @@ public AndConstraint NotHaveSecond(int unexpected, /// public DateTimeRangeAssertions BeMoreThan(TimeSpan timeSpan) { - return new DateTimeRangeAssertions((TAssertions)this, Subject, TimeSpanCondition.MoreThan, timeSpan); + return new DateTimeRangeAssertions((TAssertions)this, assertionChain, Subject, TimeSpanCondition.MoreThan, + timeSpan); } /// @@ -737,7 +728,8 @@ public DateTimeRangeAssertions BeMoreThan(TimeSpan timeSpan) /// public DateTimeRangeAssertions BeAtLeast(TimeSpan timeSpan) { - return new DateTimeRangeAssertions((TAssertions)this, Subject, TimeSpanCondition.AtLeast, timeSpan); + return new DateTimeRangeAssertions((TAssertions)this, assertionChain, Subject, TimeSpanCondition.AtLeast, + timeSpan); } /// @@ -749,7 +741,8 @@ public DateTimeRangeAssertions BeAtLeast(TimeSpan timeSpan) /// public DateTimeRangeAssertions BeExactly(TimeSpan timeSpan) { - return new DateTimeRangeAssertions((TAssertions)this, Subject, TimeSpanCondition.Exactly, timeSpan); + return new DateTimeRangeAssertions((TAssertions)this, assertionChain, Subject, TimeSpanCondition.Exactly, + timeSpan); } /// @@ -761,7 +754,8 @@ public DateTimeRangeAssertions BeExactly(TimeSpan timeSpan) /// public DateTimeRangeAssertions BeWithin(TimeSpan timeSpan) { - return new DateTimeRangeAssertions((TAssertions)this, Subject, TimeSpanCondition.Within, timeSpan); + return new DateTimeRangeAssertions((TAssertions)this, assertionChain, Subject, TimeSpanCondition.Within, + timeSpan); } /// @@ -773,7 +767,8 @@ public DateTimeRangeAssertions BeWithin(TimeSpan timeSpan) /// public DateTimeRangeAssertions BeLessThan(TimeSpan timeSpan) { - return new DateTimeRangeAssertions((TAssertions)this, Subject, TimeSpanCondition.LessThan, timeSpan); + return new DateTimeRangeAssertions((TAssertions)this, assertionChain, Subject, TimeSpanCondition.LessThan, + timeSpan); } /// @@ -792,16 +787,15 @@ public AndConstraint BeSameDateAs(DateTime expected, { DateTime expectedDate = expected.Date; - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected the date part of {context:the date and time} to be {0}{reason}", expectedDate) - .ForCondition(Subject.HasValue) - .FailWith(", but found a DateTime.", expectedDate) - .Then - .ForCondition(Subject.Value.Date == expectedDate) - .FailWith(", but found {1}.", expectedDate, Subject.Value) - .Then - .ClearExpectation(); + .WithExpectation("Expected the date part of {context:the date and time} to be {0}{reason}", expectedDate, + chain => chain + .ForCondition(Subject.HasValue) + .FailWith(", but found a DateTime.", expectedDate) + .Then + .ForCondition(Subject.Value.Date == expectedDate) + .FailWith(", but found {1}.", expectedDate, Subject.Value)); return new AndConstraint((TAssertions)this); } @@ -822,16 +816,15 @@ public AndConstraint NotBeSameDateAs(DateTime unexpected, { DateTime unexpectedDate = unexpected.Date; - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Did not expect the date part of {context:the date and time} to be {0}{reason}", unexpectedDate) - .ForCondition(Subject.HasValue) - .FailWith(", but found a DateTime.") - .Then - .ForCondition(Subject.Value.Date != unexpectedDate) - .FailWith(", but it was.") - .Then - .ClearExpectation(); + .WithExpectation("Did not expect the date part of {context:the date and time} to be {0}{reason}", unexpectedDate, + chain => chain + .ForCondition(Subject.HasValue) + .FailWith(", but found a DateTime.") + .Then + .ForCondition(Subject.Value.Date != unexpectedDate) + .FailWith(", but it was.")); return new AndConstraint((TAssertions)this); } @@ -893,7 +886,7 @@ public AndConstraint BeOneOf(IEnumerable validValues, public AndConstraint BeOneOf(IEnumerable validValues, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(validValues.Contains(Subject)) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:date and time} to be one of {0}{reason}, but found {1}.", validValues, Subject); @@ -917,16 +910,14 @@ public AndConstraint BeOneOf(IEnumerable validValues, public AndConstraint BeIn(DateTimeKind expectedKind, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected {context:the date and time} to be in " + expectedKind + "{reason}") - .ForCondition(Subject.HasValue) - .FailWith(", but found a DateTime.") - .Then - .ForCondition(Subject.Value.Kind == expectedKind) - .FailWith(", but found " + Subject.Value.Kind + ".") - .Then - .ClearExpectation(); + .WithExpectation("Expected {context:the date and time} to be in " + expectedKind + "{reason}", chain => chain + .ForCondition(Subject.HasValue) + .FailWith(", but found a DateTime.") + .Then + .ForCondition(Subject.Value.Kind == expectedKind) + .FailWith(", but found " + Subject.Value.Kind + ".")); return new AndConstraint((TAssertions)this); } @@ -947,17 +938,15 @@ public AndConstraint BeIn(DateTimeKind expectedKind, public AndConstraint NotBeIn(DateTimeKind unexpectedKind, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Did not expect {context:the date and time} to be in " + unexpectedKind + "{reason}") - .Given(() => Subject) - .ForCondition(subject => subject.HasValue) - .FailWith(", but found a DateTime.") - .Then - .ForCondition(subject => subject.GetValueOrDefault().Kind != unexpectedKind) - .FailWith(", but it was.") - .Then - .ClearExpectation(); + .WithExpectation("Did not expect {context:the date and time} to be in " + unexpectedKind + "{reason}", chain => chain + .Given(() => Subject) + .ForCondition(subject => subject.HasValue) + .FailWith(", but found a DateTime.") + .Then + .ForCondition(subject => subject.GetValueOrDefault().Kind != unexpectedKind) + .FailWith(", but it was.")); return new AndConstraint((TAssertions)this); } diff --git a/Src/FluentAssertions/Primitives/DateTimeOffsetAssertions.cs b/Src/FluentAssertions/Primitives/DateTimeOffsetAssertions.cs index 35a03100c1..83d27c8ad0 100644 --- a/Src/FluentAssertions/Primitives/DateTimeOffsetAssertions.cs +++ b/Src/FluentAssertions/Primitives/DateTimeOffsetAssertions.cs @@ -19,8 +19,8 @@ namespace FluentAssertions.Primitives; public class DateTimeOffsetAssertions : DateTimeOffsetAssertions { - public DateTimeOffsetAssertions(DateTimeOffset? value) - : base(value) + public DateTimeOffsetAssertions(DateTimeOffset? value, AssertionChain assertionChain) + : base(value, assertionChain) { } } @@ -38,8 +38,11 @@ public DateTimeOffsetAssertions(DateTimeOffset? value) public class DateTimeOffsetAssertions where TAssertions : DateTimeOffsetAssertions { - public DateTimeOffsetAssertions(DateTimeOffset? value) + private readonly AssertionChain assertionChain; + + public DateTimeOffsetAssertions(DateTimeOffset? value, AssertionChain assertionChain) { + this.assertionChain = assertionChain; Subject = value; } @@ -62,17 +65,15 @@ public DateTimeOffsetAssertions(DateTimeOffset? value) public AndConstraint Be(DateTimeOffset expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .WithExpectation("Expected {context:the date and time} to represent the same point in time as {0}{reason}, ", - expected) - .ForCondition(Subject.HasValue) - .FailWith("but found a DateTimeOffset.") - .Then - .ForCondition(Subject == expected) - .FailWith("but {0} does not.", Subject) - .Then - .ClearExpectation(); + expected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith("but found a DateTimeOffset.") + .Then + .ForCondition(Subject == expected) + .FailWith("but {0} does not.", Subject)); return new AndConstraint((TAssertions)this); } @@ -93,24 +94,21 @@ public AndConstraint Be(DateTimeOffset? expected, { if (!expected.HasValue) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(!Subject.HasValue) .FailWith("Expected {context:the date and time} to be {reason}, but it was {0}.", Subject); } else { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .WithExpectation("Expected {context:the date and time} to represent the same point in time as {0}{reason}, ", - expected) - .ForCondition(Subject.HasValue) - .FailWith("but found a DateTimeOffset.") - .Then - .ForCondition(Subject == expected) - .FailWith("but {0} does not.", Subject) - .Then - .ClearExpectation(); + expected, chain => chain.ForCondition(Subject.HasValue) + .FailWith("but found a DateTimeOffset.") + .Then + .ForCondition(Subject == expected) + .FailWith("but {0} does not.", Subject)); } return new AndConstraint((TAssertions)this); @@ -130,7 +128,7 @@ public AndConstraint Be(DateTimeOffset? expected, public AndConstraint NotBe(DateTimeOffset unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject != unexpected) .BecauseOf(because, becauseArgs) .FailWith( @@ -154,7 +152,7 @@ public AndConstraint NotBe(DateTimeOffset unexpected, public AndConstraint NotBe(DateTimeOffset? unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject != unexpected) .BecauseOf(because, becauseArgs) .FailWith( @@ -178,16 +176,14 @@ public AndConstraint NotBe(DateTimeOffset? unexpected, public AndConstraint BeExactly(DateTimeOffset expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected {context:the date and time} to be exactly {0}{reason}, ", expected) - .ForCondition(Subject.HasValue) - .FailWith("but found a DateTimeOffset.") - .Then - .ForCondition(Subject.Value.EqualsExact(expected)) - .FailWith("but it was {0}.", Subject) - .Then - .ClearExpectation(); + .WithExpectation("Expected {context:the date and time} to be exactly {0}{reason}, ", expected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith("but found a DateTimeOffset.") + .Then + .ForCondition(Subject.Value.EqualsExact(expected)) + .FailWith("but it was {0}.", Subject)); return new AndConstraint((TAssertions)this); } @@ -209,23 +205,21 @@ public AndConstraint BeExactly(DateTimeOffset? expected, { if (!expected.HasValue) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(!Subject.HasValue) .FailWith("Expected {context:the date and time} to be {reason}, but it was {0}.", Subject); } else { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected {context:the date and time} to be exactly {0}{reason}, ", expected) - .ForCondition(Subject.HasValue) - .FailWith("but found a DateTimeOffset.") - .Then - .ForCondition(Subject.Value.EqualsExact(expected.Value)) - .FailWith("but it was {0}.", Subject) - .Then - .ClearExpectation(); + .WithExpectation("Expected {context:the date and time} to be exactly {0}{reason}, ", expected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith("but found a DateTimeOffset.") + .Then + .ForCondition(Subject.Value.EqualsExact(expected.Value)) + .FailWith("but it was {0}.", Subject)); } return new AndConstraint((TAssertions)this); @@ -246,7 +240,7 @@ public AndConstraint BeExactly(DateTimeOffset? expected, public AndConstraint NotBeExactly(DateTimeOffset unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject?.EqualsExact(unexpected) != true) .BecauseOf(because, becauseArgs) .FailWith("Did not expect {context:the date and time} to be exactly {0}{reason}, but it was.", unexpected); @@ -268,7 +262,7 @@ public AndConstraint NotBeExactly(DateTimeOffset unexpected, public AndConstraint NotBeExactly(DateTimeOffset? unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(!((Subject == null && unexpected == null) || (Subject != null && unexpected != null && Subject.Value.EqualsExact(unexpected.Value)))) .BecauseOf(because, becauseArgs) @@ -312,16 +306,15 @@ public AndConstraint BeCloseTo(DateTimeOffset nearbyTime, TimeSpan TimeSpan? difference = (Subject - nearbyTime)?.Duration(); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected {context:the date and time} to be within {0} from {1}{reason}", precision, nearbyTime) - .ForCondition(Subject is not null) - .FailWith(", but found .") - .Then - .ForCondition(Subject >= minimumValue && Subject <= maximumValue) - .FailWith(", but {0} was off by {1}.", Subject, difference) - .Then - .ClearExpectation(); + .WithExpectation("Expected {context:the date and time} to be within {0} from {1}{reason}", precision, nearbyTime, + chain => chain + .ForCondition(Subject is not null) + .FailWith(", but found .") + .Then + .ForCondition(Subject >= minimumValue && Subject <= maximumValue) + .FailWith(", but {0} was off by {1}.", Subject, difference)); return new AndConstraint((TAssertions)this); } @@ -359,7 +352,7 @@ public AndConstraint NotBeCloseTo(DateTimeOffset distantTime, TimeS long distanceToMaxInTicks = (DateTimeOffset.MaxValue - distantTime).Ticks; DateTimeOffset maximumValue = distantTime.AddTicks(Math.Min(precision.Ticks, distanceToMaxInTicks)); - Execute.Assertion + assertionChain .ForCondition(Subject < minimumValue || Subject > maximumValue) .BecauseOf(because, becauseArgs) .FailWith( @@ -384,7 +377,7 @@ public AndConstraint NotBeCloseTo(DateTimeOffset distantTime, TimeS public AndConstraint BeBefore(DateTimeOffset expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject < expected) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:the date and time} to be before {0}{reason}, but it was {1}.", expected, @@ -424,7 +417,7 @@ public AndConstraint NotBeBefore(DateTimeOffset unexpected, public AndConstraint BeOnOrBefore(DateTimeOffset expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject <= expected) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:the date and time} to be on or before {0}{reason}, but it was {1}.", expected, @@ -464,7 +457,7 @@ public AndConstraint NotBeOnOrBefore(DateTimeOffset unexpected, public AndConstraint BeAfter(DateTimeOffset expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject > expected) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:the date and time} to be after {0}{reason}, but it was {1}.", expected, @@ -504,7 +497,7 @@ public AndConstraint NotBeAfter(DateTimeOffset unexpected, public AndConstraint BeOnOrAfter(DateTimeOffset expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject >= expected) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:the date and time} to be on or after {0}{reason}, but it was {1}.", expected, @@ -544,16 +537,14 @@ public AndConstraint NotBeOnOrAfter(DateTimeOffset unexpected, public AndConstraint HaveYear(int expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected the year part of {context:the date} to be {0}{reason}, ", expected) - .ForCondition(Subject.HasValue) - .FailWith("but found a DateTimeOffset.") - .Then - .ForCondition(Subject.Value.Year == expected) - .FailWith("but it was {0}.", Subject.Value.Year) - .Then - .ClearExpectation(); + .WithExpectation("Expected the year part of {context:the date} to be {0}{reason}, ", expected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith("but found a DateTimeOffset.") + .Then + .ForCondition(Subject.Value.Year == expected) + .FailWith("but it was {0}.", Subject.Value.Year)); return new AndConstraint((TAssertions)this); } @@ -572,16 +563,14 @@ public AndConstraint HaveYear(int expected, public AndConstraint NotHaveYear(int unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Did not expect the year part of {context:the date} to be {0}{reason}, ", unexpected) - .ForCondition(Subject.HasValue) - .FailWith("but found a DateTimeOffset.") - .Then - .ForCondition(Subject.Value.Year != unexpected) - .FailWith("but it was.") - .Then - .ClearExpectation(); + .WithExpectation("Did not expect the year part of {context:the date} to be {0}{reason}, ", unexpected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith("but found a DateTimeOffset.") + .Then + .ForCondition(Subject.Value.Year != unexpected) + .FailWith("but it was.")); return new AndConstraint((TAssertions)this); } @@ -600,16 +589,14 @@ public AndConstraint NotHaveYear(int unexpected, public AndConstraint HaveMonth(int expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected the month part of {context:the date} to be {0}{reason}, ", expected) - .ForCondition(Subject.HasValue) - .FailWith("but found a DateTimeOffset.") - .Then - .ForCondition(Subject.Value.Month == expected) - .FailWith("but it was {0}.", Subject.Value.Month) - .Then - .ClearExpectation(); + .WithExpectation("Expected the month part of {context:the date} to be {0}{reason}, ", expected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith("but found a DateTimeOffset.") + .Then + .ForCondition(Subject.Value.Month == expected) + .FailWith("but it was {0}.", Subject.Value.Month)); return new AndConstraint((TAssertions)this); } @@ -628,16 +615,14 @@ public AndConstraint HaveMonth(int expected, public AndConstraint NotHaveMonth(int unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Did not expect the month part of {context:the date} to be {0}{reason}, ", unexpected) - .ForCondition(Subject.HasValue) - .FailWith("but found a DateTimeOffset.") - .Then - .ForCondition(Subject.Value.Month != unexpected) - .FailWith("but it was.") - .Then - .ClearExpectation(); + .WithExpectation("Did not expect the month part of {context:the date} to be {0}{reason}, ", unexpected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith("but found a DateTimeOffset.") + .Then + .ForCondition(Subject.Value.Month != unexpected) + .FailWith("but it was.")); return new AndConstraint((TAssertions)this); } @@ -656,16 +641,14 @@ public AndConstraint NotHaveMonth(int unexpected, public AndConstraint HaveDay(int expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected the day part of {context:the date} to be {0}{reason}, ", expected) - .ForCondition(Subject.HasValue) - .FailWith("but found a DateTimeOffset.") - .Then - .ForCondition(Subject.Value.Day == expected) - .FailWith("but it was {0}.", Subject.Value.Day) - .Then - .ClearExpectation(); + .WithExpectation("Expected the day part of {context:the date} to be {0}{reason}, ", expected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith("but found a DateTimeOffset.") + .Then + .ForCondition(Subject.Value.Day == expected) + .FailWith("but it was {0}.", Subject.Value.Day)); return new AndConstraint((TAssertions)this); } @@ -684,16 +667,14 @@ public AndConstraint HaveDay(int expected, public AndConstraint NotHaveDay(int unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Did not expect the day part of {context:the date} to be {0}{reason}, ", unexpected) - .ForCondition(Subject.HasValue) - .FailWith("but found a DateTimeOffset.") - .Then - .ForCondition(Subject.Value.Day != unexpected) - .FailWith("but it was.") - .Then - .ClearExpectation(); + .WithExpectation("Did not expect the day part of {context:the date} to be {0}{reason}, ", unexpected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith("but found a DateTimeOffset.") + .Then + .ForCondition(Subject.Value.Day != unexpected) + .FailWith("but it was.")); return new AndConstraint((TAssertions)this); } @@ -712,16 +693,14 @@ public AndConstraint NotHaveDay(int unexpected, public AndConstraint HaveHour(int expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected the hour part of {context:the time} to be {0}{reason}, ", expected) - .ForCondition(Subject.HasValue) - .FailWith("but found a DateTimeOffset.") - .Then - .ForCondition(Subject.Value.Hour == expected) - .FailWith("but it was {0}.", Subject.Value.Hour) - .Then - .ClearExpectation(); + .WithExpectation("Expected the hour part of {context:the time} to be {0}{reason}, ", expected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith("but found a DateTimeOffset.") + .Then + .ForCondition(Subject.Value.Hour == expected) + .FailWith("but it was {0}.", Subject.Value.Hour)); return new AndConstraint((TAssertions)this); } @@ -740,16 +719,14 @@ public AndConstraint HaveHour(int expected, public AndConstraint NotHaveHour(int unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Did not expect the hour part of {context:the time} to be {0}{reason}, ", unexpected) - .ForCondition(Subject.HasValue) - .FailWith("but found a DateTimeOffset.") - .Then - .ForCondition(Subject.Value.Hour != unexpected) - .FailWith("but it was.") - .Then - .ClearExpectation(); + .WithExpectation("Did not expect the hour part of {context:the time} to be {0}{reason}, ", unexpected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith("but found a DateTimeOffset.") + .Then + .ForCondition(Subject.Value.Hour != unexpected) + .FailWith("but it was.")); return new AndConstraint((TAssertions)this); } @@ -768,16 +745,14 @@ public AndConstraint NotHaveHour(int unexpected, public AndConstraint HaveMinute(int expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected the minute part of {context:the time} to be {0}{reason}, ", expected) - .ForCondition(Subject.HasValue) - .FailWith("but found a DateTimeOffset.") - .Then - .ForCondition(Subject.Value.Minute == expected) - .FailWith("but it was {0}.", Subject.Value.Minute) - .Then - .ClearExpectation(); + .WithExpectation("Expected the minute part of {context:the time} to be {0}{reason}, ", expected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith("but found a DateTimeOffset.") + .Then + .ForCondition(Subject.Value.Minute == expected) + .FailWith("but it was {0}.", Subject.Value.Minute)); return new AndConstraint((TAssertions)this); } @@ -796,16 +771,15 @@ public AndConstraint HaveMinute(int expected, public AndConstraint NotHaveMinute(int unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Did not expect the minute part of {context:the time} to be {0}{reason}, ", unexpected) - .ForCondition(Subject.HasValue) - .FailWith("but found a DateTimeOffset.") - .Then - .ForCondition(Subject.Value.Minute != unexpected) - .FailWith("but it was.") - .Then - .ClearExpectation(); + .WithExpectation("Did not expect the minute part of {context:the time} to be {0}{reason}, ", unexpected, + chain => chain + .ForCondition(Subject.HasValue) + .FailWith("but found a DateTimeOffset.") + .Then + .ForCondition(Subject.Value.Minute != unexpected) + .FailWith("but it was.")); return new AndConstraint((TAssertions)this); } @@ -824,16 +798,14 @@ public AndConstraint NotHaveMinute(int unexpected, public AndConstraint HaveSecond(int expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected the seconds part of {context:the time} to be {0}{reason}, ", expected) - .ForCondition(Subject.HasValue) - .FailWith("but found a DateTimeOffset.") - .Then - .ForCondition(Subject.Value.Second == expected) - .FailWith("but it was {0}.", Subject.Value.Second) - .Then - .ClearExpectation(); + .WithExpectation("Expected the seconds part of {context:the time} to be {0}{reason}, ", expected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith("but found a DateTimeOffset.") + .Then + .ForCondition(Subject.Value.Second == expected) + .FailWith("but it was {0}.", Subject.Value.Second)); return new AndConstraint((TAssertions)this); } @@ -852,16 +824,15 @@ public AndConstraint HaveSecond(int expected, public AndConstraint NotHaveSecond(int unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Did not expect the seconds part of {context:the time} to be {0}{reason}, ", unexpected) - .ForCondition(Subject.HasValue) - .FailWith("but found a DateTimeOffset.") - .Then - .ForCondition(Subject.Value.Second != unexpected) - .FailWith("but it was.") - .Then - .ClearExpectation(); + .WithExpectation("Did not expect the seconds part of {context:the time} to be {0}{reason}, ", unexpected, + chain => chain + .ForCondition(Subject.HasValue) + .FailWith("but found a DateTimeOffset.") + .Then + .ForCondition(Subject.Value.Second != unexpected) + .FailWith("but it was.")); return new AndConstraint((TAssertions)this); } @@ -880,16 +851,14 @@ public AndConstraint NotHaveSecond(int unexpected, public AndConstraint HaveOffset(TimeSpan expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected the offset of {context:the date} to be {0}{reason}, ", expected) - .ForCondition(Subject.HasValue) - .FailWith("but found a DateTimeOffset.") - .Then - .ForCondition(Subject.Value.Offset == expected) - .FailWith("but it was {0}.", Subject.Value.Offset) - .Then - .ClearExpectation(); + .WithExpectation("Expected the offset of {context:the date} to be {0}{reason}, ", expected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith("but found a DateTimeOffset.") + .Then + .ForCondition(Subject.Value.Offset == expected) + .FailWith("but it was {0}.", Subject.Value.Offset)); return new AndConstraint((TAssertions)this); } @@ -908,16 +877,14 @@ public AndConstraint HaveOffset(TimeSpan expected, public AndConstraint NotHaveOffset(TimeSpan unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Did not expect the offset of {context:the date} to be {0}{reason}, ", unexpected) - .ForCondition(Subject.HasValue) - .FailWith("but found a DateTimeOffset.") - .Then - .ForCondition(Subject.Value.Offset != unexpected) - .FailWith("but it was.") - .Then - .ClearExpectation(); + .WithExpectation("Did not expect the offset of {context:the date} to be {0}{reason}, ", unexpected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith("but found a DateTimeOffset.") + .Then + .ForCondition(Subject.Value.Offset != unexpected) + .FailWith("but it was.")); return new AndConstraint((TAssertions)this); } @@ -931,7 +898,8 @@ public AndConstraint NotHaveOffset(TimeSpan unexpected, /// public DateTimeOffsetRangeAssertions BeMoreThan(TimeSpan timeSpan) { - return new DateTimeOffsetRangeAssertions((TAssertions)this, Subject, TimeSpanCondition.MoreThan, timeSpan); + return new DateTimeOffsetRangeAssertions((TAssertions)this, assertionChain, Subject, + TimeSpanCondition.MoreThan, timeSpan); } /// @@ -944,7 +912,8 @@ public DateTimeOffsetRangeAssertions BeMoreThan(TimeSpan timeSpan) /// public DateTimeOffsetRangeAssertions BeAtLeast(TimeSpan timeSpan) { - return new DateTimeOffsetRangeAssertions((TAssertions)this, Subject, TimeSpanCondition.AtLeast, timeSpan); + return new DateTimeOffsetRangeAssertions((TAssertions)this, assertionChain, Subject, + TimeSpanCondition.AtLeast, timeSpan); } /// @@ -956,7 +925,8 @@ public DateTimeOffsetRangeAssertions BeAtLeast(TimeSpan timeSpan) /// public DateTimeOffsetRangeAssertions BeExactly(TimeSpan timeSpan) { - return new DateTimeOffsetRangeAssertions((TAssertions)this, Subject, TimeSpanCondition.Exactly, timeSpan); + return new DateTimeOffsetRangeAssertions((TAssertions)this, assertionChain, Subject, + TimeSpanCondition.Exactly, timeSpan); } /// @@ -968,7 +938,8 @@ public DateTimeOffsetRangeAssertions BeExactly(TimeSpan timeSpan) /// public DateTimeOffsetRangeAssertions BeWithin(TimeSpan timeSpan) { - return new DateTimeOffsetRangeAssertions((TAssertions)this, Subject, TimeSpanCondition.Within, timeSpan); + return new DateTimeOffsetRangeAssertions((TAssertions)this, assertionChain, Subject, + TimeSpanCondition.Within, timeSpan); } /// @@ -980,7 +951,8 @@ public DateTimeOffsetRangeAssertions BeWithin(TimeSpan timeSpan) /// public DateTimeOffsetRangeAssertions BeLessThan(TimeSpan timeSpan) { - return new DateTimeOffsetRangeAssertions((TAssertions)this, Subject, TimeSpanCondition.LessThan, timeSpan); + return new DateTimeOffsetRangeAssertions((TAssertions)this, assertionChain, Subject, + TimeSpanCondition.LessThan, timeSpan); } /// @@ -999,16 +971,15 @@ public AndConstraint BeSameDateAs(DateTimeOffset expected, { DateTime expectedDate = expected.Date; - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected the date part of {context:the date and time} to be {0}{reason}, ", expectedDate) - .ForCondition(Subject.HasValue) - .FailWith("but found a DateTimeOffset.", expectedDate) - .Then - .ForCondition(Subject.Value.Date == expectedDate) - .FailWith("but it was {0}.", Subject.Value.Date) - .Then - .ClearExpectation(); + .WithExpectation("Expected the date part of {context:the date and time} to be {0}{reason}, ", expectedDate, + chain => chain + .ForCondition(Subject.HasValue) + .FailWith("but found a DateTimeOffset.", expectedDate) + .Then + .ForCondition(Subject.Value.Date == expectedDate) + .FailWith("but it was {0}.", Subject.Value.Date)); return new AndConstraint((TAssertions)this); } @@ -1029,16 +1000,15 @@ public AndConstraint NotBeSameDateAs(DateTimeOffset unexpected, { DateTime unexpectedDate = unexpected.Date; - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Did not expect the date part of {context:the date and time} to be {0}{reason}, ", unexpectedDate) - .ForCondition(Subject.HasValue) - .FailWith("but found a DateTimeOffset.") - .Then - .ForCondition(Subject.Value.Date != unexpectedDate) - .FailWith("but it was.") - .Then - .ClearExpectation(); + .WithExpectation("Did not expect the date part of {context:the date and time} to be {0}{reason}, ", unexpectedDate, + chain => chain + .ForCondition(Subject.HasValue) + .FailWith("but found a DateTimeOffset.") + .Then + .ForCondition(Subject.Value.Date != unexpectedDate) + .FailWith("but it was.")); return new AndConstraint((TAssertions)this); } @@ -1100,7 +1070,7 @@ public AndConstraint BeOneOf(IEnumerable validValue public AndConstraint BeOneOf(IEnumerable validValues, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(validValues.Contains(Subject)) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:the date and time} to be one of {0}{reason}, but it was {1}.", validValues, Subject); diff --git a/Src/FluentAssertions/Primitives/DateTimeOffsetRangeAssertions.cs b/Src/FluentAssertions/Primitives/DateTimeOffsetRangeAssertions.cs index deea7a999e..964b73c9db 100644 --- a/Src/FluentAssertions/Primitives/DateTimeOffsetRangeAssertions.cs +++ b/Src/FluentAssertions/Primitives/DateTimeOffsetRangeAssertions.cs @@ -23,6 +23,7 @@ public class DateTimeOffsetRangeAssertions #region Private Definitions private readonly TAssertions parentAssertions; + private readonly AssertionChain assertionChain; private readonly TimeSpanPredicate predicate; private readonly Dictionary predicates = new() @@ -39,11 +40,13 @@ public class DateTimeOffsetRangeAssertions #endregion - protected internal DateTimeOffsetRangeAssertions(TAssertions parentAssertions, DateTimeOffset? subject, + protected internal DateTimeOffsetRangeAssertions(TAssertions parentAssertions, AssertionChain assertionChain, + DateTimeOffset? subject, TimeSpanCondition condition, TimeSpan timeSpan) { this.parentAssertions = parentAssertions; + this.assertionChain = assertionChain; this.subject = subject; this.timeSpan = timeSpan; @@ -66,17 +69,17 @@ protected internal DateTimeOffsetRangeAssertions(TAssertions parentAssertions, D public AndConstraint Before(DateTimeOffset target, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .ForCondition(subject.HasValue) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:the date and time} to be " + predicate.DisplayText + " {0} before {1}{reason}, but found a DateTime.", timeSpan, target); - if (success) + if (assertionChain.Succeeded) { TimeSpan actual = target - subject.Value; - Execute.Assertion + assertionChain .ForCondition(predicate.IsMatchedBy(actual, timeSpan)) .BecauseOf(because, becauseArgs) .FailWith( @@ -104,17 +107,17 @@ public AndConstraint Before(DateTimeOffset target, public AndConstraint After(DateTimeOffset target, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .ForCondition(subject.HasValue) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:the date and time} to be " + predicate.DisplayText + " {0} after {1}{reason}, but found a DateTime.", timeSpan, target); - if (success) + if (assertionChain.Succeeded) { TimeSpan actual = subject.Value - target; - Execute.Assertion + assertionChain .ForCondition(predicate.IsMatchedBy(actual, timeSpan)) .BecauseOf(because, becauseArgs) .FailWith( diff --git a/Src/FluentAssertions/Primitives/DateTimeRangeAssertions.cs b/Src/FluentAssertions/Primitives/DateTimeRangeAssertions.cs index c6976b51bc..6192cfa16b 100644 --- a/Src/FluentAssertions/Primitives/DateTimeRangeAssertions.cs +++ b/Src/FluentAssertions/Primitives/DateTimeRangeAssertions.cs @@ -23,6 +23,7 @@ public class DateTimeRangeAssertions #region Private Definitions private readonly TAssertions parentAssertions; + private readonly AssertionChain assertionChain; private readonly TimeSpanPredicate predicate; private readonly Dictionary predicates = new() @@ -39,11 +40,13 @@ public class DateTimeRangeAssertions #endregion - protected internal DateTimeRangeAssertions(TAssertions parentAssertions, DateTime? subject, + protected internal DateTimeRangeAssertions(TAssertions parentAssertions, AssertionChain assertionChain, + DateTime? subject, TimeSpanCondition condition, TimeSpan timeSpan) { this.parentAssertions = parentAssertions; + this.assertionChain = assertionChain; this.subject = subject; this.timeSpan = timeSpan; @@ -66,18 +69,18 @@ protected internal DateTimeRangeAssertions(TAssertions parentAssertions, DateTim public AndConstraint Before(DateTime target, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .ForCondition(subject.HasValue) .BecauseOf(because, becauseArgs) .FailWith("Expected date and/or time {0} to be " + predicate.DisplayText + " {1} before {2}{reason}, but found a DateTime.", subject, timeSpan, target); - if (success) + if (assertionChain.Succeeded) { TimeSpan actual = target - subject.Value; - Execute.Assertion + assertionChain .ForCondition(predicate.IsMatchedBy(actual, timeSpan)) .BecauseOf(because, becauseArgs) .FailWith( @@ -105,18 +108,18 @@ public AndConstraint Before(DateTime target, public AndConstraint After(DateTime target, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .ForCondition(subject.HasValue) .BecauseOf(because, becauseArgs) .FailWith("Expected date and/or time {0} to be " + predicate.DisplayText + " {1} after {2}{reason}, but found a DateTime.", subject, timeSpan, target); - if (success) + if (assertionChain.Succeeded) { TimeSpan actual = subject.Value - target; - Execute.Assertion + assertionChain .ForCondition(predicate.IsMatchedBy(actual, timeSpan)) .BecauseOf(because, becauseArgs) .FailWith( diff --git a/Src/FluentAssertions/Primitives/EnumAssertions.cs b/Src/FluentAssertions/Primitives/EnumAssertions.cs index 69d8b110ef..b740b3c7d3 100644 --- a/Src/FluentAssertions/Primitives/EnumAssertions.cs +++ b/Src/FluentAssertions/Primitives/EnumAssertions.cs @@ -15,8 +15,8 @@ namespace FluentAssertions.Primitives; public class EnumAssertions : EnumAssertions> where TEnum : struct, Enum { - public EnumAssertions(TEnum subject) - : base(subject) + public EnumAssertions(TEnum subject, AssertionChain assertionChain) + : base(subject, assertionChain) { } } @@ -30,13 +30,16 @@ public class EnumAssertions where TEnum : struct, Enum where TAssertions : EnumAssertions { - public EnumAssertions(TEnum subject) - : this((TEnum?)subject) + private readonly AssertionChain assertionChain; + + public EnumAssertions(TEnum subject, AssertionChain assertionChain) + : this((TEnum?)subject, assertionChain) { } - private protected EnumAssertions(TEnum? value) + private protected EnumAssertions(TEnum? value, AssertionChain assertionChain) { + this.assertionChain = assertionChain; Subject = value; } @@ -56,7 +59,7 @@ private protected EnumAssertions(TEnum? value) public AndConstraint Be(TEnum expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject?.Equals(expected) == true) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:the enum} to be {0}{reason}, but found {1}.", @@ -79,7 +82,7 @@ public AndConstraint Be(TEnum expected, public AndConstraint Be(TEnum? expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Nullable.Equals(Subject, expected)) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:the enum} to be {0}{reason}, but found {1}.", @@ -102,7 +105,7 @@ public AndConstraint Be(TEnum? expected, public AndConstraint NotBe(TEnum unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject?.Equals(unexpected) != true) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:the enum} not to be {0}{reason}, but it is.", unexpected); @@ -124,7 +127,7 @@ public AndConstraint NotBe(TEnum unexpected, public AndConstraint NotBe(TEnum? unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(!Nullable.Equals(Subject, unexpected)) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:the enum} not to be {0}{reason}, but it is.", unexpected); @@ -142,18 +145,17 @@ public AndConstraint NotBe(TEnum? unexpected, /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeDefined([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint BeDefined([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected {context:the enum} to be defined in {0}{reason}, ", typeof(TEnum)) - .ForCondition(Subject is not null) - .FailWith("but found .") - .Then - .ForCondition(Enum.IsDefined(typeof(TEnum), Subject)) - .FailWith("but it is not.") - .Then - .ClearExpectation(); + .WithExpectation("Expected {context:the enum} to be defined in {0}{reason}, ", typeof(TEnum), chain => chain + .ForCondition(Subject is not null) + .FailWith("but found .") + .Then + .ForCondition(Enum.IsDefined(typeof(TEnum), Subject)) + .FailWith("but it is not.")); return new AndConstraint((TAssertions)this); } @@ -168,18 +170,17 @@ public AndConstraint BeDefined([StringSyntax("CompositeFormat")] st /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeDefined([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint NotBeDefined([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Did not expect {context:the enum} to be defined in {0}{reason}, ", typeof(TEnum)) - .ForCondition(Subject is not null) - .FailWith("but found .") - .Then - .ForCondition(!Enum.IsDefined(typeof(TEnum), Subject)) - .FailWith("but it is.") - .Then - .ClearExpectation(); + .WithExpectation("Did not expect {context:the enum} to be defined in {0}{reason}, ", typeof(TEnum), chain => chain + .ForCondition(Subject is not null) + .FailWith("but found .") + .Then + .ForCondition(!Enum.IsDefined(typeof(TEnum), Subject)) + .FailWith("but it is.")); return new AndConstraint((TAssertions)this); } @@ -198,7 +199,7 @@ public AndConstraint NotBeDefined([StringSyntax("CompositeFormat")] public AndConstraint HaveValue(decimal expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject is { } value && GetValue(value) == expected) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:the enum} to have value {0}{reason}, but found {1}.", @@ -221,7 +222,7 @@ public AndConstraint HaveValue(decimal expected, public AndConstraint NotHaveValue(decimal unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(!(Subject is { } value && GetValue(value) == unexpected)) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:the enum} to not have value {0}{reason}, but found {1}.", @@ -245,7 +246,7 @@ public AndConstraint HaveSameValueAs(T expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where T : struct, Enum { - Execute.Assertion + assertionChain .ForCondition(Subject is { } value && GetValue(value) == GetValue(expected)) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:the enum} to have same value as {0}{reason}, but found {1}.", @@ -269,7 +270,7 @@ public AndConstraint NotHaveSameValueAs(T unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where T : struct, Enum { - Execute.Assertion + assertionChain .ForCondition(!(Subject is { } value && GetValue(value) == GetValue(unexpected))) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:the enum} to not have same value as {0}{reason}, but found {1}.", @@ -293,7 +294,7 @@ public AndConstraint HaveSameNameAs(T expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where T : struct, Enum { - Execute.Assertion + assertionChain .ForCondition(Subject is { } value && GetName(value) == GetName(expected)) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:the enum} to have same name as {0}{reason}, but found {1}.", @@ -317,7 +318,7 @@ public AndConstraint NotHaveSameNameAs(T unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where T : struct, Enum { - Execute.Assertion + assertionChain .ForCondition(!(Subject is { } value && GetName(value) == GetName(unexpected))) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:the enum} to not have same name as {0}{reason}, but found {1}.", @@ -340,7 +341,7 @@ public AndConstraint NotHaveSameNameAs(T unexpected, public AndConstraint HaveFlag(TEnum expectedFlag, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject?.HasFlag(expectedFlag) == true) .FailWith("Expected {context:the enum} to have flag {0}{reason}, but found {1}.", expectedFlag, Subject); @@ -362,7 +363,7 @@ public AndConstraint HaveFlag(TEnum expectedFlag, public AndConstraint NotHaveFlag(TEnum unexpectedFlag, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject?.HasFlag(unexpectedFlag) != true) .FailWith("Expected {context:the enum} to not have flag {0}{reason}.", unexpectedFlag); @@ -389,7 +390,7 @@ public AndConstraint Match(Expression> predicate { Guard.ThrowIfArgumentIsNull(predicate, nameof(predicate), "Cannot match an enum against a predicate."); - Execute.Assertion + assertionChain .ForCondition(predicate.Compile()(Subject)) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:the enum} to match {1}{reason}, but found {0}.", Subject, predicate.Body); @@ -432,7 +433,7 @@ public AndConstraint BeOneOf(IEnumerable validValues, Guard.ThrowIfArgumentIsEmpty(validValues, nameof(validValues), "Cannot assert that an enum is one of an empty list of enums"); - Execute.Assertion + assertionChain .ForCondition(Subject is not null) .FailWith("Expected {context:the enum} to be one of {0}{reason}, but found ", validValues) .Then diff --git a/Src/FluentAssertions/Primitives/GuidAssertions.cs b/Src/FluentAssertions/Primitives/GuidAssertions.cs index 648faa7964..d30fe808df 100644 --- a/Src/FluentAssertions/Primitives/GuidAssertions.cs +++ b/Src/FluentAssertions/Primitives/GuidAssertions.cs @@ -11,8 +11,8 @@ namespace FluentAssertions.Primitives; [DebuggerNonUserCode] public class GuidAssertions : GuidAssertions { - public GuidAssertions(Guid? value) - : base(value) + public GuidAssertions(Guid? value, AssertionChain assertionChain) + : base(value, assertionChain) { } } @@ -26,8 +26,11 @@ public GuidAssertions(Guid? value) public class GuidAssertions where TAssertions : GuidAssertions { - public GuidAssertions(Guid? value) + private readonly AssertionChain assertionChain; + + public GuidAssertions(Guid? value, AssertionChain assertionChain) { + this.assertionChain = assertionChain; Subject = value; } @@ -50,7 +53,7 @@ public GuidAssertions(Guid? value) /// public AndConstraint BeEmpty([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject == Guid.Empty) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:Guid} to be empty{reason}, but found {0}.", Subject); @@ -70,7 +73,7 @@ public AndConstraint BeEmpty([StringSyntax("CompositeFormat")] stri /// public AndConstraint NotBeEmpty([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject is { } value && value != Guid.Empty) .BecauseOf(because, becauseArgs) .FailWith("Did not expect {context:Guid} to be empty{reason}."); @@ -119,7 +122,7 @@ public AndConstraint Be(string expected, public AndConstraint Be(Guid expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject == expected) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:Guid} to be {0}{reason}, but found {1}.", expected, Subject); @@ -164,7 +167,7 @@ public AndConstraint NotBe(string unexpected, public AndConstraint NotBe(Guid unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject != unexpected) .BecauseOf(because, becauseArgs) .FailWith("Did not expect {context:Guid} to be {0}{reason}.", Subject); diff --git a/Src/FluentAssertions/Primitives/HttpResponseMessageAssertions.cs b/Src/FluentAssertions/Primitives/HttpResponseMessageAssertions.cs index f688e7a32f..e483ac43b7 100644 --- a/Src/FluentAssertions/Primitives/HttpResponseMessageAssertions.cs +++ b/Src/FluentAssertions/Primitives/HttpResponseMessageAssertions.cs @@ -12,8 +12,8 @@ namespace FluentAssertions.Primitives; [DebuggerNonUserCode] public class HttpResponseMessageAssertions : HttpResponseMessageAssertions { - public HttpResponseMessageAssertions(HttpResponseMessage value) - : base(value) + public HttpResponseMessageAssertions(HttpResponseMessage value, AssertionChain assertionChain) + : base(value, assertionChain) { } } @@ -25,9 +25,12 @@ public HttpResponseMessageAssertions(HttpResponseMessage value) public class HttpResponseMessageAssertions : ObjectAssertions where TAssertions : HttpResponseMessageAssertions { - protected HttpResponseMessageAssertions(HttpResponseMessage value) - : base(value) + private readonly AssertionChain assertionChain; + + protected HttpResponseMessageAssertions(HttpResponseMessage value, AssertionChain assertionChain) + : base(value, assertionChain) { + this.assertionChain = assertionChain; } /// @@ -42,14 +45,14 @@ protected HttpResponseMessageAssertions(HttpResponseMessage value) /// public AndConstraint BeSuccessful([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - var success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected HttpStatusCode to be successful (2xx){reason}, but HttpResponseMessage was ."); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .ForCondition(Subject!.IsSuccessStatusCode) .BecauseOf(because, becauseArgs) .FailWith("Expected HttpStatusCode to be successful (2xx){reason}, but found {0}.", Subject.StatusCode); @@ -70,14 +73,14 @@ public AndConstraint BeSuccessful([StringSyntax("CompositeFormat")] /// public AndConstraint BeRedirection([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - var success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected HttpStatusCode to be redirection (3xx){reason}, but HttpResponseMessage was ."); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .ForCondition((int)Subject!.StatusCode is >= 300 and <= 399) .BecauseOf(because, becauseArgs) .FailWith("Expected HttpStatusCode to be redirection (3xx){reason}, but found {0}.", Subject.StatusCode); @@ -98,14 +101,14 @@ public AndConstraint BeRedirection([StringSyntax("CompositeFormat") /// public AndConstraint HaveError([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - var success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected HttpStatusCode to be an error{reason}, but HttpResponseMessage was ."); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .ForCondition(IsClientError() || IsServerError()) .BecauseOf(because, becauseArgs) .FailWith("Expected HttpStatusCode to be an error{reason}, but found {0}.", Subject.StatusCode); @@ -126,14 +129,14 @@ public AndConstraint HaveError([StringSyntax("CompositeFormat")] st /// public AndConstraint HaveClientError([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - var success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected HttpStatusCode to be client error (4xx){reason}, but HttpResponseMessage was ."); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .ForCondition(IsClientError()) .BecauseOf(because, becauseArgs) .FailWith("Expected HttpStatusCode to be client error (4xx){reason}, but found {0}.", Subject.StatusCode); @@ -154,14 +157,14 @@ public AndConstraint HaveClientError([StringSyntax("CompositeFormat /// public AndConstraint HaveServerError([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - var success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected HttpStatusCode to be server error (5xx){reason}, but HttpResponseMessage was ."); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .ForCondition(IsServerError()) .BecauseOf(because, becauseArgs) .FailWith("Expected HttpStatusCode to be server error (5xx){reason}, but found {0}.", Subject.StatusCode); @@ -184,14 +187,14 @@ public AndConstraint HaveServerError([StringSyntax("CompositeFormat public AndConstraint HaveStatusCode(HttpStatusCode expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - var success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected HttpStatusCode to be {0}{reason}, but HttpResponseMessage was .", expected); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .ForCondition(Subject!.StatusCode == expected) .BecauseOf(because, becauseArgs) .FailWith("Expected HttpStatusCode to be {0}{reason}, but found {1}.", expected, Subject.StatusCode); @@ -214,14 +217,14 @@ public AndConstraint HaveStatusCode(HttpStatusCode expected, public AndConstraint NotHaveStatusCode(HttpStatusCode unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - var success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected HttpStatusCode not to be {0}{reason}, but HttpResponseMessage was .", unexpected); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .ForCondition(Subject!.StatusCode != unexpected) .BecauseOf(because, becauseArgs) .FailWith("Expected HttpStatusCode not to be {0}{reason}, but found {1}.", unexpected, Subject.StatusCode); diff --git a/Src/FluentAssertions/Primitives/IStringComparisonStrategy.cs b/Src/FluentAssertions/Primitives/IStringComparisonStrategy.cs index da95c7ccbb..6dffb8b1f4 100644 --- a/Src/FluentAssertions/Primitives/IStringComparisonStrategy.cs +++ b/Src/FluentAssertions/Primitives/IStringComparisonStrategy.cs @@ -15,5 +15,5 @@ internal interface IStringComparisonStrategy /// /// Asserts that the matches the value. /// - void ValidateAgainstMismatch(IAssertionScope assertion, string subject, string expected); + void ValidateAgainstMismatch(AssertionChain assertionChain, string subject, string expected); } diff --git a/Src/FluentAssertions/Primitives/NullableBooleanAssertions.cs b/Src/FluentAssertions/Primitives/NullableBooleanAssertions.cs index 6b5954d946..0a856363fa 100644 --- a/Src/FluentAssertions/Primitives/NullableBooleanAssertions.cs +++ b/Src/FluentAssertions/Primitives/NullableBooleanAssertions.cs @@ -10,8 +10,8 @@ namespace FluentAssertions.Primitives; [DebuggerNonUserCode] public class NullableBooleanAssertions : NullableBooleanAssertions { - public NullableBooleanAssertions(bool? value) - : base(value) + public NullableBooleanAssertions(bool? value, AssertionChain assertionChain) + : base(value, assertionChain) { } } @@ -23,9 +23,12 @@ public NullableBooleanAssertions(bool? value) public class NullableBooleanAssertions : BooleanAssertions where TAssertions : NullableBooleanAssertions { - public NullableBooleanAssertions(bool? value) - : base(value) + private readonly AssertionChain assertionChain; + + public NullableBooleanAssertions(bool? value, AssertionChain assertionChain) + : base(value, assertionChain) { + this.assertionChain = assertionChain; } /// @@ -40,7 +43,7 @@ public NullableBooleanAssertions(bool? value) /// public AndConstraint HaveValue([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject.HasValue) .BecauseOf(because, becauseArgs) .FailWith("Expected a value{reason}."); @@ -75,7 +78,7 @@ public AndConstraint NotBeNull([StringSyntax("CompositeFormat")] st /// public AndConstraint NotHaveValue([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(!Subject.HasValue) .BecauseOf(because, becauseArgs) .FailWith("Did not expect a value{reason}, but found {0}.", Subject); @@ -112,7 +115,7 @@ public AndConstraint BeNull([StringSyntax("CompositeFormat")] strin public AndConstraint Be(bool? expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject == expected) .BecauseOf(because, becauseArgs) .FailWith("Expected {0}{reason}, but found {1}.", expected, Subject); @@ -134,7 +137,7 @@ public AndConstraint Be(bool? expected, public AndConstraint NotBe(bool? unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject != unexpected) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:nullable boolean} not to be {0}{reason}, but found {1}.", unexpected, Subject); @@ -154,7 +157,7 @@ public AndConstraint NotBe(bool? unexpected, /// public AndConstraint NotBeFalse([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject is not false) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:nullable boolean} not to be {0}{reason}, but found {1}.", false, Subject); @@ -174,7 +177,7 @@ public AndConstraint NotBeFalse([StringSyntax("CompositeFormat")] s /// public AndConstraint NotBeTrue([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject is not true) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:nullable boolean} not to be {0}{reason}, but found {1}.", true, Subject); diff --git a/Src/FluentAssertions/Primitives/NullableDateOnlyAssertions.cs b/Src/FluentAssertions/Primitives/NullableDateOnlyAssertions.cs index b4733fbf01..ecdde62a34 100644 --- a/Src/FluentAssertions/Primitives/NullableDateOnlyAssertions.cs +++ b/Src/FluentAssertions/Primitives/NullableDateOnlyAssertions.cs @@ -13,8 +13,8 @@ namespace FluentAssertions.Primitives; [DebuggerNonUserCode] public class NullableDateOnlyAssertions : NullableDateOnlyAssertions { - public NullableDateOnlyAssertions(DateOnly? value) - : base(value) + public NullableDateOnlyAssertions(DateOnly? value, AssertionChain assertionChain) + : base(value, assertionChain) { } } @@ -26,9 +26,12 @@ public NullableDateOnlyAssertions(DateOnly? value) public class NullableDateOnlyAssertions : DateOnlyAssertions where TAssertions : NullableDateOnlyAssertions { - public NullableDateOnlyAssertions(DateOnly? value) - : base(value) + private readonly AssertionChain assertionChain; + + public NullableDateOnlyAssertions(DateOnly? value, AssertionChain assertionChain) + : base(value, assertionChain) { + this.assertionChain = assertionChain; } /// @@ -43,7 +46,7 @@ public NullableDateOnlyAssertions(DateOnly? value) /// public AndConstraint HaveValue([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject.HasValue) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:nullable date} to have a value{reason}, but found {0}.", Subject); @@ -78,7 +81,7 @@ public AndConstraint NotBeNull([StringSyntax("CompositeFormat")] st /// public AndConstraint NotHaveValue([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(!Subject.HasValue) .BecauseOf(because, becauseArgs) .FailWith("Did not expect {context:nullable date} to have a value{reason}, but found {0}.", Subject); diff --git a/Src/FluentAssertions/Primitives/NullableDateTimeAssertions.cs b/Src/FluentAssertions/Primitives/NullableDateTimeAssertions.cs index 2f897eeaa0..d7b8f1bc13 100644 --- a/Src/FluentAssertions/Primitives/NullableDateTimeAssertions.cs +++ b/Src/FluentAssertions/Primitives/NullableDateTimeAssertions.cs @@ -15,8 +15,8 @@ namespace FluentAssertions.Primitives; [DebuggerNonUserCode] public class NullableDateTimeAssertions : NullableDateTimeAssertions { - public NullableDateTimeAssertions(DateTime? expected) - : base(expected) + public NullableDateTimeAssertions(DateTime? expected, AssertionChain assertionChain) + : base(expected, assertionChain) { } } @@ -31,9 +31,12 @@ public NullableDateTimeAssertions(DateTime? expected) public class NullableDateTimeAssertions : DateTimeAssertions where TAssertions : NullableDateTimeAssertions { - public NullableDateTimeAssertions(DateTime? expected) - : base(expected) + private readonly AssertionChain assertionChain; + + public NullableDateTimeAssertions(DateTime? expected, AssertionChain assertionChain) + : base(expected, assertionChain) { + this.assertionChain = assertionChain; } /// @@ -48,7 +51,7 @@ public NullableDateTimeAssertions(DateTime? expected) /// public AndConstraint HaveValue([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject.HasValue) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:nullable date and time} to have a value{reason}, but found {0}.", Subject); @@ -83,7 +86,7 @@ public AndConstraint NotBeNull([StringSyntax("CompositeFormat")] st /// public AndConstraint NotHaveValue([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(!Subject.HasValue) .BecauseOf(because, becauseArgs) .FailWith("Did not expect {context:nullable date and time} to have a value{reason}, but found {0}.", Subject); diff --git a/Src/FluentAssertions/Primitives/NullableDateTimeOffsetAssertions.cs b/Src/FluentAssertions/Primitives/NullableDateTimeOffsetAssertions.cs index 48553d4ada..3508fb2f36 100644 --- a/Src/FluentAssertions/Primitives/NullableDateTimeOffsetAssertions.cs +++ b/Src/FluentAssertions/Primitives/NullableDateTimeOffsetAssertions.cs @@ -15,8 +15,8 @@ namespace FluentAssertions.Primitives; [DebuggerNonUserCode] public class NullableDateTimeOffsetAssertions : NullableDateTimeOffsetAssertions { - public NullableDateTimeOffsetAssertions(DateTimeOffset? expected) - : base(expected) + public NullableDateTimeOffsetAssertions(DateTimeOffset? expected, AssertionChain assertionChain) + : base(expected, assertionChain) { } } @@ -32,9 +32,12 @@ public NullableDateTimeOffsetAssertions(DateTimeOffset? expected) public class NullableDateTimeOffsetAssertions : DateTimeOffsetAssertions where TAssertions : NullableDateTimeOffsetAssertions { - public NullableDateTimeOffsetAssertions(DateTimeOffset? expected) - : base(expected) + private readonly AssertionChain assertionChain; + + public NullableDateTimeOffsetAssertions(DateTimeOffset? expected, AssertionChain assertionChain) + : base(expected, assertionChain) { + this.assertionChain = assertionChain; } /// @@ -49,7 +52,7 @@ public NullableDateTimeOffsetAssertions(DateTimeOffset? expected) /// public AndConstraint HaveValue([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject.HasValue) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:variable} to have a value{reason}, but found {0}", Subject); @@ -85,7 +88,7 @@ public AndConstraint NotBeNull([StringSyntax("CompositeFormat")] st public AndConstraint NotHaveValue([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(!Subject.HasValue) .BecauseOf(because, becauseArgs) .FailWith("Did not expect {context:variable} to have a value{reason}, but found {0}", Subject); diff --git a/Src/FluentAssertions/Primitives/NullableEnumAssertions.cs b/Src/FluentAssertions/Primitives/NullableEnumAssertions.cs index 85f2ce95e6..7096c1ab53 100644 --- a/Src/FluentAssertions/Primitives/NullableEnumAssertions.cs +++ b/Src/FluentAssertions/Primitives/NullableEnumAssertions.cs @@ -10,8 +10,8 @@ namespace FluentAssertions.Primitives; public class NullableEnumAssertions : NullableEnumAssertions> where TEnum : struct, Enum { - public NullableEnumAssertions(TEnum? subject) - : base(subject) + public NullableEnumAssertions(TEnum? subject, AssertionChain assertionChain) + : base(subject, assertionChain) { } } @@ -23,9 +23,12 @@ public class NullableEnumAssertions : EnumAssertions { - public NullableEnumAssertions(TEnum? subject) - : base(subject) + private readonly AssertionChain assertionChain; + + public NullableEnumAssertions(TEnum? subject, AssertionChain assertionChain) + : base(subject, assertionChain) { + this.assertionChain = assertionChain; } /// @@ -40,12 +43,12 @@ public NullableEnumAssertions(TEnum? subject) /// public AndWhichConstraint HaveValue([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject.HasValue) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:nullable enum} to have a value{reason}, but found {0}.", Subject); - return new AndWhichConstraint((TAssertions)this, Subject.GetValueOrDefault()); + return new AndWhichConstraint((TAssertions)this, Subject.GetValueOrDefault(), assertionChain, ".Value"); } /// @@ -75,7 +78,7 @@ public AndWhichConstraint NotBeNull([StringSyntax("Composite /// public AndConstraint NotHaveValue([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(!Subject.HasValue) .BecauseOf(because, becauseArgs) .FailWith("Did not expect {context:nullable enum} to have a value{reason}, but found {0}.", Subject); diff --git a/Src/FluentAssertions/Primitives/NullableGuidAssertions.cs b/Src/FluentAssertions/Primitives/NullableGuidAssertions.cs index 45c26f7915..1eea1e758b 100644 --- a/Src/FluentAssertions/Primitives/NullableGuidAssertions.cs +++ b/Src/FluentAssertions/Primitives/NullableGuidAssertions.cs @@ -11,8 +11,8 @@ namespace FluentAssertions.Primitives; [DebuggerNonUserCode] public class NullableGuidAssertions : NullableGuidAssertions { - public NullableGuidAssertions(Guid? value) - : base(value) + public NullableGuidAssertions(Guid? value, AssertionChain assertionChain) + : base(value, assertionChain) { } } @@ -24,9 +24,12 @@ public NullableGuidAssertions(Guid? value) public class NullableGuidAssertions : GuidAssertions where TAssertions : NullableGuidAssertions { - public NullableGuidAssertions(Guid? value) - : base(value) + private readonly AssertionChain assertionChain; + + public NullableGuidAssertions(Guid? value, AssertionChain assertionChain) + : base(value, assertionChain) { + this.assertionChain = assertionChain; } /// @@ -41,7 +44,7 @@ public NullableGuidAssertions(Guid? value) /// public AndConstraint HaveValue([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject.HasValue) .BecauseOf(because, becauseArgs) .FailWith("Expected a value{reason}."); @@ -76,7 +79,7 @@ public AndConstraint NotBeNull([StringSyntax("CompositeFormat")] st /// public AndConstraint NotHaveValue([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(!Subject.HasValue) .BecauseOf(because, becauseArgs) .FailWith("Did not expect a value{reason}, but found {0}.", Subject); @@ -112,7 +115,7 @@ public AndConstraint BeNull([StringSyntax("CompositeFormat")] strin /// public AndConstraint Be(Guid? expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject == expected) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:Guid} to be {0}{reason}, but found {1}.", expected, Subject); diff --git a/Src/FluentAssertions/Primitives/NullableSimpleTimeSpanAssertions.cs b/Src/FluentAssertions/Primitives/NullableSimpleTimeSpanAssertions.cs index da52304bd1..03ffb3d9bd 100644 --- a/Src/FluentAssertions/Primitives/NullableSimpleTimeSpanAssertions.cs +++ b/Src/FluentAssertions/Primitives/NullableSimpleTimeSpanAssertions.cs @@ -15,8 +15,8 @@ namespace FluentAssertions.Primitives; [DebuggerNonUserCode] public class NullableSimpleTimeSpanAssertions : NullableSimpleTimeSpanAssertions { - public NullableSimpleTimeSpanAssertions(TimeSpan? value) - : base(value) + public NullableSimpleTimeSpanAssertions(TimeSpan? value, AssertionChain assertionChain) + : base(value, assertionChain) { } } @@ -32,9 +32,12 @@ public NullableSimpleTimeSpanAssertions(TimeSpan? value) public class NullableSimpleTimeSpanAssertions : SimpleTimeSpanAssertions where TAssertions : NullableSimpleTimeSpanAssertions { - public NullableSimpleTimeSpanAssertions(TimeSpan? value) - : base(value) + private readonly AssertionChain assertionChain; + + public NullableSimpleTimeSpanAssertions(TimeSpan? value, AssertionChain assertionChain) + : base(value, assertionChain) { + this.assertionChain = assertionChain; } /// @@ -49,7 +52,7 @@ public NullableSimpleTimeSpanAssertions(TimeSpan? value) /// public AndConstraint HaveValue([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject.HasValue) .BecauseOf(because, becauseArgs) .FailWith("Expected a value{reason}."); @@ -84,7 +87,7 @@ public AndConstraint NotBeNull([StringSyntax("CompositeFormat")] st /// public AndConstraint NotHaveValue([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(!Subject.HasValue) .BecauseOf(because, becauseArgs) .FailWith("Did not expect a value{reason}, but found {0}.", Subject); @@ -121,7 +124,7 @@ public AndConstraint BeNull([StringSyntax("CompositeFormat")] strin public AndConstraint Be(TimeSpan? expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject == expected) .BecauseOf(because, becauseArgs) .FailWith("Expected {0}{reason}, but found {1}.", expected, Subject); diff --git a/Src/FluentAssertions/Primitives/NullableTimeOnlyAssertions.cs b/Src/FluentAssertions/Primitives/NullableTimeOnlyAssertions.cs index d0235efee0..8e6ede5140 100644 --- a/Src/FluentAssertions/Primitives/NullableTimeOnlyAssertions.cs +++ b/Src/FluentAssertions/Primitives/NullableTimeOnlyAssertions.cs @@ -13,8 +13,8 @@ namespace FluentAssertions.Primitives; [DebuggerNonUserCode] public class NullableTimeOnlyAssertions : NullableTimeOnlyAssertions { - public NullableTimeOnlyAssertions(TimeOnly? value) - : base(value) + public NullableTimeOnlyAssertions(TimeOnly? value, AssertionChain assertionChain) + : base(value, assertionChain) { } } @@ -26,9 +26,12 @@ public NullableTimeOnlyAssertions(TimeOnly? value) public class NullableTimeOnlyAssertions : TimeOnlyAssertions where TAssertions : NullableTimeOnlyAssertions { - public NullableTimeOnlyAssertions(TimeOnly? value) - : base(value) + private readonly AssertionChain assertionChain; + + public NullableTimeOnlyAssertions(TimeOnly? value, AssertionChain assertionChain) + : base(value, assertionChain) { + this.assertionChain = assertionChain; } /// @@ -43,7 +46,7 @@ public NullableTimeOnlyAssertions(TimeOnly? value) /// public AndConstraint HaveValue([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject.HasValue) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:nullable time} to have a value{reason}, but found {0}.", Subject); @@ -78,7 +81,7 @@ public AndConstraint NotBeNull([StringSyntax("CompositeFormat")] st /// public AndConstraint NotHaveValue([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(!Subject.HasValue) .BecauseOf(because, becauseArgs) .FailWith("Did not expect {context:nullable time} to have a value{reason}, but found {0}.", Subject); diff --git a/Src/FluentAssertions/Primitives/ObjectAssertions.cs b/Src/FluentAssertions/Primitives/ObjectAssertions.cs index d6bdccfab0..9c536c3d66 100644 --- a/Src/FluentAssertions/Primitives/ObjectAssertions.cs +++ b/Src/FluentAssertions/Primitives/ObjectAssertions.cs @@ -13,9 +13,12 @@ namespace FluentAssertions.Primitives; /// public class ObjectAssertions : ObjectAssertions { - public ObjectAssertions(object value) - : base(value) + private readonly AssertionChain assertionChain; + + public ObjectAssertions(object value, AssertionChain assertionChain) + : base(value, assertionChain) { + this.assertionChain = assertionChain; } /// @@ -38,7 +41,7 @@ public AndConstraint Be(TExpectation expected, I { Guard.ThrowIfArgumentIsNull(comparer); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is TExpectation subject && comparer.Equals(subject, expected)) .WithDefaultIdentifier(Identifier) @@ -68,7 +71,7 @@ public AndConstraint NotBe(TExpectation unexpect { Guard.ThrowIfArgumentIsNull(comparer); - Execute.Assertion + assertionChain .ForCondition(Subject is not TExpectation subject || !comparer.Equals(subject, unexpected)) .BecauseOf(because, becauseArgs) .WithDefaultIdentifier(Identifier) @@ -102,7 +105,7 @@ public AndConstraint BeOneOf(IEnumerable BeOneOf(IEnumerable : ReferenceTypeAssertions where TAssertions : ObjectAssertions { - public ObjectAssertions(TSubject value) - : base(value) + private readonly AssertionChain assertionChain; + + public ObjectAssertions(TSubject value, AssertionChain assertionChain) + : base(value, assertionChain) { + this.assertionChain = assertionChain; } /// @@ -138,12 +144,11 @@ public ObjectAssertions(TSubject value) public AndConstraint Be(TSubject expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(ObjectExtensions.GetComparer()(Subject, expected)) .WithDefaultIdentifier(Identifier) - .FailWith("Expected {context} to be {0}{reason}, but found {1}.", expected, - Subject); + .FailWith("Expected {context} to be {0}{reason}, but found {1}.", expected, Subject); return new AndConstraint((TAssertions)this); } @@ -168,7 +173,7 @@ public AndConstraint Be(TSubject expected, IEqualityComparer Be(TSubject expected, IEqualityComparer NotBe(TSubject unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(!ObjectExtensions.GetComparer()(Subject, unexpected)) .BecauseOf(because, becauseArgs) .WithDefaultIdentifier(Identifier) @@ -221,7 +226,7 @@ public AndConstraint NotBe(TSubject unexpected, IEqualityComparer BeEquivalentTo(TExpectation expe EquivalencyOptions options = config(AssertionOptions.CloneDefaults()); var context = new EquivalencyValidationContext(Node.From(() => - AssertionScope.Current.CallerIdentity), options) + CurrentAssertionChain.CallerIdentifier), options) { Reason = new Reason(because, becauseArgs), TraceWriter = options.TraceWriter @@ -369,7 +374,7 @@ public AndConstraint NotBeEquivalentTo( hasMismatches = scope.Discard().Length > 0; } - Execute.Assertion + assertionChain .ForCondition(hasMismatches) .BecauseOf(because, becauseArgs) .WithDefaultIdentifier(Identifier) @@ -405,7 +410,7 @@ public AndConstraint BeOneOf(params TSubject[] validValues) public AndConstraint BeOneOf(IEnumerable validValues, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(validValues.Contains(Subject)) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:object} to be one of {0}{reason}, but found {1}.", validValues, Subject); @@ -438,7 +443,7 @@ public AndConstraint BeOneOf(IEnumerable validValues, Guard.ThrowIfArgumentIsNull(validValues); Guard.ThrowIfArgumentIsNull(comparer); - Execute.Assertion + assertionChain .ForCondition(validValues.Contains(Subject, comparer)) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:object} to be one of {0}{reason}, but found {1}.", validValues, Subject); diff --git a/Src/FluentAssertions/Primitives/ReferenceTypeAssertions.cs b/Src/FluentAssertions/Primitives/ReferenceTypeAssertions.cs index 8817613104..9a08c161f7 100644 --- a/Src/FluentAssertions/Primitives/ReferenceTypeAssertions.cs +++ b/Src/FluentAssertions/Primitives/ReferenceTypeAssertions.cs @@ -17,8 +17,9 @@ namespace FluentAssertions.Primitives; public abstract class ReferenceTypeAssertions where TAssertions : ReferenceTypeAssertions { - protected ReferenceTypeAssertions(TSubject subject) + protected ReferenceTypeAssertions(TSubject subject, AssertionChain assertionChain) { + CurrentAssertionChain = assertionChain; Subject = subject; } @@ -39,7 +40,7 @@ protected ReferenceTypeAssertions(TSubject subject) /// public AndConstraint BeNull([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + CurrentAssertionChain .ForCondition(Subject is null) .BecauseOf(because, becauseArgs) .WithDefaultIdentifier(Identifier) @@ -58,9 +59,10 @@ public AndConstraint BeNull([StringSyntax("CompositeFormat")] strin /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeNull([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint NotBeNull([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - Execute.Assertion + CurrentAssertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .WithDefaultIdentifier(Identifier) @@ -83,7 +85,7 @@ public AndConstraint NotBeNull([StringSyntax("CompositeFormat")] st public AndConstraint BeSameAs(TSubject expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + CurrentAssertionChain .ForCondition(ReferenceEquals(Subject, expected)) .BecauseOf(because, becauseArgs) .WithDefaultIdentifier(Identifier) @@ -106,7 +108,7 @@ public AndConstraint BeSameAs(TSubject expected, public AndConstraint NotBeSameAs(TSubject unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + CurrentAssertionChain .ForCondition(!ReferenceEquals(Subject, unexpected)) .BecauseOf(because, becauseArgs) .WithDefaultIdentifier(Identifier) @@ -126,7 +128,8 @@ public AndConstraint NotBeSameAs(TSubject unexpected, /// /// Zero or more objects to format using the placeholders in . /// - public AndWhichConstraint BeOfType([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndWhichConstraint BeOfType([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { BeOfType(typeof(T), because, becauseArgs); @@ -156,13 +159,13 @@ public AndConstraint BeOfType(Type expectedType, { Guard.ThrowIfArgumentIsNull(expectedType); - bool success = Execute.Assertion + CurrentAssertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .WithDefaultIdentifier("type") .FailWith("Expected {context} to be {0}{reason}, but found .", expectedType); - if (success) + if (CurrentAssertionChain.Succeeded) { Type subjectType = Subject.GetType(); @@ -190,7 +193,8 @@ public AndConstraint BeOfType(Type expectedType, /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeOfType([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint NotBeOfType([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { NotBeOfType(typeof(T), because, becauseArgs); @@ -216,13 +220,13 @@ public AndConstraint NotBeOfType(Type unexpectedType, { Guard.ThrowIfArgumentIsNull(unexpectedType); - bool success = Execute.Assertion + CurrentAssertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .WithDefaultIdentifier("type") .FailWith("Expected {context} not to be {0}{reason}, but found .", unexpectedType); - if (success) + if (CurrentAssertionChain.Succeeded) { Type subjectType = Subject.GetType(); @@ -250,18 +254,19 @@ public AndConstraint NotBeOfType(Type unexpectedType, /// /// Zero or more objects to format using the placeholders in . /// - /// An which can be used to chain assertions. - public AndWhichConstraint BeAssignableTo([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + /// An which can be used to chain assertions. + public AndWhichConstraint BeAssignableTo([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - bool success = Execute.Assertion + CurrentAssertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .WithDefaultIdentifier("type") .FailWith("Expected {context} to be assignable to {0}{reason}, but found .", typeof(T)); - if (success) + if (CurrentAssertionChain.Succeeded) { - Execute.Assertion + CurrentAssertionChain .ForCondition(Subject is T) .BecauseOf(because, becauseArgs) .WithDefaultIdentifier(Identifier) @@ -293,19 +298,19 @@ public AndConstraint BeAssignableTo(Type type, { Guard.ThrowIfArgumentIsNull(type); - bool success = Execute.Assertion + CurrentAssertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .WithDefaultIdentifier("type") .FailWith("Expected {context} to be assignable to {0}{reason}, but found .", type); - if (success) + if (CurrentAssertionChain.Succeeded) { bool isAssignable = type.IsGenericTypeDefinition ? Subject.GetType().IsAssignableToOpenGeneric(type) : type.IsAssignableFrom(Subject.GetType()); - Execute.Assertion + CurrentAssertionChain .ForCondition(isAssignable) .BecauseOf(because, becauseArgs) .WithDefaultIdentifier(Identifier) @@ -329,7 +334,8 @@ public AndConstraint BeAssignableTo(Type type, /// Zero or more objects to format using the placeholders in . /// /// An which can be used to chain assertions. - public AndConstraint NotBeAssignableTo([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint NotBeAssignableTo([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { return NotBeAssignableTo(typeof(T), because, becauseArgs); } @@ -352,19 +358,19 @@ public AndConstraint NotBeAssignableTo(Type type, { Guard.ThrowIfArgumentIsNull(type); - bool success = Execute.Assertion + CurrentAssertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .WithDefaultIdentifier("type") .FailWith("Expected {context} to not be assignable to {0}{reason}, but found .", type); - if (success) + if (CurrentAssertionChain.Succeeded) { bool isAssignable = type.IsGenericTypeDefinition ? Subject.GetType().IsAssignableToOpenGeneric(type) : type.IsAssignableFrom(Subject.GetType()); - Execute.Assertion + CurrentAssertionChain .ForCondition(!isAssignable) .BecauseOf(because, becauseArgs) .WithDefaultIdentifier(Identifier) @@ -411,7 +417,7 @@ public AndConstraint Match(Expression> predicate, { Guard.ThrowIfArgumentIsNull(predicate, nameof(predicate), "Cannot match an object against a predicate."); - Execute.Assertion + CurrentAssertionChain .ForCondition(predicate.Compile()((T)Subject)) .BecauseOf(because, becauseArgs) .WithDefaultIdentifier(Identifier) @@ -434,16 +440,17 @@ public AndConstraint Satisfy(Action assertion) { Guard.ThrowIfArgumentIsNull(assertion, nameof(assertion), "Cannot verify an object against a inspector."); - var success = Execute.Assertion + CurrentAssertionChain .ForCondition(Subject is not null) .WithDefaultIdentifier(Identifier) .FailWith("Expected {context:object} to be assignable to {0}{reason}, but found .", typeof(T)) .Then .ForCondition(Subject is T) .WithDefaultIdentifier(Identifier) - .FailWith("Expected {context:object} to be assignable to {0}{reason}, but {1} is not.", typeof(T), Subject?.GetType()); + .FailWith("Expected {context:object} to be assignable to {0}{reason}, but {1} is not.", typeof(T), + Subject?.GetType()); - if (success) + if (CurrentAssertionChain.Succeeded) { string[] failuresFromInspector; @@ -458,10 +465,11 @@ public AndConstraint Satisfy(Action assertion) string failureMessage = Environment.NewLine + string.Join(Environment.NewLine, failuresFromInspector.Select(x => x.IndentLines())); - Execute.Assertion + CurrentAssertionChain .WithDefaultIdentifier(Identifier) - .WithExpectation("Expected {context:object} to match inspector, but the inspector was not satisfied:", Subject) - .FailWithPreFormatted(failureMessage); + .WithExpectation("Expected {context:object} to match inspector, but the inspector was not satisfied:", + Subject, + chain => chain.FailWithPreFormatted(failureMessage)); } } @@ -477,4 +485,6 @@ public AndConstraint Satisfy(Action assertion) /// public override bool Equals(object obj) => throw new NotSupportedException("Equals is not part of Fluent Assertions. Did you mean BeSameAs() instead?"); + + public AssertionChain CurrentAssertionChain { get; } } diff --git a/Src/FluentAssertions/Primitives/SimpleTimeSpanAssertions.cs b/Src/FluentAssertions/Primitives/SimpleTimeSpanAssertions.cs index 7387544b22..634feee5a7 100644 --- a/Src/FluentAssertions/Primitives/SimpleTimeSpanAssertions.cs +++ b/Src/FluentAssertions/Primitives/SimpleTimeSpanAssertions.cs @@ -12,8 +12,8 @@ namespace FluentAssertions.Primitives; [DebuggerNonUserCode] public class SimpleTimeSpanAssertions : SimpleTimeSpanAssertions { - public SimpleTimeSpanAssertions(TimeSpan? value) - : base(value) + public SimpleTimeSpanAssertions(TimeSpan? value, AssertionChain assertionChain) + : base(value, assertionChain) { } } @@ -27,8 +27,11 @@ public SimpleTimeSpanAssertions(TimeSpan? value) public class SimpleTimeSpanAssertions where TAssertions : SimpleTimeSpanAssertions { - public SimpleTimeSpanAssertions(TimeSpan? value) + private readonly AssertionChain assertionChain; + + public SimpleTimeSpanAssertions(TimeSpan? value, AssertionChain assertionChain) { + this.assertionChain = assertionChain; Subject = value; } @@ -49,7 +52,7 @@ public SimpleTimeSpanAssertions(TimeSpan? value) /// public AndConstraint BePositive([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject > TimeSpan.Zero) .FailWith("Expected {context:time} to be positive{reason}, but found {0}.", Subject); @@ -69,7 +72,7 @@ public AndConstraint BePositive([StringSyntax("CompositeFormat")] s /// public AndConstraint BeNegative([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject < TimeSpan.Zero) .FailWith("Expected {context:time} to be negative{reason}, but found {0}.", Subject); @@ -92,7 +95,7 @@ public AndConstraint BeNegative([StringSyntax("CompositeFormat")] s public AndConstraint Be(TimeSpan expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(expected == Subject) .FailWith("Expected {0}{reason}, but found {1}.", expected, Subject); @@ -115,7 +118,7 @@ public AndConstraint Be(TimeSpan expected, public AndConstraint NotBe(TimeSpan unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(unexpected != Subject) .BecauseOf(because, becauseArgs) .FailWith("Did not expect {0}{reason}.", unexpected); @@ -138,7 +141,7 @@ public AndConstraint NotBe(TimeSpan unexpected, public AndConstraint BeLessThan(TimeSpan expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject < expected) .FailWith("Expected {context:time} to be less than {0}{reason}, but found {1}.", expected, Subject); @@ -161,7 +164,7 @@ public AndConstraint BeLessThan(TimeSpan expected, public AndConstraint BeLessThanOrEqualTo(TimeSpan expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject <= expected) .FailWith("Expected {context:time} to be less than or equal to {0}{reason}, but found {1}.", expected, Subject); @@ -184,7 +187,7 @@ public AndConstraint BeLessThanOrEqualTo(TimeSpan expected, public AndConstraint BeGreaterThan(TimeSpan expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject > expected) .FailWith("Expected {context:time} to be greater than {0}{reason}, but found {1}.", expected, Subject); @@ -207,7 +210,7 @@ public AndConstraint BeGreaterThan(TimeSpan expected, public AndConstraint BeGreaterThanOrEqualTo(TimeSpan expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject >= expected) .FailWith("Expected {context:time} to be greater than or equal to {0}{reason}, but found {1}.", expected, Subject); @@ -245,7 +248,7 @@ public AndConstraint BeCloseTo(TimeSpan nearbyTime, TimeSpan precis TimeSpan minimumValue = nearbyTime - precision; TimeSpan maximumValue = nearbyTime + precision; - Execute.Assertion + assertionChain .ForCondition(Subject >= minimumValue && Subject.Value <= maximumValue) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:time} to be within {0} from {1}{reason}, but found {2}.", @@ -285,7 +288,7 @@ public AndConstraint NotBeCloseTo(TimeSpan distantTime, TimeSpan pr TimeSpan minimumValue = distantTime - precision; TimeSpan maximumValue = distantTime + precision; - Execute.Assertion + assertionChain .ForCondition(Subject < minimumValue || Subject > maximumValue) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:time} to not be within {0} from {1}{reason}, but found {2}.", diff --git a/Src/FluentAssertions/Primitives/StringAssertions.cs b/Src/FluentAssertions/Primitives/StringAssertions.cs index dbc6a5e7f2..7e1fac4730 100644 --- a/Src/FluentAssertions/Primitives/StringAssertions.cs +++ b/Src/FluentAssertions/Primitives/StringAssertions.cs @@ -20,8 +20,8 @@ public class StringAssertions : StringAssertions /// /// Initializes a new instance of the class. /// - public StringAssertions(string value) - : base(value) + public StringAssertions(string value, AssertionChain assertionChain) + : base(value, assertionChain) { } } @@ -33,12 +33,15 @@ public StringAssertions(string value) public class StringAssertions : ReferenceTypeAssertions where TAssertions : StringAssertions { + private readonly AssertionChain assertionChain; + /// /// Initializes a new instance of the class. /// - public StringAssertions(string value) - : base(value) + public StringAssertions(string value, AssertionChain assertionChain) + : base(value, assertionChain) { + this.assertionChain = assertionChain; } /// @@ -55,7 +58,7 @@ public StringAssertions(string value) public AndConstraint Be(string expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - var stringEqualityValidator = new StringValidator( + var stringEqualityValidator = new StringValidator(assertionChain, new StringEqualityStrategy(StringComparer.Ordinal, "be"), because, becauseArgs); @@ -91,7 +94,7 @@ public AndConstraint BeOneOf(params string[] validValues) public AndConstraint BeOneOf(IEnumerable validValues, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(validValues.Contains(Subject)) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:string} to be one of {0}{reason}, but found {1}.", validValues, Subject); @@ -116,7 +119,7 @@ public AndConstraint BeOneOf(IEnumerable validValues, public AndConstraint BeEquivalentTo(string expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - var expectation = new StringValidator( + var expectation = new StringValidator(assertionChain, new StringEqualityStrategy(StringComparer.OrdinalIgnoreCase, "be equivalent to"), because, becauseArgs); @@ -149,7 +152,7 @@ public AndConstraint BeEquivalentTo(string expected, EquivalencyOptions options = config(AssertionOptions.CloneDefaults()); - var expectation = new StringValidator( + var expectation = new StringValidator(assertionChain, new StringEqualityStrategy(options.GetStringComparerOrDefault(), "be equivalent to"), because, becauseArgs); @@ -185,7 +188,7 @@ public AndConstraint NotBeEquivalentTo(string unexpected, notEquivalent = scope.Discard().Length > 0; } - Execute.Assertion + assertionChain .ForCondition(notEquivalent) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:string} not to be equivalent to {0}{reason}, but they are.", unexpected); @@ -223,7 +226,7 @@ public AndConstraint NotBeEquivalentTo(string unexpected, notEquivalent = scope.Discard().Length > 0; } - Execute.Assertion + assertionChain .ForCondition(notEquivalent) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:string} not to be equivalent to {0}{reason}, but they are.", unexpected); @@ -246,7 +249,7 @@ public AndConstraint NotBeEquivalentTo(string unexpected, public AndConstraint NotBe(string unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject != unexpected) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:string} not to be {0}{reason}.", unexpected); @@ -298,7 +301,7 @@ public AndConstraint Match(string wildcardPattern, Guard.ThrowIfArgumentIsEmpty(wildcardPattern, nameof(wildcardPattern), "Cannot match string against an empty string. Provide a wildcard pattern or use the BeEmpty method."); - var stringWildcardMatchingValidator = new StringValidator( + var stringWildcardMatchingValidator = new StringValidator(assertionChain, new StringWildcardMatchingStrategy(), because, becauseArgs); @@ -351,7 +354,7 @@ public AndConstraint NotMatch(string wildcardPattern, Guard.ThrowIfArgumentIsEmpty(wildcardPattern, nameof(wildcardPattern), "Cannot match string against an empty string. Provide a wildcard pattern or use the NotBeEmpty method."); - var stringWildcardMatchingValidator = new StringValidator( + var stringWildcardMatchingValidator = new StringValidator(assertionChain, new StringWildcardMatchingStrategy { Negate = true @@ -407,7 +410,7 @@ public AndConstraint MatchEquivalentOf(string wildcardPattern, Guard.ThrowIfArgumentIsEmpty(wildcardPattern, nameof(wildcardPattern), "Cannot match string against an empty string. Provide a wildcard pattern or use the BeEmpty method."); - var stringWildcardMatchingValidator = new StringValidator( + var stringWildcardMatchingValidator = new StringValidator(assertionChain, new StringWildcardMatchingStrategy { IgnoreCase = true, @@ -472,7 +475,7 @@ public AndConstraint MatchEquivalentOf(string wildcardPattern, EquivalencyOptions options = config(AssertionOptions.CloneDefaults()); - var stringWildcardMatchingValidator = new StringValidator( + var stringWildcardMatchingValidator = new StringValidator(assertionChain, new StringWildcardMatchingStrategy { IgnoreCase = options.IgnoreCase, @@ -531,7 +534,7 @@ public AndConstraint NotMatchEquivalentOf(string wildcardPattern, Guard.ThrowIfArgumentIsEmpty(wildcardPattern, nameof(wildcardPattern), "Cannot match string against an empty string. Provide a wildcard pattern or use the NotBeEmpty method."); - var stringWildcardMatchingValidator = new StringValidator( + var stringWildcardMatchingValidator = new StringValidator(assertionChain, new StringWildcardMatchingStrategy { IgnoreCase = true, @@ -597,7 +600,7 @@ public AndConstraint NotMatchEquivalentOf(string wildcardPattern, EquivalencyOptions options = config(AssertionOptions.CloneDefaults()); - var stringWildcardMatchingValidator = new StringValidator( + var stringWildcardMatchingValidator = new StringValidator(assertionChain, new StringWildcardMatchingStrategy { IgnoreCase = options.IgnoreCase, @@ -648,7 +651,7 @@ public AndConstraint MatchRegex([RegexPattern][StringSyntax("Regex" } catch (ArgumentException) { - Execute.Assertion.FailWith("Cannot match {context:string} against {0} because it is not a valid regular expression.", + assertionChain.FailWith("Cannot match {context:string} against {0} because it is not a valid regular expression.", regularExpression); return new AndConstraint((TAssertions)this); @@ -685,7 +688,7 @@ public AndConstraint MatchRegex([RegexPattern][StringSyntax("Regex" } catch (ArgumentException) { - Execute.Assertion.FailWith("Cannot match {context:string} against {0} because it is not a valid regular expression.", + assertionChain.FailWith("Cannot match {context:string} against {0} because it is not a valid regular expression.", regularExpression); return new AndConstraint((TAssertions)this); @@ -727,17 +730,17 @@ public AndConstraint MatchRegex(Regex regularExpression, Guard.ThrowIfArgumentIsEmpty(regexStr, nameof(regularExpression), "Cannot match string against an empty string. Provide a regex pattern or use the BeEmpty method."); - bool success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .UsingLineBreaks .BecauseOf(because, becauseArgs) .FailWith("Expected {context:string} to match regex {0}{reason}, but it was .", regexStr); - if (success) + if (assertionChain.Succeeded) { int actual = regularExpression.Matches(Subject!).Count; - Execute.Assertion + assertionChain .ForConstraint(occurrenceConstraint, actual) .UsingLineBreaks .BecauseOf(because, becauseArgs) @@ -775,15 +778,15 @@ public AndConstraint MatchRegex(Regex regularExpression, Guard.ThrowIfArgumentIsEmpty(regexStr, nameof(regularExpression), "Cannot match string against an empty string. Provide a regex pattern or use the BeEmpty method."); - bool success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .UsingLineBreaks .BecauseOf(because, becauseArgs) .FailWith("Expected {context:string} to match regex {0}{reason}, but it was .", regexStr); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .ForCondition(regularExpression.IsMatch(Subject!)) .BecauseOf(because, becauseArgs) .UsingLineBreaks @@ -821,7 +824,7 @@ public AndConstraint NotMatchRegex([RegexPattern][StringSyntax("Reg } catch (ArgumentException) { - Execute.Assertion.FailWith("Cannot match {context:string} against {0} because it is not a valid regular expression.", + assertionChain.FailWith("Cannot match {context:string} against {0} because it is not a valid regular expression.", regularExpression); return new AndConstraint((TAssertions)this); @@ -856,15 +859,15 @@ public AndConstraint NotMatchRegex(Regex regularExpression, Guard.ThrowIfArgumentIsEmpty(regexStr, nameof(regularExpression), "Cannot match string against an empty regex pattern. Provide a regex pattern or use the NotBeEmpty method."); - bool success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .UsingLineBreaks .BecauseOf(because, becauseArgs) .FailWith("Expected {context:string} to not match regex {0}{reason}, but it was .", regexStr); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .ForCondition(!regularExpression.IsMatch(Subject!)) .BecauseOf(because, becauseArgs) .UsingLineBreaks @@ -892,7 +895,7 @@ public AndConstraint StartWith(string expected, { Guard.ThrowIfArgumentIsNull(expected, nameof(expected), "Cannot compare start of string with ."); - var stringStartValidator = new StringValidator( + var stringStartValidator = new StringValidator(assertionChain, new StringStartStrategy(StringComparer.Ordinal, "start with"), because, becauseArgs); @@ -927,7 +930,7 @@ public AndConstraint NotStartWith(string unexpected, notEquivalent = scope.Discard().Length > 0; } - Execute.Assertion + assertionChain .ForCondition(Subject != null && notEquivalent) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:string} not to start with {0}{reason}, but found {1}.", unexpected, Subject); @@ -953,7 +956,7 @@ public AndConstraint StartWithEquivalentOf(string expected, { Guard.ThrowIfArgumentIsNull(expected, nameof(expected), "Cannot compare string start equivalence with ."); - var stringStartValidator = new StringValidator( + var stringStartValidator = new StringValidator(assertionChain, new StringStartStrategy(StringComparer.OrdinalIgnoreCase, "start with equivalent of"), because, becauseArgs); @@ -986,7 +989,7 @@ public AndConstraint StartWithEquivalentOf(string expected, EquivalencyOptions options = config(AssertionOptions.CloneDefaults()); - var stringStartValidator = new StringValidator( + var stringStartValidator = new StringValidator(assertionChain, new StringStartStrategy(options.GetStringComparerOrDefault(), "start with equivalent of"), because, becauseArgs); @@ -1023,7 +1026,7 @@ public AndConstraint NotStartWithEquivalentOf(string unexpected, notEquivalent = scope.Discard().Length > 0; } - Execute.Assertion + assertionChain .ForCondition(Subject != null && notEquivalent) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:string} not to start with equivalent of {0}{reason}, but found {1}.", unexpected, Subject); @@ -1061,7 +1064,7 @@ public AndConstraint NotStartWithEquivalentOf(string unexpected, notEquivalent = scope.Discard().Length > 0; } - Execute.Assertion + assertionChain .ForCondition(Subject != null && notEquivalent) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:string} not to start with equivalent of {0}{reason}, but found {1}.", unexpected, Subject); @@ -1087,7 +1090,7 @@ public AndConstraint EndWith(string expected, { Guard.ThrowIfArgumentIsNull(expected, nameof(expected), "Cannot compare string end with ."); - var stringEndValidator = new StringValidator( + var stringEndValidator = new StringValidator(assertionChain, new StringEndStrategy(StringComparer.Ordinal, "end with"), because, becauseArgs); @@ -1122,7 +1125,7 @@ public AndConstraint NotEndWith(string unexpected, notEquivalent = scope.Discard().Length > 0; } - Execute.Assertion + assertionChain .ForCondition(Subject != null && notEquivalent) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:string} not to end with {0}{reason}, but found {1}.", unexpected, Subject); @@ -1148,7 +1151,7 @@ public AndConstraint EndWithEquivalentOf(string expected, { Guard.ThrowIfArgumentIsNull(expected, nameof(expected), "Cannot compare string end equivalence with ."); - var stringEndValidator = new StringValidator( + var stringEndValidator = new StringValidator(assertionChain, new StringEndStrategy(StringComparer.OrdinalIgnoreCase, "end with equivalent of"), because, becauseArgs); @@ -1181,7 +1184,7 @@ public AndConstraint EndWithEquivalentOf(string expected, EquivalencyOptions options = config(AssertionOptions.CloneDefaults()); - var stringEndValidator = new StringValidator( + var stringEndValidator = new StringValidator(assertionChain, new StringEndStrategy(options.GetStringComparerOrDefault(), "end with equivalent of"), because, becauseArgs); @@ -1218,7 +1221,7 @@ public AndConstraint NotEndWithEquivalentOf(string unexpected, notEquivalent = scope.Discard().Length > 0; } - Execute.Assertion + assertionChain .ForCondition(Subject != null && notEquivalent) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:string} not to end with equivalent of {0}{reason}, but found {1}.", unexpected, Subject); @@ -1256,7 +1259,7 @@ public AndConstraint NotEndWithEquivalentOf(string unexpected, notEquivalent = scope.Discard().Length > 0; } - Execute.Assertion + assertionChain .ForCondition(Subject != null && notEquivalent) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:string} not to end with equivalent of {0}{reason}, but found {1}.", unexpected, Subject); @@ -1285,7 +1288,7 @@ public AndConstraint Contain(string expected, Guard.ThrowIfArgumentIsNull(expected, nameof(expected), "Cannot assert string containment against ."); Guard.ThrowIfArgumentIsEmpty(expected, nameof(expected), "Cannot assert string containment against an empty string."); - Execute.Assertion + assertionChain .ForCondition(Contains(Subject, expected, StringComparison.Ordinal)) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:string} {0} to contain {1}{reason}.", Subject, expected); @@ -1322,7 +1325,7 @@ public AndConstraint Contain(string expected, OccurrenceConstraint int actual = Subject.CountSubstring(expected, StringComparer.Ordinal); - Execute.Assertion + assertionChain .ForConstraint(occurrenceConstraint, actual) .BecauseOf(because, becauseArgs) .FailWith( @@ -1352,7 +1355,7 @@ public AndConstraint ContainEquivalentOf(string expected, Guard.ThrowIfArgumentIsNull(expected, nameof(expected), "Cannot assert string containment against ."); Guard.ThrowIfArgumentIsEmpty(expected, nameof(expected), "Cannot assert string containment against an empty string."); - var stringContainValidator = new StringValidatorSupportingNull( + var stringContainValidator = new StringValidatorSupportingNull(assertionChain, new StringContainsStrategy(StringComparer.OrdinalIgnoreCase, AtLeast.Once()), because, becauseArgs); @@ -1418,7 +1421,7 @@ public AndConstraint ContainEquivalentOf(string expected, EquivalencyOptions options = config(AssertionOptions.CloneDefaults()); - var stringContainValidator = new StringValidatorSupportingNull( + var stringContainValidator = new StringValidatorSupportingNull(assertionChain, new StringContainsStrategy(options.GetStringComparerOrDefault(), occurrenceConstraint), because, becauseArgs); @@ -1459,7 +1462,7 @@ public AndConstraint ContainEquivalentOf(string expected, Guard.ThrowIfArgumentIsEmpty(expected, nameof(expected), "Cannot assert string containment against an empty string."); Guard.ThrowIfArgumentIsNull(occurrenceConstraint); - var stringContainValidator = new StringValidatorSupportingNull( + var stringContainValidator = new StringValidatorSupportingNull(assertionChain, new StringContainsStrategy(StringComparer.OrdinalIgnoreCase, occurrenceConstraint), because, becauseArgs); @@ -1488,7 +1491,7 @@ public AndConstraint ContainAll(IEnumerable values, IEnumerable missing = values.Where(v => !Contains(Subject, v, StringComparison.Ordinal)); - Execute.Assertion + assertionChain .ForCondition(values.All(v => Contains(Subject, v, StringComparison.Ordinal))) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:string} {0} to contain the strings: {1}{reason}.", Subject, missing); @@ -1525,7 +1528,7 @@ public AndConstraint ContainAny(IEnumerable values, { ThrowIfValuesNullOrEmpty(values); - Execute.Assertion + assertionChain .ForCondition(values.Any(v => Contains(Subject, v, StringComparison.Ordinal))) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:string} {0} to contain at least one of the strings: {1}{reason}.", Subject, values); @@ -1565,7 +1568,7 @@ public AndConstraint NotContain(string unexpected, Guard.ThrowIfArgumentIsNull(unexpected, nameof(unexpected), "Cannot assert string containment against ."); Guard.ThrowIfArgumentIsEmpty(unexpected, nameof(unexpected), "Cannot assert string containment against an empty string."); - Execute.Assertion + assertionChain .ForCondition(!Contains(Subject, unexpected, StringComparison.Ordinal)) .BecauseOf(because, becauseArgs) .FailWith("Did not expect {context:string} {0} to contain {1}{reason}.", Subject, unexpected); @@ -1594,7 +1597,7 @@ public AndConstraint NotContainAll(IEnumerable values, var matches = values.Count(v => Contains(Subject, v, StringComparison.Ordinal)); - Execute.Assertion + assertionChain .ForCondition(matches != values.Count()) .BecauseOf(because, becauseArgs) .FailWith("Did not expect {context:string} {0} to contain all of the strings: {1}{reason}.", Subject, values); @@ -1634,7 +1637,7 @@ public AndConstraint NotContainAny(IEnumerable values, IEnumerable matches = values.Where(v => Contains(Subject, v, StringComparison.Ordinal)); - Execute.Assertion + assertionChain .ForCondition(!matches.Any()) .BecauseOf(because, becauseArgs) .FailWith("Did not expect {context:string} {0} to contain any of the strings: {1}{reason}.", Subject, matches); @@ -1668,7 +1671,7 @@ public AndConstraint NotContainAny(params string[] values) public AndConstraint NotContainEquivalentOf(string unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(!string.IsNullOrEmpty(unexpected) && Subject != null) .BecauseOf(because, becauseArgs) .FailWith("Did not expect {context:string} to contain the equivalent of {0}{reason}, but found {1}.", unexpected, Subject); @@ -1681,7 +1684,7 @@ public AndConstraint NotContainEquivalentOf(string unexpected, notEquivalent = scope.Discard().Length > 0; } - Execute.Assertion + assertionChain .ForCondition(notEquivalent) .BecauseOf(because, becauseArgs) .FailWith("Did not expect {context:string} to contain the equivalent of {0}{reason} but found {1}.", unexpected, Subject); @@ -1709,7 +1712,7 @@ public AndConstraint NotContainEquivalentOf(string unexpected, { Guard.ThrowIfArgumentIsNull(config); - Execute.Assertion + assertionChain .ForCondition(!string.IsNullOrEmpty(unexpected) && Subject != null) .BecauseOf(because, becauseArgs) .FailWith("Did not expect {context:string} to contain the equivalent of {0}{reason}, but found {1}.", unexpected, Subject); @@ -1722,7 +1725,7 @@ public AndConstraint NotContainEquivalentOf(string unexpected, notEquivalent = scope.Discard().Length > 0; } - Execute.Assertion + assertionChain .ForCondition(notEquivalent) .BecauseOf(because, becauseArgs) .FailWith("Did not expect {context:string} to contain the equivalent of {0}{reason}, but found {1}.", unexpected, Subject); @@ -1742,7 +1745,7 @@ public AndConstraint NotContainEquivalentOf(string unexpected, /// public AndConstraint BeEmpty([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject?.Length == 0) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:string} to be empty{reason}, but found {0}.", Subject); @@ -1762,7 +1765,7 @@ public AndConstraint BeEmpty([StringSyntax("CompositeFormat")] stri /// public AndConstraint NotBeEmpty([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject is null || Subject.Length > 0) .BecauseOf(because, becauseArgs) .FailWith("Did not expect {context:string} to be empty{reason}."); @@ -1784,14 +1787,14 @@ public AndConstraint NotBeEmpty([StringSyntax("CompositeFormat")] s public AndConstraint HaveLength(int expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected {context:string} with length {0}{reason}, but found .", expected); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject!.Length == expected) .FailWith("Expected {context:string} with length {0}{reason}, but found string {1} with length {2}.", @@ -1813,7 +1816,7 @@ public AndConstraint HaveLength(int expected, /// public AndConstraint NotBeNullOrEmpty([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(!string.IsNullOrEmpty(Subject)) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:string} not to be or empty{reason}, but found {0}.", Subject); @@ -1833,7 +1836,7 @@ public AndConstraint NotBeNullOrEmpty([StringSyntax("CompositeForma /// public AndConstraint BeNullOrEmpty([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(string.IsNullOrEmpty(Subject)) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:string} to be or empty{reason}, but found {0}.", Subject); @@ -1853,7 +1856,7 @@ public AndConstraint BeNullOrEmpty([StringSyntax("CompositeFormat") /// public AndConstraint NotBeNullOrWhiteSpace([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(!string.IsNullOrWhiteSpace(Subject)) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:string} not to be or whitespace{reason}, but found {0}.", Subject); @@ -1873,7 +1876,7 @@ public AndConstraint NotBeNullOrWhiteSpace([StringSyntax("Composite /// public AndConstraint BeNullOrWhiteSpace([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(string.IsNullOrWhiteSpace(Subject)) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:string} to be or whitespace{reason}, but found {0}.", Subject); @@ -1898,7 +1901,7 @@ public AndConstraint BeNullOrWhiteSpace([StringSyntax("CompositeFor /// public AndConstraint BeUpperCased([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject is not null && !Subject.Any(char.IsLower)) .BecauseOf(because, becauseArgs) .FailWith("Expected all alphabetic characters in {context:string} to be upper-case{reason}, but found {0}.", Subject); @@ -1923,7 +1926,7 @@ public AndConstraint BeUpperCased([StringSyntax("CompositeFormat")] /// public AndConstraint NotBeUpperCased([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject is null || HasMixedOrNoCase(Subject)) .BecauseOf(because, becauseArgs) .FailWith("Expected some characters in {context:string} to be lower-case{reason}."); @@ -1948,7 +1951,7 @@ public AndConstraint NotBeUpperCased([StringSyntax("CompositeFormat /// public AndConstraint BeLowerCased([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject is not null && !Subject.Any(char.IsUpper)) .BecauseOf(because, becauseArgs) .FailWith("Expected all alphabetic characters in {context:string} to be lower cased{reason}, but found {0}.", Subject); @@ -1973,7 +1976,7 @@ public AndConstraint BeLowerCased([StringSyntax("CompositeFormat")] /// public AndConstraint NotBeLowerCased([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject is null || HasMixedOrNoCase(Subject)) .BecauseOf(because, becauseArgs) .FailWith("Expected some characters in {context:string} to be upper-case{reason}."); @@ -2008,7 +2011,7 @@ internal AndConstraint Be(string expected, EquivalencyOptions options = config(AssertionOptions.CloneDefaults()); - var expectation = new StringValidator( + var expectation = new StringValidator(assertionChain, new StringEqualityStrategy(options.GetStringComparerOrDefault(), "be"), because, becauseArgs); diff --git a/Src/FluentAssertions/Primitives/StringContainsStrategy.cs b/Src/FluentAssertions/Primitives/StringContainsStrategy.cs index 46c8b5b2e1..c82a053b73 100644 --- a/Src/FluentAssertions/Primitives/StringContainsStrategy.cs +++ b/Src/FluentAssertions/Primitives/StringContainsStrategy.cs @@ -17,11 +17,11 @@ public StringContainsStrategy(IEqualityComparer comparer, OccurrenceCons public string ExpectationDescription => "Expected {context:string} to contain the equivalent of "; - public void ValidateAgainstMismatch(IAssertionScope assertion, string subject, string expected) + public void ValidateAgainstMismatch(AssertionChain assertionChain, string subject, string expected) { int actual = subject.CountSubstring(expected, comparer); - assertion + assertionChain .ForConstraint(occurrenceConstraint, actual) .FailWith( $"Expected {{context:string}} {{0}} to contain the equivalent of {{1}} {{expectedOccurrence}}{{reason}}, but found it {actual.Times()}.", diff --git a/Src/FluentAssertions/Primitives/StringEndStrategy.cs b/Src/FluentAssertions/Primitives/StringEndStrategy.cs index bdb59f9cbf..a2a332491f 100644 --- a/Src/FluentAssertions/Primitives/StringEndStrategy.cs +++ b/Src/FluentAssertions/Primitives/StringEndStrategy.cs @@ -17,11 +17,13 @@ public StringEndStrategy(IEqualityComparer comparer, string predicateDes public string ExpectationDescription => "Expected {context:string} to " + predicateDescription + " "; - public void ValidateAgainstMismatch(IAssertionScope assertion, string subject, string expected) + public void ValidateAgainstMismatch(AssertionChain assertionChain, string subject, string expected) { - if (!assertion - .ForCondition(subject!.Length >= expected.Length) - .FailWith(ExpectationDescription + "{0}{reason}, but {1} is too short.", expected, subject)) + assertionChain + .ForCondition(subject!.Length >= expected.Length) + .FailWith(ExpectationDescription + "{0}{reason}, but {1} is too short.", expected, subject); + + if (!assertionChain.Succeeded) { return; } @@ -33,7 +35,7 @@ public void ValidateAgainstMismatch(IAssertionScope assertion, string subject, s return; } - assertion.FailWith( + assertionChain.FailWith( ExpectationDescription + "{0}{reason}, but {1} differs near " + subject.IndexedSegmentAt(indexOfMismatch) + ".", expected, subject); diff --git a/Src/FluentAssertions/Primitives/StringEqualityStrategy.cs b/Src/FluentAssertions/Primitives/StringEqualityStrategy.cs index 37ad2a72e5..d2c00a4ac0 100644 --- a/Src/FluentAssertions/Primitives/StringEqualityStrategy.cs +++ b/Src/FluentAssertions/Primitives/StringEqualityStrategy.cs @@ -18,9 +18,9 @@ public StringEqualityStrategy(IEqualityComparer comparer, string predica this.predicateDescription = predicateDescription; } - public void ValidateAgainstMismatch(IAssertionScope assertion, string subject, string expected) + public void ValidateAgainstMismatch(AssertionChain assertionChain, string subject, string expected) { - ValidateAgainstSuperfluousWhitespace(assertion, subject, expected); + ValidateAgainstSuperfluousWhitespace(assertionChain, subject, expected); if (expected.IsLongOrMultiline() || subject.IsLongOrMultiline()) { @@ -28,7 +28,7 @@ public void ValidateAgainstMismatch(IAssertionScope assertion, string subject, s if (indexOfMismatch == -1) { - ValidateAgainstLengthDifferences(assertion, subject, expected); + ValidateAgainstLengthDifferences(assertionChain, subject, expected); return; } @@ -43,18 +43,18 @@ public void ValidateAgainstMismatch(IAssertionScope assertion, string subject, s locationDescription = $"on line {lineNumber + 1} and column {column} (index {indexOfMismatch})"; } - assertion.FailWith( + assertionChain.FailWith( ExpectationDescription + "the same string{reason}, but they differ " + locationDescription + ":" + Environment.NewLine + GetMismatchSegmentForLongStrings(subject, expected, indexOfMismatch) + "."); } - else if (ValidateAgainstLengthDifferences(assertion, subject, expected)) + else if (ValidateAgainstLengthDifferences(assertionChain, subject, expected)) { int indexOfMismatch = subject.IndexOfFirstMismatch(expected, comparer); if (indexOfMismatch != -1) { - assertion.FailWith( + assertionChain.FailWith( ExpectationDescription + "{0}{reason}, but {1} differs near " + subject.IndexedSegmentAt(indexOfMismatch) + ".", expected, subject); @@ -64,7 +64,7 @@ public void ValidateAgainstMismatch(IAssertionScope assertion, string subject, s public string ExpectationDescription => "Expected {context:string} to " + predicateDescription + " "; - private void ValidateAgainstSuperfluousWhitespace(IAssertionScope assertion, string subject, string expected) + private void ValidateAgainstSuperfluousWhitespace(AssertionChain assertion, string subject, string expected) { assertion .ForCondition(!(expected.Length > subject.Length && comparer.Equals(expected.TrimEnd(), subject))) @@ -74,9 +74,9 @@ private void ValidateAgainstSuperfluousWhitespace(IAssertionScope assertion, str .FailWith(ExpectationDescription + "{0}{reason}, but it has unexpected whitespace at the end.", expected); } - private bool ValidateAgainstLengthDifferences(IAssertionScope assertion, string subject, string expected) + private bool ValidateAgainstLengthDifferences(AssertionChain assertion, string subject, string expected) { - return assertion + assertion .ForCondition(subject.Length == expected.Length) .FailWith(() => { @@ -87,6 +87,8 @@ private bool ValidateAgainstLengthDifferences(IAssertionScope assertion, string return new FailReason(message, expected, expected.Length, subject, subject.Length); }); + + return assertion.Succeeded; } private string GetMismatchSegmentForStringsOfDifferentLengths(string subject, string expected) diff --git a/Src/FluentAssertions/Primitives/StringStartStrategy.cs b/Src/FluentAssertions/Primitives/StringStartStrategy.cs index e8bd42c53d..40b6b7e834 100644 --- a/Src/FluentAssertions/Primitives/StringStartStrategy.cs +++ b/Src/FluentAssertions/Primitives/StringStartStrategy.cs @@ -17,11 +17,13 @@ public StringStartStrategy(IEqualityComparer comparer, string predicateD public string ExpectationDescription => "Expected {context:string} to " + predicateDescription + " "; - public void ValidateAgainstMismatch(IAssertionScope assertion, string subject, string expected) + public void ValidateAgainstMismatch(AssertionChain assertionChain, string subject, string expected) { - if (!assertion - .ForCondition(subject.Length >= expected.Length) - .FailWith(ExpectationDescription + "{0}{reason}, but {1} is too short.", expected, subject)) + assertionChain + .ForCondition(subject.Length >= expected.Length) + .FailWith(ExpectationDescription + "{0}{reason}, but {1} is too short.", expected, subject); + + if (!assertionChain.Succeeded) { return; } @@ -33,7 +35,7 @@ public void ValidateAgainstMismatch(IAssertionScope assertion, string subject, s return; } - assertion.FailWith( + assertionChain.FailWith( ExpectationDescription + "{0}{reason}, but {1} differs near " + subject.IndexedSegmentAt(indexOfMismatch) + ".", expected, subject); diff --git a/Src/FluentAssertions/Primitives/StringValidator.cs b/Src/FluentAssertions/Primitives/StringValidator.cs index 8e7e49d7a0..e3402dde81 100644 --- a/Src/FluentAssertions/Primitives/StringValidator.cs +++ b/Src/FluentAssertions/Primitives/StringValidator.cs @@ -1,4 +1,3 @@ -using System.Diagnostics.CodeAnalysis; using FluentAssertions.Common; using FluentAssertions.Execution; @@ -7,12 +6,13 @@ namespace FluentAssertions.Primitives; internal class StringValidator { private readonly IStringComparisonStrategy comparisonStrategy; - private IAssertionScope assertion; + private AssertionChain assertionChain; - public StringValidator(IStringComparisonStrategy comparisonStrategy, [StringSyntax("CompositeFormat")] string because, object[] becauseArgs) + public StringValidator(AssertionChain assertionChain, IStringComparisonStrategy comparisonStrategy, string because, + object[] becauseArgs) { this.comparisonStrategy = comparisonStrategy; - assertion = Execute.Assertion.BecauseOf(because, becauseArgs); + this.assertionChain = assertionChain.BecauseOf(because, becauseArgs); } public void Validate(string subject, string expected) @@ -29,10 +29,10 @@ public void Validate(string subject, string expected) if (expected.IsLongOrMultiline() || subject.IsLongOrMultiline()) { - assertion = assertion.UsingLineBreaks; + assertionChain = assertionChain.UsingLineBreaks; } - comparisonStrategy.ValidateAgainstMismatch(assertion, subject, expected); + comparisonStrategy.ValidateAgainstMismatch(assertionChain, subject, expected); } private bool ValidateAgainstNulls(string subject, string expected) @@ -42,7 +42,7 @@ private bool ValidateAgainstNulls(string subject, string expected) return true; } - assertion.FailWith(comparisonStrategy.ExpectationDescription + "{0}{reason}, but found {1}.", expected, subject); + assertionChain.FailWith(comparisonStrategy.ExpectationDescription + "{0}{reason}, but found {1}.", expected, subject); return false; } } diff --git a/Src/FluentAssertions/Primitives/StringValidatorSupportingNull.cs b/Src/FluentAssertions/Primitives/StringValidatorSupportingNull.cs index deaf451a21..f0a20b55e7 100644 --- a/Src/FluentAssertions/Primitives/StringValidatorSupportingNull.cs +++ b/Src/FluentAssertions/Primitives/StringValidatorSupportingNull.cs @@ -7,12 +7,13 @@ namespace FluentAssertions.Primitives; internal class StringValidatorSupportingNull { private readonly IStringComparisonStrategy comparisonStrategy; - private IAssertionScope assertion; + private AssertionChain assertionChain; - public StringValidatorSupportingNull(IStringComparisonStrategy comparisonStrategy, [StringSyntax("CompositeFormat")] string because, object[] becauseArgs) + public StringValidatorSupportingNull(AssertionChain assertionChain, IStringComparisonStrategy comparisonStrategy, + [StringSyntax("CompositeFormat")] string because, object[] becauseArgs) { this.comparisonStrategy = comparisonStrategy; - assertion = Execute.Assertion.BecauseOf(because, becauseArgs); + this.assertionChain = assertionChain.BecauseOf(because, becauseArgs); } public void Validate(string subject, string expected) @@ -25,9 +26,9 @@ public void Validate(string subject, string expected) if (expected?.IsLongOrMultiline() == true || subject?.IsLongOrMultiline() == true) { - assertion = assertion.UsingLineBreaks; + assertionChain = assertionChain.UsingLineBreaks; } - comparisonStrategy.ValidateAgainstMismatch(assertion, subject, expected); + comparisonStrategy.ValidateAgainstMismatch(assertionChain, subject, expected); } } diff --git a/Src/FluentAssertions/Primitives/StringWildcardMatchingStrategy.cs b/Src/FluentAssertions/Primitives/StringWildcardMatchingStrategy.cs index 82afb98462..eca22d9815 100644 --- a/Src/FluentAssertions/Primitives/StringWildcardMatchingStrategy.cs +++ b/Src/FluentAssertions/Primitives/StringWildcardMatchingStrategy.cs @@ -8,7 +8,7 @@ namespace FluentAssertions.Primitives; internal class StringWildcardMatchingStrategy : IStringComparisonStrategy { - public void ValidateAgainstMismatch(IAssertionScope assertion, string subject, string expected) + public void ValidateAgainstMismatch(AssertionChain assertionChain, string subject, string expected) { bool isMatch = IsMatch(subject, expected); @@ -19,11 +19,11 @@ public void ValidateAgainstMismatch(IAssertionScope assertion, string subject, s if (Negate) { - assertion.FailWith(ExpectationDescription + "but {1} matches.", expected, subject); + assertionChain.FailWith(ExpectationDescription + "but {1} matches.", expected, subject); } else { - assertion.FailWith(ExpectationDescription + "but {1} does not.", expected, subject); + assertionChain.FailWith(ExpectationDescription + "but {1} does not.", expected, subject); } } diff --git a/Src/FluentAssertions/Primitives/TimeOnlyAssertions.cs b/Src/FluentAssertions/Primitives/TimeOnlyAssertions.cs index ce959601fe..ed92a9c0cb 100644 --- a/Src/FluentAssertions/Primitives/TimeOnlyAssertions.cs +++ b/Src/FluentAssertions/Primitives/TimeOnlyAssertions.cs @@ -16,8 +16,8 @@ namespace FluentAssertions.Primitives; [DebuggerNonUserCode] public class TimeOnlyAssertions : TimeOnlyAssertions { - public TimeOnlyAssertions(TimeOnly? value) - : base(value) + public TimeOnlyAssertions(TimeOnly? value, AssertionChain assertionChain) + : base(value, assertionChain) { } } @@ -31,8 +31,11 @@ public TimeOnlyAssertions(TimeOnly? value) public class TimeOnlyAssertions where TAssertions : TimeOnlyAssertions { - public TimeOnlyAssertions(TimeOnly? value) + private readonly AssertionChain assertionChain; + + public TimeOnlyAssertions(TimeOnly? value, AssertionChain assertionChain) { + this.assertionChain = assertionChain; Subject = value; } @@ -55,7 +58,7 @@ public TimeOnlyAssertions(TimeOnly? value) public AndConstraint Be(TimeOnly expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject == expected) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:time} to be {0}{reason}, but found {1}.", @@ -78,7 +81,7 @@ public AndConstraint Be(TimeOnly expected, public AndConstraint Be(TimeOnly? expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject == expected) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:time} to be {0}{reason}, but found {1}.", @@ -101,7 +104,7 @@ public AndConstraint Be(TimeOnly? expected, public AndConstraint NotBe(TimeOnly unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject != unexpected) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:time} not to be {0}{reason}, but it is.", unexpected); @@ -123,7 +126,7 @@ public AndConstraint NotBe(TimeOnly unexpected, public AndConstraint NotBe(TimeOnly? unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject != unexpected) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:time} not to be {0}{reason}, but it is.", unexpected); @@ -158,16 +161,15 @@ public AndConstraint BeCloseTo(TimeOnly nearbyTime, TimeSpan precis ? MinimumDifference(Subject.Value, nearbyTime) : null; - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected {context:the time} to be within {0} from {1}{reason}, ", precision, nearbyTime) - .ForCondition(Subject is not null) - .FailWith("but found .") - .Then - .ForCondition(Subject?.IsCloseTo(nearbyTime, precision) == true) - .FailWith("but {0} was off by {1}.", Subject, difference) - .Then - .ClearExpectation(); + .WithExpectation("Expected {context:the time} to be within {0} from {1}{reason}, ", precision, nearbyTime, + chain => chain + .ForCondition(Subject is not null) + .FailWith("but found .") + .Then + .ForCondition(Subject?.IsCloseTo(nearbyTime, precision) == true) + .FailWith("but {0} was off by {1}.", Subject, difference)); return new AndConstraint((TAssertions)this); } @@ -203,16 +205,15 @@ public AndConstraint NotBeCloseTo(TimeOnly distantTime, TimeSpan pr { Guard.ThrowIfArgumentIsNegative(precision); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Did not expect {context:the time} to be within {0} from {1}{reason}, ", precision, distantTime) - .ForCondition(Subject is not null) - .FailWith("but found .") - .Then - .ForCondition(Subject?.IsCloseTo(distantTime, precision) == false) - .FailWith("but it was {0}.", Subject) - .Then - .ClearExpectation(); + .WithExpectation("Did not expect {context:the time} to be within {0} from {1}{reason}, ", precision, distantTime, + chain => chain + .ForCondition(Subject is not null) + .FailWith("but found .") + .Then + .ForCondition(Subject?.IsCloseTo(distantTime, precision) == false) + .FailWith("but it was {0}.", Subject)); return new AndConstraint((TAssertions)this); } @@ -231,7 +232,7 @@ public AndConstraint NotBeCloseTo(TimeOnly distantTime, TimeSpan pr public AndConstraint BeBefore(TimeOnly expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject < expected) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:time} to be before {0}{reason}, but found {1}.", expected, @@ -271,7 +272,7 @@ public AndConstraint NotBeBefore(TimeOnly unexpected, public AndConstraint BeOnOrBefore(TimeOnly expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject <= expected) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:time} to be on or before {0}{reason}, but found {1}.", expected, @@ -311,7 +312,7 @@ public AndConstraint NotBeOnOrBefore(TimeOnly unexpected, public AndConstraint BeAfter(TimeOnly expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject > expected) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:time} to be after {0}{reason}, but found {1}.", expected, @@ -351,7 +352,7 @@ public AndConstraint NotBeAfter(TimeOnly unexpected, public AndConstraint BeOnOrAfter(TimeOnly expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject >= expected) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:time} to be on or after {0}{reason}, but found {1}.", expected, @@ -391,16 +392,14 @@ public AndConstraint NotBeOnOrAfter(TimeOnly unexpected, public AndConstraint HaveHours(int expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected the hours part of {context:the time} to be {0}{reason}", expected) - .ForCondition(Subject.HasValue) - .FailWith(", but found .") - .Then - .ForCondition(Subject.Value.Hour == expected) - .FailWith(", but found {0}.", Subject.Value.Hour) - .Then - .ClearExpectation(); + .WithExpectation("Expected the hours part of {context:the time} to be {0}{reason}", expected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith(", but found .") + .Then + .ForCondition(Subject.Value.Hour == expected) + .FailWith(", but found {0}.", Subject.Value.Hour)); return new AndConstraint((TAssertions)this); } @@ -419,7 +418,7 @@ public AndConstraint HaveHours(int expected, public AndConstraint NotHaveHours(int unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject.HasValue) .FailWith("Did not expect the hours part of {context:the time} to be {0}{reason}, but found a TimeOnly.", @@ -446,16 +445,14 @@ public AndConstraint NotHaveHours(int unexpected, public AndConstraint HaveMinutes(int expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected the minutes part of {context:the time} to be {0}{reason}", expected) - .ForCondition(Subject.HasValue) - .FailWith(", but found a TimeOnly.") - .Then - .ForCondition(Subject.Value.Minute == expected) - .FailWith(", but found {0}.", Subject.Value.Minute) - .Then - .ClearExpectation(); + .WithExpectation("Expected the minutes part of {context:the time} to be {0}{reason}", expected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith(", but found a TimeOnly.") + .Then + .ForCondition(Subject.Value.Minute == expected) + .FailWith(", but found {0}.", Subject.Value.Minute)); return new AndConstraint((TAssertions)this); } @@ -474,16 +471,14 @@ public AndConstraint HaveMinutes(int expected, public AndConstraint NotHaveMinutes(int unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Did not expect the minutes part of {context:the time} to be {0}{reason}", unexpected) - .ForCondition(Subject.HasValue) - .FailWith(", but found a TimeOnly.") - .Then - .ForCondition(Subject.Value.Minute != unexpected) - .FailWith(", but it was.") - .Then - .ClearExpectation(); + .WithExpectation("Did not expect the minutes part of {context:the time} to be {0}{reason}", unexpected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith(", but found a TimeOnly.") + .Then + .ForCondition(Subject.Value.Minute != unexpected) + .FailWith(", but it was.")); return new AndConstraint((TAssertions)this); } @@ -502,16 +497,14 @@ public AndConstraint NotHaveMinutes(int unexpected, public AndConstraint HaveSeconds(int expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected the seconds part of {context:the time} to be {0}{reason}", expected) - .ForCondition(Subject.HasValue) - .FailWith(", but found a TimeOnly.") - .Then - .ForCondition(Subject.Value.Second == expected) - .FailWith(", but found {0}.", Subject.Value.Second) - .Then - .ClearExpectation(); + .WithExpectation("Expected the seconds part of {context:the time} to be {0}{reason}", expected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith(", but found a TimeOnly.") + .Then + .ForCondition(Subject.Value.Second == expected) + .FailWith(", but found {0}.", Subject.Value.Second)); return new AndConstraint((TAssertions)this); } @@ -530,16 +523,14 @@ public AndConstraint HaveSeconds(int expected, public AndConstraint NotHaveSeconds(int unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Did not expect the seconds part of {context:the time} to be {0}{reason}", unexpected) - .ForCondition(Subject.HasValue) - .FailWith(", but found a TimeOnly.") - .Then - .ForCondition(Subject.Value.Second != unexpected) - .FailWith(", but it was.") - .Then - .ClearExpectation(); + .WithExpectation("Did not expect the seconds part of {context:the time} to be {0}{reason}", unexpected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith(", but found a TimeOnly.") + .Then + .ForCondition(Subject.Value.Second != unexpected) + .FailWith(", but it was.")); return new AndConstraint((TAssertions)this); } @@ -558,16 +549,14 @@ public AndConstraint NotHaveSeconds(int unexpected, public AndConstraint HaveMilliseconds(int expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected the milliseconds part of {context:the time} to be {0}{reason}", expected) - .ForCondition(Subject.HasValue) - .FailWith(", but found a TimeOnly.") - .Then - .ForCondition(Subject.Value.Millisecond == expected) - .FailWith(", but found {0}.", Subject.Value.Millisecond) - .Then - .ClearExpectation(); + .WithExpectation("Expected the milliseconds part of {context:the time} to be {0}{reason}", expected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith(", but found a TimeOnly.") + .Then + .ForCondition(Subject.Value.Millisecond == expected) + .FailWith(", but found {0}.", Subject.Value.Millisecond)); return new AndConstraint((TAssertions)this); } @@ -586,16 +575,15 @@ public AndConstraint HaveMilliseconds(int expected, public AndConstraint NotHaveMilliseconds(int unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Did not expect the milliseconds part of {context:the time} to be {0}{reason}", unexpected) - .ForCondition(Subject.HasValue) - .FailWith(", but found a TimeOnly.") - .Then - .ForCondition(Subject.Value.Millisecond != unexpected) - .FailWith(", but it was.") - .Then - .ClearExpectation(); + .WithExpectation("Did not expect the milliseconds part of {context:the time} to be {0}{reason}", unexpected, + chain => chain + .ForCondition(Subject.HasValue) + .FailWith(", but found a TimeOnly.") + .Then + .ForCondition(Subject.Value.Millisecond != unexpected) + .FailWith(", but it was.")); return new AndConstraint((TAssertions)this); } @@ -657,7 +645,7 @@ public AndConstraint BeOneOf(IEnumerable validValues, public AndConstraint BeOneOf(IEnumerable validValues, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(validValues.Contains(Subject)) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:time} to be one of {0}{reason}, but found {1}.", validValues, Subject); diff --git a/Src/FluentAssertions/Specialized/ActionAssertions.cs b/Src/FluentAssertions/Specialized/ActionAssertions.cs index 0051f0dee8..94be29f8c4 100644 --- a/Src/FluentAssertions/Specialized/ActionAssertions.cs +++ b/Src/FluentAssertions/Specialized/ActionAssertions.cs @@ -12,14 +12,18 @@ namespace FluentAssertions.Specialized; [DebuggerNonUserCode] public class ActionAssertions : DelegateAssertions { - public ActionAssertions(Action subject, IExtractExceptions extractor) - : base(subject, extractor) + private readonly AssertionChain assertionChain; + + public ActionAssertions(Action subject, IExtractExceptions extractor, AssertionChain assertionChain) + : base(subject, extractor, assertionChain) { + this.assertionChain = assertionChain; } - public ActionAssertions(Action subject, IExtractExceptions extractor, IClock clock) - : base(subject, extractor, clock) + public ActionAssertions(Action subject, IExtractExceptions extractor, AssertionChain assertionChain, IClock clock) + : base(subject, extractor, assertionChain, clock) { + this.assertionChain = assertionChain; } /// @@ -34,12 +38,12 @@ public ActionAssertions(Action subject, IExtractExceptions extractor, IClock clo /// public AndConstraint NotThrow([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {context} not to throw{reason}, but found ."); - if (success) + if (assertionChain.Succeeded) { FailIfSubjectIsAsyncVoid(); Exception exception = InvokeSubjectWithInterception(); @@ -78,12 +82,12 @@ public AndConstraint NotThrowAfter(TimeSpan waitTime, TimeSpan Guard.ThrowIfArgumentIsNegative(waitTime); Guard.ThrowIfArgumentIsNegative(pollInterval); - bool success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {context} not to throw after {0}{reason}, but found .", waitTime); - if (success) + if (assertionChain.Succeeded) { FailIfSubjectIsAsyncVoid(); @@ -104,7 +108,7 @@ public AndConstraint NotThrowAfter(TimeSpan waitTime, TimeSpan invocationEndTime = timer.Elapsed; } - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(exception is null) .FailWith("Did not expect any exceptions after {0}{reason}, but found {1}.", waitTime, exception); diff --git a/Src/FluentAssertions/Specialized/AsyncFunctionAssertions.cs b/Src/FluentAssertions/Specialized/AsyncFunctionAssertions.cs index 30791f52a2..7a120af2a0 100644 --- a/Src/FluentAssertions/Specialized/AsyncFunctionAssertions.cs +++ b/Src/FluentAssertions/Specialized/AsyncFunctionAssertions.cs @@ -19,9 +19,13 @@ public class AsyncFunctionAssertions : DelegateAssertionsBas where TTask : Task where TAssertions : AsyncFunctionAssertions { - protected AsyncFunctionAssertions(Func subject, IExtractExceptions extractor, IClock clock) - : base(subject, extractor, clock) + private readonly AssertionChain assertionChain; + + protected AsyncFunctionAssertions(Func subject, IExtractExceptions extractor, AssertionChain assertionChain, + IClock clock) + : base(subject, extractor, assertionChain, clock) { + this.assertionChain = assertionChain; } protected override string Identifier => "async function"; @@ -40,12 +44,12 @@ protected AsyncFunctionAssertions(Func subject, IExtractExceptions extrac public async Task> NotCompleteWithinAsync(TimeSpan timeSpan, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Did not expect {context:task} to complete within {0}{reason}, but found .", timeSpan); - if (success) + if (assertionChain.Succeeded) { (Task task, TimeSpan remainingTime) = InvokeWithTimer(timeSpan); @@ -53,7 +57,7 @@ public async Task> NotCompleteWithinAsync(TimeSpan ti { bool completesWithinTimeout = await CompletesWithinTimeoutAsync(task, remainingTime); - Execute.Assertion + assertionChain .ForCondition(!completesWithinTimeout) .BecauseOf(because, becauseArgs) .FailWith("Did not expect {context:task} to complete within {0}{reason}.", timeSpan); @@ -83,29 +87,29 @@ public async Task> ThrowExactlyAsync { Type expectedType = typeof(TException); - bool success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {context} to throw exactly {0}{reason}, but found .", expectedType); - if (success) + if (assertionChain.Succeeded) { Exception exception = await InvokeWithInterceptionAsync(Subject); - success = Execute.Assertion + assertionChain .ForCondition(exception is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {0}{reason}, but no exception was thrown.", expectedType); - if (success) + if (assertionChain.Succeeded) { exception.Should().BeOfType(expectedType, because, becauseArgs); } - return new ExceptionAssertions([exception as TException]); + return new ExceptionAssertions([exception as TException], assertionChain); } - return new ExceptionAssertions([]); + return new ExceptionAssertions([], assertionChain); } /// @@ -123,18 +127,18 @@ public async Task> ThrowAsync( [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TException : Exception { - bool success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {context} to throw {0}{reason}, but found .", typeof(TException)); - if (success) + if (assertionChain.Succeeded) { Exception exception = await InvokeWithInterceptionAsync(Subject); return ThrowInternal(exception, because, becauseArgs); } - return new ExceptionAssertions([]); + return new ExceptionAssertions([], assertionChain); } /// @@ -154,19 +158,19 @@ public async Task> ThrowWithinAsync( [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TException : Exception { - bool success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {context} to throw {0} within {1}{reason}, but found .", typeof(TException), timeSpan); - if (success) + if (assertionChain.Succeeded) { Exception caughtException = await InvokeWithInterceptionAsync(timeSpan); return AssertThrows(caughtException, timeSpan, because, becauseArgs); } - return new ExceptionAssertions([]); + return new ExceptionAssertions([], assertionChain); } private ExceptionAssertions AssertThrows( @@ -176,21 +180,18 @@ private ExceptionAssertions AssertThrows( { TException[] expectedExceptions = Extractor.OfType(exception).ToArray(); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected a <{0}> to be thrown within {1}{reason}, ", - typeof(TException), timeSpan) - .ForCondition(exception is not null) - .FailWith("but no exception was thrown.") - .Then - .ForCondition(expectedExceptions.Length > 0) - .FailWith("but found <{0}>:" + Environment.NewLine + "{1}.", - exception?.GetType(), - exception) - .Then - .ClearExpectation(); - - return new ExceptionAssertions(expectedExceptions); + .WithExpectation("Expected a <{0}> to be thrown within {1}{reason}, ", typeof(TException), timeSpan, chain => chain + .ForCondition(exception is not null) + .FailWith("but no exception was thrown.") + .Then + .ForCondition(expectedExceptions.Length > 0) + .FailWith("but found <{0}>:" + Environment.NewLine + "{1}.", + exception?.GetType(), + exception)); + + return new ExceptionAssertions(expectedExceptions, assertionChain); } private async Task InvokeWithInterceptionAsync(TimeSpan timeout) @@ -210,6 +211,7 @@ private async Task InvokeWithInterceptionAsync(TimeSpan timeout) : default) { (TTask task, TimeSpan remainingTime) = InvokeWithTimer(timeout); + if (remainingTime < TimeSpan.Zero) { // timeout reached without exception @@ -248,15 +250,16 @@ private async Task InvokeWithInterceptionAsync(TimeSpan timeout) /// /// Zero or more objects to format using the placeholders in . /// - public async Task> NotThrowAsync([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public async Task> NotThrowAsync([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) where TException : Exception { - bool success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {context} not to throw{reason}, but found ."); - if (success) + if (assertionChain.Succeeded) { try { diff --git a/Src/FluentAssertions/Specialized/DelegateAssertions.cs b/Src/FluentAssertions/Specialized/DelegateAssertions.cs index a8084e3255..f10a4b5885 100644 --- a/Src/FluentAssertions/Specialized/DelegateAssertions.cs +++ b/Src/FluentAssertions/Specialized/DelegateAssertions.cs @@ -16,14 +16,18 @@ public abstract class DelegateAssertions : DelegateAsser where TDelegate : Delegate where TAssertions : DelegateAssertions { - protected DelegateAssertions(TDelegate @delegate, IExtractExceptions extractor) - : base(@delegate, extractor, new Clock()) + private readonly AssertionChain assertionChain; + + protected DelegateAssertions(TDelegate @delegate, IExtractExceptions extractor, AssertionChain assertionChain) + : base(@delegate, extractor, assertionChain, new Clock()) { + this.assertionChain = assertionChain; } - private protected DelegateAssertions(TDelegate @delegate, IExtractExceptions extractor, IClock clock) - : base(@delegate, extractor, clock) + private protected DelegateAssertions(TDelegate @delegate, IExtractExceptions extractor, AssertionChain assertionChain, IClock clock) + : base(@delegate, extractor, assertionChain, clock) { + this.assertionChain = assertionChain; } /// @@ -39,19 +43,19 @@ private protected DelegateAssertions(TDelegate @delegate, IExtractExceptions ext public ExceptionAssertions Throw([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TException : Exception { - bool success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {context} to throw {0}{reason}, but found .", typeof(TException)); - if (success) + if (assertionChain.Succeeded) { FailIfSubjectIsAsyncVoid(); Exception exception = InvokeSubjectWithInterception(); return ThrowInternal(exception, because, becauseArgs); } - return new ExceptionAssertions([]); + return new ExceptionAssertions([], assertionChain); } /// @@ -67,12 +71,12 @@ public ExceptionAssertions Throw([StringSyntax("Composit public AndConstraint NotThrow([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TException : Exception { - bool success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {context} not to throw {0}{reason}, but found .", typeof(TException)); - if (success) + if (assertionChain.Succeeded) { FailIfSubjectIsAsyncVoid(); Exception exception = InvokeSubjectWithInterception(); @@ -102,32 +106,32 @@ public ExceptionAssertions ThrowExactly([StringSyntax("C params object[] becauseArgs) where TException : Exception { - bool success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {context} to throw exactly {0}{reason}, but found .", typeof(TException)); - if (success) + if (assertionChain.Succeeded) { FailIfSubjectIsAsyncVoid(); Exception exception = InvokeSubjectWithInterception(); Type expectedType = typeof(TException); - success = Execute.Assertion + assertionChain .ForCondition(exception is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {0}{reason}, but no exception was thrown.", expectedType); - if (success) + if (assertionChain.Succeeded) { exception.Should().BeOfType(expectedType, because, becauseArgs); } - return new ExceptionAssertions([exception as TException]); + return new ExceptionAssertions([exception as TException], assertionChain); } - return new ExceptionAssertions([]); + return new ExceptionAssertions([], assertionChain); } protected abstract void InvokeSubject(); diff --git a/Src/FluentAssertions/Specialized/DelegateAssertionsBase.cs b/Src/FluentAssertions/Specialized/DelegateAssertionsBase.cs index 77b687f54e..fe662c0424 100644 --- a/Src/FluentAssertions/Specialized/DelegateAssertionsBase.cs +++ b/Src/FluentAssertions/Specialized/DelegateAssertionsBase.cs @@ -18,11 +18,15 @@ public abstract class DelegateAssertionsBase where TDelegate : Delegate where TAssertions : DelegateAssertionsBase { + private readonly AssertionChain assertionChain; + private protected IExtractExceptions Extractor { get; } - private protected DelegateAssertionsBase(TDelegate @delegate, IExtractExceptions extractor, IClock clock) - : base(@delegate) + private protected DelegateAssertionsBase(TDelegate @delegate, IExtractExceptions extractor, AssertionChain assertionChain, + IClock clock) + : base(@delegate, assertionChain) { + this.assertionChain = assertionChain; Extractor = extractor ?? throw new ArgumentNullException(nameof(extractor)); Clock = clock ?? throw new ArgumentNullException(nameof(clock)); } @@ -36,25 +40,24 @@ protected ExceptionAssertions ThrowInternal( { TException[] expectedExceptions = Extractor.OfType(exception).ToArray(); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected a <{0}> to be thrown{reason}, ", typeof(TException)) - .ForCondition(exception is not null) - .FailWith("but no exception was thrown.") - .Then - .ForCondition(expectedExceptions.Length > 0) - .FailWith("but found <{0}>:" + Environment.NewLine + "{1}.", - exception?.GetType(), - exception) - .Then - .ClearExpectation(); + .WithExpectation("Expected a <{0}> to be thrown{reason}, ", typeof(TException), chain => chain + .ForCondition(exception is not null) + .FailWith("but no exception was thrown.") + .Then + .ForCondition(expectedExceptions.Length > 0) + .FailWith("but found <{0}>:" + Environment.NewLine + "{1}.", + exception?.GetType(), + exception)); - return new ExceptionAssertions(expectedExceptions); + return new ExceptionAssertions(expectedExceptions, assertionChain); } - protected AndConstraint NotThrowInternal(Exception exception, [StringSyntax("CompositeFormat")] string because, object[] becauseArgs) + protected AndConstraint NotThrowInternal(Exception exception, [StringSyntax("CompositeFormat")] string because, + object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(exception is null) .BecauseOf(because, becauseArgs) .FailWith("Did not expect any exception{reason}, but found {0}.", exception); @@ -62,12 +65,13 @@ protected AndConstraint NotThrowInternal(Exception exception, [Stri return new AndConstraint((TAssertions)this); } - protected AndConstraint NotThrowInternal(Exception exception, [StringSyntax("CompositeFormat")] string because, object[] becauseArgs) + protected AndConstraint NotThrowInternal(Exception exception, + [StringSyntax("CompositeFormat")] string because, object[] becauseArgs) where TException : Exception { IEnumerable exceptions = Extractor.OfType(exception); - Execute.Assertion + assertionChain .ForCondition(!exceptions.Any()) .BecauseOf(because, becauseArgs) .FailWith("Did not expect {0}{reason}, but found {1}.", typeof(TException), exception); diff --git a/Src/FluentAssertions/Specialized/ExceptionAssertions.cs b/Src/FluentAssertions/Specialized/ExceptionAssertions.cs index 3cec526e9a..6c249a969d 100644 --- a/Src/FluentAssertions/Specialized/ExceptionAssertions.cs +++ b/Src/FluentAssertions/Specialized/ExceptionAssertions.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; using FluentAssertions.Common; @@ -19,9 +18,12 @@ namespace FluentAssertions.Specialized; public class ExceptionAssertions : ReferenceTypeAssertions, ExceptionAssertions> where TException : Exception { - public ExceptionAssertions(IEnumerable exceptions) - : base(exceptions) + private readonly AssertionChain assertionChain; + + public ExceptionAssertions(IEnumerable exceptions, AssertionChain assertionChain) + : base(exceptions, assertionChain) { + this.assertionChain = assertionChain; } /// @@ -72,16 +74,16 @@ public ExceptionAssertions(IEnumerable exceptions) /// /// /// - public virtual ExceptionAssertions WithMessage(string expectedWildcardPattern, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public virtual ExceptionAssertions WithMessage(string expectedWildcardPattern, string because = "", + params object[] becauseArgs) { - AssertionScope assertion = Execute.Assertion.BecauseOf(because, becauseArgs).UsingLineBreaks; - - assertion + assertionChain + .BecauseOf(because, becauseArgs) + .UsingLineBreaks .ForCondition(Subject.Any()) .FailWith("Expected exception with message {0}{reason}, but no exception was thrown.", expectedWildcardPattern); - ExceptionMessageAssertion.Execute(Subject.Select(exc => exc.Message), expectedWildcardPattern, because, + AssertExceptionMessage(Subject.Select(exc => exc.Message), expectedWildcardPattern, because, becauseArgs); return this; @@ -98,12 +100,12 @@ public virtual ExceptionAssertions WithMessage(string expectedWildca /// /// Zero or more objects to format using the placeholders in . /// - public virtual ExceptionAssertions WithInnerException( - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public virtual ExceptionAssertions WithInnerException(string because = "", + params object[] becauseArgs) where TInnerException : Exception { var expectedInnerExceptions = AssertInnerExceptions(typeof(TInnerException), because, becauseArgs); - return new ExceptionAssertions(expectedInnerExceptions.Cast()); + return new ExceptionAssertions(expectedInnerExceptions.Cast(), assertionChain); } /// @@ -117,12 +119,12 @@ public virtual ExceptionAssertions WithInnerException /// Zero or more objects to format using the placeholders in . /// - public ExceptionAssertions WithInnerException(Type innerException, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public ExceptionAssertions WithInnerException(Type innerException, string because = "", + params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(innerException); - return new ExceptionAssertions(AssertInnerExceptions(innerException, because, becauseArgs)); + return new ExceptionAssertions(AssertInnerExceptions(innerException, because, becauseArgs), assertionChain); } /// @@ -136,12 +138,12 @@ public ExceptionAssertions WithInnerException(Type innerException, /// /// Zero or more objects to format using the placeholders in . /// - public virtual ExceptionAssertions WithInnerExceptionExactly([StringSyntax("CompositeFormat")] string because = "", + public virtual ExceptionAssertions WithInnerExceptionExactly(string because = "", params object[] becauseArgs) where TInnerException : Exception { var exceptionExpression = AssertInnerExceptionExactly(typeof(TInnerException), because, becauseArgs); - return new ExceptionAssertions(exceptionExpression.Cast()); + return new ExceptionAssertions(exceptionExpression.Cast(), assertionChain); } /// @@ -155,12 +157,12 @@ public virtual ExceptionAssertions WithInnerExceptionExactly /// Zero or more objects to format using the placeholders in . /// - public ExceptionAssertions WithInnerExceptionExactly(Type innerException, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public ExceptionAssertions WithInnerExceptionExactly(Type innerException, string because = "", + params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(innerException); - return new ExceptionAssertions(AssertInnerExceptionExactly(innerException, because, becauseArgs)); + return new ExceptionAssertions(AssertInnerExceptionExactly(innerException, because, becauseArgs), assertionChain); } /// @@ -178,13 +180,13 @@ public ExceptionAssertions WithInnerExceptionExactly(Type innerExcept /// /// is . public ExceptionAssertions Where(Expression> exceptionExpression, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(exceptionExpression); Func condition = exceptionExpression.Compile(); - Execute.Assertion + assertionChain .ForCondition(condition(SingleSubject)) .BecauseOf(because, becauseArgs) .FailWith("Expected exception where {0}{reason}, but the condition was not met by:" @@ -194,10 +196,10 @@ public ExceptionAssertions Where(Expression> return this; } - private IEnumerable AssertInnerExceptionExactly(Type innerException, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + private IEnumerable AssertInnerExceptionExactly(Type innerException, string because = "", + params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject.Any(e => e.InnerException is not null)) .FailWith("Expected inner {0}{reason}, but the thrown exception has no inner exception.", innerException); @@ -206,7 +208,7 @@ private IEnumerable AssertInnerExceptionExactly(Type innerException, .Select(e => e.InnerException) .Where(e => e?.GetType() == innerException).ToArray(); - Execute.Assertion + assertionChain .ForCondition(expectedExceptions.Length > 0) .BecauseOf(because, becauseArgs) .FailWith("Expected inner {0}{reason}, but found {1}.", innerException, SingleSubject.InnerException); @@ -214,10 +216,10 @@ private IEnumerable AssertInnerExceptionExactly(Type innerException, return expectedExceptions; } - private IEnumerable AssertInnerExceptions(Type innerException, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + private IEnumerable AssertInnerExceptions(Type innerException, string because = "", + params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject.Any(e => e.InnerException is not null)) .FailWith("Expected inner {0}{reason}, but the thrown exception has no inner exception.", innerException); @@ -227,7 +229,7 @@ private IEnumerable AssertInnerExceptions(Type innerException, .Where(e => e != null && e.GetType().IsSameOrInherits(innerException)) .ToArray(); - Execute.Assertion + assertionChain .ForCondition(expectedInnerExceptions.Length > 0) .BecauseOf(because, becauseArgs) .FailWith("Expected inner {0}{reason}, but found {1}.", innerException, SingleSubject.InnerException); @@ -259,39 +261,35 @@ private static string BuildExceptionsString(IEnumerable exceptions) "\t" + Formatter.ToString(exception))); } - private static class ExceptionMessageAssertion + private void AssertExceptionMessage(IEnumerable messages, string expectation, string because, params object[] becauseArgs) { - private const string Context = "exception message"; + var results = new AssertionResultSet(); - public static void Execute(IEnumerable messages, string expectation, [StringSyntax("CompositeFormat")] string because, params object[] becauseArgs) + foreach (string message in messages) { - using var _ = new AssertionScope(); - var results = new AssertionResultSet(); - - foreach (string message in messages) + using (var scope = new AssertionScope()) { - using (var scope = new AssertionScope()) - { - scope.Context = new Lazy(() => Context); + var chain = AssertionChain.GetOrCreate(); + chain.OverrideCallerIdentifier(() => "exception message"); + chain.ReuseOnce(); - message.Should().MatchEquivalentOf(expectation, because, becauseArgs); + message.Should().MatchEquivalentOf(expectation, because, becauseArgs); - results.AddSet(message, scope.Discard()); - } - - if (results.ContainsSuccessfulSet()) - { - break; - } + results.AddSet(message, scope.Discard()); } - foreach (string failure in results.GetTheFailuresForTheSetWithTheFewestFailures()) + if (results.ContainsSuccessfulSet()) { - string replacedCurlyBraces = - failure.EscapePlaceholders(); - - AssertionScope.Current.FailWith(replacedCurlyBraces); + break; } } + + foreach (string failure in results.GetTheFailuresForTheSetWithTheFewestFailures()) + { + string replacedCurlyBraces = + failure.EscapePlaceholders(); + + assertionChain.FailWith(replacedCurlyBraces); + } } } diff --git a/Src/FluentAssertions/Specialized/ExecutionTimeAssertions.cs b/Src/FluentAssertions/Specialized/ExecutionTimeAssertions.cs index 1c63fcc35a..0dfaf1446d 100644 --- a/Src/FluentAssertions/Specialized/ExecutionTimeAssertions.cs +++ b/Src/FluentAssertions/Specialized/ExecutionTimeAssertions.cs @@ -13,14 +13,16 @@ namespace FluentAssertions.Specialized; public class ExecutionTimeAssertions { private readonly ExecutionTime execution; + private readonly AssertionChain assertionChain; /// /// Initializes a new instance of the class. /// /// The execution on which time must be asserted. - public ExecutionTimeAssertions(ExecutionTime executionTime) + public ExecutionTimeAssertions(ExecutionTime executionTime, AssertionChain assertionChain) { execution = executionTime ?? throw new ArgumentNullException(nameof(executionTime)); + this.assertionChain = assertionChain; } /// @@ -74,7 +76,7 @@ public AndConstraint BeLessThanOrEqualTo(TimeSpan maxDu { (bool isRunning, TimeSpan elapsed) = PollUntil(duration => duration <= maxDuration, expectedResult: false, rate: maxDuration); - Execute.Assertion + assertionChain .ForCondition(elapsed <= maxDuration) .BecauseOf(because, becauseArgs) .FailWith("Execution of " + @@ -105,7 +107,7 @@ public AndConstraint BeLessThan(TimeSpan maxDuration, { (bool isRunning, TimeSpan elapsed) = PollUntil(duration => duration < maxDuration, expectedResult: false, rate: maxDuration); - Execute.Assertion + assertionChain .ForCondition(elapsed < maxDuration) .BecauseOf(because, becauseArgs) .FailWith("Execution of " + @@ -135,7 +137,7 @@ public AndConstraint BeGreaterThanOrEqualTo(TimeSpan mi { (bool isRunning, TimeSpan elapsed) = PollUntil(duration => duration >= minDuration, expectedResult: true, rate: minDuration); - Execute.Assertion + assertionChain .ForCondition(elapsed >= minDuration) .BecauseOf(because, becauseArgs) .FailWith("Execution of " + @@ -166,7 +168,7 @@ public AndConstraint BeGreaterThan(TimeSpan minDuration { (bool isRunning, TimeSpan elapsed) = PollUntil(duration => duration > minDuration, expectedResult: true, rate: minDuration); - Execute.Assertion + assertionChain .ForCondition(elapsed > minDuration) .BecauseOf(because, becauseArgs) .FailWith("Execution of " + @@ -208,7 +210,7 @@ public AndConstraint BeCloseTo(TimeSpan expectedDuratio // elapsed time didn't even get to the acceptable range (bool isRunning, TimeSpan elapsed) = PollUntil(duration => duration <= maximumValue, expectedResult: false, rate: maximumValue); - Execute.Assertion + assertionChain .ForCondition(elapsed >= minimumValue && elapsed <= maximumValue) .BecauseOf(because, becauseArgs) .FailWith("Execution of " + execution.ActionDescription.EscapePlaceholders() + diff --git a/Src/FluentAssertions/Specialized/FunctionAssertionHelpers.cs b/Src/FluentAssertions/Specialized/FunctionAssertionHelpers.cs deleted file mode 100644 index 28c4e0983f..0000000000 --- a/Src/FluentAssertions/Specialized/FunctionAssertionHelpers.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using FluentAssertions.Common; -using FluentAssertions.Execution; - -namespace FluentAssertions.Specialized; - -internal static class FunctionAssertionHelpers -{ - internal static T NotThrow(Func subject, [StringSyntax("CompositeFormat")] string because, object[] becauseArgs) - { - try - { - return subject(); - } - catch (Exception exception) - { - Execute.Assertion - .BecauseOf(because, becauseArgs) - .FailWith("Did not expect any exception{reason}, but found {0}.", exception); - - return default; - } - } - - internal static TResult NotThrowAfter(Func subject, IClock clock, TimeSpan waitTime, TimeSpan pollInterval, - [StringSyntax("CompositeFormat")] string because, object[] becauseArgs) - { - Guard.ThrowIfArgumentIsNegative(waitTime); - Guard.ThrowIfArgumentIsNegative(pollInterval); - - TimeSpan? invocationEndTime = null; - Exception exception = null; - ITimer timer = clock.StartTimer(); - - while (invocationEndTime is null || invocationEndTime < waitTime) - { - try - { - return subject(); - } - catch (Exception ex) - { - exception = ex; - } - - clock.Delay(pollInterval); - invocationEndTime = timer.Elapsed; - } - - Execute.Assertion - .BecauseOf(because, becauseArgs) - .FailWith("Did not expect any exceptions after {0}{reason}, but found {1}.", waitTime, exception); - - return default; - } -} diff --git a/Src/FluentAssertions/Specialized/FunctionAssertions.cs b/Src/FluentAssertions/Specialized/FunctionAssertions.cs index 011b961165..0219cab38f 100644 --- a/Src/FluentAssertions/Specialized/FunctionAssertions.cs +++ b/Src/FluentAssertions/Specialized/FunctionAssertions.cs @@ -12,14 +12,18 @@ namespace FluentAssertions.Specialized; [DebuggerNonUserCode] public class FunctionAssertions : DelegateAssertions, FunctionAssertions> { - public FunctionAssertions(Func subject, IExtractExceptions extractor) - : base(subject, extractor) + private readonly AssertionChain assertionChain; + + public FunctionAssertions(Func subject, IExtractExceptions extractor, AssertionChain assertionChain) + : base(subject, extractor, assertionChain) { + this.assertionChain = assertionChain; } - public FunctionAssertions(Func subject, IExtractExceptions extractor, IClock clock) - : base(subject, extractor, clock) + public FunctionAssertions(Func subject, IExtractExceptions extractor, AssertionChain assertionChain, IClock clock) + : base(subject, extractor, assertionChain, clock) { + this.assertionChain = assertionChain; } protected override void InvokeSubject() @@ -41,19 +45,30 @@ protected override void InvokeSubject() /// public AndWhichConstraint, T> NotThrow([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {context} not to throw{reason}, but found ."); T result = default; - if (success) + if (assertionChain.Succeeded) { - result = FunctionAssertionHelpers.NotThrow(Subject, because, becauseArgs); + try + { + result = Subject!(); + } + catch (Exception exception) + { + assertionChain + .BecauseOf(because, becauseArgs) + .FailWith("Did not expect any exception{reason}, but found {0}.", exception); + + result = default; + } } - return new AndWhichConstraint, T>(this, result); + return new AndWhichConstraint, T>(this, result, assertionChain, ".Result"); } /// @@ -82,18 +97,50 @@ public AndWhichConstraint, T> NotThrow([StringSyntax("Comp public AndWhichConstraint, T> NotThrowAfter(TimeSpan waitTime, TimeSpan pollInterval, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {context} not to throw any exceptions after {0}{reason}, but found .", waitTime); T result = default; - if (success) + if (assertionChain.Succeeded) { - result = FunctionAssertionHelpers.NotThrowAfter(Subject, Clock, waitTime, pollInterval, because, becauseArgs); + result = NotThrowAfter(Subject, Clock, waitTime, pollInterval, because, becauseArgs); } - return new AndWhichConstraint, T>(this, result); + return new AndWhichConstraint, T>(this, result, assertionChain, ".Result"); + } + + internal TResult NotThrowAfter(Func subject, IClock clock, TimeSpan waitTime, TimeSpan pollInterval, + string because, object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNegative(waitTime); + Guard.ThrowIfArgumentIsNegative(pollInterval); + + TimeSpan? invocationEndTime = null; + Exception exception = null; + ITimer timer = clock.StartTimer(); + + while (invocationEndTime is null || invocationEndTime < waitTime) + { + try + { + return subject(); + } + catch (Exception ex) + { + exception = ex; + } + + clock.Delay(pollInterval); + invocationEndTime = timer.Elapsed; + } + + assertionChain + .BecauseOf(because, becauseArgs) + .FailWith("Did not expect any exceptions after {0}{reason}, but found {1}.", waitTime, exception); + + return default; } } diff --git a/Src/FluentAssertions/Specialized/GenericAsyncFunctionAssertions.cs b/Src/FluentAssertions/Specialized/GenericAsyncFunctionAssertions.cs index 109d1918b3..ea78c3b77b 100644 --- a/Src/FluentAssertions/Specialized/GenericAsyncFunctionAssertions.cs +++ b/Src/FluentAssertions/Specialized/GenericAsyncFunctionAssertions.cs @@ -14,20 +14,25 @@ namespace FluentAssertions.Specialized; public class GenericAsyncFunctionAssertions : AsyncFunctionAssertions, GenericAsyncFunctionAssertions> { + private readonly AssertionChain assertionChain; + /// /// Initializes a new instance of the class. /// - public GenericAsyncFunctionAssertions(Func> subject, IExtractExceptions extractor) - : this(subject, extractor, new Clock()) + public GenericAsyncFunctionAssertions(Func> subject, IExtractExceptions extractor, AssertionChain assertionChain) + : this(subject, extractor, assertionChain, new Clock()) { + this.assertionChain = assertionChain; } /// /// Initializes a new instance of the class with custom . /// - public GenericAsyncFunctionAssertions(Func> subject, IExtractExceptions extractor, IClock clock) - : base(subject, extractor, clock) + public GenericAsyncFunctionAssertions(Func> subject, IExtractExceptions extractor, AssertionChain assertionChain, + IClock clock) + : base(subject, extractor, assertionChain, clock) { + this.assertionChain = assertionChain; } /// @@ -44,34 +49,34 @@ public GenericAsyncFunctionAssertions(Func> subject, IExtractExcep public async Task, TResult>> CompleteWithinAsync( TimeSpan timeSpan, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {context} to complete within {0}{reason}, but found .", timeSpan); - if (success) + if (assertionChain.Succeeded) { (Task task, TimeSpan remainingTime) = InvokeWithTimer(timeSpan); - success = Execute.Assertion + assertionChain .ForCondition(remainingTime >= TimeSpan.Zero) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:task} to complete within {0}{reason}.", timeSpan); - if (success) + if (assertionChain.Succeeded) { bool completesWithinTimeout = await CompletesWithinTimeoutAsync(task, remainingTime); - success = Execute.Assertion + assertionChain .ForCondition(completesWithinTimeout) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:task} to complete within {0}{reason}.", timeSpan); } #pragma warning disable CA1849 // Call async methods when in an async method - TResult result = success ? task.Result : default; + TResult result = assertionChain.Succeeded ? task.Result : default; #pragma warning restore CA1849 // Call async methods when in an async method - return new AndWhichConstraint, TResult>(this, result); + return new AndWhichConstraint, TResult>(this, result, assertionChain, ".Result"); } return new AndWhichConstraint, TResult>(this, default(TResult)); @@ -90,17 +95,17 @@ public async Task, TR public async Task, TResult>> NotThrowAsync( [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {context} not to throw{reason}, but found ."); - if (success) + if (assertionChain.Succeeded) { try { TResult result = await Subject!.Invoke(); - return new AndWhichConstraint, TResult>(this, result); + return new AndWhichConstraint, TResult>(this, result, assertionChain, ".Result"); } catch (Exception exception) { @@ -140,12 +145,12 @@ public Task, TResult> Guard.ThrowIfArgumentIsNegative(waitTime); Guard.ThrowIfArgumentIsNegative(pollInterval); - bool success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {context} not to throw any exceptions after {0}{reason}, but found .", waitTime); - if (success) + if (assertionChain.Succeeded) { return AssertionTaskAsync(); @@ -160,7 +165,7 @@ async Task, TResult>> try { TResult result = await Subject.Invoke(); - return new AndWhichConstraint, TResult>(this, result); + return new AndWhichConstraint, TResult>(this, result, assertionChain, ".Result"); } catch (Exception ex) { @@ -170,7 +175,7 @@ async Task, TResult>> } } - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Did not expect any exceptions after {0}{reason}, but found {1}.", waitTime, exception); diff --git a/Src/FluentAssertions/Specialized/NonGenericAsyncFunctionAssertions.cs b/Src/FluentAssertions/Specialized/NonGenericAsyncFunctionAssertions.cs index 91f2b85022..8c672ff0d2 100644 --- a/Src/FluentAssertions/Specialized/NonGenericAsyncFunctionAssertions.cs +++ b/Src/FluentAssertions/Specialized/NonGenericAsyncFunctionAssertions.cs @@ -12,20 +12,24 @@ namespace FluentAssertions.Specialized; /// public class NonGenericAsyncFunctionAssertions : AsyncFunctionAssertions { + private readonly AssertionChain assertionChain; + /// /// Initializes a new instance of the class. /// - public NonGenericAsyncFunctionAssertions(Func subject, IExtractExceptions extractor) - : this(subject, extractor, new Clock()) + public NonGenericAsyncFunctionAssertions(Func subject, IExtractExceptions extractor, AssertionChain assertionChain) + : this(subject, extractor, assertionChain, new Clock()) { + this.assertionChain = assertionChain; } /// /// Initializes a new instance of the class with custom . /// - public NonGenericAsyncFunctionAssertions(Func subject, IExtractExceptions extractor, IClock clock) - : base(subject, extractor, clock) + public NonGenericAsyncFunctionAssertions(Func subject, IExtractExceptions extractor, AssertionChain assertionChain, IClock clock) + : base(subject, extractor, assertionChain, clock) { + this.assertionChain = assertionChain; } /// @@ -42,25 +46,25 @@ public NonGenericAsyncFunctionAssertions(Func subject, IExtractExceptions public async Task> CompleteWithinAsync( TimeSpan timeSpan, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:task} to complete within {0}{reason}, but found .", timeSpan); - if (success) + if (assertionChain.Succeeded) { (Task task, TimeSpan remainingTime) = InvokeWithTimer(timeSpan); - success = Execute.Assertion + assertionChain .ForCondition(remainingTime >= TimeSpan.Zero) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:task} to complete within {0}{reason}.", timeSpan); - if (success) + if (assertionChain.Succeeded) { bool completesWithinTimeout = await CompletesWithinTimeoutAsync(task, remainingTime); - Execute.Assertion + assertionChain .ForCondition(completesWithinTimeout) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:task} to complete within {0}{reason}.", timeSpan); @@ -83,12 +87,12 @@ public async Task> CompleteWith public async Task> NotThrowAsync( [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {context} not to throw{reason}, but found ."); - if (success) + if (assertionChain.Succeeded) { try { @@ -132,12 +136,12 @@ public Task> NotThrowAfterAsync Guard.ThrowIfArgumentIsNegative(waitTime); Guard.ThrowIfArgumentIsNegative(pollInterval); - bool success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {context} not to throw any exceptions after {0}{reason}, but found .", waitTime); - if (success) + if (assertionChain.Succeeded) { return AssertionTaskAsync(); @@ -160,7 +164,7 @@ async Task> AssertionTaskAsync( invocationEndTime = timer.Elapsed; } - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Did not expect any exceptions after {0}{reason}, but found {1}.", waitTime, exception); diff --git a/Src/FluentAssertions/Specialized/TaskCompletionSourceAssertions.cs b/Src/FluentAssertions/Specialized/TaskCompletionSourceAssertions.cs index a79d9d5cf2..0081053767 100644 --- a/Src/FluentAssertions/Specialized/TaskCompletionSourceAssertions.cs +++ b/Src/FluentAssertions/Specialized/TaskCompletionSourceAssertions.cs @@ -12,17 +12,20 @@ namespace FluentAssertions.Specialized; #if NET6_0_OR_GREATER public class TaskCompletionSourceAssertions : TaskCompletionSourceAssertionsBase { + private readonly AssertionChain assertionChain; private readonly TaskCompletionSource subject; - public TaskCompletionSourceAssertions(TaskCompletionSource tcs) - : this(tcs, new Clock()) + public TaskCompletionSourceAssertions(TaskCompletionSource tcs, AssertionChain assertionChain) + : this(tcs, assertionChain, new Clock()) { + this.assertionChain = assertionChain; } - public TaskCompletionSourceAssertions(TaskCompletionSource tcs, IClock clock) + public TaskCompletionSourceAssertions(TaskCompletionSource tcs, AssertionChain assertionChain, IClock clock) : base(clock) { subject = tcs; + this.assertionChain = assertionChain; } /// @@ -39,15 +42,15 @@ public TaskCompletionSourceAssertions(TaskCompletionSource tcs, IClock clock) public async Task> CompleteWithinAsync( TimeSpan timeSpan, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - var success = Execute.Assertion + assertionChain .ForCondition(subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {context} to complete within {0}{reason}, but found .", timeSpan); - if (success) + if (assertionChain.Succeeded) { bool completesWithinTimeout = await CompletesWithinTimeoutAsync(subject!.Task, timeSpan); - Execute.Assertion + assertionChain .ForCondition(completesWithinTimeout) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:task} to complete within {0}{reason}.", timeSpan); @@ -70,15 +73,15 @@ public async Task> CompleteWithinA public async Task> NotCompleteWithinAsync( TimeSpan timeSpan, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - var success = Execute.Assertion + assertionChain .ForCondition(subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {context} to not complete within {0}{reason}, but found .", timeSpan); - if (success) + if (assertionChain.Succeeded) { bool completesWithinTimeout = await CompletesWithinTimeoutAsync(subject!.Task, timeSpan); - Execute.Assertion + assertionChain .ForCondition(!completesWithinTimeout) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:task} to not complete within {0}{reason}.", timeSpan); @@ -91,17 +94,20 @@ public async Task> NotCompleteWith public class TaskCompletionSourceAssertions : TaskCompletionSourceAssertionsBase { + private readonly AssertionChain assertionChain; private readonly TaskCompletionSource subject; - public TaskCompletionSourceAssertions(TaskCompletionSource tcs) - : this(tcs, new Clock()) + public TaskCompletionSourceAssertions(TaskCompletionSource tcs, AssertionChain assertionChain) + : this(tcs, assertionChain, new Clock()) { + this.assertionChain = assertionChain; } - public TaskCompletionSourceAssertions(TaskCompletionSource tcs, IClock clock) + public TaskCompletionSourceAssertions(TaskCompletionSource tcs, AssertionChain assertionChain, IClock clock) : base(clock) { subject = tcs; + this.assertionChain = assertionChain; } /// @@ -118,16 +124,16 @@ public TaskCompletionSourceAssertions(TaskCompletionSource tcs, IClock clock) public async Task, T>> CompleteWithinAsync( TimeSpan timeSpan, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - var success = Execute.Assertion + assertionChain .ForCondition(subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected {context} to complete within {0}{reason}, but found .", timeSpan); - if (success) + if (assertionChain.Succeeded) { bool completesWithinTimeout = await CompletesWithinTimeoutAsync(subject!.Task, timeSpan); - Execute.Assertion + assertionChain .ForCondition(completesWithinTimeout) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:task} to complete within {0}{reason}.", timeSpan); @@ -135,7 +141,7 @@ public async Task, T>> Comp #pragma warning disable CA1849 // Call async methods when in an async method T result = subject.Task.IsCompleted ? subject.Task.Result : default; #pragma warning restore CA1849 // Call async methods when in an async method - return new AndWhichConstraint, T>(this, result); + return new AndWhichConstraint, T>(this, result, assertionChain, ".Result"); } return new AndWhichConstraint, T>(this, default(T)); @@ -155,16 +161,16 @@ public async Task, T>> Comp public async Task>> NotCompleteWithinAsync( TimeSpan timeSpan, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - var success = Execute.Assertion + assertionChain .ForCondition(subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Did not expect {context} to complete within {0}{reason}, but found .", timeSpan); - if (success) + if (assertionChain.Succeeded) { bool completesWithinTimeout = await CompletesWithinTimeoutAsync(subject!.Task, timeSpan); - Execute.Assertion + assertionChain .ForCondition(!completesWithinTimeout) .BecauseOf(because, becauseArgs) .FailWith("Did not expect {context:task} to complete within {0}{reason}.", timeSpan); diff --git a/Src/FluentAssertions/Streams/BufferedStreamAssertions.cs b/Src/FluentAssertions/Streams/BufferedStreamAssertions.cs index 70ce652d07..1f4a9dd604 100644 --- a/Src/FluentAssertions/Streams/BufferedStreamAssertions.cs +++ b/Src/FluentAssertions/Streams/BufferedStreamAssertions.cs @@ -1,9 +1,7 @@ -using System.Diagnostics; +#if NET6_0_OR_GREATER || NETSTANDARD2_1 +using System.Diagnostics; using System.IO; -#if NET6_0_OR_GREATER || NETSTANDARD2_1 -using System.Diagnostics.CodeAnalysis; using FluentAssertions.Execution; -#endif namespace FluentAssertions.Streams; @@ -14,8 +12,8 @@ namespace FluentAssertions.Streams; [DebuggerNonUserCode] public class BufferedStreamAssertions : BufferedStreamAssertions { - public BufferedStreamAssertions(BufferedStream stream) - : base(stream) + public BufferedStreamAssertions(BufferedStream stream, AssertionChain assertionChain) + : base(stream, assertionChain) { } } @@ -23,14 +21,16 @@ public BufferedStreamAssertions(BufferedStream stream) public class BufferedStreamAssertions : StreamAssertions where TAssertions : BufferedStreamAssertions { - public BufferedStreamAssertions(BufferedStream stream) - : base(stream) + private readonly AssertionChain assertionChain; + + public BufferedStreamAssertions(BufferedStream stream, AssertionChain assertionChain) + : base(stream, assertionChain) { + this.assertionChain = assertionChain; } protected override string Identifier => "buffered stream"; -#if NET6_0_OR_GREATER || NETSTANDARD2_1 /// /// Asserts that the current has the buffer size. /// @@ -42,18 +42,17 @@ public BufferedStreamAssertions(BufferedStream stream) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveBufferSize(int expected, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint HaveBufferSize(int expected, string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected the buffer size of {context:stream} to be {0}{reason}, but found a reference.", expected); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject!.BufferSize == expected) .FailWith("Expected the buffer size of {context:stream} to be {0}{reason}, but it was {1}.", @@ -74,18 +73,17 @@ public AndConstraint HaveBufferSize(int expected, /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveBufferSize(int unexpected, - [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint NotHaveBufferSize(int unexpected, string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected the buffer size of {context:stream} not to be {0}{reason}, but found a reference.", unexpected); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject!.BufferSize != unexpected) .FailWith("Expected the buffer size of {context:stream} not to be {0}{reason}, but it was.", @@ -94,5 +92,5 @@ public AndConstraint NotHaveBufferSize(int unexpected, return new AndConstraint((TAssertions)this); } -#endif } +#endif diff --git a/Src/FluentAssertions/Streams/StreamAssertions.cs b/Src/FluentAssertions/Streams/StreamAssertions.cs index 7b61dd41f8..3116721541 100644 --- a/Src/FluentAssertions/Streams/StreamAssertions.cs +++ b/Src/FluentAssertions/Streams/StreamAssertions.cs @@ -13,8 +13,8 @@ namespace FluentAssertions.Streams; [DebuggerNonUserCode] public class StreamAssertions : StreamAssertions { - public StreamAssertions(Stream stream) - : base(stream) + public StreamAssertions(Stream stream, AssertionChain assertionChain) + : base(stream, assertionChain) { } } @@ -26,9 +26,12 @@ public class StreamAssertions : ReferenceTypeAssertions { - public StreamAssertions(TSubject stream) - : base(stream) + private readonly AssertionChain assertionChain; + + public StreamAssertions(TSubject stream, AssertionChain assertionChain) + : base(stream, assertionChain) { + this.assertionChain = assertionChain; } protected override string Identifier => "stream"; @@ -45,14 +48,14 @@ public StreamAssertions(TSubject stream) /// public AndConstraint BeWritable([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected {context:stream} to be writable{reason}, but found a reference."); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject!.CanWrite) .FailWith("Expected {context:stream} to be writable{reason}, but it was not."); @@ -73,14 +76,14 @@ public AndConstraint BeWritable([StringSyntax("CompositeFormat")] s /// public AndConstraint NotBeWritable([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected {context:stream} not to be writable{reason}, but found a reference."); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(!Subject!.CanWrite) .FailWith("Expected {context:stream} not to be writable{reason}, but it was."); @@ -101,14 +104,14 @@ public AndConstraint NotBeWritable([StringSyntax("CompositeFormat") /// public AndConstraint BeSeekable([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected {context:stream} to be seekable{reason}, but found a reference."); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject!.CanSeek) .FailWith("Expected {context:stream} to be seekable{reason}, but it was not."); @@ -129,14 +132,14 @@ public AndConstraint BeSeekable([StringSyntax("CompositeFormat")] s /// public AndConstraint NotBeSeekable([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected {context:stream} not to be seekable{reason}, but found a reference."); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(!Subject!.CanSeek) .FailWith("Expected {context:stream} not to be seekable{reason}, but it was."); @@ -157,14 +160,14 @@ public AndConstraint NotBeSeekable([StringSyntax("CompositeFormat") /// public AndConstraint BeReadable([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected {context:stream} to be readable{reason}, but found a reference."); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject!.CanRead) .FailWith("Expected {context:stream} to be readable{reason}, but it was not."); @@ -185,14 +188,14 @@ public AndConstraint BeReadable([StringSyntax("CompositeFormat")] s /// public AndConstraint NotBeReadable([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected {context:stream} not to be readable{reason}, but found a reference."); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(!Subject!.CanRead) .FailWith("Expected {context:stream} not to be readable{reason}, but it was."); @@ -215,13 +218,13 @@ public AndConstraint NotBeReadable([StringSyntax("CompositeFormat") public AndConstraint HavePosition(long expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected the position of {context:stream} to be {0}{reason}, but found a reference.", expected); - if (success) + if (assertionChain.Succeeded) { long position; @@ -232,7 +235,7 @@ public AndConstraint HavePosition(long expected, catch (Exception exception) when (exception is IOException or NotSupportedException or ObjectDisposedException) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Expected the position of {context:stream} to be {0}{reason}, but it failed with:" + Environment.NewLine + "{1}", @@ -241,7 +244,7 @@ public AndConstraint HavePosition(long expected, return new AndConstraint((TAssertions)this); } - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(position == expected) .FailWith("Expected the position of {context:stream} to be {0}{reason}, but it was {1}.", @@ -265,13 +268,13 @@ public AndConstraint HavePosition(long expected, public AndConstraint NotHavePosition(long unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected the position of {context:stream} not to be {0}{reason}, but found a reference.", unexpected); - if (success) + if (assertionChain.Succeeded) { long position; @@ -282,7 +285,7 @@ public AndConstraint NotHavePosition(long unexpected, catch (Exception exception) when (exception is IOException or NotSupportedException or ObjectDisposedException) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Expected the position of {context:stream} not to be {0}{reason}, but it failed with:" + Environment.NewLine + "{1}", @@ -291,7 +294,7 @@ public AndConstraint NotHavePosition(long unexpected, return new AndConstraint((TAssertions)this); } - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(position != unexpected) .FailWith("Expected the position of {context:stream} not to be {0}{reason}, but it was.", @@ -315,13 +318,13 @@ public AndConstraint NotHavePosition(long unexpected, public AndConstraint HaveLength(long expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected the length of {context:stream} to be {0}{reason}, but found a reference.", expected); - if (success) + if (assertionChain.Succeeded) { long length; @@ -332,7 +335,7 @@ public AndConstraint HaveLength(long expected, catch (Exception exception) when (exception is IOException or NotSupportedException or ObjectDisposedException) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Expected the length of {context:stream} to be {0}{reason}, but it failed with:" + Environment.NewLine + "{1}", @@ -341,7 +344,7 @@ public AndConstraint HaveLength(long expected, return new AndConstraint((TAssertions)this); } - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(length == expected) .FailWith("Expected the length of {context:stream} to be {0}{reason}, but it was {1}.", @@ -365,13 +368,13 @@ public AndConstraint HaveLength(long expected, public AndConstraint NotHaveLength(long unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected the length of {context:stream} not to be {0}{reason}, but found a reference.", unexpected); - if (success) + if (assertionChain.Succeeded) { long length; @@ -382,7 +385,7 @@ public AndConstraint NotHaveLength(long unexpected, catch (Exception exception) when (exception is IOException or NotSupportedException or ObjectDisposedException) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Expected the length of {context:stream} not to be {0}{reason}, but it failed with:" + Environment.NewLine + "{1}", @@ -391,7 +394,7 @@ public AndConstraint NotHaveLength(long unexpected, return new AndConstraint((TAssertions)this); } - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(length != unexpected) .FailWith("Expected the length of {context:stream} not to be {0}{reason}, but it was.", @@ -413,14 +416,14 @@ public AndConstraint NotHaveLength(long unexpected, /// public AndConstraint BeReadOnly([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected {context:stream} to be read-only{reason}, but found a reference."); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(!Subject!.CanWrite && Subject.CanRead) .FailWith("Expected {context:stream} to be read-only{reason}, but it was writable or not readable."); @@ -441,14 +444,14 @@ public AndConstraint BeReadOnly([StringSyntax("CompositeFormat")] s /// public AndConstraint NotBeReadOnly([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected {context:stream} not to be read-only{reason}, but found a reference."); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject!.CanWrite || !Subject.CanRead) .FailWith("Expected {context:stream} not to be read-only{reason}, but it was."); @@ -469,14 +472,14 @@ public AndConstraint NotBeReadOnly([StringSyntax("CompositeFormat") /// public AndConstraint BeWriteOnly([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected {context:stream} to be write-only{reason}, but found a reference."); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject!.CanWrite && !Subject.CanRead) .FailWith("Expected {context:stream} to be write-only{reason}, but it was readable or not writable."); @@ -497,14 +500,14 @@ public AndConstraint BeWriteOnly([StringSyntax("CompositeFormat")] /// public AndConstraint NotBeWriteOnly([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected {context:stream} not to be write-only{reason}, but found a reference."); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(!Subject!.CanWrite || Subject.CanRead) .FailWith("Expected {context:stream} not to be write-only{reason}, but it was."); diff --git a/Src/FluentAssertions/Types/AssemblyAssertions.cs b/Src/FluentAssertions/Types/AssemblyAssertions.cs index 91e170fa67..5ceac78772 100644 --- a/Src/FluentAssertions/Types/AssemblyAssertions.cs +++ b/Src/FluentAssertions/Types/AssemblyAssertions.cs @@ -14,12 +14,15 @@ namespace FluentAssertions.Types; /// public class AssemblyAssertions : ReferenceTypeAssertions { + private readonly AssertionChain assertionChain; + /// /// Initializes a new instance of the class. /// - public AssemblyAssertions(Assembly assembly) - : base(assembly) + public AssemblyAssertions(Assembly assembly, AssertionChain assertionChain) + : base(assembly, assertionChain) { + this.assertionChain = assertionChain; } /// @@ -41,19 +44,19 @@ public AndConstraint NotReference(Assembly assembly, var assemblyName = assembly.GetName().Name; - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected assembly not to reference assembly {0}{reason}, but {context:assembly} is .", assemblyName); - if (success) + if (assertionChain.Succeeded) { var subjectName = Subject!.GetName().Name; IEnumerable references = Subject.GetReferencedAssemblies().Select(x => x.Name); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(!references.Contains(assemblyName)) .FailWith("Expected assembly {0} not to reference assembly {1}{reason}.", subjectName, assemblyName); @@ -81,18 +84,18 @@ public AndConstraint Reference(Assembly assembly, var assemblyName = assembly.GetName().Name; - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected assembly to reference assembly {0}{reason}, but {context:assembly} is .", assemblyName); - if (success) + if (assertionChain.Succeeded) { var subjectName = Subject!.GetName().Name; IEnumerable references = Subject.GetReferencedAssemblies().Select(x => x.Name); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(references.Contains(assemblyName)) .FailWith("Expected assembly {0} to reference assembly {1}{reason}, but it does not.", subjectName, assemblyName); @@ -120,7 +123,7 @@ public AndWhichConstraint DefineType(string @namespace { Guard.ThrowIfArgumentIsNullOrEmpty(name); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected assembly to define type {0}.{1}{reason}, but {context:assembly} is .", @@ -128,11 +131,11 @@ public AndWhichConstraint DefineType(string @namespace Type foundType = null; - if (success) + if (assertionChain.Succeeded) { foundType = Subject!.GetTypes().SingleOrDefault(t => t.Namespace == @namespace && t.Name == name); - Execute.Assertion + assertionChain .ForCondition(foundType is not null) .BecauseOf(because, becauseArgs) .FailWith("Expected assembly {0} to define type {1}.{2}{reason}, but it does not.", @@ -150,15 +153,16 @@ public AndWhichConstraint DefineType(string @namespace /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeUnsigned([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint BeUnsigned([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .FailWith("Can't check for assembly signing if {context:assembly} reference is ."); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject!.GetName().GetPublicKey() is not { Length: > 0 }) .FailWith( @@ -181,29 +185,28 @@ public AndConstraint BeUnsigned([StringSyntax("CompositeForm /// /// is . /// is empty. - public AndConstraint BeSignedWithPublicKey(string publicKey, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint BeSignedWithPublicKey(string publicKey, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNullOrEmpty(publicKey); - bool success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .FailWith("Can't check for assembly signing if {context:assembly} reference is ."); - if (success) + if (assertionChain.Succeeded) { var bytes = Subject!.GetName().GetPublicKey() ?? []; string assemblyKey = ToHexString(bytes); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected assembly {0} to have public key {1} ", Subject.FullName, publicKey) - .ForCondition(bytes.Length != 0) - .FailWith("{reason}, but it is unsigned.") - .Then - .ForCondition(string.Equals(assemblyKey, publicKey, StringComparison.OrdinalIgnoreCase)) - .FailWith("{reason}, but it has {0} instead.", assemblyKey) - .Then - .ClearExpectation(); + .WithExpectation("Expected assembly {0} to have public key {1} ", Subject.FullName, publicKey, chain => chain + .ForCondition(bytes.Length != 0) + .FailWith("{reason}, but it is unsigned.") + .Then + .ForCondition(string.Equals(assemblyKey, publicKey, StringComparison.OrdinalIgnoreCase)) + .FailWith("{reason}, but it has {0} instead.", assemblyKey)); } return new AndConstraint(this); diff --git a/Src/FluentAssertions/Types/ConstructorInfoAssertions.cs b/Src/FluentAssertions/Types/ConstructorInfoAssertions.cs index cb66005a01..d3b1a3353e 100644 --- a/Src/FluentAssertions/Types/ConstructorInfoAssertions.cs +++ b/Src/FluentAssertions/Types/ConstructorInfoAssertions.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Reflection; +using FluentAssertions.Execution; namespace FluentAssertions.Types; @@ -13,8 +14,9 @@ public class ConstructorInfoAssertions : MethodBaseAssertions class. /// /// The constructorInfo from which to select properties. - public ConstructorInfoAssertions(ConstructorInfo constructorInfo) - : base(constructorInfo) + /// + public ConstructorInfoAssertions(ConstructorInfo constructorInfo, AssertionChain assertionChain) + : base(constructorInfo, assertionChain) { } @@ -23,7 +25,7 @@ internal static string GetDescriptionFor(ConstructorInfo constructorInfo) return $"{constructorInfo.DeclaringType}({GetParameterString(constructorInfo)})"; } - internal override string SubjectDescription => GetDescriptionFor(Subject); + protected override string SubjectDescription => GetDescriptionFor(Subject); protected override string Identifier => "constructor"; } diff --git a/Src/FluentAssertions/Types/MemberInfoAssertions.cs b/Src/FluentAssertions/Types/MemberInfoAssertions.cs index 082e9de153..7f447ecbd8 100644 --- a/Src/FluentAssertions/Types/MemberInfoAssertions.cs +++ b/Src/FluentAssertions/Types/MemberInfoAssertions.cs @@ -19,9 +19,12 @@ public abstract class MemberInfoAssertions : ReferenceTyp where TSubject : MemberInfo where TAssertions : MemberInfoAssertions { - protected MemberInfoAssertions(TSubject subject) - : base(subject) + private readonly AssertionChain assertionChain; + + protected MemberInfoAssertions(TSubject subject, AssertionChain assertionChain) + : base(subject, assertionChain) { + this.assertionChain = assertionChain; } /// @@ -80,7 +83,7 @@ public AndWhichConstraint, TAttribut { Guard.ThrowIfArgumentIsNull(isMatchingAttributePredicate); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith( @@ -89,11 +92,11 @@ public AndWhichConstraint, TAttribut IEnumerable attributes = []; - if (success) + if (assertionChain.Succeeded) { attributes = Subject.GetMatchingAttributes(isMatchingAttributePredicate); - Execute.Assertion + assertionChain .ForCondition(attributes.Any()) .BecauseOf(because, becauseArgs) .FailWith( @@ -126,18 +129,18 @@ public AndConstraint NotBeDecoratedWith( { Guard.ThrowIfArgumentIsNull(isMatchingAttributePredicate); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith( $"Expected {Identifier} to not be decorated with {typeof(TAttribute)}{{reason}}" + ", but {context:member} is ."); - if (success) + if (assertionChain.Succeeded) { IEnumerable attributes = Subject.GetMatchingAttributes(isMatchingAttributePredicate); - Execute.Assertion + assertionChain .ForCondition(!attributes.Any()) .BecauseOf(because, becauseArgs) .FailWith( @@ -150,5 +153,5 @@ public AndConstraint NotBeDecoratedWith( protected override string Identifier => "member"; - internal virtual string SubjectDescription => $"{Subject.DeclaringType}.{Subject.Name}"; + protected virtual string SubjectDescription => $"{Subject.DeclaringType}.{Subject.Name}"; } diff --git a/Src/FluentAssertions/Types/MethodBaseAssertions.cs b/Src/FluentAssertions/Types/MethodBaseAssertions.cs index c45591c3c6..3b7c995b5d 100644 --- a/Src/FluentAssertions/Types/MethodBaseAssertions.cs +++ b/Src/FluentAssertions/Types/MethodBaseAssertions.cs @@ -17,9 +17,12 @@ public abstract class MethodBaseAssertions : MemberInfoAs where TSubject : MethodBase where TAssertions : MethodBaseAssertions { - protected MethodBaseAssertions(TSubject subject) - : base(subject) + private readonly AssertionChain assertionChain; + + protected MethodBaseAssertions(TSubject subject, AssertionChain assertionChain) + : base(subject, assertionChain) { + this.assertionChain = assertionChain; } /// @@ -41,20 +44,23 @@ public AndConstraint HaveAccessModifier( { Guard.ThrowIfArgumentIsOutOfRange(accessModifier); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) - .FailWith($"Expected method to be {accessModifier}{{reason}}, but {{context:member}} is ."); + .FailWith($"Expected method to be {accessModifier}{{reason}}, but {{context:method}} is ."); - if (success) + if (assertionChain.Succeeded) { CSharpAccessModifier subjectAccessModifier = Subject.GetCSharpAccessModifier(); - Execute.Assertion + var subject = assertionChain.HasOverriddenCallerIdentifier + ? assertionChain.CallerIdentifier + : "method " + Subject.ToFormattedString(); + + assertionChain .ForCondition(accessModifier == subjectAccessModifier) .BecauseOf(because, becauseArgs) - .FailWith( - $"Expected method {Subject!.Name} to be {accessModifier}{{reason}}, but it is {subjectAccessModifier}."); + .FailWith($"Expected {subject} to be {accessModifier}{{reason}}, but it is {subjectAccessModifier}."); } return new AndConstraint((TAssertions)this); @@ -78,19 +84,23 @@ public AndConstraint NotHaveAccessModifier(CSharpAccessModifier acc { Guard.ThrowIfArgumentIsOutOfRange(accessModifier); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith($"Expected method not to be {accessModifier}{{reason}}, but {{context:member}} is ."); - if (success) + if (assertionChain.Succeeded) { CSharpAccessModifier subjectAccessModifier = Subject.GetCSharpAccessModifier(); - Execute.Assertion + var subject = assertionChain.HasOverriddenCallerIdentifier + ? assertionChain.CallerIdentifier + : "method " + Subject.ToFormattedString(); + + assertionChain .ForCondition(accessModifier != subjectAccessModifier) .BecauseOf(because, becauseArgs) - .FailWith($"Expected method {Subject!.Name} not to be {accessModifier}{{reason}}, but it is."); + .FailWith($"Expected {subject} not to be {accessModifier}{{reason}}, but it is."); } return new AndConstraint((TAssertions)this); diff --git a/Src/FluentAssertions/Types/MethodInfoAssertions.cs b/Src/FluentAssertions/Types/MethodInfoAssertions.cs index c4dff054eb..9aa4beef1e 100644 --- a/Src/FluentAssertions/Types/MethodInfoAssertions.cs +++ b/Src/FluentAssertions/Types/MethodInfoAssertions.cs @@ -13,9 +13,12 @@ namespace FluentAssertions.Types; [DebuggerNonUserCode] public class MethodInfoAssertions : MethodBaseAssertions { - public MethodInfoAssertions(MethodInfo methodInfo) - : base(methodInfo) + private readonly AssertionChain assertionChain; + + public MethodInfoAssertions(MethodInfo methodInfo, AssertionChain assertionChain) + : base(methodInfo, assertionChain) { + this.assertionChain = assertionChain; } /// @@ -30,14 +33,14 @@ public MethodInfoAssertions(MethodInfo methodInfo) /// public AndConstraint BeVirtual([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected method to be virtual{reason}, but {context:member} is ."); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .ForCondition(!Subject.IsNonVirtual()) .BecauseOf(because, becauseArgs) .FailWith("Expected method " + SubjectDescription + " to be virtual{reason}, but it is not virtual."); @@ -58,14 +61,14 @@ public AndConstraint BeVirtual([StringSyntax("CompositeFor /// public AndConstraint NotBeVirtual([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected method not to be virtual{reason}, but {context:member} is ."); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .ForCondition(Subject.IsNonVirtual()) .BecauseOf(because, becauseArgs) .FailWith("Expected method " + SubjectDescription + " not to be virtual{reason}, but it is."); @@ -86,14 +89,14 @@ public AndConstraint NotBeVirtual([StringSyntax("Composite /// public AndConstraint BeAsync([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected method to be async{reason}, but {context:member} is ."); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .ForCondition(Subject.IsAsync()) .BecauseOf(because, becauseArgs) .FailWith("Expected method " + SubjectDescription + " to be async{reason}, but it is not."); @@ -114,14 +117,14 @@ public AndConstraint BeAsync([StringSyntax("CompositeForma /// public AndConstraint NotBeAsync([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected method not to be async{reason}, but {context:member} is ."); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .ForCondition(!Subject.IsAsync()) .BecauseOf(because, becauseArgs) .FailWith("Expected method " + SubjectDescription + " not to be async{reason}, but it is."); @@ -143,14 +146,14 @@ public AndConstraint NotBeAsync([StringSyntax("CompositeFo public AndConstraint> ReturnVoid( [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected the return type of method to be void{reason}, but {context:member} is ."); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .ForCondition(typeof(void) == Subject!.ReturnType) .BecauseOf(because, becauseArgs) .FailWith("Expected the return type of method " + Subject.Name + " to be void{reason}, but it is {0}.", @@ -177,14 +180,14 @@ public AndConstraint> Ret { Guard.ThrowIfArgumentIsNull(returnType); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected the return type of method to be {0}{reason}, but {context:member} is .", returnType); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .ForCondition(returnType == Subject!.ReturnType) .BecauseOf(because, becauseArgs) .FailWith("Expected the return type of method " + Subject.Name + " to be {0}{reason}, but it is {1}.", @@ -224,14 +227,14 @@ public AndConstraint> Ret public AndConstraint> NotReturnVoid( [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected the return type of method not to be void{reason}, but {context:member} is ."); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .ForCondition(typeof(void) != Subject!.ReturnType) .BecauseOf(because, becauseArgs) .FailWith("Expected the return type of method " + Subject.Name + " not to be void{reason}, but it is."); @@ -257,15 +260,15 @@ public AndConstraint> Not { Guard.ThrowIfArgumentIsNull(returnType); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith( "Expected the return type of method not to be {0}{reason}, but {context:member} is .", returnType); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .ForCondition(returnType != Subject!.ReturnType) .BecauseOf(because, becauseArgs) .FailWith( @@ -304,7 +307,7 @@ internal static string GetDescriptionFor(MethodInfo method) return $"{returnTypeName} {method.DeclaringType}.{method.Name}"; } - internal override string SubjectDescription => GetDescriptionFor(Subject); + protected override string SubjectDescription => GetDescriptionFor(Subject); protected override string Identifier => "method"; } diff --git a/Src/FluentAssertions/Types/MethodInfoSelectorAssertions.cs b/Src/FluentAssertions/Types/MethodInfoSelectorAssertions.cs index dfc2effa49..71b3c51213 100644 --- a/Src/FluentAssertions/Types/MethodInfoSelectorAssertions.cs +++ b/Src/FluentAssertions/Types/MethodInfoSelectorAssertions.cs @@ -18,13 +18,16 @@ namespace FluentAssertions.Types; [DebuggerNonUserCode] public class MethodInfoSelectorAssertions { + private readonly AssertionChain assertionChain; + /// /// Initializes a new instance of the class. /// /// The methods to assert. /// is . - public MethodInfoSelectorAssertions(params MethodInfo[] methods) + public MethodInfoSelectorAssertions(AssertionChain assertionChain, params MethodInfo[] methods) { + this.assertionChain = assertionChain; Guard.ThrowIfArgumentIsNull(methods); SubjectMethods = methods; @@ -54,7 +57,7 @@ public AndConstraint BeVirtual([StringSyntax("Comp Environment.NewLine + GetDescriptionsFor(nonVirtualMethods); - Execute.Assertion + assertionChain .ForCondition(nonVirtualMethods.Length == 0) .BecauseOf(because, becauseArgs) .FailWith(failureMessage); @@ -81,7 +84,7 @@ public AndConstraint NotBeVirtual([StringSyntax("C Environment.NewLine + GetDescriptionsFor(virtualMethods); - Execute.Assertion + assertionChain .ForCondition(virtualMethods.Length == 0) .BecauseOf(because, becauseArgs) .FailWith(failureMessage); @@ -118,7 +121,7 @@ public AndConstraint BeAsync([StringSyntax("Compos Environment.NewLine + GetDescriptionsFor(nonAsyncMethods); - Execute.Assertion + assertionChain .ForCondition(nonAsyncMethods.Length == 0) .BecauseOf(because, becauseArgs) .FailWith(failureMessage); @@ -145,7 +148,7 @@ public AndConstraint NotBeAsync([StringSyntax("Com Environment.NewLine + GetDescriptionsFor(asyncMethods); - Execute.Assertion + assertionChain .ForCondition(asyncMethods.Length == 0) .BecauseOf(because, becauseArgs) .FailWith(failureMessage); @@ -199,7 +202,7 @@ public AndConstraint BeDecoratedWith( Environment.NewLine + GetDescriptionsFor(methodsWithoutAttribute); - Execute.Assertion + assertionChain .ForCondition(methodsWithoutAttribute.Length == 0) .BecauseOf(because, becauseArgs) .FailWith(failureMessage, typeof(TAttribute)); @@ -253,7 +256,7 @@ public AndConstraint NotBeDecoratedWith Be(CSharpAccessModifier acces var message = $"Expected all selected methods to be {accessModifier}{{reason}}, but the following methods are not:" + Environment.NewLine + GetDescriptionsFor(methods); - Execute.Assertion + assertionChain .ForCondition(methods.Length == 0) .BecauseOf(because, becauseArgs) .FailWith(message); @@ -307,7 +310,7 @@ public AndConstraint NotBe(CSharpAccessModifier ac var message = $"Expected all selected methods to not be {accessModifier}{{reason}}, but the following methods are:" + Environment.NewLine + GetDescriptionsFor(methods); - Execute.Assertion + assertionChain .ForCondition(methods.Length == 0) .BecauseOf(because, becauseArgs) .FailWith(message); diff --git a/Src/FluentAssertions/Types/PropertyInfoAssertions.cs b/Src/FluentAssertions/Types/PropertyInfoAssertions.cs index 0201ccde6b..713b00468b 100644 --- a/Src/FluentAssertions/Types/PropertyInfoAssertions.cs +++ b/Src/FluentAssertions/Types/PropertyInfoAssertions.cs @@ -4,6 +4,7 @@ using System.Reflection; using FluentAssertions.Common; using FluentAssertions.Execution; +using FluentAssertions.Formatting; namespace FluentAssertions.Types; @@ -13,9 +14,12 @@ namespace FluentAssertions.Types; [DebuggerNonUserCode] public class PropertyInfoAssertions : MemberInfoAssertions { - public PropertyInfoAssertions(PropertyInfo propertyInfo) - : base(propertyInfo) + private readonly AssertionChain assertionChain; + + public PropertyInfoAssertions(PropertyInfo propertyInfo, AssertionChain assertionChain) + : base(propertyInfo, assertionChain) { + this.assertionChain = assertionChain; } /// @@ -31,18 +35,18 @@ public PropertyInfoAssertions(PropertyInfo propertyInfo) public AndConstraint BeVirtual( [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + var subjectDescription = assertionChain.HasOverriddenCallerIdentifier + ? assertionChain.CallerIdentifier + : "property " + Subject.ToFormattedString(); + + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) - .FailWith("Expected property to be virtual{reason}, but {context:property} is ."); - - if (success) - { - Execute.Assertion - .ForCondition(Subject.IsVirtual()) - .BecauseOf(because, becauseArgs) - .FailWith($"Expected property {GetDescriptionFor(Subject)} to be virtual{{reason}}, but it is not."); - } + .FailWith("Expected property to be virtual{reason}, but {context:property} is .") + .Then + .ForCondition(Subject.IsVirtual()) + .BecauseOf(because, becauseArgs) + .FailWith($"Expected {subjectDescription} to be virtual{{reason}}, but it is not."); return new AndConstraint(this); } @@ -57,20 +61,21 @@ public AndConstraint BeVirtual( /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeVirtual([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint NotBeVirtual([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - bool success = Execute.Assertion + var subjectDescription = assertionChain.HasOverriddenCallerIdentifier + ? assertionChain.CallerIdentifier + : "property " + Subject.ToFormattedString(); + + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) - .FailWith("Expected property not to be virtual{reason}, but {context:property} is ."); - - if (success) - { - Execute.Assertion - .ForCondition(!Subject.IsVirtual()) - .BecauseOf(because, becauseArgs) - .FailWith($"Expected property {GetDescriptionFor(Subject)} not to be virtual{{reason}}, but it is."); - } + .FailWith("Expected property not to be virtual{reason}, but {context:property} is .") + .Then + .ForCondition(!Subject.IsVirtual()) + .BecauseOf(because, becauseArgs) + .FailWith($"Expected property {subjectDescription} not to be virtual{{reason}}, but it is."); return new AndConstraint(this); } @@ -88,20 +93,20 @@ public AndConstraint NotBeVirtual([StringSyntax("Composi public AndConstraint BeWritable( [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + var subjectDescription = assertionChain.HasOverriddenCallerIdentifier + ? assertionChain.CallerIdentifier + : "property " + Subject.ToFormattedString(); + + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) - .FailWith("Expected property to have a setter{reason}, but {context:property} is ."); - - if (success) - { - Execute.Assertion - .ForCondition(Subject!.CanWrite) - .BecauseOf(because, becauseArgs) - .FailWith( - "Expected {context:property} {0} to have a setter{reason}.", - Subject); - } + .FailWith("Expected property to have a setter{reason}, but {context:property} is .") + .Then + .ForCondition(Subject!.CanWrite) + .BecauseOf(because, becauseArgs) + .FailWith( + $"Expected {subjectDescription} to have a setter{{reason}}.", + Subject); return new AndConstraint(this); } @@ -124,24 +129,25 @@ public AndConstraint BeWritable(CSharpAccessModifier acc { Guard.ThrowIfArgumentIsOutOfRange(accessModifier); - bool success = Execute.Assertion + var subjectDescription = assertionChain.HasOverriddenCallerIdentifier + ? assertionChain.CallerIdentifier + : "property " + Subject.ToFormattedString(); + + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) - .FailWith($"Expected {Identifier} to be {accessModifier}{{reason}}, but {{context:property}} is ."); + .FailWith($"Expected {{context:project}} to be {accessModifier}{{reason}}, but it is .") + .Then + .ForCondition(Subject!.CanWrite) + .BecauseOf(because, becauseArgs) + .FailWith($"Expected {subjectDescription} to have a setter{{reason}}."); - if (success) + if (assertionChain.Succeeded) { - success = Execute.Assertion - .ForCondition(Subject!.CanWrite) - .BecauseOf(because, becauseArgs) - .FailWith( - "Expected {context:property} {0} to have a setter{reason}.", - Subject); + assertionChain.OverrideCallerIdentifier(() => "setter of " + subjectDescription); + assertionChain.ReuseOnce(); - if (success) - { - Subject!.GetSetMethod(nonPublic: true).Should().HaveAccessModifier(accessModifier, because, becauseArgs); - } + Subject!.GetSetMethod(nonPublic: true).Should().HaveAccessModifier(accessModifier, because, becauseArgs); } return new AndConstraint(this); @@ -160,20 +166,18 @@ public AndConstraint BeWritable(CSharpAccessModifier acc public AndConstraint NotBeWritable( [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + var subjectDescription = assertionChain.HasOverriddenCallerIdentifier + ? assertionChain.CallerIdentifier + : "property " + Subject.ToFormattedString(); + + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) - .FailWith("Expected property not to have a setter{reason}, but {context:property} is ."); - - if (success) - { - Execute.Assertion - .ForCondition(!Subject!.CanWrite) - .BecauseOf(because, becauseArgs) - .FailWith( - "Expected {context:property} {0} not to have a setter{reason}.", - Subject); - } + .FailWith("Expected {context:property} not to have a setter{reason}, but it is .") + .Then + .ForCondition(!Subject!.CanWrite) + .BecauseOf(because, becauseArgs) + .FailWith($"Did not expect {subjectDescription} to have a setter{{reason}}."); return new AndConstraint(this); } @@ -188,18 +192,23 @@ public AndConstraint NotBeWritable( /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeReadable([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint BeReadable([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected property to have a getter{reason}, but {context:property} is ."); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion.ForCondition(Subject!.CanRead) + var subjectDescription = assertionChain.HasOverriddenCallerIdentifier + ? assertionChain.CallerIdentifier + : "property " + Subject.ToFormattedString(); + + assertionChain.ForCondition(Subject!.CanRead) .BecauseOf(because, becauseArgs) - .FailWith("Expected property " + Subject.Name + " to have a getter{reason}, but it does not."); + .FailWith($"Expected property {subjectDescription} to have a getter{{reason}}, but it does not."); } return new AndConstraint(this); @@ -223,21 +232,25 @@ public AndConstraint BeReadable(CSharpAccessModifier acc { Guard.ThrowIfArgumentIsOutOfRange(accessModifier); - bool success = Execute.Assertion + var subjectDescription = assertionChain.HasOverriddenCallerIdentifier + ? assertionChain.CallerIdentifier + : "property " + Subject.ToFormattedString(); + + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) - .FailWith($"Expected {Identifier} to be {accessModifier}{{reason}}, but {{context:property}} is ."); + .FailWith($"Expected {{context:property}} to be {accessModifier}{{reason}}, but it is .") + .Then + .ForCondition(Subject!.CanRead) + .BecauseOf(because, becauseArgs) + .FailWith($"Expected {subjectDescription} to have a getter{{reason}}, but it does not."); - if (success) + if (assertionChain.Succeeded) { - success = Execute.Assertion.ForCondition(Subject!.CanRead) - .BecauseOf(because, becauseArgs) - .FailWith("Expected property " + Subject.Name + " to have a getter{reason}, but it does not."); + assertionChain.OverrideCallerIdentifier(() => "getter of " + subjectDescription); + assertionChain.ReuseOnce(); - if (success) - { - Subject!.GetGetMethod(nonPublic: true).Should().HaveAccessModifier(accessModifier, because, becauseArgs); - } + Subject!.GetGetMethod(nonPublic: true).Should().HaveAccessModifier(accessModifier, because, becauseArgs); } return new AndConstraint(this); @@ -256,19 +269,22 @@ public AndConstraint BeReadable(CSharpAccessModifier acc public AndConstraint NotBeReadable( [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected property not to have a getter{reason}, but {context:property} is ."); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + var subjectDescription = assertionChain.HasOverriddenCallerIdentifier + ? assertionChain.CallerIdentifier + : "property " + Subject.ToFormattedString(); + + assertionChain .ForCondition(!Subject!.CanRead) .BecauseOf(because, becauseArgs) .FailWith( - "Expected {context:property} {0} not to have a getter{reason}.", - Subject); + $"Did not expect {subjectDescription} to have a getter{{reason}}.", Subject); } return new AndConstraint(this); @@ -291,17 +307,17 @@ public AndConstraint Return(Type propertyType, { Guard.ThrowIfArgumentIsNull(propertyType); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected type of property to be {0}{reason}, but {context:property} is .", propertyType); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion.ForCondition(Subject!.PropertyType == propertyType) + assertionChain.ForCondition(Subject!.PropertyType == propertyType) .BecauseOf(because, becauseArgs) - .FailWith("Expected Type of property " + Subject.Name + " to be {0}{reason}, but it is {1}.", - propertyType, Subject.PropertyType); + .FailWith("Expected type of property {2} to be {0}{reason}, but it is {1}.", + propertyType, Subject.PropertyType, Subject); } return new AndConstraint(this); @@ -318,7 +334,8 @@ public AndConstraint Return(Type propertyType, /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint Return([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint Return([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { return Return(typeof(TReturn), because, becauseArgs); } @@ -340,17 +357,17 @@ public AndConstraint NotReturn(Type propertyType, { Guard.ThrowIfArgumentIsNull(propertyType); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected type of property not to be {0}{reason}, but {context:property} is .", propertyType); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .ForCondition(Subject!.PropertyType != propertyType) .BecauseOf(because, becauseArgs) - .FailWith("Expected Type of property " + Subject.Name + " not to be {0}{reason}, but it is.", propertyType); + .FailWith("Expected type of property {1} not to be {0}{reason}, but it is.", propertyType, Subject); } return new AndConstraint(this); @@ -367,24 +384,13 @@ public AndConstraint NotReturn(Type propertyType, /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotReturn([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint NotReturn([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { return NotReturn(typeof(TReturn), because, becauseArgs); } - internal static string GetDescriptionFor(PropertyInfo property) - { - if (property is null) - { - return string.Empty; - } - - var propTypeName = property.PropertyType.Name; - - return $"{propTypeName} {property.DeclaringType}.{property.Name}"; - } - - internal override string SubjectDescription => GetDescriptionFor(Subject); + protected override string SubjectDescription => Formatter.ToString(Subject); /// /// Returns the type of the subject the assertion applies on. diff --git a/Src/FluentAssertions/Types/PropertyInfoSelectorAssertions.cs b/Src/FluentAssertions/Types/PropertyInfoSelectorAssertions.cs index 6605f823e9..4aca174681 100644 --- a/Src/FluentAssertions/Types/PropertyInfoSelectorAssertions.cs +++ b/Src/FluentAssertions/Types/PropertyInfoSelectorAssertions.cs @@ -6,6 +6,7 @@ using System.Reflection; using FluentAssertions.Common; using FluentAssertions.Execution; +using FluentAssertions.Formatting; namespace FluentAssertions.Types; @@ -17,6 +18,8 @@ namespace FluentAssertions.Types; [DebuggerNonUserCode] public class PropertyInfoSelectorAssertions { + private readonly AssertionChain assertionChain; + /// /// Gets the object whose value is being asserted. /// @@ -27,8 +30,9 @@ public class PropertyInfoSelectorAssertions /// /// The properties to assert. /// is . - public PropertyInfoSelectorAssertions(params PropertyInfo[] properties) + public PropertyInfoSelectorAssertions(AssertionChain assertionChain, params PropertyInfo[] properties) { + this.assertionChain = assertionChain; Guard.ThrowIfArgumentIsNull(properties); SubjectProperties = properties; @@ -48,7 +52,7 @@ public AndConstraint BeVirtual([StringSyntax("Co { PropertyInfo[] nonVirtualProperties = GetAllNonVirtualPropertiesFromSelection(); - Execute.Assertion + assertionChain .ForCondition(nonVirtualProperties.Length == 0) .BecauseOf(because, becauseArgs) .FailWith( @@ -72,7 +76,7 @@ public AndConstraint NotBeVirtual([StringSyntax( { PropertyInfo[] virtualProperties = GetAllVirtualPropertiesFromSelection(); - Execute.Assertion + assertionChain .ForCondition(virtualProperties.Length == 0) .BecauseOf(because, becauseArgs) .FailWith( @@ -96,7 +100,7 @@ public AndConstraint BeWritable([StringSyntax("C { PropertyInfo[] readOnlyProperties = GetAllReadOnlyPropertiesFromSelection(); - Execute.Assertion + assertionChain .ForCondition(readOnlyProperties.Length == 0) .BecauseOf(because, becauseArgs) .FailWith( @@ -120,7 +124,7 @@ public AndConstraint NotBeWritable([StringSyntax { PropertyInfo[] writableProperties = GetAllWritablePropertiesFromSelection(); - Execute.Assertion + assertionChain .ForCondition(writableProperties.Length == 0) .BecauseOf(because, becauseArgs) .FailWith( @@ -166,7 +170,7 @@ public AndConstraint BeDecoratedWith { PropertyInfo[] propertiesWithoutAttribute = GetPropertiesWithout(); - Execute.Assertion + assertionChain .ForCondition(propertiesWithoutAttribute.Length == 0) .BecauseOf(because, becauseArgs) .FailWith( @@ -193,7 +197,7 @@ public AndConstraint NotBeDecoratedWith(); - Execute.Assertion + assertionChain .ForCondition(propertiesWithAttribute.Length == 0) .BecauseOf(because, becauseArgs) .FailWith( @@ -218,7 +222,7 @@ private PropertyInfo[] GetPropertiesWith() private static string GetDescriptionsFor(IEnumerable properties) { - IEnumerable descriptions = properties.Select(property => PropertyInfoAssertions.GetDescriptionFor(property)); + IEnumerable descriptions = properties.Select(property => Formatter.ToString(property)); return string.Join(Environment.NewLine, descriptions); } diff --git a/Src/FluentAssertions/Types/TypeAssertions.cs b/Src/FluentAssertions/Types/TypeAssertions.cs index 45cf1618cc..f16fadc14a 100644 --- a/Src/FluentAssertions/Types/TypeAssertions.cs +++ b/Src/FluentAssertions/Types/TypeAssertions.cs @@ -17,12 +17,15 @@ namespace FluentAssertions.Types; [DebuggerNonUserCode] public class TypeAssertions : ReferenceTypeAssertions { + private readonly AssertionChain assertionChain; + /// /// Initializes a new instance of the class. /// - public TypeAssertions(Type type) - : base(type) + public TypeAssertions(Type type, AssertionChain assertionChain) + : base(type, assertionChain) { + this.assertionChain = assertionChain; } /// @@ -35,7 +38,8 @@ public TypeAssertions(Type type) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint Be([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint Be([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { return Be(typeof(TExpected), because, becauseArgs); } @@ -54,7 +58,7 @@ public AndConstraint Be([StringSyntax("CompositeForma public AndConstraint Be(Type expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject == expected) .FailWith(GetFailureMessageIfTypesAreDifferent(Subject, expected)); @@ -74,7 +78,8 @@ public AndConstraint Be(Type expected, /// Zero or more objects to format using the placeholders in . /// /// An which can be used to chain assertions. - public new AndConstraint BeAssignableTo([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public new AndConstraint BeAssignableTo([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { return BeAssignableTo(typeof(T), because, becauseArgs); } @@ -101,7 +106,7 @@ public AndConstraint Be(Type expected, ? Subject.IsAssignableToOpenGeneric(type) : type.IsAssignableFrom(Subject); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(isAssignable) .FailWith("Expected {context:type} {0} to be assignable to {1}{reason}, but it is not.", Subject, type); @@ -121,7 +126,8 @@ public AndConstraint Be(Type expected, /// Zero or more objects to format using the placeholders in . /// /// An which can be used to chain assertions. - public new AndConstraint NotBeAssignableTo([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public new AndConstraint NotBeAssignableTo([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { return NotBeAssignableTo(typeof(T), because, becauseArgs); } @@ -148,7 +154,7 @@ public AndConstraint Be(Type expected, ? Subject.IsAssignableToOpenGeneric(type) : type.IsAssignableFrom(Subject); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(!isAssignable) .FailWith("Expected {context:type} {0} to not be assignable to {1}{reason}, but it is.", Subject, type); @@ -193,7 +199,8 @@ private static string GetFailureMessageIfTypesAreDifferent(Type actual, Type exp /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBe([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint NotBe([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { return NotBe(typeof(TUnexpected), because, becauseArgs); } @@ -214,7 +221,7 @@ public AndConstraint NotBe(Type unexpected, { string nameOfUnexpectedType = unexpected is not null ? $"[{unexpected.AssemblyQualifiedName}]" : ""; - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject != unexpected) .FailWith("Expected type not to be " + nameOfUnexpectedType + "{reason}, but it is."); @@ -238,7 +245,7 @@ public AndWhichConstraint BeDecoratedWith attributes = Subject.GetMatchingAttributes(); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(attributes.Any()) .FailWith("Expected type {0} to be decorated with {1}{reason}, but the attribute was not found.", @@ -273,7 +280,7 @@ public AndWhichConstraint BeDecoratedWith attributes = Subject.GetMatchingAttributes(isMatchingAttributePredicate); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(attributes.Any()) .FailWith( @@ -299,7 +306,7 @@ public AndWhichConstraint BeDecoratedWithOrInherit attributes = Subject.GetMatchingOrInheritedAttributes(); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(attributes.Any()) .FailWith("Expected type {0} to be decorated with or inherit {1}{reason}, but the attribute was not found.", @@ -334,7 +341,7 @@ public AndWhichConstraint BeDecoratedWithOrInherit attributes = Subject.GetMatchingOrInheritedAttributes(isMatchingAttributePredicate); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(attributes.Any()) .FailWith( @@ -354,10 +361,11 @@ public AndWhichConstraint BeDecoratedWithOrInherit /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeDecoratedWith([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint NotBeDecoratedWith([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) where TAttribute : Attribute { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(!Subject.IsDecoratedWith()) .FailWith("Expected type {0} to not be decorated with {1}{reason}, but the attribute was found.", @@ -388,7 +396,7 @@ public AndConstraint NotBeDecoratedWith( { Guard.ThrowIfArgumentIsNull(isMatchingAttributePredicate); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(!Subject.IsDecoratedWith(isMatchingAttributePredicate)) .FailWith( @@ -413,7 +421,7 @@ public AndConstraint NotBeDecoratedWithOrInherit( [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TAttribute : Attribute { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(!Subject.IsDecoratedWithOrInherit()) .FailWith("Expected type {0} to not be decorated with or inherit {1}{reason}, but the attribute was found.", @@ -445,7 +453,7 @@ public AndConstraint NotBeDecoratedWithOrInherit( { Guard.ThrowIfArgumentIsNull(isMatchingAttributePredicate); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(!Subject.IsDecoratedWithOrInherit(isMatchingAttributePredicate)) .FailWith( @@ -482,16 +490,17 @@ private bool AssertSubjectImplements(Type interfaceType, { bool containsInterface = interfaceType.IsAssignableFrom(Subject) && interfaceType != Subject; - return Execute.Assertion - .BecauseOf(because, becauseArgs) - .WithExpectation("Expected type {0} to implement interface {1}{reason}", Subject, interfaceType) - .ForCondition(interfaceType.IsInterface) - .FailWith(", but {0} is not an interface.", interfaceType) - .Then - .ForCondition(containsInterface) - .FailWith(", but it does not.") - .Then - .ClearExpectation(); + assertionChain + .BecauseOf(because, becauseArgs) + .WithExpectation("Expected type {0} to implement interface {1}{reason}", Subject, interfaceType, chain => chain + + .ForCondition(interfaceType.IsInterface) + .FailWith(", but {0} is not an interface.", interfaceType) + .Then + .ForCondition(containsInterface) + .FailWith(", but it does not.")); + + return assertionChain.Succeeded; } /// @@ -505,7 +514,8 @@ private bool AssertSubjectImplements(Type interfaceType, /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint Implement([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint Implement([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) where TInterface : class { return Implement(typeof(TInterface), because, becauseArgs); @@ -530,16 +540,15 @@ public AndConstraint NotImplement(Type interfaceType, bool containsInterface = interfaceType.IsAssignableFrom(Subject) && interfaceType != Subject; - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected type {0} to not implement interface {1}{reason}", Subject, interfaceType) - .ForCondition(interfaceType.IsInterface) + .WithExpectation("Expected type {0} to not implement interface {1}{reason}", Subject, interfaceType, chain => chain + + .ForCondition(interfaceType.IsInterface) .FailWith(", but {0} is not an interface.", interfaceType) .Then .ForCondition(!containsInterface) - .FailWith(", but it does.", interfaceType) - .Then - .ClearExpectation(); + .FailWith(", but it does.", interfaceType)); return new AndConstraint(this); } @@ -555,7 +564,8 @@ public AndConstraint NotImplement(Type interfaceType, /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotImplement([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint NotImplement([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) where TInterface : class { return NotImplement(typeof(TInterface), because, becauseArgs); @@ -582,16 +592,15 @@ public AndConstraint BeDerivedFrom(Type baseType, ? Subject.IsDerivedFromOpenGeneric(baseType) : Subject.IsSubclassOf(baseType); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected type {0} to be derived from {1}{reason}", Subject, baseType) - .ForCondition(!baseType.IsInterface) + .WithExpectation("Expected type {0} to be derived from {1}{reason}", Subject, baseType, chain => chain + + .ForCondition(!baseType.IsInterface) .FailWith(", but {0} is an interface.", baseType) .Then .ForCondition(isDerivedFrom) - .FailWith(", but it is not.") - .Then - .ClearExpectation(); + .FailWith(", but it is not.")); return new AndConstraint(this); } @@ -607,7 +616,8 @@ public AndConstraint BeDerivedFrom(Type baseType, /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeDerivedFrom([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint BeDerivedFrom([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) where TBaseClass : class { return BeDerivedFrom(typeof(TBaseClass), because, becauseArgs); @@ -634,16 +644,15 @@ public AndConstraint NotBeDerivedFrom(Type baseType, ? Subject.IsDerivedFromOpenGeneric(baseType) : Subject.IsSubclassOf(baseType); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .WithExpectation("Expected type {0} not to be derived from {1}{reason}", Subject, baseType) - .ForCondition(!baseType.IsInterface) + .WithExpectation("Expected type {0} not to be derived from {1}{reason}", Subject, baseType, chain => chain + + .ForCondition(!baseType.IsInterface) .FailWith(", but {0} is an interface.", baseType) .Then .ForCondition(!isDerivedFrom) - .FailWith(", but it is.") - .Then - .ClearExpectation(); + .FailWith(", but it is.")); return new AndConstraint(this); } @@ -659,7 +668,8 @@ public AndConstraint NotBeDerivedFrom(Type baseType, /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeDerivedFrom([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint NotBeDerivedFrom([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) where TBaseClass : class { return NotBeDerivedFrom(typeof(TBaseClass), because, becauseArgs); @@ -677,18 +687,19 @@ public AndConstraint NotBeDerivedFrom([StringSyntax( /// /// /// is not a class. - public AndConstraint BeSealed([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint BeSealed([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected type to be sealed{reason}, but {context:type} is ."); - if (success) + if (assertionChain.Succeeded) { AssertThatSubjectIsClass(); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject.IsCSharpSealed()) .FailWith("Expected type {0} to be sealed{reason}.", Subject); @@ -709,18 +720,19 @@ public AndConstraint BeSealed([StringSyntax("CompositeFormat")] /// /// /// is not a class. - public AndConstraint NotBeSealed([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint NotBeSealed([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected type not to be sealed{reason}, but {context:type} is ."); - if (success) + if (assertionChain.Succeeded) { AssertThatSubjectIsClass(); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(!Subject.IsCSharpSealed()) .FailWith("Expected type {0} not to be sealed{reason}.", Subject); @@ -741,18 +753,19 @@ public AndConstraint NotBeSealed([StringSyntax("CompositeFormat" /// /// /// is not a class. - public AndConstraint BeAbstract([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint BeAbstract([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected type to be abstract{reason}, but {context:type} is ."); - if (success) + if (assertionChain.Succeeded) { AssertThatSubjectIsClass(); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject.IsCSharpAbstract()) .FailWith("Expected {context:type} {0} to be abstract{reason}.", Subject); @@ -773,18 +786,19 @@ public AndConstraint BeAbstract([StringSyntax("CompositeFormat") /// /// /// is not a class. - public AndConstraint NotBeAbstract([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint NotBeAbstract([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected type not to be abstract{reason}, but {context:type} is ."); - if (success) + if (assertionChain.Succeeded) { AssertThatSubjectIsClass(); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(!Subject.IsCSharpAbstract()) .FailWith("Expected type {0} not to be abstract{reason}.", Subject); @@ -805,18 +819,19 @@ public AndConstraint NotBeAbstract([StringSyntax("CompositeForma /// /// /// is not a class. - public AndConstraint BeStatic([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint BeStatic([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected type to be static{reason}, but {context:type} is ."); - if (success) + if (assertionChain.Succeeded) { AssertThatSubjectIsClass(); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject.IsCSharpStatic()) .FailWith("Expected type {0} to be static{reason}.", Subject); @@ -837,18 +852,19 @@ public AndConstraint BeStatic([StringSyntax("CompositeFormat")] /// /// /// is not a class. - public AndConstraint NotBeStatic([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + public AndConstraint NotBeStatic([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected type not to be static{reason}, but {context:type} is ."); - if (success) + if (assertionChain.Succeeded) { AssertThatSubjectIsClass(); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(!Subject.IsCSharpStatic()) .FailWith("Expected type {0} not to be static{reason}.", Subject); @@ -880,26 +896,27 @@ public AndWhichConstraint HaveProperty( Guard.ThrowIfArgumentIsNull(propertyType); Guard.ThrowIfArgumentIsNullOrEmpty(name); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith( - $"Expected {propertyType.Name} {{context:type}}.{name} to exist{{reason}}, but {{context:type}} is ."); + $"Cannot determine if a type has a property named {name} if the type is ."); PropertyInfo propertyInfo = null; - if (success) + if (assertionChain.Succeeded) { propertyInfo = Subject.FindPropertyByName(name); - var propertyInfoDescription = PropertyInfoAssertions.GetDescriptionFor(propertyInfo); - Execute.Assertion + var subjectDescription = assertionChain.HasOverriddenCallerIdentifier ? assertionChain.CallerIdentifier : Subject!.Name; + + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(propertyInfo is not null) - .FailWith($"Expected {propertyType.Name} {Subject}.{name} to exist{{reason}}, but it does not.") + .FailWith($"Expected {subjectDescription} to have a property {name} of type {propertyType.Name}{{reason}}, but it does not.") .Then .ForCondition(propertyInfo.PropertyType == propertyType) - .FailWith($"Expected {propertyInfoDescription} to be of type {propertyType}{{reason}}, but it is not."); + .FailWith($"Expected property {propertyInfo.Name} to be of type {propertyType}{{reason}}, but it is not.", propertyInfo); } return new AndWhichConstraint(this, propertyInfo); @@ -944,20 +961,21 @@ public AndConstraint NotHaveProperty(string name, { Guard.ThrowIfArgumentIsNullOrEmpty(name); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) - .FailWith($"Expected {{context:type}}.{name} to not exist{{reason}}, but {{context:type}} is ."); + .FailWith($"Cannot determine if a type has an unexpected property named {name} if the type is ."); - if (success) + if (assertionChain.Succeeded) { + var subjectDescription = assertionChain.HasOverriddenCallerIdentifier ? assertionChain.CallerIdentifier : Subject!.Name; + PropertyInfo propertyInfo = Subject.FindPropertyByName(name); - var propertyInfoDescription = PropertyInfoAssertions.GetDescriptionFor(propertyInfo); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(propertyInfo is null) - .FailWith($"Expected {propertyInfoDescription} to not exist{{reason}}, but it does."); + .FailWith($"Did not expect {subjectDescription} to have a property {propertyInfo?.Name}{{reason}}, but it does."); } return new AndConstraint(this); @@ -986,27 +1004,22 @@ public AndConstraint HaveExplicitProperty( Guard.ThrowIfArgumentIsNull(interfaceType); Guard.ThrowIfArgumentIsNullOrEmpty(name); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith( $"Expected {{context:type}} to explicitly implement {interfaceType}.{name}{{reason}}" + ", but {context:type} is ."); - if (success) + if (assertionChain.Succeeded && AssertSubjectImplements(interfaceType, because, becauseArgs)) { - success = AssertSubjectImplements(interfaceType, because, becauseArgs); - - if (success) - { - var explicitlyImplementsProperty = Subject.HasExplicitlyImplementedProperty(interfaceType, name); - - Execute.Assertion - .BecauseOf(because, becauseArgs) - .ForCondition(explicitlyImplementsProperty) - .FailWith( - $"Expected {Subject} to explicitly implement {interfaceType}.{name}{{reason}}, but it does not."); - } + var explicitlyImplementsProperty = Subject.HasExplicitlyImplementedProperty(interfaceType, name); + + assertionChain + .BecauseOf(because, becauseArgs) + .ForCondition(explicitlyImplementsProperty) + .FailWith( + $"Expected {Subject} to explicitly implement {interfaceType}.{name}{{reason}}, but it does not."); } return new AndConstraint(this); @@ -1058,28 +1071,23 @@ public AndConstraint NotHaveExplicitProperty( Guard.ThrowIfArgumentIsNull(interfaceType); Guard.ThrowIfArgumentIsNullOrEmpty(name); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith( $"Expected {{context:type}} to not explicitly implement {interfaceType}.{name}{{reason}}" + ", but {context:type} is ."); - if (success) + if (assertionChain.Succeeded && AssertSubjectImplements(interfaceType, because, becauseArgs)) { - success = AssertSubjectImplements(interfaceType, because, becauseArgs); - - if (success) - { - var explicitlyImplementsProperty = Subject.HasExplicitlyImplementedProperty(interfaceType, name); - - Execute.Assertion - .BecauseOf(because, becauseArgs) - .ForCondition(!explicitlyImplementsProperty) - .FailWith( - $"Expected {Subject} to not explicitly implement {interfaceType}.{name}{{reason}}" + - ", but it does."); - } + var explicitlyImplementsProperty = Subject.HasExplicitlyImplementedProperty(interfaceType, name); + + assertionChain + .BecauseOf(because, becauseArgs) + .ForCondition(!explicitlyImplementsProperty) + .FailWith( + $"Expected {Subject} to not explicitly implement {interfaceType}.{name}{{reason}}" + + ", but it does."); } return new AndConstraint(this); @@ -1134,28 +1142,23 @@ public AndConstraint HaveExplicitMethod( Guard.ThrowIfArgumentIsNullOrEmpty(name); Guard.ThrowIfArgumentIsNull(parameterTypes); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith( $"Expected {{context:type}} to explicitly implement {interfaceType}.{name}" + $"({GetParameterString(parameterTypes)}){{reason}}, but {{context:type}} is ."); - if (success) + if (assertionChain.Succeeded && AssertSubjectImplements(interfaceType, because, becauseArgs)) { - success = AssertSubjectImplements(interfaceType, because, becauseArgs); - - if (success) - { - var explicitlyImplementsMethod = Subject.HasMethod($"{interfaceType}.{name}", parameterTypes); - - Execute.Assertion - .BecauseOf(because, becauseArgs) - .ForCondition(explicitlyImplementsMethod) - .FailWith( - $"Expected {Subject} to explicitly implement {interfaceType}.{name}" + - $"({GetParameterString(parameterTypes)}){{reason}}, but it does not."); - } + var explicitlyImplementsMethod = Subject.HasMethod($"{interfaceType}.{name}", parameterTypes); + + assertionChain + .BecauseOf(because, becauseArgs) + .ForCondition(explicitlyImplementsMethod) + .FailWith( + $"Expected {Subject} to explicitly implement {interfaceType}.{name}" + + $"({GetParameterString(parameterTypes)}){{reason}}, but it does not."); } return new AndConstraint(this); @@ -1212,28 +1215,23 @@ public AndConstraint NotHaveExplicitMethod( Guard.ThrowIfArgumentIsNullOrEmpty(name); Guard.ThrowIfArgumentIsNull(parameterTypes); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith( $"Expected {{context:type}} to not explicitly implement {interfaceType}.{name}" + $"({GetParameterString(parameterTypes)}){{reason}}, but {{context:type}} is ."); - if (success) + if (assertionChain.Succeeded && AssertSubjectImplements(interfaceType, because, becauseArgs)) { - success = AssertSubjectImplements(interfaceType, because, becauseArgs); - - if (success) - { - var explicitlyImplementsMethod = Subject.HasMethod($"{interfaceType}.{name}", parameterTypes); - - Execute.Assertion - .BecauseOf(because, becauseArgs) - .ForCondition(!explicitlyImplementsMethod) - .FailWith( - $"Expected {Subject} to not explicitly implement {interfaceType}.{name}" + - $"({GetParameterString(parameterTypes)}){{reason}}, but it does."); - } + var explicitlyImplementsMethod = Subject.HasMethod($"{interfaceType}.{name}", parameterTypes); + + assertionChain + .BecauseOf(because, becauseArgs) + .ForCondition(!explicitlyImplementsMethod) + .FailWith( + $"Expected {Subject} to not explicitly implement {interfaceType}.{name}" + + $"({GetParameterString(parameterTypes)}){{reason}}, but it does."); } return new AndConstraint(this); @@ -1286,32 +1284,34 @@ public AndWhichConstraint HaveIndexer( Guard.ThrowIfArgumentIsNull(indexerType); Guard.ThrowIfArgumentIsNull(parameterTypes); - bool success = Execute.Assertion + string parameterString = GetParameterString(parameterTypes); + + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith( - $"Expected {indexerType.Name} {{context:type}}[{GetParameterString(parameterTypes)}] to exist{{reason}}" + + $"Expected {indexerType.Name} {{context:type}}[{parameterString}] to exist{{reason}}" + ", but {context:type} is ."); PropertyInfo propertyInfo = null; - if (success) + if (assertionChain.Succeeded) { propertyInfo = Subject.GetIndexerByParameterTypes(parameterTypes); - var propertyInfoDescription = PropertyInfoAssertions.GetDescriptionFor(propertyInfo); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(propertyInfo is not null) .FailWith( - $"Expected {indexerType.Name} {Subject}[{GetParameterString(parameterTypes)}] to exist{{reason}}" + + $"Expected {indexerType.Name} {Subject}[{parameterString}] to exist{{reason}}" + ", but it does not.") .Then .ForCondition(propertyInfo.PropertyType == indexerType) - .FailWith($"Expected {propertyInfoDescription} to be of type {indexerType}{{reason}}, but it is not."); + .FailWith("Expected {0} to be of type {1}{reason}, but it is not.", propertyInfo, indexerType); } - return new AndWhichConstraint(this, propertyInfo); + return new AndWhichConstraint(this, propertyInfo, assertionChain, + $"[{parameterString}]"); } /// @@ -1333,18 +1333,18 @@ public AndConstraint NotHaveIndexer( { Guard.ThrowIfArgumentIsNull(parameterTypes); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith( $"Expected indexer {{context:type}}[{GetParameterString(parameterTypes)}] to not exist{{reason}}" + ", but {context:type} is ."); - if (success) + if (assertionChain.Succeeded) { PropertyInfo propertyInfo = Subject.GetIndexerByParameterTypes(parameterTypes); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(propertyInfo is null) .FailWith( @@ -1378,7 +1378,7 @@ public AndWhichConstraint HaveMethod( Guard.ThrowIfArgumentIsNullOrEmpty(name); Guard.ThrowIfArgumentIsNull(parameterTypes); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith( @@ -1387,11 +1387,11 @@ public AndWhichConstraint HaveMethod( MethodInfo methodInfo = null; - if (success) + if (assertionChain.Succeeded) { methodInfo = Subject.GetMethod(name, parameterTypes); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(methodInfo is not null) .FailWith( @@ -1425,19 +1425,19 @@ public AndConstraint NotHaveMethod( Guard.ThrowIfArgumentIsNullOrEmpty(name); Guard.ThrowIfArgumentIsNull(parameterTypes); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith( $"Expected method {{context:type}}.{name}({GetParameterString(parameterTypes)}) to not exist{{reason}}" + ", but {context:type} is ."); - if (success) + if (assertionChain.Succeeded) { MethodInfo methodInfo = Subject.GetMethod(name, parameterTypes); var methodInfoDescription = MethodInfoAssertions.GetDescriptionFor(methodInfo); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(methodInfo is null) .FailWith( @@ -1466,7 +1466,7 @@ public AndWhichConstraint HaveConstructor( { Guard.ThrowIfArgumentIsNull(parameterTypes); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith( @@ -1475,11 +1475,11 @@ public AndWhichConstraint HaveConstructor( ConstructorInfo constructorInfo = null; - if (success) + if (assertionChain.Succeeded) { constructorInfo = Subject.GetConstructor(parameterTypes); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(constructorInfo is not null) .FailWith( @@ -1524,7 +1524,7 @@ public AndWhichConstraint NotHaveConstructor( { Guard.ThrowIfArgumentIsNull(parameterTypes); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith( @@ -1533,11 +1533,11 @@ public AndWhichConstraint NotHaveConstructor( ConstructorInfo constructorInfo = null; - if (success) + if (assertionChain.Succeeded) { constructorInfo = Subject.GetConstructor(parameterTypes); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(constructorInfo is null) .FailWith( @@ -1588,16 +1588,16 @@ public AndConstraint HaveAccessModifier( { Guard.ThrowIfArgumentIsOutOfRange(accessModifier); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith($"Expected {{context:type}} to be {accessModifier}{{reason}}, but {{context:type}} is ."); - if (success) + if (assertionChain.Succeeded) { CSharpAccessModifier subjectAccessModifier = Subject.GetCSharpAccessModifier(); - Execute.Assertion.ForCondition(accessModifier == subjectAccessModifier) + assertionChain.ForCondition(accessModifier == subjectAccessModifier) .BecauseOf(because, becauseArgs) .ForCondition(accessModifier == subjectAccessModifier) .FailWith( @@ -1627,16 +1627,16 @@ public AndConstraint NotHaveAccessModifier( { Guard.ThrowIfArgumentIsOutOfRange(accessModifier); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith($"Expected {{context:type}} not to be {accessModifier}{{reason}}, but {{context:type}} is ."); - if (success) + if (assertionChain.Succeeded) { CSharpAccessModifier subjectAccessModifier = Subject.GetCSharpAccessModifier(); - Execute.Assertion + assertionChain .ForCondition(accessModifier != subjectAccessModifier) .BecauseOf(because, becauseArgs) .ForCondition(accessModifier != subjectAccessModifier) @@ -1687,7 +1687,7 @@ public AndWhichConstraint HaveImplicitConversionOper Guard.ThrowIfArgumentIsNull(sourceType); Guard.ThrowIfArgumentIsNull(targetType); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected public static implicit {0}({1}) to exist{reason}, but {context:type} is .", @@ -1695,18 +1695,18 @@ public AndWhichConstraint HaveImplicitConversionOper MethodInfo methodInfo = null; - if (success) + if (assertionChain.Succeeded) { methodInfo = Subject.GetImplicitConversionOperator(sourceType, targetType); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(methodInfo is not null) .FailWith("Expected public static implicit {0}({1}) to exist{reason}, but it does not.", targetType, sourceType); } - return new AndWhichConstraint(this, methodInfo); + return new AndWhichConstraint(this, methodInfo, assertionChain); } /// @@ -1750,17 +1750,17 @@ public AndConstraint NotHaveImplicitConversionOperator( Guard.ThrowIfArgumentIsNull(sourceType); Guard.ThrowIfArgumentIsNull(targetType); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected public static implicit {0}({1}) to not exist{reason}, but {context:type} is .", targetType, sourceType); - if (success) + if (assertionChain.Succeeded) { MethodInfo methodInfo = Subject.GetImplicitConversionOperator(sourceType, targetType); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(methodInfo is null) .FailWith("Expected public static implicit {0}({1}) to not exist{reason}, but it does.", @@ -1811,7 +1811,7 @@ public AndWhichConstraint HaveExplicitConversionOper Guard.ThrowIfArgumentIsNull(sourceType); Guard.ThrowIfArgumentIsNull(targetType); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected public static explicit {0}({1}) to exist{reason}, but {context:type} is .", @@ -1819,11 +1819,11 @@ public AndWhichConstraint HaveExplicitConversionOper MethodInfo methodInfo = null; - if (success) + if (assertionChain.Succeeded) { methodInfo = Subject.GetExplicitConversionOperator(sourceType, targetType); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(methodInfo is not null) .FailWith("Expected public static explicit {0}({1}) to exist{reason}, but it does not.", @@ -1874,17 +1874,17 @@ public AndConstraint NotHaveExplicitConversionOperator( Guard.ThrowIfArgumentIsNull(sourceType); Guard.ThrowIfArgumentIsNull(targetType); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected public static explicit {0}({1}) to not exist{reason}, but {context:type} is .", targetType, sourceType); - if (success) + if (assertionChain.Succeeded) { MethodInfo methodInfo = Subject.GetExplicitConversionOperator(sourceType, targetType); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(methodInfo is null) .FailWith("Expected public static explicit {0}({1}) to not exist{reason}, but it does.", diff --git a/Src/FluentAssertions/Types/TypeSelectorAssertions.cs b/Src/FluentAssertions/Types/TypeSelectorAssertions.cs index 1fa57f2532..41711ae15f 100644 --- a/Src/FluentAssertions/Types/TypeSelectorAssertions.cs +++ b/Src/FluentAssertions/Types/TypeSelectorAssertions.cs @@ -18,12 +18,15 @@ namespace FluentAssertions.Types; [DebuggerNonUserCode] public class TypeSelectorAssertions { + private readonly AssertionChain assertionChain; + /// /// Initializes a new instance of the class. /// /// is or contains . - public TypeSelectorAssertions(params Type[] types) + public TypeSelectorAssertions(AssertionChain assertionChain, params Type[] types) { + this.assertionChain = assertionChain; Guard.ThrowIfArgumentIsNull(types); Guard.ThrowIfArgumentContainsNull(types); @@ -52,7 +55,7 @@ public AndConstraint BeDecoratedWith([String .Where(type => !type.IsDecoratedWith()) .ToArray(); - Execute.Assertion + assertionChain .ForCondition(typesWithoutAttribute.Length == 0) .BecauseOf(because, becauseArgs) .FailWith("Expected all types to be decorated with {0}{reason}," + @@ -89,7 +92,7 @@ public AndConstraint BeDecoratedWith( .Where(type => !type.IsDecoratedWith(isMatchingAttributePredicate)) .ToArray(); - Execute.Assertion + assertionChain .ForCondition(typesWithoutMatchingAttribute.Length == 0) .BecauseOf(because, becauseArgs) .FailWith("Expected all types to be decorated with {0} that matches {1}{reason}," + @@ -119,7 +122,7 @@ public AndConstraint BeDecoratedWithOrInherit !type.IsDecoratedWithOrInherit()) .ToArray(); - Execute.Assertion + assertionChain .ForCondition(typesWithoutAttribute.Length == 0) .BecauseOf(because, becauseArgs) .FailWith("Expected all types to be decorated with or inherit {0}{reason}," + @@ -156,7 +159,7 @@ public AndConstraint BeDecoratedWithOrInherit !type.IsDecoratedWithOrInherit(isMatchingAttributePredicate)) .ToArray(); - Execute.Assertion + assertionChain .ForCondition(typesWithoutMatchingAttribute.Length == 0) .BecauseOf(because, becauseArgs) .FailWith("Expected all types to be decorated with or inherit {0} that matches {1}{reason}," + @@ -185,7 +188,7 @@ public AndConstraint NotBeDecoratedWith([Str .Where(type => type.IsDecoratedWith()) .ToArray(); - Execute.Assertion + assertionChain .ForCondition(typesWithAttribute.Length == 0) .BecauseOf(because, becauseArgs) .FailWith("Expected all types to not be decorated with {0}{reason}," + @@ -222,7 +225,7 @@ public AndConstraint NotBeDecoratedWith( .Where(type => type.IsDecoratedWith(isMatchingAttributePredicate)) .ToArray(); - Execute.Assertion + assertionChain .ForCondition(typesWithMatchingAttribute.Length == 0) .BecauseOf(because, becauseArgs) .FailWith("Expected all types to not be decorated with {0} that matches {1}{reason}," + @@ -252,7 +255,7 @@ public AndConstraint NotBeDecoratedWithOrInherit type.IsDecoratedWithOrInherit()) .ToArray(); - Execute.Assertion + assertionChain .ForCondition(typesWithAttribute.Length == 0) .BecauseOf(because, becauseArgs) .FailWith("Expected all types to not be decorated with or inherit {0}{reason}," + @@ -289,7 +292,7 @@ public AndConstraint NotBeDecoratedWithOrInherit type.IsDecoratedWithOrInherit(isMatchingAttributePredicate)) .ToArray(); - Execute.Assertion + assertionChain .ForCondition(typesWithMatchingAttribute.Length == 0) .BecauseOf(because, becauseArgs) .FailWith("Expected all types to not be decorated with or inherit {0} that matches {1}{reason}," + @@ -315,7 +318,7 @@ public AndConstraint BeSealed([StringSyntax("CompositeFo { var notSealedTypes = Subject.Where(type => !type.IsCSharpSealed()).ToArray(); - Execute.Assertion.ForCondition(notSealedTypes.Length == 0) + assertionChain.ForCondition(notSealedTypes.Length == 0) .BecauseOf(because, becauseArgs) .FailWith("Expected all types to be sealed{reason}, but the following types are not:" + Environment.NewLine + "{0}.", GetDescriptionsFor(notSealedTypes)); @@ -337,7 +340,7 @@ public AndConstraint NotBeSealed([StringSyntax("Composit { var sealedTypes = Subject.Where(type => type.IsCSharpSealed()).ToArray(); - Execute.Assertion.ForCondition(sealedTypes.Length == 0) + assertionChain.ForCondition(sealedTypes.Length == 0) .BecauseOf(because, becauseArgs) .FailWith("Expected all types not to be sealed{reason}, but the following types are:" + Environment.NewLine + "{0}.", GetDescriptionsFor(sealedTypes)); @@ -365,7 +368,7 @@ public AndConstraint BeInNamespace(string @namespace, .Where(t => t.Namespace != @namespace) .ToArray(); - Execute.Assertion + assertionChain .ForCondition(typesNotInNamespace.Length == 0) .BecauseOf(because, becauseArgs) .FailWith("Expected all types to be in namespace {0}{reason}," + @@ -396,7 +399,7 @@ public AndConstraint NotBeInNamespace(string @namespace, .Where(t => t.Namespace == @namespace) .ToArray(); - Execute.Assertion + assertionChain .ForCondition(typesInNamespace.Length == 0) .BecauseOf(because, becauseArgs) .FailWith("Expected no types to be in namespace {0}{reason}," + @@ -427,7 +430,7 @@ public AndConstraint BeUnderNamespace(string @namespace, .Where(t => !t.IsUnderNamespace(@namespace)) .ToArray(); - Execute.Assertion + assertionChain .ForCondition(typesNotUnderNamespace.Length == 0) .BecauseOf(because, becauseArgs) .FailWith("Expected the namespaces of all types to start with {0}{reason}," + @@ -459,7 +462,7 @@ public AndConstraint NotBeUnderNamespace(string @namespa .Where(t => t.IsUnderNamespace(@namespace)) .ToArray(); - Execute.Assertion + assertionChain .ForCondition(typesUnderNamespace.Length == 0) .BecauseOf(because, becauseArgs) .FailWith("Expected the namespaces of all types to not start with {0}{reason}," + diff --git a/Src/FluentAssertions/Xml/Equivalency/XmlReaderValidator.cs b/Src/FluentAssertions/Xml/Equivalency/XmlReaderValidator.cs index 52a223417e..b82b4bba6f 100644 --- a/Src/FluentAssertions/Xml/Equivalency/XmlReaderValidator.cs +++ b/Src/FluentAssertions/Xml/Equivalency/XmlReaderValidator.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Xml; using FluentAssertions.Execution; @@ -9,16 +8,17 @@ namespace FluentAssertions.Xml.Equivalency; internal class XmlReaderValidator { - private readonly AssertionScope assertion; + private readonly AssertionChain assertionChain; private readonly XmlReader subjectReader; private readonly XmlReader expectationReader; private XmlIterator subjectIterator; private XmlIterator expectationIterator; private Node currentNode = Node.CreateRoot(); - public XmlReaderValidator(XmlReader subjectReader, XmlReader expectationReader, [StringSyntax("CompositeFormat")] string because, object[] becauseArgs) + public XmlReaderValidator(AssertionChain assertionChain, XmlReader subjectReader, XmlReader expectationReader, string because, object[] becauseArgs) { - assertion = Execute.Assertion.BecauseOf(because, becauseArgs); + this.assertionChain = assertionChain; + assertionChain.BecauseOf(because, becauseArgs); this.subjectReader = subjectReader; this.expectationReader = expectationReader; @@ -30,12 +30,12 @@ public void Validate(bool shouldBeEquivalent) if (shouldBeEquivalent && failure is not null) { - assertion.FailWith(failure.FormatString, failure.FormatParams); + assertionChain.FailWith(failure.FormatString, failure.FormatParams); } if (!shouldBeEquivalent && failure is null) { - assertion.FailWith("Did not expect {context:subject} to be equivalent{reason}, but it is."); + assertionChain.FailWith("Did not expect {context:subject} to be equivalent{reason}, but it is."); } } diff --git a/Src/FluentAssertions/Xml/XAttributeAssertions.cs b/Src/FluentAssertions/Xml/XAttributeAssertions.cs index 807ee8ee75..5c63862d5c 100644 --- a/Src/FluentAssertions/Xml/XAttributeAssertions.cs +++ b/Src/FluentAssertions/Xml/XAttributeAssertions.cs @@ -12,12 +12,15 @@ namespace FluentAssertions.Xml; [DebuggerNonUserCode] public class XAttributeAssertions : ReferenceTypeAssertions { + private readonly AssertionChain assertionChain; + /// /// Initializes a new instance of the class. /// - public XAttributeAssertions(XAttribute attribute) - : base(attribute) + public XAttributeAssertions(XAttribute attribute, AssertionChain assertionChain) + : base(attribute, assertionChain) { + this.assertionChain = assertionChain; } /// @@ -34,7 +37,7 @@ public XAttributeAssertions(XAttribute attribute) public AndConstraint Be(XAttribute expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject?.Name == expected?.Name && Subject?.Value == expected?.Value) .BecauseOf(because, becauseArgs) .FailWith("Expected {context} to be {0}{reason}, but found {1}.", expected, Subject); @@ -57,7 +60,7 @@ public AndConstraint Be(XAttribute expected, public AndConstraint NotBe(XAttribute unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(!(Subject?.Name == unexpected?.Name && Subject?.Value == unexpected?.Value)) .BecauseOf(because, becauseArgs) .FailWith("Did not expect {context} to be {0}{reason}.", unexpected); @@ -79,14 +82,14 @@ public AndConstraint NotBe(XAttribute unexpected, public AndConstraint HaveValue(string expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected the attribute to have value {0}{reason}, but {context:member} is .", expected); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .ForCondition(Subject!.Value == expected) .BecauseOf(because, becauseArgs) .FailWith("Expected {context} \"{0}\" to have value {1}{reason}, but found {2}.", diff --git a/Src/FluentAssertions/Xml/XDocumentAssertions.cs b/Src/FluentAssertions/Xml/XDocumentAssertions.cs index 98b2662d7a..f4c39fb40d 100644 --- a/Src/FluentAssertions/Xml/XDocumentAssertions.cs +++ b/Src/FluentAssertions/Xml/XDocumentAssertions.cs @@ -18,12 +18,15 @@ namespace FluentAssertions.Xml; [DebuggerNonUserCode] public class XDocumentAssertions : ReferenceTypeAssertions { + private readonly AssertionChain assertionChain; + /// /// Initializes a new instance of the class. /// - public XDocumentAssertions(XDocument document) - : base(document) + public XDocumentAssertions(XDocument document, AssertionChain assertionChain) + : base(document, assertionChain) { + this.assertionChain = assertionChain; } /// @@ -41,7 +44,7 @@ public XDocumentAssertions(XDocument document) public AndConstraint Be(XDocument expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Equals(Subject, expected)) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:subject} to be {0}{reason}, but found {1}.", expected, Subject); @@ -64,7 +67,7 @@ public AndConstraint Be(XDocument expected, public AndConstraint NotBe(XDocument unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(!Equals(Subject, unexpected)) .FailWith("Did not expect {context:subject} to be {0}{reason}.", unexpected); @@ -90,7 +93,7 @@ public AndConstraint BeEquivalentTo(XDocument expected, using (XmlReader subjectReader = Subject?.CreateReader()) using (XmlReader otherReader = expected?.CreateReader()) { - var xmlReaderValidator = new XmlReaderValidator(subjectReader, otherReader, because, becauseArgs); + var xmlReaderValidator = new XmlReaderValidator(assertionChain, subjectReader, otherReader, because, becauseArgs); xmlReaderValidator.Validate(shouldBeEquivalent: true); } @@ -115,7 +118,7 @@ public AndConstraint NotBeEquivalentTo(XDocument unexpected using (XmlReader subjectReader = Subject?.CreateReader()) using (XmlReader otherReader = unexpected?.CreateReader()) { - var xmlReaderValidator = new XmlReaderValidator(subjectReader, otherReader, because, becauseArgs); + var xmlReaderValidator = new XmlReaderValidator(assertionChain, subjectReader, otherReader, because, becauseArgs); xmlReaderValidator.Validate(shouldBeEquivalent: false); } @@ -171,14 +174,14 @@ public AndWhichConstraint HaveRoot(XName expected XElement root = Subject.Root; - Execute.Assertion + assertionChain .ForCondition(root is not null && root.Name == expected) .BecauseOf(because, becauseArgs) .FailWith( "Expected {context:subject} to have root element {0}{reason}, but found {1}.", expected.ToString(), Subject); - return new AndWhichConstraint(this, root); + return new AndWhichConstraint(this, root, assertionChain, $"/{expected}"); } /// @@ -259,7 +262,7 @@ public AndWhichConstraint HaveElement(XName expec Guard.ThrowIfArgumentIsNull(expected, nameof(expected), "Cannot assert the document has an element if the expected name is ."); - bool success = Execute.Assertion + assertionChain .ForCondition(Subject.Root is not null) .BecauseOf(because, becauseArgs) .FailWith( @@ -268,11 +271,11 @@ public AndWhichConstraint HaveElement(XName expec XElement xElement = null; - if (success) + if (assertionChain.Succeeded) { xElement = Subject.Root!.Element(expected); - Execute.Assertion + assertionChain .ForCondition(xElement is not null) .BecauseOf(because, becauseArgs) .FailWith( @@ -280,7 +283,7 @@ public AndWhichConstraint HaveElement(XName expec expected.ToString()); } - return new AndWhichConstraint(this, xElement); + return new AndWhichConstraint(this, xElement, assertionChain, "/" + expected); } /// @@ -308,30 +311,30 @@ public AndWhichConstraint> HaveElemen Guard.ThrowIfArgumentIsNull(expected, nameof(expected), "Cannot assert the document has an element count if the element name is ."); - bool success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .FailWith("Cannot assert the count if the document itself is ."); IEnumerable xElements = []; - if (success) + if (assertionChain.Succeeded) { var root = Subject!.Root; - success = Execute.Assertion + assertionChain .ForCondition(root is not null) .BecauseOf(because, becauseArgs) .FailWith( "Expected {context:subject} to have root element containing a child {0}{reason}, but it has no root element.", expected.ToString()); - if (success) + if (assertionChain.Succeeded) { xElements = root!.Elements(expected); int actual = xElements.Count(); - Execute.Assertion + assertionChain .ForConstraint(occurrenceConstraint, actual) .BecauseOf(because, becauseArgs) .FailWith( @@ -341,7 +344,7 @@ public AndWhichConstraint> HaveElemen } } - return new AndWhichConstraint>(this, xElements); + return new AndWhichConstraint>(this, xElements, assertionChain, "/" + expected); } /// diff --git a/Src/FluentAssertions/Xml/XElementAssertions.cs b/Src/FluentAssertions/Xml/XElementAssertions.cs index 3381c4703b..c01c43748d 100644 --- a/Src/FluentAssertions/Xml/XElementAssertions.cs +++ b/Src/FluentAssertions/Xml/XElementAssertions.cs @@ -18,12 +18,15 @@ namespace FluentAssertions.Xml; [DebuggerNonUserCode] public class XElementAssertions : ReferenceTypeAssertions { + private readonly AssertionChain assertionChain; + /// /// Initializes a new instance of the class. /// - public XElementAssertions(XElement xElement) - : base(xElement) + public XElementAssertions(XElement xElement, AssertionChain assertionChain) + : base(xElement, assertionChain) { + this.assertionChain = assertionChain; } /// @@ -42,7 +45,7 @@ public XElementAssertions(XElement xElement) public AndConstraint Be(XElement expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(XNode.DeepEquals(Subject, expected)) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:subject} to be {0}{reason}, but found {1}.", expected, Subject); @@ -66,7 +69,7 @@ public AndConstraint Be(XElement expected, public AndConstraint NotBe(XElement unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition((Subject is null && unexpected is not null) || !XNode.DeepEquals(Subject, unexpected)) .BecauseOf(because, becauseArgs) .FailWith("Did not expect {context:subject} to be {0}{reason}.", unexpected); @@ -93,7 +96,7 @@ public AndConstraint BeEquivalentTo(XElement expected, using (XmlReader subjectReader = Subject?.CreateReader()) using (XmlReader expectedReader = expected?.CreateReader()) { - var xmlReaderValidator = new XmlReaderValidator(subjectReader, expectedReader, because, becauseArgs); + var xmlReaderValidator = new XmlReaderValidator(assertionChain, subjectReader, expectedReader, because, becauseArgs); xmlReaderValidator.Validate(shouldBeEquivalent: true); } @@ -119,7 +122,7 @@ public AndConstraint NotBeEquivalentTo(XElement unexpected, using (XmlReader subjectReader = Subject?.CreateReader()) using (XmlReader otherReader = unexpected?.CreateReader()) { - var xmlReaderValidator = new XmlReaderValidator(subjectReader, otherReader, because, becauseArgs); + var xmlReaderValidator = new XmlReaderValidator(assertionChain, subjectReader, otherReader, because, becauseArgs); xmlReaderValidator.Validate(shouldBeEquivalent: false); } @@ -140,14 +143,14 @@ public AndConstraint NotBeEquivalentTo(XElement unexpected, public AndConstraint HaveValue(string expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith("Expected the element to have value {0}{reason}, but {context:member} is .", expected); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .ForCondition(Subject!.Value == expected) .BecauseOf(because, becauseArgs) .FailWith( @@ -202,18 +205,18 @@ public AndConstraint HaveAttribute(XName expectedName, strin string expectedText = expectedName.ToString(); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith( "Expected attribute {0} in element to have value {1}{reason}, but {context:member} is .", expectedText, expectedValue); - if (success) + if (assertionChain.Succeeded) { XAttribute attribute = Subject!.Attribute(expectedName); - success = Execute.Assertion + assertionChain .ForCondition(attribute is not null) .BecauseOf(because, becauseArgs) .FailWith( @@ -221,9 +224,9 @@ public AndConstraint HaveAttribute(XName expectedName, strin + " but found no such attribute in {2}", expectedText, expectedValue, Subject); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .ForCondition(attribute!.Value == expectedValue) .BecauseOf(because, becauseArgs) .FailWith( @@ -275,7 +278,7 @@ public AndWhichConstraint HaveElement(XName expect { Guard.ThrowIfArgumentIsNull(expected); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith( @@ -284,11 +287,11 @@ public AndWhichConstraint HaveElement(XName expect XElement xElement = null; - if (success) + if (assertionChain.Succeeded) { xElement = Subject!.Element(expected); - Execute.Assertion + assertionChain .ForCondition(xElement is not null) .BecauseOf(because, becauseArgs) .FailWith( @@ -296,7 +299,7 @@ public AndWhichConstraint HaveElement(XName expect expected.ToString().EscapePlaceholders()); } - return new AndWhichConstraint(this, xElement); + return new AndWhichConstraint(this, xElement, assertionChain, "/" + expected); } /// @@ -324,7 +327,7 @@ public AndWhichConstraint> HaveElement Guard.ThrowIfArgumentIsNull(expected, nameof(expected), "Cannot assert the element has an element count if the element name is ."); - bool success = Execute.Assertion + assertionChain .ForCondition(Subject is not null) .BecauseOf(because, becauseArgs) .FailWith( @@ -333,12 +336,12 @@ public AndWhichConstraint> HaveElement IEnumerable xElements = []; - if (success) + if (assertionChain.Succeeded) { xElements = Subject!.Elements(expected); int actual = xElements.Count(); - Execute.Assertion + assertionChain .ForConstraint(occurrenceConstraint, actual) .BecauseOf(because, becauseArgs) .FailWith( @@ -347,7 +350,7 @@ public AndWhichConstraint> HaveElement expected.ToString()); } - return new AndWhichConstraint>(this, xElements); + return new AndWhichConstraint>(this, xElements, assertionChain, "/" + expected); } /// diff --git a/Src/FluentAssertions/Xml/XmlElementAssertions.cs b/Src/FluentAssertions/Xml/XmlElementAssertions.cs index 60bfd53dff..651eda0fd0 100644 --- a/Src/FluentAssertions/Xml/XmlElementAssertions.cs +++ b/Src/FluentAssertions/Xml/XmlElementAssertions.cs @@ -13,13 +13,16 @@ namespace FluentAssertions.Xml; [DebuggerNonUserCode] public class XmlElementAssertions : XmlNodeAssertions { + private readonly AssertionChain assertionChain; + /// /// Initializes a new instance of the class. /// /// - public XmlElementAssertions(XmlElement xmlElement) - : base(xmlElement) + public XmlElementAssertions(XmlElement xmlElement, AssertionChain assertionChain) + : base(xmlElement, assertionChain) { + this.assertionChain = assertionChain; } /// @@ -37,7 +40,7 @@ public XmlElementAssertions(XmlElement xmlElement) public AndConstraint HaveInnerText(string expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject.InnerText == expected) .BecauseOf(because, becauseArgs) .FailWith( @@ -94,7 +97,7 @@ public AndConstraint HaveAttributeWithNamespace( (string.IsNullOrEmpty(expectedNamespace) ? string.Empty : $"{{{expectedNamespace}}}") + expectedName; - bool success = Execute.Assertion + assertionChain .ForCondition(attribute is not null) .BecauseOf(because, becauseArgs) .FailWith( @@ -102,9 +105,9 @@ public AndConstraint HaveAttributeWithNamespace( + " with value {1}{reason}, but found no such attribute in {2}", expectedFormattedName, expectedValue, Subject); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .ForCondition(attribute!.Value == expectedValue) .BecauseOf(because, becauseArgs) .FailWith( @@ -158,14 +161,14 @@ public AndWhichConstraint HaveElementWithNames (string.IsNullOrEmpty(expectedNamespace) ? string.Empty : $"{{{expectedNamespace}}}") + expectedName; - Execute.Assertion + assertionChain .ForCondition(element is not null) .BecauseOf(because, becauseArgs) .FailWith( "Expected {context:subject} to have child element {0}{reason}, but no such child element was found.", expectedFormattedName.EscapePlaceholders()); - return new AndWhichConstraint(this, element); + return new AndWhichConstraint(this, element, assertionChain, "/" + expectedName); } protected override string Identifier => "XML element"; diff --git a/Src/FluentAssertions/Xml/XmlNodeAssertions.cs b/Src/FluentAssertions/Xml/XmlNodeAssertions.cs index 779317b295..1a3356ee65 100644 --- a/Src/FluentAssertions/Xml/XmlNodeAssertions.cs +++ b/Src/FluentAssertions/Xml/XmlNodeAssertions.cs @@ -1,6 +1,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Xml; +using FluentAssertions.Execution; using FluentAssertions.Primitives; using FluentAssertions.Xml.Equivalency; @@ -12,8 +13,8 @@ namespace FluentAssertions.Xml; [DebuggerNonUserCode] public class XmlNodeAssertions : XmlNodeAssertions { - public XmlNodeAssertions(XmlNode xmlNode) - : base(xmlNode) + public XmlNodeAssertions(XmlNode xmlNode, AssertionChain assertionChain) + : base(xmlNode, assertionChain) { } } @@ -26,9 +27,12 @@ public class XmlNodeAssertions : ReferenceTypeAssertions< where TSubject : XmlNode where TAssertions : XmlNodeAssertions { - public XmlNodeAssertions(TSubject xmlNode) - : base(xmlNode) + private readonly AssertionChain assertionChain; + + public XmlNodeAssertions(TSubject xmlNode, AssertionChain assertionChain) + : base(xmlNode, assertionChain) { + this.assertionChain = assertionChain; } /// @@ -48,7 +52,7 @@ public AndConstraint BeEquivalentTo(XmlNode expected, using (var subjectReader = new XmlNodeReader(Subject)) using (var expectedReader = new XmlNodeReader(expected)) { - var xmlReaderValidator = new XmlReaderValidator(subjectReader, expectedReader, because, becauseArgs); + var xmlReaderValidator = new XmlReaderValidator(assertionChain, subjectReader, expectedReader, because, becauseArgs); xmlReaderValidator.Validate(shouldBeEquivalent: true); } @@ -73,7 +77,7 @@ public AndConstraint NotBeEquivalentTo(XmlNode unexpected, using (var subjectReader = new XmlNodeReader(Subject)) using (var unexpectedReader = new XmlNodeReader(unexpected)) { - var xmlReaderValidator = new XmlReaderValidator(subjectReader, unexpectedReader, because, becauseArgs); + var xmlReaderValidator = new XmlReaderValidator(assertionChain, subjectReader, unexpectedReader, because, becauseArgs); xmlReaderValidator.Validate(shouldBeEquivalent: false); } diff --git a/Src/FluentAssertions/XmlAssertionExtensions.cs b/Src/FluentAssertions/XmlAssertionExtensions.cs index 2db9562915..79da764778 100644 --- a/Src/FluentAssertions/XmlAssertionExtensions.cs +++ b/Src/FluentAssertions/XmlAssertionExtensions.cs @@ -1,6 +1,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Xml; +using FluentAssertions.Execution; using FluentAssertions.Xml; namespace FluentAssertions; @@ -10,11 +11,11 @@ public static class XmlAssertionExtensions { public static XmlNodeAssertions Should([NotNull] this XmlNode actualValue) { - return new XmlNodeAssertions(actualValue); + return new XmlNodeAssertions(actualValue, AssertionChain.GetOrCreate()); } public static XmlElementAssertions Should([NotNull] this XmlElement actualValue) { - return new XmlElementAssertions(actualValue); + return new XmlElementAssertions(actualValue, AssertionChain.GetOrCreate()); } } diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net47.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net47.verified.txt index ce88279d5e..137f4bdcb7 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net47.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net47.verified.txt @@ -15,12 +15,14 @@ namespace FluentAssertions public AndConstraint(TParent parent) { } public TParent And { get; } } - public class AndWhichConstraint : FluentAssertions.AndConstraint + public class AndWhichConstraint : FluentAssertions.AndConstraint { - public AndWhichConstraint(TParentConstraint parentConstraint, System.Collections.Generic.IEnumerable matchedConstraint) { } - public AndWhichConstraint(TParentConstraint parentConstraint, TMatchedElement matchedConstraint) { } - public TMatchedElement Subject { get; } - public TMatchedElement Which { get; } + public AndWhichConstraint(TParent parent, System.Collections.Generic.IEnumerable subjects) { } + public AndWhichConstraint(TParent parent, TSubject subject) { } + public AndWhichConstraint(TParent parent, System.Collections.Generic.IEnumerable subjects, FluentAssertions.Execution.AssertionChain assertionChain, string pathPostfix) { } + public AndWhichConstraint(TParent parent, TSubject subject, FluentAssertions.Execution.AssertionChain assertionChain, string pathPostfix = "") { } + public TSubject Subject { get; } + public TSubject Which { get; } } public static class AssertionExtensions { @@ -67,7 +69,6 @@ namespace FluentAssertions public static FluentAssertions.Specialized.NonGenericAsyncFunctionAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Func action) { } public static FluentAssertions.Primitives.GuidAssertions Should(this System.Guid actualValue) { } public static FluentAssertions.Primitives.NullableGuidAssertions Should(this System.Guid? actualValue) { } - public static FluentAssertions.Streams.BufferedStreamAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.IO.BufferedStream actualValue) { } public static FluentAssertions.Streams.StreamAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.IO.Stream actualValue) { } public static FluentAssertions.Primitives.HttpResponseMessageAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Net.Http.HttpResponseMessage actualValue) { } public static FluentAssertions.Types.AssemblyAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Reflection.Assembly assembly) { } @@ -376,18 +377,18 @@ namespace FluentAssertions.Collections { public class GenericCollectionAssertions : FluentAssertions.Collections.GenericCollectionAssertions, T, FluentAssertions.Collections.GenericCollectionAssertions> { - public GenericCollectionAssertions(System.Collections.Generic.IEnumerable actualValue) { } + public GenericCollectionAssertions(System.Collections.Generic.IEnumerable actualValue, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class GenericCollectionAssertions : FluentAssertions.Collections.GenericCollectionAssertions> where TCollection : System.Collections.Generic.IEnumerable { - public GenericCollectionAssertions(TCollection actualValue) { } + public GenericCollectionAssertions(TCollection actualValue, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class GenericCollectionAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions where TCollection : System.Collections.Generic.IEnumerable where TAssertions : FluentAssertions.Collections.GenericCollectionAssertions { - public GenericCollectionAssertions(TCollection actualValue) { } + public GenericCollectionAssertions(TCollection actualValue, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint AllBeAssignableTo(System.Type expectedType, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint> AllBeAssignableTo(string because = "", params object[] becauseArgs) { } @@ -461,7 +462,7 @@ namespace FluentAssertions.Collections public FluentAssertions.AndConstraint NotBeSubsetOf(System.Collections.Generic.IEnumerable unexpectedSuperset, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotContain(System.Collections.Generic.IEnumerable unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotContain(System.Linq.Expressions.Expression> predicate, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint NotContain(T unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotContain(T unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotContainEquivalentOf(TExpectation unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotContainEquivalentOf(TExpectation unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotContainInConsecutiveOrder(params T[] unexpected) { } @@ -492,13 +493,13 @@ namespace FluentAssertions.Collections public class GenericDictionaryAssertions : FluentAssertions.Collections.GenericDictionaryAssertions> where TCollection : System.Collections.Generic.IEnumerable> { - public GenericDictionaryAssertions(TCollection keyValuePairs) { } + public GenericDictionaryAssertions(TCollection keyValuePairs, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class GenericDictionaryAssertions : FluentAssertions.Collections.GenericCollectionAssertions, TAssertions> where TCollection : System.Collections.Generic.IEnumerable> where TAssertions : FluentAssertions.Collections.GenericDictionaryAssertions { - public GenericDictionaryAssertions(TCollection keyValuePairs) { } + public GenericDictionaryAssertions(TCollection keyValuePairs, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint BeEquivalentTo(TExpectation expectation, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEquivalentTo(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } @@ -510,8 +511,8 @@ namespace FluentAssertions.Collections public FluentAssertions.AndConstraint ContainKeys(params TKey[] expected) { } public FluentAssertions.AndConstraint ContainKeys(System.Collections.Generic.IEnumerable expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint ContainValue(TValue expected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint ContainValues(params TValue[] expected) { } - public FluentAssertions.AndConstraint ContainValues(System.Collections.Generic.IEnumerable expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint> ContainValues(params TValue[] expected) { } + public FluentAssertions.AndWhichConstraint> ContainValues(System.Collections.Generic.IEnumerable expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Equal(T expected, string because = "", params object[] becauseArgs) where T : System.Collections.Generic.IEnumerable> { } public FluentAssertions.AndConstraint NotContain(params System.Collections.Generic.KeyValuePair[] items) { } @@ -529,18 +530,18 @@ namespace FluentAssertions.Collections } public class StringCollectionAssertions : FluentAssertions.Collections.StringCollectionAssertions> { - public StringCollectionAssertions(System.Collections.Generic.IEnumerable actualValue) { } + public StringCollectionAssertions(System.Collections.Generic.IEnumerable actualValue, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class StringCollectionAssertions : FluentAssertions.Collections.StringCollectionAssertions> where TCollection : System.Collections.Generic.IEnumerable { - public StringCollectionAssertions(TCollection actualValue) { } + public StringCollectionAssertions(TCollection actualValue, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class StringCollectionAssertions : FluentAssertions.Collections.GenericCollectionAssertions where TCollection : System.Collections.Generic.IEnumerable where TAssertions : FluentAssertions.Collections.StringCollectionAssertions { - public StringCollectionAssertions(TCollection actualValue) { } + public StringCollectionAssertions(TCollection actualValue, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint AllBe(string expectation, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint AllBe(string expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEquivalentTo(params string[] expectation) { } @@ -553,18 +554,18 @@ namespace FluentAssertions.Collections } public class SubsequentOrderingAssertions : FluentAssertions.Collections.SubsequentOrderingGenericCollectionAssertions, T, FluentAssertions.Collections.SubsequentOrderingAssertions> { - public SubsequentOrderingAssertions(System.Collections.Generic.IEnumerable actualValue, System.Linq.IOrderedEnumerable previousOrderedEnumerable) { } + public SubsequentOrderingAssertions(System.Collections.Generic.IEnumerable actualValue, System.Linq.IOrderedEnumerable previousOrderedEnumerable, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class SubsequentOrderingGenericCollectionAssertions : FluentAssertions.Collections.SubsequentOrderingGenericCollectionAssertions> where TCollection : System.Collections.Generic.IEnumerable { - public SubsequentOrderingGenericCollectionAssertions(TCollection actualValue, System.Linq.IOrderedEnumerable previousOrderedEnumerable) { } + public SubsequentOrderingGenericCollectionAssertions(TCollection actualValue, System.Linq.IOrderedEnumerable previousOrderedEnumerable, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class SubsequentOrderingGenericCollectionAssertions : FluentAssertions.Collections.GenericCollectionAssertions where TCollection : System.Collections.Generic.IEnumerable where TAssertions : FluentAssertions.Collections.SubsequentOrderingGenericCollectionAssertions { - public SubsequentOrderingGenericCollectionAssertions(TCollection actualValue, System.Linq.IOrderedEnumerable previousOrderedEnumerable) { } + public SubsequentOrderingGenericCollectionAssertions(TCollection actualValue, System.Linq.IOrderedEnumerable previousOrderedEnumerable, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint> ThenBeInAscendingOrder(System.Linq.Expressions.Expression> propertyExpression, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint> ThenBeInAscendingOrder(System.Linq.Expressions.Expression> propertyExpression, System.Collections.Generic.IComparer comparer, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint> ThenBeInDescendingOrder(System.Linq.Expressions.Expression> propertyExpression, string because = "", params object[] becauseArgs) { } @@ -705,7 +706,7 @@ namespace FluentAssertions.Equivalency { protected EquivalencyStep() { } public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } - protected abstract FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nested); + protected abstract FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nestedValidator); } public class EquivalencyValidationContext : FluentAssertions.Equivalency.IEquivalencyValidationContext { @@ -797,7 +798,7 @@ namespace FluentAssertions.Equivalency } public interface IMemberMatchingRule { - FluentAssertions.Equivalency.IMember Match(FluentAssertions.Equivalency.IMember expectedMember, object subject, FluentAssertions.Equivalency.INode parent, FluentAssertions.Equivalency.IEquivalencyOptions options); + FluentAssertions.Equivalency.IMember Match(FluentAssertions.Equivalency.IMember expectedMember, object subject, FluentAssertions.Equivalency.INode parent, FluentAssertions.Equivalency.IEquivalencyOptions options, FluentAssertions.Execution.AssertionChain assertionChain); } public interface IMemberSelectionRule { @@ -959,7 +960,7 @@ namespace FluentAssertions.Equivalency.Steps { public class AssertionRuleEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { - public AssertionRuleEquivalencyStep(System.Linq.Expressions.Expression> predicate, System.Action> assertion) { } + public AssertionRuleEquivalencyStep(System.Linq.Expressions.Expression> predicate, System.Action> assertionAction) { } public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } public override string ToString() { } } @@ -972,7 +973,7 @@ namespace FluentAssertions.Equivalency.Steps public class DictionaryEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep { public DictionaryEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nested) { } + protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nestedValidator) { } } public class EnumEqualityStep : FluentAssertions.Equivalency.IEquivalencyStep { @@ -1033,17 +1034,17 @@ namespace FluentAssertions.Equivalency.Steps public class XAttributeEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep { public XAttributeEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nested) { } + protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nestedValidator) { } } public class XDocumentEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep { public XDocumentEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nested) { } + protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nestedValidator) { } } public class XElementEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep { public XElementEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nested) { } + protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nestedValidator) { } } } namespace FluentAssertions.Equivalency.Tracing @@ -1073,7 +1074,7 @@ namespace FluentAssertions.Events { public class EventAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions> { - protected EventAssertions(FluentAssertions.Events.IMonitor monitor) { } + protected EventAssertions(FluentAssertions.Events.IMonitor monitor, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.Events.IMonitor Monitor { get; } public void NotRaise(string eventName, string because = "", params object[] becauseArgs) { } @@ -1119,76 +1120,63 @@ namespace FluentAssertions.Events } namespace FluentAssertions.Execution { + public sealed class AssertionChain + { + public string CallerIdentifier { get; } + public bool HasOverriddenCallerIdentifier { get; } + public bool Succeeded { get; } + public FluentAssertions.Execution.AssertionChain UsingLineBreaks { get; } + public void AddReportable(string key, System.Func getValue) { } + public void AddReportable(string key, string value) { } + public FluentAssertions.Execution.AssertionChain BecauseOf(FluentAssertions.Execution.Reason reason) { } + public FluentAssertions.Execution.AssertionChain BecauseOf(string because, params object[] becauseArgs) { } + public FluentAssertions.Execution.Continuation FailWith(System.Func getFailureReason) { } + public FluentAssertions.Execution.Continuation FailWith(string message) { } + public FluentAssertions.Execution.Continuation FailWith(string message, params System.Func[] argProviders) { } + public FluentAssertions.Execution.Continuation FailWith(string message, params object[] args) { } + public FluentAssertions.Execution.AssertionChain ForCondition(bool condition) { } + public FluentAssertions.Execution.AssertionChain ForConstraint(FluentAssertions.OccurrenceConstraint constraint, int actualOccurrences) { } + public FluentAssertions.Execution.GivenSelector Given(System.Func selector) { } + public void OverrideCallerIdentifier(System.Func getCallerIdentifier) { } + public void ReuseOnce() { } + public FluentAssertions.Execution.AssertionChain WithCallerPostfix(string postfix) { } + public FluentAssertions.Execution.AssertionChain WithCallerPrefix(string prefix) { } + public FluentAssertions.Execution.AssertionChain WithDefaultIdentifier(string identifier) { } + public FluentAssertions.Execution.Continuation WithExpectation(string message, System.Action chain) { } + public FluentAssertions.Execution.Continuation WithExpectation(string message, object arg1, System.Action chain) { } + public FluentAssertions.Execution.Continuation WithExpectation(string message, object arg1, object arg2, System.Action chain) { } + public FluentAssertions.Execution.AssertionChain WithReportable(string name, System.Func content) { } + public static FluentAssertions.Execution.AssertionChain GetOrCreate() { } + } [System.Serializable] public class AssertionFailedException : System.Exception { public AssertionFailedException(string message) { } protected AssertionFailedException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } } - public sealed class AssertionScope : FluentAssertions.Execution.IAssertionScope, System.IDisposable + public sealed class AssertionScope : System.IDisposable { public AssertionScope() { } public AssertionScope(FluentAssertions.Execution.IAssertionStrategy assertionStrategy) { } - public AssertionScope(System.Lazy context) { } - public AssertionScope(string context) { } - public string CallerIdentity { get; } - public System.Lazy Context { get; set; } + public AssertionScope(System.Func name) { } + public AssertionScope(string name) { } public FluentAssertions.Formatting.FormattingOptions FormattingOptions { get; } - public FluentAssertions.Execution.AssertionScope UsingLineBreaks { get; } + public System.Func Name { get; } public static FluentAssertions.Execution.AssertionScope Current { get; } - public void AddNonReportable(string key, object value) { } public void AddPreFormattedFailure(string formattedFailureMessage) { } - public void AddReportable(string key, System.Func valueFunc) { } - public void AddReportable(string key, string value) { } public void AppendTracing(string tracingBlock) { } - public void AssumeSingleCaller() { } - public FluentAssertions.Execution.AssertionScope BecauseOf(FluentAssertions.Execution.Reason reason) { } - public FluentAssertions.Execution.AssertionScope BecauseOf(string because, params object[] becauseArgs) { } - public FluentAssertions.Execution.Continuation ClearExpectation() { } public string[] Discard() { } public void Dispose() { } - public FluentAssertions.Execution.Continuation FailWith(System.Func failReasonFunc) { } - public FluentAssertions.Execution.Continuation FailWith(string message) { } - public FluentAssertions.Execution.Continuation FailWith(string message, params System.Func[] argProviders) { } - public FluentAssertions.Execution.Continuation FailWith(string message, params object[] args) { } - public FluentAssertions.Execution.AssertionScope ForCondition(bool condition) { } - public FluentAssertions.Execution.AssertionScope ForConstraint(FluentAssertions.OccurrenceConstraint constraint, int actualOccurrences) { } - public T Get(string key) { } - public FluentAssertions.Execution.GivenSelector Given(System.Func selector) { } public bool HasFailures() { } - public FluentAssertions.Execution.AssertionScope WithDefaultIdentifier(string identifier) { } - public FluentAssertions.Execution.AssertionScope WithExpectation(string message, params object[] args) { } } public class Continuation { - public FluentAssertions.Execution.IAssertionScope Then { get; } - public static bool op_Implicit(FluentAssertions.Execution.Continuation continuation) { } + public FluentAssertions.Execution.AssertionChain Then { get; } } public class ContinuationOfGiven { + public bool Succeeded { get; } public FluentAssertions.Execution.GivenSelector Then { get; } - public static bool op_Implicit(FluentAssertions.Execution.ContinuationOfGiven continuationOfGiven) { } - } - public sealed class ContinuedAssertionScope : FluentAssertions.Execution.IAssertionScope, System.IDisposable - { - public FluentAssertions.Execution.IAssertionScope UsingLineBreaks { get; } - public FluentAssertions.Execution.IAssertionScope BecauseOf(string because, params object[] becauseArgs) { } - public FluentAssertions.Execution.Continuation ClearExpectation() { } - public string[] Discard() { } - public void Dispose() { } - public FluentAssertions.Execution.Continuation FailWith(System.Func failReasonFunc) { } - public FluentAssertions.Execution.Continuation FailWith(string message) { } - public FluentAssertions.Execution.Continuation FailWith(string message, params System.Func[] argProviders) { } - public FluentAssertions.Execution.Continuation FailWith(string message, params object[] args) { } - public FluentAssertions.Execution.IAssertionScope ForCondition(bool condition) { } - public FluentAssertions.Execution.IAssertionScope ForConstraint(FluentAssertions.OccurrenceConstraint constraint, int actualOccurrences) { } - public FluentAssertions.Execution.GivenSelector Given(System.Func selector) { } - public FluentAssertions.Execution.IAssertionScope WithDefaultIdentifier(string identifier) { } - public FluentAssertions.Execution.IAssertionScope WithExpectation(string message, params object[] args) { } - } - public static class Execute - { - public static FluentAssertions.Execution.AssertionScope Assertion { get; } } public class FailReason { @@ -1198,29 +1186,13 @@ namespace FluentAssertions.Execution } public class GivenSelector { - public FluentAssertions.Execution.ContinuationOfGiven ClearExpectation() { } + public bool Succeeded { get; } public FluentAssertions.Execution.ContinuationOfGiven FailWith(string message) { } public FluentAssertions.Execution.ContinuationOfGiven FailWith(string message, params System.Func[] args) { } public FluentAssertions.Execution.ContinuationOfGiven FailWith(string message, params object[] args) { } public FluentAssertions.Execution.GivenSelector ForCondition(System.Func predicate) { } public FluentAssertions.Execution.GivenSelector Given(System.Func selector) { } } - public interface IAssertionScope : System.IDisposable - { - FluentAssertions.Execution.IAssertionScope UsingLineBreaks { get; } - FluentAssertions.Execution.IAssertionScope BecauseOf(string because, params object[] becauseArgs); - FluentAssertions.Execution.Continuation ClearExpectation(); - string[] Discard(); - FluentAssertions.Execution.Continuation FailWith(System.Func failReasonFunc); - FluentAssertions.Execution.Continuation FailWith(string message); - FluentAssertions.Execution.Continuation FailWith(string message, params System.Func[] argProviders); - FluentAssertions.Execution.Continuation FailWith(string message, params object[] args); - FluentAssertions.Execution.IAssertionScope ForCondition(bool condition); - FluentAssertions.Execution.IAssertionScope ForConstraint(FluentAssertions.OccurrenceConstraint constraint, int actualOccurrences); - FluentAssertions.Execution.GivenSelector Given(System.Func selector); - FluentAssertions.Execution.IAssertionScope WithDefaultIdentifier(string identifier); - FluentAssertions.Execution.IAssertionScope WithExpectation(string message, params object[] args); - } public interface IAssertionStrategy { System.Collections.Generic.IEnumerable FailureMessages { get; } @@ -1463,6 +1435,12 @@ namespace FluentAssertions.Formatting public MaxLinesExceededException(string message) { } public MaxLinesExceededException(string message, System.Exception innerException) { } } + public class MethodInfoFormatter : FluentAssertions.Formatting.IValueFormatter + { + public MethodInfoFormatter() { } + public bool CanHandle(object value) { } + public void Format(object value, FluentAssertions.Formatting.FormattedObjectGraph formattedGraph, FluentAssertions.Formatting.FormattingContext context, FluentAssertions.Formatting.FormatChild formatChild) { } + } public class MultidimensionalArrayFormatter : FluentAssertions.Formatting.IValueFormatter { public MultidimensionalArrayFormatter() { } @@ -1569,12 +1547,12 @@ namespace FluentAssertions.Numeric { public class ComparableTypeAssertions : FluentAssertions.Numeric.ComparableTypeAssertions> { - public ComparableTypeAssertions(System.IComparable value) { } + public ComparableTypeAssertions(System.IComparable value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class ComparableTypeAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions, TAssertions> where TAssertions : FluentAssertions.Numeric.ComparableTypeAssertions { - public ComparableTypeAssertions(System.IComparable value) { } + public ComparableTypeAssertions(System.IComparable value, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint Be(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEquivalentTo(TExpectation expectation, string because = "", params object[] becauseArgs) { } @@ -1594,13 +1572,13 @@ namespace FluentAssertions.Numeric public class NullableNumericAssertions : FluentAssertions.Numeric.NullableNumericAssertions> where T : struct, System.IComparable { - public NullableNumericAssertions(T? value) { } + public NullableNumericAssertions(T? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class NullableNumericAssertions : FluentAssertions.Numeric.NumericAssertions where T : struct, System.IComparable where TAssertions : FluentAssertions.Numeric.NullableNumericAssertions { - public NullableNumericAssertions(T? value) { } + public NullableNumericAssertions(T? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint BeNull(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveValue(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Match(System.Linq.Expressions.Expression> predicate, string because = "", params object[] becauseArgs) { } @@ -1610,13 +1588,14 @@ namespace FluentAssertions.Numeric public class NumericAssertions : FluentAssertions.Numeric.NumericAssertions> where T : struct, System.IComparable { - public NumericAssertions(T value) { } + public NumericAssertions(T value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class NumericAssertions where T : struct, System.IComparable where TAssertions : FluentAssertions.Numeric.NumericAssertions { - public NumericAssertions(T value) { } + public NumericAssertions(T value, FluentAssertions.Execution.AssertionChain assertionChain) { } + public FluentAssertions.Execution.AssertionChain CurrentAssertionChain { get; } public T? Subject { get; } public FluentAssertions.AndConstraint Be(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Be(T? expected, string because = "", params object[] becauseArgs) { } @@ -1642,12 +1621,12 @@ namespace FluentAssertions.Primitives { public class BooleanAssertions : FluentAssertions.Primitives.BooleanAssertions { - public BooleanAssertions(bool? value) { } + public BooleanAssertions(bool? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class BooleanAssertions where TAssertions : FluentAssertions.Primitives.BooleanAssertions { - public BooleanAssertions(bool? value) { } + public BooleanAssertions(bool? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public bool? Subject { get; } public FluentAssertions.AndConstraint Be(bool expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeFalse(string because = "", params object[] becauseArgs) { } @@ -1658,12 +1637,12 @@ namespace FluentAssertions.Primitives } public class DateTimeAssertions : FluentAssertions.Primitives.DateTimeAssertions { - public DateTimeAssertions(System.DateTime? value) { } + public DateTimeAssertions(System.DateTime? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class DateTimeAssertions where TAssertions : FluentAssertions.Primitives.DateTimeAssertions { - public DateTimeAssertions(System.DateTime? value) { } + public DateTimeAssertions(System.DateTime? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public System.DateTime? Subject { get; } public FluentAssertions.AndConstraint Be(System.DateTime expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Be(System.DateTime? expected, string because = "", params object[] becauseArgs) { } @@ -1708,12 +1687,12 @@ namespace FluentAssertions.Primitives } public class DateTimeOffsetAssertions : FluentAssertions.Primitives.DateTimeOffsetAssertions { - public DateTimeOffsetAssertions(System.DateTimeOffset? value) { } + public DateTimeOffsetAssertions(System.DateTimeOffset? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class DateTimeOffsetAssertions where TAssertions : FluentAssertions.Primitives.DateTimeOffsetAssertions { - public DateTimeOffsetAssertions(System.DateTimeOffset? value) { } + public DateTimeOffsetAssertions(System.DateTimeOffset? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public System.DateTimeOffset? Subject { get; } public FluentAssertions.AndConstraint Be(System.DateTimeOffset expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Be(System.DateTimeOffset? expected, string because = "", params object[] becauseArgs) { } @@ -1763,7 +1742,7 @@ namespace FluentAssertions.Primitives public class DateTimeOffsetRangeAssertions where TAssertions : FluentAssertions.Primitives.DateTimeOffsetAssertions { - protected DateTimeOffsetRangeAssertions(TAssertions parentAssertions, System.DateTimeOffset? subject, FluentAssertions.Primitives.TimeSpanCondition condition, System.TimeSpan timeSpan) { } + protected DateTimeOffsetRangeAssertions(TAssertions parentAssertions, FluentAssertions.Execution.AssertionChain assertionChain, System.DateTimeOffset? subject, FluentAssertions.Primitives.TimeSpanCondition condition, System.TimeSpan timeSpan) { } public FluentAssertions.AndConstraint After(System.DateTimeOffset target, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Before(System.DateTimeOffset target, string because = "", params object[] becauseArgs) { } public override bool Equals(object obj) { } @@ -1771,7 +1750,7 @@ namespace FluentAssertions.Primitives public class DateTimeRangeAssertions where TAssertions : FluentAssertions.Primitives.DateTimeAssertions { - protected DateTimeRangeAssertions(TAssertions parentAssertions, System.DateTime? subject, FluentAssertions.Primitives.TimeSpanCondition condition, System.TimeSpan timeSpan) { } + protected DateTimeRangeAssertions(TAssertions parentAssertions, FluentAssertions.Execution.AssertionChain assertionChain, System.DateTime? subject, FluentAssertions.Primitives.TimeSpanCondition condition, System.TimeSpan timeSpan) { } public FluentAssertions.AndConstraint After(System.DateTime target, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Before(System.DateTime target, string because = "", params object[] becauseArgs) { } public override bool Equals(object obj) { } @@ -1779,13 +1758,13 @@ namespace FluentAssertions.Primitives public class EnumAssertions : FluentAssertions.Primitives.EnumAssertions> where TEnum : struct, System.Enum { - public EnumAssertions(TEnum subject) { } + public EnumAssertions(TEnum subject, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class EnumAssertions where TEnum : struct, System.Enum where TAssertions : FluentAssertions.Primitives.EnumAssertions { - public EnumAssertions(TEnum subject) { } + public EnumAssertions(TEnum subject, FluentAssertions.Execution.AssertionChain assertionChain) { } public TEnum? Subject { get; } public FluentAssertions.AndConstraint Be(TEnum expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Be(TEnum? expected, string because = "", params object[] becauseArgs) { } @@ -1812,12 +1791,12 @@ namespace FluentAssertions.Primitives } public class GuidAssertions : FluentAssertions.Primitives.GuidAssertions { - public GuidAssertions(System.Guid? value) { } + public GuidAssertions(System.Guid? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class GuidAssertions where TAssertions : FluentAssertions.Primitives.GuidAssertions { - public GuidAssertions(System.Guid? value) { } + public GuidAssertions(System.Guid? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public System.Guid? Subject { get; } public FluentAssertions.AndConstraint Be(System.Guid expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Be(string expected, string because = "", params object[] becauseArgs) { } @@ -1829,12 +1808,12 @@ namespace FluentAssertions.Primitives } public class HttpResponseMessageAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions { - public HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { } + public HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class HttpResponseMessageAssertions : FluentAssertions.Primitives.ObjectAssertions where TAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions { - protected HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { } + protected HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint BeRedirection(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeSuccessful(string because = "", params object[] becauseArgs) { } @@ -1846,12 +1825,12 @@ namespace FluentAssertions.Primitives } public class NullableBooleanAssertions : FluentAssertions.Primitives.NullableBooleanAssertions { - public NullableBooleanAssertions(bool? value) { } + public NullableBooleanAssertions(bool? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class NullableBooleanAssertions : FluentAssertions.Primitives.BooleanAssertions where TAssertions : FluentAssertions.Primitives.NullableBooleanAssertions { - public NullableBooleanAssertions(bool? value) { } + public NullableBooleanAssertions(bool? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint Be(bool? expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeNull(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveValue(string because = "", params object[] becauseArgs) { } @@ -1863,12 +1842,12 @@ namespace FluentAssertions.Primitives } public class NullableDateTimeAssertions : FluentAssertions.Primitives.NullableDateTimeAssertions { - public NullableDateTimeAssertions(System.DateTime? expected) { } + public NullableDateTimeAssertions(System.DateTime? expected, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class NullableDateTimeAssertions : FluentAssertions.Primitives.DateTimeAssertions where TAssertions : FluentAssertions.Primitives.NullableDateTimeAssertions { - public NullableDateTimeAssertions(System.DateTime? expected) { } + public NullableDateTimeAssertions(System.DateTime? expected, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint BeNull(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveValue(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeNull(string because = "", params object[] becauseArgs) { } @@ -1876,12 +1855,12 @@ namespace FluentAssertions.Primitives } public class NullableDateTimeOffsetAssertions : FluentAssertions.Primitives.NullableDateTimeOffsetAssertions { - public NullableDateTimeOffsetAssertions(System.DateTimeOffset? expected) { } + public NullableDateTimeOffsetAssertions(System.DateTimeOffset? expected, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class NullableDateTimeOffsetAssertions : FluentAssertions.Primitives.DateTimeOffsetAssertions where TAssertions : FluentAssertions.Primitives.NullableDateTimeOffsetAssertions { - public NullableDateTimeOffsetAssertions(System.DateTimeOffset? expected) { } + public NullableDateTimeOffsetAssertions(System.DateTimeOffset? expected, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint BeNull(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveValue(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeNull(string because = "", params object[] becauseArgs) { } @@ -1890,13 +1869,13 @@ namespace FluentAssertions.Primitives public class NullableEnumAssertions : FluentAssertions.Primitives.NullableEnumAssertions> where TEnum : struct, System.Enum { - public NullableEnumAssertions(TEnum? subject) { } + public NullableEnumAssertions(TEnum? subject, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class NullableEnumAssertions : FluentAssertions.Primitives.EnumAssertions where TEnum : struct, System.Enum where TAssertions : FluentAssertions.Primitives.NullableEnumAssertions { - public NullableEnumAssertions(TEnum? subject) { } + public NullableEnumAssertions(TEnum? subject, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint BeNull(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint HaveValue(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint NotBeNull(string because = "", params object[] becauseArgs) { } @@ -1904,12 +1883,12 @@ namespace FluentAssertions.Primitives } public class NullableGuidAssertions : FluentAssertions.Primitives.NullableGuidAssertions { - public NullableGuidAssertions(System.Guid? value) { } + public NullableGuidAssertions(System.Guid? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class NullableGuidAssertions : FluentAssertions.Primitives.GuidAssertions where TAssertions : FluentAssertions.Primitives.NullableGuidAssertions { - public NullableGuidAssertions(System.Guid? value) { } + public NullableGuidAssertions(System.Guid? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint Be(System.Guid? expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeNull(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveValue(string because = "", params object[] becauseArgs) { } @@ -1918,12 +1897,12 @@ namespace FluentAssertions.Primitives } public class NullableSimpleTimeSpanAssertions : FluentAssertions.Primitives.NullableSimpleTimeSpanAssertions { - public NullableSimpleTimeSpanAssertions(System.TimeSpan? value) { } + public NullableSimpleTimeSpanAssertions(System.TimeSpan? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class NullableSimpleTimeSpanAssertions : FluentAssertions.Primitives.SimpleTimeSpanAssertions where TAssertions : FluentAssertions.Primitives.NullableSimpleTimeSpanAssertions { - public NullableSimpleTimeSpanAssertions(System.TimeSpan? value) { } + public NullableSimpleTimeSpanAssertions(System.TimeSpan? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint Be(System.TimeSpan? expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeNull(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveValue(string because = "", params object[] becauseArgs) { } @@ -1932,7 +1911,7 @@ namespace FluentAssertions.Primitives } public class ObjectAssertions : FluentAssertions.Primitives.ObjectAssertions { - public ObjectAssertions(object value) { } + public ObjectAssertions(object value, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint Be(TExpectation expected, System.Collections.Generic.IEqualityComparer comparer, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOneOf(System.Collections.Generic.IEnumerable validValues, System.Collections.Generic.IEqualityComparer comparer, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBe(TExpectation unexpected, System.Collections.Generic.IEqualityComparer comparer, string because = "", params object[] becauseArgs) { } @@ -1940,7 +1919,7 @@ namespace FluentAssertions.Primitives public class ObjectAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions where TAssertions : FluentAssertions.Primitives.ObjectAssertions { - public ObjectAssertions(TSubject value) { } + public ObjectAssertions(TSubject value, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint Be(TSubject expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Be(TSubject expected, System.Collections.Generic.IEqualityComparer comparer, string because = "", params object[] becauseArgs) { } @@ -1958,7 +1937,8 @@ namespace FluentAssertions.Primitives public abstract class ReferenceTypeAssertions where TAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions { - protected ReferenceTypeAssertions(TSubject subject) { } + protected ReferenceTypeAssertions(TSubject subject, FluentAssertions.Execution.AssertionChain assertionChain) { } + public FluentAssertions.Execution.AssertionChain CurrentAssertionChain { get; } protected abstract string Identifier { get; } public TSubject Subject { get; } public FluentAssertions.AndConstraint BeAssignableTo(System.Type type, string because = "", params object[] becauseArgs) { } @@ -1982,12 +1962,12 @@ namespace FluentAssertions.Primitives } public class SimpleTimeSpanAssertions : FluentAssertions.Primitives.SimpleTimeSpanAssertions { - public SimpleTimeSpanAssertions(System.TimeSpan? value) { } + public SimpleTimeSpanAssertions(System.TimeSpan? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class SimpleTimeSpanAssertions where TAssertions : FluentAssertions.Primitives.SimpleTimeSpanAssertions { - public SimpleTimeSpanAssertions(System.TimeSpan? value) { } + public SimpleTimeSpanAssertions(System.TimeSpan? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public System.TimeSpan? Subject { get; } public FluentAssertions.AndConstraint Be(System.TimeSpan expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeCloseTo(System.TimeSpan nearbyTime, System.TimeSpan precision, string because = "", params object[] becauseArgs) { } @@ -2003,12 +1983,12 @@ namespace FluentAssertions.Primitives } public class StringAssertions : FluentAssertions.Primitives.StringAssertions { - public StringAssertions(string value) { } + public StringAssertions(string value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class StringAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions where TAssertions : FluentAssertions.Primitives.StringAssertions { - public StringAssertions(string value) { } + public StringAssertions(string value, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint Be(string expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEmpty(string because = "", params object[] becauseArgs) { } @@ -2084,8 +2064,8 @@ namespace FluentAssertions.Specialized { public class ActionAssertions : FluentAssertions.Specialized.DelegateAssertions { - public ActionAssertions(System.Action subject, FluentAssertions.Specialized.IExtractExceptions extractor) { } - public ActionAssertions(System.Action subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Common.IClock clock) { } + public ActionAssertions(System.Action subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain) { } + public ActionAssertions(System.Action subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain, FluentAssertions.Common.IClock clock) { } protected override string Identifier { get; } protected override void InvokeSubject() { } public FluentAssertions.AndConstraint NotThrow(string because = "", params object[] becauseArgs) { } @@ -2095,7 +2075,7 @@ namespace FluentAssertions.Specialized where TTask : System.Threading.Tasks.Task where TAssertions : FluentAssertions.Specialized.AsyncFunctionAssertions { - protected AsyncFunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Common.IClock clock) { } + protected AsyncFunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain, FluentAssertions.Common.IClock clock) { } protected override string Identifier { get; } public System.Threading.Tasks.Task> NotCompleteWithinAsync(System.TimeSpan timeSpan, string because = "", params object[] becauseArgs) { } public System.Threading.Tasks.Task> NotThrowAsync(string because = "", params object[] becauseArgs) @@ -2121,7 +2101,7 @@ namespace FluentAssertions.Specialized where TDelegate : System.Delegate where TAssertions : FluentAssertions.Specialized.DelegateAssertions { - protected DelegateAssertions(TDelegate @delegate, FluentAssertions.Specialized.IExtractExceptions extractor) { } + protected DelegateAssertions(TDelegate @delegate, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain) { } protected abstract void InvokeSubject(); public FluentAssertions.AndConstraint NotThrow(string because = "", params object[] becauseArgs) where TException : System.Exception { } @@ -2133,7 +2113,7 @@ namespace FluentAssertions.Specialized public class ExceptionAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions, FluentAssertions.Specialized.ExceptionAssertions> where TException : System.Exception { - public ExceptionAssertions(System.Collections.Generic.IEnumerable exceptions) { } + public ExceptionAssertions(System.Collections.Generic.IEnumerable exceptions, FluentAssertions.Execution.AssertionChain assertionChain) { } public TException And { get; } protected override string Identifier { get; } public TException Which { get; } @@ -2155,7 +2135,7 @@ namespace FluentAssertions.Specialized } public class ExecutionTimeAssertions { - public ExecutionTimeAssertions(FluentAssertions.Specialized.ExecutionTime executionTime) { } + public ExecutionTimeAssertions(FluentAssertions.Specialized.ExecutionTime executionTime, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint BeCloseTo(System.TimeSpan expectedDuration, System.TimeSpan precision, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeGreaterThan(System.TimeSpan minDuration, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeGreaterThanOrEqualTo(System.TimeSpan minDuration, string because = "", params object[] becauseArgs) { } @@ -2165,8 +2145,8 @@ namespace FluentAssertions.Specialized } public class FunctionAssertions : FluentAssertions.Specialized.DelegateAssertions, FluentAssertions.Specialized.FunctionAssertions> { - public FunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor) { } - public FunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Common.IClock clock) { } + public FunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain) { } + public FunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain, FluentAssertions.Common.IClock clock) { } protected override string Identifier { get; } protected override void InvokeSubject() { } public FluentAssertions.AndWhichConstraint, T> NotThrow(string because = "", params object[] becauseArgs) { } @@ -2174,8 +2154,8 @@ namespace FluentAssertions.Specialized } public class GenericAsyncFunctionAssertions : FluentAssertions.Specialized.AsyncFunctionAssertions, FluentAssertions.Specialized.GenericAsyncFunctionAssertions> { - public GenericAsyncFunctionAssertions(System.Func> subject, FluentAssertions.Specialized.IExtractExceptions extractor) { } - public GenericAsyncFunctionAssertions(System.Func> subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Common.IClock clock) { } + public GenericAsyncFunctionAssertions(System.Func> subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain) { } + public GenericAsyncFunctionAssertions(System.Func> subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain, FluentAssertions.Common.IClock clock) { } public System.Threading.Tasks.Task, TResult>> CompleteWithinAsync(System.TimeSpan timeSpan, string because = "", params object[] becauseArgs) { } public System.Threading.Tasks.Task, TResult>> NotThrowAfterAsync(System.TimeSpan waitTime, System.TimeSpan pollInterval, string because = "", params object[] becauseArgs) { } public System.Threading.Tasks.Task, TResult>> NotThrowAsync(string because = "", params object[] becauseArgs) { } @@ -2191,8 +2171,8 @@ namespace FluentAssertions.Specialized } public class NonGenericAsyncFunctionAssertions : FluentAssertions.Specialized.AsyncFunctionAssertions { - public NonGenericAsyncFunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor) { } - public NonGenericAsyncFunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Common.IClock clock) { } + public NonGenericAsyncFunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain) { } + public NonGenericAsyncFunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain, FluentAssertions.Common.IClock clock) { } public System.Threading.Tasks.Task> CompleteWithinAsync(System.TimeSpan timeSpan, string because = "", params object[] becauseArgs) { } public System.Threading.Tasks.Task> NotThrowAfterAsync(System.TimeSpan waitTime, System.TimeSpan pollInterval, string because = "", params object[] becauseArgs) { } public System.Threading.Tasks.Task> NotThrowAsync(string because = "", params object[] becauseArgs) { } @@ -2204,33 +2184,23 @@ namespace FluentAssertions.Specialized } public class TaskCompletionSourceAssertions : FluentAssertions.Specialized.TaskCompletionSourceAssertionsBase { - public TaskCompletionSourceAssertions(System.Threading.Tasks.TaskCompletionSource tcs) { } - public TaskCompletionSourceAssertions(System.Threading.Tasks.TaskCompletionSource tcs, FluentAssertions.Common.IClock clock) { } + public TaskCompletionSourceAssertions(System.Threading.Tasks.TaskCompletionSource tcs, FluentAssertions.Execution.AssertionChain assertionChain) { } + public TaskCompletionSourceAssertions(System.Threading.Tasks.TaskCompletionSource tcs, FluentAssertions.Execution.AssertionChain assertionChain, FluentAssertions.Common.IClock clock) { } public System.Threading.Tasks.Task, T>> CompleteWithinAsync(System.TimeSpan timeSpan, string because = "", params object[] becauseArgs) { } public System.Threading.Tasks.Task>> NotCompleteWithinAsync(System.TimeSpan timeSpan, string because = "", params object[] becauseArgs) { } } } namespace FluentAssertions.Streams { - public class BufferedStreamAssertions : FluentAssertions.Streams.BufferedStreamAssertions - { - public BufferedStreamAssertions(System.IO.BufferedStream stream) { } - } - public class BufferedStreamAssertions : FluentAssertions.Streams.StreamAssertions - where TAssertions : FluentAssertions.Streams.BufferedStreamAssertions - { - public BufferedStreamAssertions(System.IO.BufferedStream stream) { } - protected override string Identifier { get; } - } public class StreamAssertions : FluentAssertions.Streams.StreamAssertions { - public StreamAssertions(System.IO.Stream stream) { } + public StreamAssertions(System.IO.Stream stream, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class StreamAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions where TSubject : System.IO.Stream where TAssertions : FluentAssertions.Streams.StreamAssertions { - public StreamAssertions(TSubject stream) { } + public StreamAssertions(TSubject stream, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint BeReadOnly(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeReadable(string because = "", params object[] becauseArgs) { } @@ -2256,7 +2226,7 @@ namespace FluentAssertions.Types } public class AssemblyAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions { - public AssemblyAssertions(System.Reflection.Assembly assembly) { } + public AssemblyAssertions(System.Reflection.Assembly assembly, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint BeSignedWithPublicKey(string publicKey, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeUnsigned(string because = "", params object[] becauseArgs) { } @@ -2266,15 +2236,17 @@ namespace FluentAssertions.Types } public class ConstructorInfoAssertions : FluentAssertions.Types.MethodBaseAssertions { - public ConstructorInfoAssertions(System.Reflection.ConstructorInfo constructorInfo) { } + public ConstructorInfoAssertions(System.Reflection.ConstructorInfo constructorInfo, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } + protected override string SubjectDescription { get; } } public abstract class MemberInfoAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions where TSubject : System.Reflection.MemberInfo where TAssertions : FluentAssertions.Types.MemberInfoAssertions { - protected MemberInfoAssertions(TSubject subject) { } + protected MemberInfoAssertions(TSubject subject, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } + protected virtual string SubjectDescription { get; } public FluentAssertions.AndWhichConstraint, TAttribute> BeDecoratedWith(string because = "", params object[] becauseArgs) where TAttribute : System.Attribute { } public FluentAssertions.AndWhichConstraint, TAttribute> BeDecoratedWith(System.Linq.Expressions.Expression> isMatchingAttributePredicate, string because = "", params object[] becauseArgs) @@ -2288,14 +2260,15 @@ namespace FluentAssertions.Types where TSubject : System.Reflection.MethodBase where TAssertions : FluentAssertions.Types.MethodBaseAssertions { - protected MethodBaseAssertions(TSubject subject) { } + protected MethodBaseAssertions(TSubject subject, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint HaveAccessModifier(FluentAssertions.Common.CSharpAccessModifier accessModifier, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveAccessModifier(FluentAssertions.Common.CSharpAccessModifier accessModifier, string because = "", params object[] becauseArgs) { } } public class MethodInfoAssertions : FluentAssertions.Types.MethodBaseAssertions { - public MethodInfoAssertions(System.Reflection.MethodInfo methodInfo) { } + public MethodInfoAssertions(System.Reflection.MethodInfo methodInfo, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } + protected override string SubjectDescription { get; } public FluentAssertions.AndConstraint BeAsync(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeVirtual(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeAsync(string because = "", params object[] becauseArgs) { } @@ -2338,7 +2311,7 @@ namespace FluentAssertions.Types } public class MethodInfoSelectorAssertions { - public MethodInfoSelectorAssertions(params System.Reflection.MethodInfo[] methods) { } + public MethodInfoSelectorAssertions(FluentAssertions.Execution.AssertionChain assertionChain, params System.Reflection.MethodInfo[] methods) { } protected string Context { get; } public System.Collections.Generic.IEnumerable SubjectMethods { get; } public FluentAssertions.AndConstraint Be(FluentAssertions.Common.CSharpAccessModifier accessModifier, string because = "", params object[] becauseArgs) { } @@ -2359,8 +2332,9 @@ namespace FluentAssertions.Types } public class PropertyInfoAssertions : FluentAssertions.Types.MemberInfoAssertions { - public PropertyInfoAssertions(System.Reflection.PropertyInfo propertyInfo) { } + public PropertyInfoAssertions(System.Reflection.PropertyInfo propertyInfo, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } + protected override string SubjectDescription { get; } public FluentAssertions.AndConstraint BeReadable(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeReadable(FluentAssertions.Common.CSharpAccessModifier accessModifier, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeVirtual(string because = "", params object[] becauseArgs) { } @@ -2401,7 +2375,7 @@ namespace FluentAssertions.Types } public class PropertyInfoSelectorAssertions { - public PropertyInfoSelectorAssertions(params System.Reflection.PropertyInfo[] properties) { } + public PropertyInfoSelectorAssertions(FluentAssertions.Execution.AssertionChain assertionChain, params System.Reflection.PropertyInfo[] properties) { } protected string Context { get; } public System.Collections.Generic.IEnumerable SubjectProperties { get; } public FluentAssertions.AndConstraint BeDecoratedWith(string because = "", params object[] becauseArgs) @@ -2416,7 +2390,7 @@ namespace FluentAssertions.Types } public class TypeAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions { - public TypeAssertions(System.Type type) { } + public TypeAssertions(System.Type type, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint Be(System.Type expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Be(string because = "", params object[] becauseArgs) { } @@ -2534,7 +2508,7 @@ namespace FluentAssertions.Types } public class TypeSelectorAssertions { - public TypeSelectorAssertions(params System.Type[] types) { } + public TypeSelectorAssertions(FluentAssertions.Execution.AssertionChain assertionChain, params System.Type[] types) { } public System.Collections.Generic.IEnumerable Subject { get; } public FluentAssertions.AndConstraint BeDecoratedWith(string because = "", params object[] becauseArgs) where TAttribute : System.Attribute { } @@ -2565,7 +2539,7 @@ namespace FluentAssertions.Xml { public class XAttributeAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions { - public XAttributeAssertions(System.Xml.Linq.XAttribute attribute) { } + public XAttributeAssertions(System.Xml.Linq.XAttribute attribute, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint Be(System.Xml.Linq.XAttribute expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveValue(string expected, string because = "", params object[] becauseArgs) { } @@ -2573,7 +2547,7 @@ namespace FluentAssertions.Xml } public class XDocumentAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions { - public XDocumentAssertions(System.Xml.Linq.XDocument document) { } + public XDocumentAssertions(System.Xml.Linq.XDocument document, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint Be(System.Xml.Linq.XDocument expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEquivalentTo(System.Xml.Linq.XDocument expected, string because = "", params object[] becauseArgs) { } @@ -2588,7 +2562,7 @@ namespace FluentAssertions.Xml } public class XElementAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions { - public XElementAssertions(System.Xml.Linq.XElement xElement) { } + public XElementAssertions(System.Xml.Linq.XElement xElement, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint Be(System.Xml.Linq.XElement expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEquivalentTo(System.Xml.Linq.XElement expected, string because = "", params object[] becauseArgs) { } @@ -2604,7 +2578,7 @@ namespace FluentAssertions.Xml } public class XmlElementAssertions : FluentAssertions.Xml.XmlNodeAssertions { - public XmlElementAssertions(System.Xml.XmlElement xmlElement) { } + public XmlElementAssertions(System.Xml.XmlElement xmlElement, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint HaveAttribute(string expectedName, string expectedValue, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveAttributeWithNamespace(string expectedName, string expectedNamespace, string expectedValue, string because = "", params object[] becauseArgs) { } @@ -2614,13 +2588,13 @@ namespace FluentAssertions.Xml } public class XmlNodeAssertions : FluentAssertions.Xml.XmlNodeAssertions { - public XmlNodeAssertions(System.Xml.XmlNode xmlNode) { } + public XmlNodeAssertions(System.Xml.XmlNode xmlNode, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class XmlNodeAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions where TSubject : System.Xml.XmlNode where TAssertions : FluentAssertions.Xml.XmlNodeAssertions { - public XmlNodeAssertions(TSubject xmlNode) { } + public XmlNodeAssertions(TSubject xmlNode, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint BeEquivalentTo(System.Xml.XmlNode expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEquivalentTo(System.Xml.XmlNode unexpected, string because = "", params object[] becauseArgs) { } diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net6.0.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net6.0.verified.txt index c9ad572ad9..1e6e21a99b 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net6.0.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net6.0.verified.txt @@ -15,12 +15,14 @@ namespace FluentAssertions public AndConstraint(TParent parent) { } public TParent And { get; } } - public class AndWhichConstraint : FluentAssertions.AndConstraint + public class AndWhichConstraint : FluentAssertions.AndConstraint { - public AndWhichConstraint(TParentConstraint parentConstraint, System.Collections.Generic.IEnumerable matchedConstraint) { } - public AndWhichConstraint(TParentConstraint parentConstraint, TMatchedElement matchedConstraint) { } - public TMatchedElement Subject { get; } - public TMatchedElement Which { get; } + public AndWhichConstraint(TParent parent, System.Collections.Generic.IEnumerable subjects) { } + public AndWhichConstraint(TParent parent, TSubject subject) { } + public AndWhichConstraint(TParent parent, System.Collections.Generic.IEnumerable subjects, FluentAssertions.Execution.AssertionChain assertionChain, string pathPostfix) { } + public AndWhichConstraint(TParent parent, TSubject subject, FluentAssertions.Execution.AssertionChain assertionChain, string pathPostfix = "") { } + public TSubject Subject { get; } + public TSubject Which { get; } } public static class AssertionExtensions { @@ -389,18 +391,18 @@ namespace FluentAssertions.Collections { public class GenericCollectionAssertions : FluentAssertions.Collections.GenericCollectionAssertions, T, FluentAssertions.Collections.GenericCollectionAssertions> { - public GenericCollectionAssertions(System.Collections.Generic.IEnumerable actualValue) { } + public GenericCollectionAssertions(System.Collections.Generic.IEnumerable actualValue, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class GenericCollectionAssertions : FluentAssertions.Collections.GenericCollectionAssertions> where TCollection : System.Collections.Generic.IEnumerable { - public GenericCollectionAssertions(TCollection actualValue) { } + public GenericCollectionAssertions(TCollection actualValue, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class GenericCollectionAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions where TCollection : System.Collections.Generic.IEnumerable where TAssertions : FluentAssertions.Collections.GenericCollectionAssertions { - public GenericCollectionAssertions(TCollection actualValue) { } + public GenericCollectionAssertions(TCollection actualValue, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint AllBeAssignableTo(System.Type expectedType, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint> AllBeAssignableTo(string because = "", params object[] becauseArgs) { } @@ -474,7 +476,7 @@ namespace FluentAssertions.Collections public FluentAssertions.AndConstraint NotBeSubsetOf(System.Collections.Generic.IEnumerable unexpectedSuperset, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotContain(System.Collections.Generic.IEnumerable unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotContain(System.Linq.Expressions.Expression> predicate, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint NotContain(T unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotContain(T unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotContainEquivalentOf(TExpectation unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotContainEquivalentOf(TExpectation unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotContainInConsecutiveOrder(params T[] unexpected) { } @@ -505,13 +507,13 @@ namespace FluentAssertions.Collections public class GenericDictionaryAssertions : FluentAssertions.Collections.GenericDictionaryAssertions> where TCollection : System.Collections.Generic.IEnumerable> { - public GenericDictionaryAssertions(TCollection keyValuePairs) { } + public GenericDictionaryAssertions(TCollection keyValuePairs, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class GenericDictionaryAssertions : FluentAssertions.Collections.GenericCollectionAssertions, TAssertions> where TCollection : System.Collections.Generic.IEnumerable> where TAssertions : FluentAssertions.Collections.GenericDictionaryAssertions { - public GenericDictionaryAssertions(TCollection keyValuePairs) { } + public GenericDictionaryAssertions(TCollection keyValuePairs, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint BeEquivalentTo(TExpectation expectation, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEquivalentTo(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } @@ -523,8 +525,8 @@ namespace FluentAssertions.Collections public FluentAssertions.AndConstraint ContainKeys(params TKey[] expected) { } public FluentAssertions.AndConstraint ContainKeys(System.Collections.Generic.IEnumerable expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint ContainValue(TValue expected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint ContainValues(params TValue[] expected) { } - public FluentAssertions.AndConstraint ContainValues(System.Collections.Generic.IEnumerable expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint> ContainValues(params TValue[] expected) { } + public FluentAssertions.AndWhichConstraint> ContainValues(System.Collections.Generic.IEnumerable expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Equal(T expected, string because = "", params object[] becauseArgs) where T : System.Collections.Generic.IEnumerable> { } public FluentAssertions.AndConstraint NotContain(params System.Collections.Generic.KeyValuePair[] items) { } @@ -542,18 +544,18 @@ namespace FluentAssertions.Collections } public class StringCollectionAssertions : FluentAssertions.Collections.StringCollectionAssertions> { - public StringCollectionAssertions(System.Collections.Generic.IEnumerable actualValue) { } + public StringCollectionAssertions(System.Collections.Generic.IEnumerable actualValue, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class StringCollectionAssertions : FluentAssertions.Collections.StringCollectionAssertions> where TCollection : System.Collections.Generic.IEnumerable { - public StringCollectionAssertions(TCollection actualValue) { } + public StringCollectionAssertions(TCollection actualValue, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class StringCollectionAssertions : FluentAssertions.Collections.GenericCollectionAssertions where TCollection : System.Collections.Generic.IEnumerable where TAssertions : FluentAssertions.Collections.StringCollectionAssertions { - public StringCollectionAssertions(TCollection actualValue) { } + public StringCollectionAssertions(TCollection actualValue, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint AllBe(string expectation, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint AllBe(string expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEquivalentTo(params string[] expectation) { } @@ -566,18 +568,18 @@ namespace FluentAssertions.Collections } public class SubsequentOrderingAssertions : FluentAssertions.Collections.SubsequentOrderingGenericCollectionAssertions, T, FluentAssertions.Collections.SubsequentOrderingAssertions> { - public SubsequentOrderingAssertions(System.Collections.Generic.IEnumerable actualValue, System.Linq.IOrderedEnumerable previousOrderedEnumerable) { } + public SubsequentOrderingAssertions(System.Collections.Generic.IEnumerable actualValue, System.Linq.IOrderedEnumerable previousOrderedEnumerable, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class SubsequentOrderingGenericCollectionAssertions : FluentAssertions.Collections.SubsequentOrderingGenericCollectionAssertions> where TCollection : System.Collections.Generic.IEnumerable { - public SubsequentOrderingGenericCollectionAssertions(TCollection actualValue, System.Linq.IOrderedEnumerable previousOrderedEnumerable) { } + public SubsequentOrderingGenericCollectionAssertions(TCollection actualValue, System.Linq.IOrderedEnumerable previousOrderedEnumerable, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class SubsequentOrderingGenericCollectionAssertions : FluentAssertions.Collections.GenericCollectionAssertions where TCollection : System.Collections.Generic.IEnumerable where TAssertions : FluentAssertions.Collections.SubsequentOrderingGenericCollectionAssertions { - public SubsequentOrderingGenericCollectionAssertions(TCollection actualValue, System.Linq.IOrderedEnumerable previousOrderedEnumerable) { } + public SubsequentOrderingGenericCollectionAssertions(TCollection actualValue, System.Linq.IOrderedEnumerable previousOrderedEnumerable, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint> ThenBeInAscendingOrder(System.Linq.Expressions.Expression> propertyExpression, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint> ThenBeInAscendingOrder(System.Linq.Expressions.Expression> propertyExpression, System.Collections.Generic.IComparer comparer, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint> ThenBeInDescendingOrder(System.Linq.Expressions.Expression> propertyExpression, string because = "", params object[] becauseArgs) { } @@ -718,7 +720,7 @@ namespace FluentAssertions.Equivalency { protected EquivalencyStep() { } public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } - protected abstract FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nested); + protected abstract FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nestedValidator); } public class EquivalencyValidationContext : FluentAssertions.Equivalency.IEquivalencyValidationContext { @@ -810,7 +812,7 @@ namespace FluentAssertions.Equivalency } public interface IMemberMatchingRule { - FluentAssertions.Equivalency.IMember Match(FluentAssertions.Equivalency.IMember expectedMember, object subject, FluentAssertions.Equivalency.INode parent, FluentAssertions.Equivalency.IEquivalencyOptions options); + FluentAssertions.Equivalency.IMember Match(FluentAssertions.Equivalency.IMember expectedMember, object subject, FluentAssertions.Equivalency.INode parent, FluentAssertions.Equivalency.IEquivalencyOptions options, FluentAssertions.Execution.AssertionChain assertionChain); } public interface IMemberSelectionRule { @@ -972,7 +974,7 @@ namespace FluentAssertions.Equivalency.Steps { public class AssertionRuleEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { - public AssertionRuleEquivalencyStep(System.Linq.Expressions.Expression> predicate, System.Action> assertion) { } + public AssertionRuleEquivalencyStep(System.Linq.Expressions.Expression> predicate, System.Action> assertionAction) { } public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } public override string ToString() { } } @@ -985,7 +987,7 @@ namespace FluentAssertions.Equivalency.Steps public class DictionaryEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep { public DictionaryEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nested) { } + protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nestedValidator) { } } public class EnumEqualityStep : FluentAssertions.Equivalency.IEquivalencyStep { @@ -1046,17 +1048,17 @@ namespace FluentAssertions.Equivalency.Steps public class XAttributeEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep { public XAttributeEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nested) { } + protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nestedValidator) { } } public class XDocumentEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep { public XDocumentEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nested) { } + protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nestedValidator) { } } public class XElementEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep { public XElementEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nested) { } + protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nestedValidator) { } } } namespace FluentAssertions.Equivalency.Tracing @@ -1086,7 +1088,7 @@ namespace FluentAssertions.Events { public class EventAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions> { - protected EventAssertions(FluentAssertions.Events.IMonitor monitor) { } + protected EventAssertions(FluentAssertions.Events.IMonitor monitor, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.Events.IMonitor Monitor { get; } public void NotRaise(string eventName, string because = "", params object[] becauseArgs) { } @@ -1132,76 +1134,63 @@ namespace FluentAssertions.Events } namespace FluentAssertions.Execution { + public sealed class AssertionChain + { + public string CallerIdentifier { get; } + public bool HasOverriddenCallerIdentifier { get; } + public bool Succeeded { get; } + public FluentAssertions.Execution.AssertionChain UsingLineBreaks { get; } + public void AddReportable(string key, System.Func getValue) { } + public void AddReportable(string key, string value) { } + public FluentAssertions.Execution.AssertionChain BecauseOf(FluentAssertions.Execution.Reason reason) { } + public FluentAssertions.Execution.AssertionChain BecauseOf(string because, params object[] becauseArgs) { } + public FluentAssertions.Execution.Continuation FailWith(System.Func getFailureReason) { } + public FluentAssertions.Execution.Continuation FailWith(string message) { } + public FluentAssertions.Execution.Continuation FailWith(string message, params System.Func[] argProviders) { } + public FluentAssertions.Execution.Continuation FailWith(string message, params object[] args) { } + public FluentAssertions.Execution.AssertionChain ForCondition(bool condition) { } + public FluentAssertions.Execution.AssertionChain ForConstraint(FluentAssertions.OccurrenceConstraint constraint, int actualOccurrences) { } + public FluentAssertions.Execution.GivenSelector Given(System.Func selector) { } + public void OverrideCallerIdentifier(System.Func getCallerIdentifier) { } + public void ReuseOnce() { } + public FluentAssertions.Execution.AssertionChain WithCallerPostfix(string postfix) { } + public FluentAssertions.Execution.AssertionChain WithCallerPrefix(string prefix) { } + public FluentAssertions.Execution.AssertionChain WithDefaultIdentifier(string identifier) { } + public FluentAssertions.Execution.Continuation WithExpectation(string message, System.Action chain) { } + public FluentAssertions.Execution.Continuation WithExpectation(string message, object arg1, System.Action chain) { } + public FluentAssertions.Execution.Continuation WithExpectation(string message, object arg1, object arg2, System.Action chain) { } + public FluentAssertions.Execution.AssertionChain WithReportable(string name, System.Func content) { } + public static FluentAssertions.Execution.AssertionChain GetOrCreate() { } + } [System.Serializable] public class AssertionFailedException : System.Exception { public AssertionFailedException(string message) { } protected AssertionFailedException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } } - public sealed class AssertionScope : FluentAssertions.Execution.IAssertionScope, System.IDisposable + public sealed class AssertionScope : System.IDisposable { public AssertionScope() { } public AssertionScope(FluentAssertions.Execution.IAssertionStrategy assertionStrategy) { } - public AssertionScope(System.Lazy context) { } - public AssertionScope(string context) { } - public string CallerIdentity { get; } - public System.Lazy Context { get; set; } + public AssertionScope(System.Func name) { } + public AssertionScope(string name) { } public FluentAssertions.Formatting.FormattingOptions FormattingOptions { get; } - public FluentAssertions.Execution.AssertionScope UsingLineBreaks { get; } + public System.Func Name { get; } public static FluentAssertions.Execution.AssertionScope Current { get; } - public void AddNonReportable(string key, object value) { } public void AddPreFormattedFailure(string formattedFailureMessage) { } - public void AddReportable(string key, System.Func valueFunc) { } - public void AddReportable(string key, string value) { } public void AppendTracing(string tracingBlock) { } - public void AssumeSingleCaller() { } - public FluentAssertions.Execution.AssertionScope BecauseOf(FluentAssertions.Execution.Reason reason) { } - public FluentAssertions.Execution.AssertionScope BecauseOf(string because, params object[] becauseArgs) { } - public FluentAssertions.Execution.Continuation ClearExpectation() { } public string[] Discard() { } public void Dispose() { } - public FluentAssertions.Execution.Continuation FailWith(System.Func failReasonFunc) { } - public FluentAssertions.Execution.Continuation FailWith(string message) { } - public FluentAssertions.Execution.Continuation FailWith(string message, params System.Func[] argProviders) { } - public FluentAssertions.Execution.Continuation FailWith(string message, params object[] args) { } - public FluentAssertions.Execution.AssertionScope ForCondition(bool condition) { } - public FluentAssertions.Execution.AssertionScope ForConstraint(FluentAssertions.OccurrenceConstraint constraint, int actualOccurrences) { } - public T Get(string key) { } - public FluentAssertions.Execution.GivenSelector Given(System.Func selector) { } public bool HasFailures() { } - public FluentAssertions.Execution.AssertionScope WithDefaultIdentifier(string identifier) { } - public FluentAssertions.Execution.AssertionScope WithExpectation(string message, params object[] args) { } } public class Continuation { - public FluentAssertions.Execution.IAssertionScope Then { get; } - public static bool op_Implicit(FluentAssertions.Execution.Continuation continuation) { } + public FluentAssertions.Execution.AssertionChain Then { get; } } public class ContinuationOfGiven { + public bool Succeeded { get; } public FluentAssertions.Execution.GivenSelector Then { get; } - public static bool op_Implicit(FluentAssertions.Execution.ContinuationOfGiven continuationOfGiven) { } - } - public sealed class ContinuedAssertionScope : FluentAssertions.Execution.IAssertionScope, System.IDisposable - { - public FluentAssertions.Execution.IAssertionScope UsingLineBreaks { get; } - public FluentAssertions.Execution.IAssertionScope BecauseOf(string because, params object[] becauseArgs) { } - public FluentAssertions.Execution.Continuation ClearExpectation() { } - public string[] Discard() { } - public void Dispose() { } - public FluentAssertions.Execution.Continuation FailWith(System.Func failReasonFunc) { } - public FluentAssertions.Execution.Continuation FailWith(string message) { } - public FluentAssertions.Execution.Continuation FailWith(string message, params System.Func[] argProviders) { } - public FluentAssertions.Execution.Continuation FailWith(string message, params object[] args) { } - public FluentAssertions.Execution.IAssertionScope ForCondition(bool condition) { } - public FluentAssertions.Execution.IAssertionScope ForConstraint(FluentAssertions.OccurrenceConstraint constraint, int actualOccurrences) { } - public FluentAssertions.Execution.GivenSelector Given(System.Func selector) { } - public FluentAssertions.Execution.IAssertionScope WithDefaultIdentifier(string identifier) { } - public FluentAssertions.Execution.IAssertionScope WithExpectation(string message, params object[] args) { } - } - public static class Execute - { - public static FluentAssertions.Execution.AssertionScope Assertion { get; } } public class FailReason { @@ -1211,29 +1200,13 @@ namespace FluentAssertions.Execution } public class GivenSelector { - public FluentAssertions.Execution.ContinuationOfGiven ClearExpectation() { } + public bool Succeeded { get; } public FluentAssertions.Execution.ContinuationOfGiven FailWith(string message) { } public FluentAssertions.Execution.ContinuationOfGiven FailWith(string message, params System.Func[] args) { } public FluentAssertions.Execution.ContinuationOfGiven FailWith(string message, params object[] args) { } public FluentAssertions.Execution.GivenSelector ForCondition(System.Func predicate) { } public FluentAssertions.Execution.GivenSelector Given(System.Func selector) { } } - public interface IAssertionScope : System.IDisposable - { - FluentAssertions.Execution.IAssertionScope UsingLineBreaks { get; } - FluentAssertions.Execution.IAssertionScope BecauseOf(string because, params object[] becauseArgs); - FluentAssertions.Execution.Continuation ClearExpectation(); - string[] Discard(); - FluentAssertions.Execution.Continuation FailWith(System.Func failReasonFunc); - FluentAssertions.Execution.Continuation FailWith(string message); - FluentAssertions.Execution.Continuation FailWith(string message, params System.Func[] argProviders); - FluentAssertions.Execution.Continuation FailWith(string message, params object[] args); - FluentAssertions.Execution.IAssertionScope ForCondition(bool condition); - FluentAssertions.Execution.IAssertionScope ForConstraint(FluentAssertions.OccurrenceConstraint constraint, int actualOccurrences); - FluentAssertions.Execution.GivenSelector Given(System.Func selector); - FluentAssertions.Execution.IAssertionScope WithDefaultIdentifier(string identifier); - FluentAssertions.Execution.IAssertionScope WithExpectation(string message, params object[] args); - } public interface IAssertionStrategy { System.Collections.Generic.IEnumerable FailureMessages { get; } @@ -1482,6 +1455,12 @@ namespace FluentAssertions.Formatting public MaxLinesExceededException(string message) { } public MaxLinesExceededException(string message, System.Exception innerException) { } } + public class MethodInfoFormatter : FluentAssertions.Formatting.IValueFormatter + { + public MethodInfoFormatter() { } + public bool CanHandle(object value) { } + public void Format(object value, FluentAssertions.Formatting.FormattedObjectGraph formattedGraph, FluentAssertions.Formatting.FormattingContext context, FluentAssertions.Formatting.FormatChild formatChild) { } + } public class MultidimensionalArrayFormatter : FluentAssertions.Formatting.IValueFormatter { public MultidimensionalArrayFormatter() { } @@ -1594,12 +1573,12 @@ namespace FluentAssertions.Numeric { public class ComparableTypeAssertions : FluentAssertions.Numeric.ComparableTypeAssertions> { - public ComparableTypeAssertions(System.IComparable value) { } + public ComparableTypeAssertions(System.IComparable value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class ComparableTypeAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions, TAssertions> where TAssertions : FluentAssertions.Numeric.ComparableTypeAssertions { - public ComparableTypeAssertions(System.IComparable value) { } + public ComparableTypeAssertions(System.IComparable value, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint Be(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEquivalentTo(TExpectation expectation, string because = "", params object[] becauseArgs) { } @@ -1619,13 +1598,13 @@ namespace FluentAssertions.Numeric public class NullableNumericAssertions : FluentAssertions.Numeric.NullableNumericAssertions> where T : struct, System.IComparable { - public NullableNumericAssertions(T? value) { } + public NullableNumericAssertions(T? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class NullableNumericAssertions : FluentAssertions.Numeric.NumericAssertions where T : struct, System.IComparable where TAssertions : FluentAssertions.Numeric.NullableNumericAssertions { - public NullableNumericAssertions(T? value) { } + public NullableNumericAssertions(T? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint BeNull(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveValue(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Match(System.Linq.Expressions.Expression> predicate, string because = "", params object[] becauseArgs) { } @@ -1635,13 +1614,14 @@ namespace FluentAssertions.Numeric public class NumericAssertions : FluentAssertions.Numeric.NumericAssertions> where T : struct, System.IComparable { - public NumericAssertions(T value) { } + public NumericAssertions(T value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class NumericAssertions where T : struct, System.IComparable where TAssertions : FluentAssertions.Numeric.NumericAssertions { - public NumericAssertions(T value) { } + public NumericAssertions(T value, FluentAssertions.Execution.AssertionChain assertionChain) { } + public FluentAssertions.Execution.AssertionChain CurrentAssertionChain { get; } public T? Subject { get; } public FluentAssertions.AndConstraint Be(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Be(T? expected, string because = "", params object[] becauseArgs) { } @@ -1667,12 +1647,12 @@ namespace FluentAssertions.Primitives { public class BooleanAssertions : FluentAssertions.Primitives.BooleanAssertions { - public BooleanAssertions(bool? value) { } + public BooleanAssertions(bool? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class BooleanAssertions where TAssertions : FluentAssertions.Primitives.BooleanAssertions { - public BooleanAssertions(bool? value) { } + public BooleanAssertions(bool? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public bool? Subject { get; } public FluentAssertions.AndConstraint Be(bool expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeFalse(string because = "", params object[] becauseArgs) { } @@ -1683,12 +1663,12 @@ namespace FluentAssertions.Primitives } public class DateOnlyAssertions : FluentAssertions.Primitives.DateOnlyAssertions { - public DateOnlyAssertions(System.DateOnly? value) { } + public DateOnlyAssertions(System.DateOnly? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class DateOnlyAssertions where TAssertions : FluentAssertions.Primitives.DateOnlyAssertions { - public DateOnlyAssertions(System.DateOnly? value) { } + public DateOnlyAssertions(System.DateOnly? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public System.DateOnly? Subject { get; } public FluentAssertions.AndConstraint Be(System.DateOnly expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Be(System.DateOnly? expected, string because = "", params object[] becauseArgs) { } @@ -1716,12 +1696,12 @@ namespace FluentAssertions.Primitives } public class DateTimeAssertions : FluentAssertions.Primitives.DateTimeAssertions { - public DateTimeAssertions(System.DateTime? value) { } + public DateTimeAssertions(System.DateTime? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class DateTimeAssertions where TAssertions : FluentAssertions.Primitives.DateTimeAssertions { - public DateTimeAssertions(System.DateTime? value) { } + public DateTimeAssertions(System.DateTime? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public System.DateTime? Subject { get; } public FluentAssertions.AndConstraint Be(System.DateTime expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Be(System.DateTime? expected, string because = "", params object[] becauseArgs) { } @@ -1766,12 +1746,12 @@ namespace FluentAssertions.Primitives } public class DateTimeOffsetAssertions : FluentAssertions.Primitives.DateTimeOffsetAssertions { - public DateTimeOffsetAssertions(System.DateTimeOffset? value) { } + public DateTimeOffsetAssertions(System.DateTimeOffset? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class DateTimeOffsetAssertions where TAssertions : FluentAssertions.Primitives.DateTimeOffsetAssertions { - public DateTimeOffsetAssertions(System.DateTimeOffset? value) { } + public DateTimeOffsetAssertions(System.DateTimeOffset? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public System.DateTimeOffset? Subject { get; } public FluentAssertions.AndConstraint Be(System.DateTimeOffset expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Be(System.DateTimeOffset? expected, string because = "", params object[] becauseArgs) { } @@ -1821,7 +1801,7 @@ namespace FluentAssertions.Primitives public class DateTimeOffsetRangeAssertions where TAssertions : FluentAssertions.Primitives.DateTimeOffsetAssertions { - protected DateTimeOffsetRangeAssertions(TAssertions parentAssertions, System.DateTimeOffset? subject, FluentAssertions.Primitives.TimeSpanCondition condition, System.TimeSpan timeSpan) { } + protected DateTimeOffsetRangeAssertions(TAssertions parentAssertions, FluentAssertions.Execution.AssertionChain assertionChain, System.DateTimeOffset? subject, FluentAssertions.Primitives.TimeSpanCondition condition, System.TimeSpan timeSpan) { } public FluentAssertions.AndConstraint After(System.DateTimeOffset target, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Before(System.DateTimeOffset target, string because = "", params object[] becauseArgs) { } public override bool Equals(object obj) { } @@ -1829,7 +1809,7 @@ namespace FluentAssertions.Primitives public class DateTimeRangeAssertions where TAssertions : FluentAssertions.Primitives.DateTimeAssertions { - protected DateTimeRangeAssertions(TAssertions parentAssertions, System.DateTime? subject, FluentAssertions.Primitives.TimeSpanCondition condition, System.TimeSpan timeSpan) { } + protected DateTimeRangeAssertions(TAssertions parentAssertions, FluentAssertions.Execution.AssertionChain assertionChain, System.DateTime? subject, FluentAssertions.Primitives.TimeSpanCondition condition, System.TimeSpan timeSpan) { } public FluentAssertions.AndConstraint After(System.DateTime target, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Before(System.DateTime target, string because = "", params object[] becauseArgs) { } public override bool Equals(object obj) { } @@ -1837,13 +1817,13 @@ namespace FluentAssertions.Primitives public class EnumAssertions : FluentAssertions.Primitives.EnumAssertions> where TEnum : struct, System.Enum { - public EnumAssertions(TEnum subject) { } + public EnumAssertions(TEnum subject, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class EnumAssertions where TEnum : struct, System.Enum where TAssertions : FluentAssertions.Primitives.EnumAssertions { - public EnumAssertions(TEnum subject) { } + public EnumAssertions(TEnum subject, FluentAssertions.Execution.AssertionChain assertionChain) { } public TEnum? Subject { get; } public FluentAssertions.AndConstraint Be(TEnum expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Be(TEnum? expected, string because = "", params object[] becauseArgs) { } @@ -1870,12 +1850,12 @@ namespace FluentAssertions.Primitives } public class GuidAssertions : FluentAssertions.Primitives.GuidAssertions { - public GuidAssertions(System.Guid? value) { } + public GuidAssertions(System.Guid? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class GuidAssertions where TAssertions : FluentAssertions.Primitives.GuidAssertions { - public GuidAssertions(System.Guid? value) { } + public GuidAssertions(System.Guid? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public System.Guid? Subject { get; } public FluentAssertions.AndConstraint Be(System.Guid expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Be(string expected, string because = "", params object[] becauseArgs) { } @@ -1887,12 +1867,12 @@ namespace FluentAssertions.Primitives } public class HttpResponseMessageAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions { - public HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { } + public HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class HttpResponseMessageAssertions : FluentAssertions.Primitives.ObjectAssertions where TAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions { - protected HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { } + protected HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint BeRedirection(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeSuccessful(string because = "", params object[] becauseArgs) { } @@ -1904,12 +1884,12 @@ namespace FluentAssertions.Primitives } public class NullableBooleanAssertions : FluentAssertions.Primitives.NullableBooleanAssertions { - public NullableBooleanAssertions(bool? value) { } + public NullableBooleanAssertions(bool? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class NullableBooleanAssertions : FluentAssertions.Primitives.BooleanAssertions where TAssertions : FluentAssertions.Primitives.NullableBooleanAssertions { - public NullableBooleanAssertions(bool? value) { } + public NullableBooleanAssertions(bool? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint Be(bool? expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeNull(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveValue(string because = "", params object[] becauseArgs) { } @@ -1921,12 +1901,12 @@ namespace FluentAssertions.Primitives } public class NullableDateOnlyAssertions : FluentAssertions.Primitives.NullableDateOnlyAssertions { - public NullableDateOnlyAssertions(System.DateOnly? value) { } + public NullableDateOnlyAssertions(System.DateOnly? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class NullableDateOnlyAssertions : FluentAssertions.Primitives.DateOnlyAssertions where TAssertions : FluentAssertions.Primitives.NullableDateOnlyAssertions { - public NullableDateOnlyAssertions(System.DateOnly? value) { } + public NullableDateOnlyAssertions(System.DateOnly? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint BeNull(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveValue(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeNull(string because = "", params object[] becauseArgs) { } @@ -1934,12 +1914,12 @@ namespace FluentAssertions.Primitives } public class NullableDateTimeAssertions : FluentAssertions.Primitives.NullableDateTimeAssertions { - public NullableDateTimeAssertions(System.DateTime? expected) { } + public NullableDateTimeAssertions(System.DateTime? expected, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class NullableDateTimeAssertions : FluentAssertions.Primitives.DateTimeAssertions where TAssertions : FluentAssertions.Primitives.NullableDateTimeAssertions { - public NullableDateTimeAssertions(System.DateTime? expected) { } + public NullableDateTimeAssertions(System.DateTime? expected, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint BeNull(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveValue(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeNull(string because = "", params object[] becauseArgs) { } @@ -1947,12 +1927,12 @@ namespace FluentAssertions.Primitives } public class NullableDateTimeOffsetAssertions : FluentAssertions.Primitives.NullableDateTimeOffsetAssertions { - public NullableDateTimeOffsetAssertions(System.DateTimeOffset? expected) { } + public NullableDateTimeOffsetAssertions(System.DateTimeOffset? expected, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class NullableDateTimeOffsetAssertions : FluentAssertions.Primitives.DateTimeOffsetAssertions where TAssertions : FluentAssertions.Primitives.NullableDateTimeOffsetAssertions { - public NullableDateTimeOffsetAssertions(System.DateTimeOffset? expected) { } + public NullableDateTimeOffsetAssertions(System.DateTimeOffset? expected, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint BeNull(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveValue(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeNull(string because = "", params object[] becauseArgs) { } @@ -1961,13 +1941,13 @@ namespace FluentAssertions.Primitives public class NullableEnumAssertions : FluentAssertions.Primitives.NullableEnumAssertions> where TEnum : struct, System.Enum { - public NullableEnumAssertions(TEnum? subject) { } + public NullableEnumAssertions(TEnum? subject, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class NullableEnumAssertions : FluentAssertions.Primitives.EnumAssertions where TEnum : struct, System.Enum where TAssertions : FluentAssertions.Primitives.NullableEnumAssertions { - public NullableEnumAssertions(TEnum? subject) { } + public NullableEnumAssertions(TEnum? subject, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint BeNull(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint HaveValue(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint NotBeNull(string because = "", params object[] becauseArgs) { } @@ -1975,12 +1955,12 @@ namespace FluentAssertions.Primitives } public class NullableGuidAssertions : FluentAssertions.Primitives.NullableGuidAssertions { - public NullableGuidAssertions(System.Guid? value) { } + public NullableGuidAssertions(System.Guid? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class NullableGuidAssertions : FluentAssertions.Primitives.GuidAssertions where TAssertions : FluentAssertions.Primitives.NullableGuidAssertions { - public NullableGuidAssertions(System.Guid? value) { } + public NullableGuidAssertions(System.Guid? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint Be(System.Guid? expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeNull(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveValue(string because = "", params object[] becauseArgs) { } @@ -1989,12 +1969,12 @@ namespace FluentAssertions.Primitives } public class NullableSimpleTimeSpanAssertions : FluentAssertions.Primitives.NullableSimpleTimeSpanAssertions { - public NullableSimpleTimeSpanAssertions(System.TimeSpan? value) { } + public NullableSimpleTimeSpanAssertions(System.TimeSpan? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class NullableSimpleTimeSpanAssertions : FluentAssertions.Primitives.SimpleTimeSpanAssertions where TAssertions : FluentAssertions.Primitives.NullableSimpleTimeSpanAssertions { - public NullableSimpleTimeSpanAssertions(System.TimeSpan? value) { } + public NullableSimpleTimeSpanAssertions(System.TimeSpan? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint Be(System.TimeSpan? expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeNull(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveValue(string because = "", params object[] becauseArgs) { } @@ -2003,12 +1983,12 @@ namespace FluentAssertions.Primitives } public class NullableTimeOnlyAssertions : FluentAssertions.Primitives.NullableTimeOnlyAssertions { - public NullableTimeOnlyAssertions(System.TimeOnly? value) { } + public NullableTimeOnlyAssertions(System.TimeOnly? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class NullableTimeOnlyAssertions : FluentAssertions.Primitives.TimeOnlyAssertions where TAssertions : FluentAssertions.Primitives.NullableTimeOnlyAssertions { - public NullableTimeOnlyAssertions(System.TimeOnly? value) { } + public NullableTimeOnlyAssertions(System.TimeOnly? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint BeNull(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveValue(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeNull(string because = "", params object[] becauseArgs) { } @@ -2016,7 +1996,7 @@ namespace FluentAssertions.Primitives } public class ObjectAssertions : FluentAssertions.Primitives.ObjectAssertions { - public ObjectAssertions(object value) { } + public ObjectAssertions(object value, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint Be(TExpectation expected, System.Collections.Generic.IEqualityComparer comparer, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOneOf(System.Collections.Generic.IEnumerable validValues, System.Collections.Generic.IEqualityComparer comparer, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBe(TExpectation unexpected, System.Collections.Generic.IEqualityComparer comparer, string because = "", params object[] becauseArgs) { } @@ -2024,7 +2004,7 @@ namespace FluentAssertions.Primitives public class ObjectAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions where TAssertions : FluentAssertions.Primitives.ObjectAssertions { - public ObjectAssertions(TSubject value) { } + public ObjectAssertions(TSubject value, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint Be(TSubject expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Be(TSubject expected, System.Collections.Generic.IEqualityComparer comparer, string because = "", params object[] becauseArgs) { } @@ -2042,7 +2022,8 @@ namespace FluentAssertions.Primitives public abstract class ReferenceTypeAssertions where TAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions { - protected ReferenceTypeAssertions(TSubject subject) { } + protected ReferenceTypeAssertions(TSubject subject, FluentAssertions.Execution.AssertionChain assertionChain) { } + public FluentAssertions.Execution.AssertionChain CurrentAssertionChain { get; } protected abstract string Identifier { get; } public TSubject Subject { get; } public FluentAssertions.AndConstraint BeAssignableTo(System.Type type, string because = "", params object[] becauseArgs) { } @@ -2066,12 +2047,12 @@ namespace FluentAssertions.Primitives } public class SimpleTimeSpanAssertions : FluentAssertions.Primitives.SimpleTimeSpanAssertions { - public SimpleTimeSpanAssertions(System.TimeSpan? value) { } + public SimpleTimeSpanAssertions(System.TimeSpan? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class SimpleTimeSpanAssertions where TAssertions : FluentAssertions.Primitives.SimpleTimeSpanAssertions { - public SimpleTimeSpanAssertions(System.TimeSpan? value) { } + public SimpleTimeSpanAssertions(System.TimeSpan? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public System.TimeSpan? Subject { get; } public FluentAssertions.AndConstraint Be(System.TimeSpan expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeCloseTo(System.TimeSpan nearbyTime, System.TimeSpan precision, string because = "", params object[] becauseArgs) { } @@ -2087,12 +2068,12 @@ namespace FluentAssertions.Primitives } public class StringAssertions : FluentAssertions.Primitives.StringAssertions { - public StringAssertions(string value) { } + public StringAssertions(string value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class StringAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions where TAssertions : FluentAssertions.Primitives.StringAssertions { - public StringAssertions(string value) { } + public StringAssertions(string value, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint Be(string expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEmpty(string because = "", params object[] becauseArgs) { } @@ -2157,12 +2138,12 @@ namespace FluentAssertions.Primitives } public class TimeOnlyAssertions : FluentAssertions.Primitives.TimeOnlyAssertions { - public TimeOnlyAssertions(System.TimeOnly? value) { } + public TimeOnlyAssertions(System.TimeOnly? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class TimeOnlyAssertions where TAssertions : FluentAssertions.Primitives.TimeOnlyAssertions { - public TimeOnlyAssertions(System.TimeOnly? value) { } + public TimeOnlyAssertions(System.TimeOnly? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public System.TimeOnly? Subject { get; } public FluentAssertions.AndConstraint Be(System.TimeOnly expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Be(System.TimeOnly? expected, string because = "", params object[] becauseArgs) { } @@ -2205,8 +2186,8 @@ namespace FluentAssertions.Specialized { public class ActionAssertions : FluentAssertions.Specialized.DelegateAssertions { - public ActionAssertions(System.Action subject, FluentAssertions.Specialized.IExtractExceptions extractor) { } - public ActionAssertions(System.Action subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Common.IClock clock) { } + public ActionAssertions(System.Action subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain) { } + public ActionAssertions(System.Action subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain, FluentAssertions.Common.IClock clock) { } protected override string Identifier { get; } protected override void InvokeSubject() { } public FluentAssertions.AndConstraint NotThrow(string because = "", params object[] becauseArgs) { } @@ -2216,7 +2197,7 @@ namespace FluentAssertions.Specialized where TTask : System.Threading.Tasks.Task where TAssertions : FluentAssertions.Specialized.AsyncFunctionAssertions { - protected AsyncFunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Common.IClock clock) { } + protected AsyncFunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain, FluentAssertions.Common.IClock clock) { } protected override string Identifier { get; } public System.Threading.Tasks.Task> NotCompleteWithinAsync(System.TimeSpan timeSpan, string because = "", params object[] becauseArgs) { } public System.Threading.Tasks.Task> NotThrowAsync(string because = "", params object[] becauseArgs) @@ -2242,7 +2223,7 @@ namespace FluentAssertions.Specialized where TDelegate : System.Delegate where TAssertions : FluentAssertions.Specialized.DelegateAssertions { - protected DelegateAssertions(TDelegate @delegate, FluentAssertions.Specialized.IExtractExceptions extractor) { } + protected DelegateAssertions(TDelegate @delegate, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain) { } protected abstract void InvokeSubject(); public FluentAssertions.AndConstraint NotThrow(string because = "", params object[] becauseArgs) where TException : System.Exception { } @@ -2254,7 +2235,7 @@ namespace FluentAssertions.Specialized public class ExceptionAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions, FluentAssertions.Specialized.ExceptionAssertions> where TException : System.Exception { - public ExceptionAssertions(System.Collections.Generic.IEnumerable exceptions) { } + public ExceptionAssertions(System.Collections.Generic.IEnumerable exceptions, FluentAssertions.Execution.AssertionChain assertionChain) { } public TException And { get; } protected override string Identifier { get; } public TException Which { get; } @@ -2276,7 +2257,7 @@ namespace FluentAssertions.Specialized } public class ExecutionTimeAssertions { - public ExecutionTimeAssertions(FluentAssertions.Specialized.ExecutionTime executionTime) { } + public ExecutionTimeAssertions(FluentAssertions.Specialized.ExecutionTime executionTime, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint BeCloseTo(System.TimeSpan expectedDuration, System.TimeSpan precision, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeGreaterThan(System.TimeSpan minDuration, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeGreaterThanOrEqualTo(System.TimeSpan minDuration, string because = "", params object[] becauseArgs) { } @@ -2286,8 +2267,8 @@ namespace FluentAssertions.Specialized } public class FunctionAssertions : FluentAssertions.Specialized.DelegateAssertions, FluentAssertions.Specialized.FunctionAssertions> { - public FunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor) { } - public FunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Common.IClock clock) { } + public FunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain) { } + public FunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain, FluentAssertions.Common.IClock clock) { } protected override string Identifier { get; } protected override void InvokeSubject() { } public FluentAssertions.AndWhichConstraint, T> NotThrow(string because = "", params object[] becauseArgs) { } @@ -2295,8 +2276,8 @@ namespace FluentAssertions.Specialized } public class GenericAsyncFunctionAssertions : FluentAssertions.Specialized.AsyncFunctionAssertions, FluentAssertions.Specialized.GenericAsyncFunctionAssertions> { - public GenericAsyncFunctionAssertions(System.Func> subject, FluentAssertions.Specialized.IExtractExceptions extractor) { } - public GenericAsyncFunctionAssertions(System.Func> subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Common.IClock clock) { } + public GenericAsyncFunctionAssertions(System.Func> subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain) { } + public GenericAsyncFunctionAssertions(System.Func> subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain, FluentAssertions.Common.IClock clock) { } public System.Threading.Tasks.Task, TResult>> CompleteWithinAsync(System.TimeSpan timeSpan, string because = "", params object[] becauseArgs) { } public System.Threading.Tasks.Task, TResult>> NotThrowAfterAsync(System.TimeSpan waitTime, System.TimeSpan pollInterval, string because = "", params object[] becauseArgs) { } public System.Threading.Tasks.Task, TResult>> NotThrowAsync(string because = "", params object[] becauseArgs) { } @@ -2312,16 +2293,16 @@ namespace FluentAssertions.Specialized } public class NonGenericAsyncFunctionAssertions : FluentAssertions.Specialized.AsyncFunctionAssertions { - public NonGenericAsyncFunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor) { } - public NonGenericAsyncFunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Common.IClock clock) { } + public NonGenericAsyncFunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain) { } + public NonGenericAsyncFunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain, FluentAssertions.Common.IClock clock) { } public System.Threading.Tasks.Task> CompleteWithinAsync(System.TimeSpan timeSpan, string because = "", params object[] becauseArgs) { } public System.Threading.Tasks.Task> NotThrowAfterAsync(System.TimeSpan waitTime, System.TimeSpan pollInterval, string because = "", params object[] becauseArgs) { } public System.Threading.Tasks.Task> NotThrowAsync(string because = "", params object[] becauseArgs) { } } public class TaskCompletionSourceAssertions : FluentAssertions.Specialized.TaskCompletionSourceAssertionsBase { - public TaskCompletionSourceAssertions(System.Threading.Tasks.TaskCompletionSource tcs) { } - public TaskCompletionSourceAssertions(System.Threading.Tasks.TaskCompletionSource tcs, FluentAssertions.Common.IClock clock) { } + public TaskCompletionSourceAssertions(System.Threading.Tasks.TaskCompletionSource tcs, FluentAssertions.Execution.AssertionChain assertionChain) { } + public TaskCompletionSourceAssertions(System.Threading.Tasks.TaskCompletionSource tcs, FluentAssertions.Execution.AssertionChain assertionChain, FluentAssertions.Common.IClock clock) { } public System.Threading.Tasks.Task> CompleteWithinAsync(System.TimeSpan timeSpan, string because = "", params object[] becauseArgs) { } public System.Threading.Tasks.Task> NotCompleteWithinAsync(System.TimeSpan timeSpan, string because = "", params object[] becauseArgs) { } } @@ -2332,8 +2313,8 @@ namespace FluentAssertions.Specialized } public class TaskCompletionSourceAssertions : FluentAssertions.Specialized.TaskCompletionSourceAssertionsBase { - public TaskCompletionSourceAssertions(System.Threading.Tasks.TaskCompletionSource tcs) { } - public TaskCompletionSourceAssertions(System.Threading.Tasks.TaskCompletionSource tcs, FluentAssertions.Common.IClock clock) { } + public TaskCompletionSourceAssertions(System.Threading.Tasks.TaskCompletionSource tcs, FluentAssertions.Execution.AssertionChain assertionChain) { } + public TaskCompletionSourceAssertions(System.Threading.Tasks.TaskCompletionSource tcs, FluentAssertions.Execution.AssertionChain assertionChain, FluentAssertions.Common.IClock clock) { } public System.Threading.Tasks.Task, T>> CompleteWithinAsync(System.TimeSpan timeSpan, string because = "", params object[] becauseArgs) { } public System.Threading.Tasks.Task>> NotCompleteWithinAsync(System.TimeSpan timeSpan, string because = "", params object[] becauseArgs) { } } @@ -2342,25 +2323,25 @@ namespace FluentAssertions.Streams { public class BufferedStreamAssertions : FluentAssertions.Streams.BufferedStreamAssertions { - public BufferedStreamAssertions(System.IO.BufferedStream stream) { } + public BufferedStreamAssertions(System.IO.BufferedStream stream, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class BufferedStreamAssertions : FluentAssertions.Streams.StreamAssertions where TAssertions : FluentAssertions.Streams.BufferedStreamAssertions { - public BufferedStreamAssertions(System.IO.BufferedStream stream) { } + public BufferedStreamAssertions(System.IO.BufferedStream stream, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint HaveBufferSize(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveBufferSize(int unexpected, string because = "", params object[] becauseArgs) { } } public class StreamAssertions : FluentAssertions.Streams.StreamAssertions { - public StreamAssertions(System.IO.Stream stream) { } + public StreamAssertions(System.IO.Stream stream, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class StreamAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions where TSubject : System.IO.Stream where TAssertions : FluentAssertions.Streams.StreamAssertions { - public StreamAssertions(TSubject stream) { } + public StreamAssertions(TSubject stream, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint BeReadOnly(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeReadable(string because = "", params object[] becauseArgs) { } @@ -2386,7 +2367,7 @@ namespace FluentAssertions.Types } public class AssemblyAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions { - public AssemblyAssertions(System.Reflection.Assembly assembly) { } + public AssemblyAssertions(System.Reflection.Assembly assembly, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint BeSignedWithPublicKey(string publicKey, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeUnsigned(string because = "", params object[] becauseArgs) { } @@ -2396,15 +2377,17 @@ namespace FluentAssertions.Types } public class ConstructorInfoAssertions : FluentAssertions.Types.MethodBaseAssertions { - public ConstructorInfoAssertions(System.Reflection.ConstructorInfo constructorInfo) { } + public ConstructorInfoAssertions(System.Reflection.ConstructorInfo constructorInfo, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } + protected override string SubjectDescription { get; } } public abstract class MemberInfoAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions where TSubject : System.Reflection.MemberInfo where TAssertions : FluentAssertions.Types.MemberInfoAssertions { - protected MemberInfoAssertions(TSubject subject) { } + protected MemberInfoAssertions(TSubject subject, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } + protected virtual string SubjectDescription { get; } public FluentAssertions.AndWhichConstraint, TAttribute> BeDecoratedWith(string because = "", params object[] becauseArgs) where TAttribute : System.Attribute { } public FluentAssertions.AndWhichConstraint, TAttribute> BeDecoratedWith(System.Linq.Expressions.Expression> isMatchingAttributePredicate, string because = "", params object[] becauseArgs) @@ -2418,14 +2401,15 @@ namespace FluentAssertions.Types where TSubject : System.Reflection.MethodBase where TAssertions : FluentAssertions.Types.MethodBaseAssertions { - protected MethodBaseAssertions(TSubject subject) { } + protected MethodBaseAssertions(TSubject subject, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint HaveAccessModifier(FluentAssertions.Common.CSharpAccessModifier accessModifier, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveAccessModifier(FluentAssertions.Common.CSharpAccessModifier accessModifier, string because = "", params object[] becauseArgs) { } } public class MethodInfoAssertions : FluentAssertions.Types.MethodBaseAssertions { - public MethodInfoAssertions(System.Reflection.MethodInfo methodInfo) { } + public MethodInfoAssertions(System.Reflection.MethodInfo methodInfo, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } + protected override string SubjectDescription { get; } public FluentAssertions.AndConstraint BeAsync(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeVirtual(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeAsync(string because = "", params object[] becauseArgs) { } @@ -2468,7 +2452,7 @@ namespace FluentAssertions.Types } public class MethodInfoSelectorAssertions { - public MethodInfoSelectorAssertions(params System.Reflection.MethodInfo[] methods) { } + public MethodInfoSelectorAssertions(FluentAssertions.Execution.AssertionChain assertionChain, params System.Reflection.MethodInfo[] methods) { } protected string Context { get; } public System.Collections.Generic.IEnumerable SubjectMethods { get; } public FluentAssertions.AndConstraint Be(FluentAssertions.Common.CSharpAccessModifier accessModifier, string because = "", params object[] becauseArgs) { } @@ -2489,8 +2473,9 @@ namespace FluentAssertions.Types } public class PropertyInfoAssertions : FluentAssertions.Types.MemberInfoAssertions { - public PropertyInfoAssertions(System.Reflection.PropertyInfo propertyInfo) { } + public PropertyInfoAssertions(System.Reflection.PropertyInfo propertyInfo, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } + protected override string SubjectDescription { get; } public FluentAssertions.AndConstraint BeReadable(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeReadable(FluentAssertions.Common.CSharpAccessModifier accessModifier, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeVirtual(string because = "", params object[] becauseArgs) { } @@ -2531,7 +2516,7 @@ namespace FluentAssertions.Types } public class PropertyInfoSelectorAssertions { - public PropertyInfoSelectorAssertions(params System.Reflection.PropertyInfo[] properties) { } + public PropertyInfoSelectorAssertions(FluentAssertions.Execution.AssertionChain assertionChain, params System.Reflection.PropertyInfo[] properties) { } protected string Context { get; } public System.Collections.Generic.IEnumerable SubjectProperties { get; } public FluentAssertions.AndConstraint BeDecoratedWith(string because = "", params object[] becauseArgs) @@ -2546,7 +2531,7 @@ namespace FluentAssertions.Types } public class TypeAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions { - public TypeAssertions(System.Type type) { } + public TypeAssertions(System.Type type, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint Be(System.Type expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Be(string because = "", params object[] becauseArgs) { } @@ -2664,7 +2649,7 @@ namespace FluentAssertions.Types } public class TypeSelectorAssertions { - public TypeSelectorAssertions(params System.Type[] types) { } + public TypeSelectorAssertions(FluentAssertions.Execution.AssertionChain assertionChain, params System.Type[] types) { } public System.Collections.Generic.IEnumerable Subject { get; } public FluentAssertions.AndConstraint BeDecoratedWith(string because = "", params object[] becauseArgs) where TAttribute : System.Attribute { } @@ -2695,7 +2680,7 @@ namespace FluentAssertions.Xml { public class XAttributeAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions { - public XAttributeAssertions(System.Xml.Linq.XAttribute attribute) { } + public XAttributeAssertions(System.Xml.Linq.XAttribute attribute, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint Be(System.Xml.Linq.XAttribute expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveValue(string expected, string because = "", params object[] becauseArgs) { } @@ -2703,7 +2688,7 @@ namespace FluentAssertions.Xml } public class XDocumentAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions { - public XDocumentAssertions(System.Xml.Linq.XDocument document) { } + public XDocumentAssertions(System.Xml.Linq.XDocument document, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint Be(System.Xml.Linq.XDocument expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEquivalentTo(System.Xml.Linq.XDocument expected, string because = "", params object[] becauseArgs) { } @@ -2718,7 +2703,7 @@ namespace FluentAssertions.Xml } public class XElementAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions { - public XElementAssertions(System.Xml.Linq.XElement xElement) { } + public XElementAssertions(System.Xml.Linq.XElement xElement, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint Be(System.Xml.Linq.XElement expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEquivalentTo(System.Xml.Linq.XElement expected, string because = "", params object[] becauseArgs) { } @@ -2734,7 +2719,7 @@ namespace FluentAssertions.Xml } public class XmlElementAssertions : FluentAssertions.Xml.XmlNodeAssertions { - public XmlElementAssertions(System.Xml.XmlElement xmlElement) { } + public XmlElementAssertions(System.Xml.XmlElement xmlElement, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint HaveAttribute(string expectedName, string expectedValue, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveAttributeWithNamespace(string expectedName, string expectedNamespace, string expectedValue, string because = "", params object[] becauseArgs) { } @@ -2744,13 +2729,13 @@ namespace FluentAssertions.Xml } public class XmlNodeAssertions : FluentAssertions.Xml.XmlNodeAssertions { - public XmlNodeAssertions(System.Xml.XmlNode xmlNode) { } + public XmlNodeAssertions(System.Xml.XmlNode xmlNode, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class XmlNodeAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions where TSubject : System.Xml.XmlNode where TAssertions : FluentAssertions.Xml.XmlNodeAssertions { - public XmlNodeAssertions(TSubject xmlNode) { } + public XmlNodeAssertions(TSubject xmlNode, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint BeEquivalentTo(System.Xml.XmlNode expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEquivalentTo(System.Xml.XmlNode unexpected, string because = "", params object[] becauseArgs) { } diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.0.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.0.verified.txt index 1de3630d79..f1cce1f656 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.0.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.0.verified.txt @@ -15,12 +15,14 @@ namespace FluentAssertions public AndConstraint(TParent parent) { } public TParent And { get; } } - public class AndWhichConstraint : FluentAssertions.AndConstraint + public class AndWhichConstraint : FluentAssertions.AndConstraint { - public AndWhichConstraint(TParentConstraint parentConstraint, System.Collections.Generic.IEnumerable matchedConstraint) { } - public AndWhichConstraint(TParentConstraint parentConstraint, TMatchedElement matchedConstraint) { } - public TMatchedElement Subject { get; } - public TMatchedElement Which { get; } + public AndWhichConstraint(TParent parent, System.Collections.Generic.IEnumerable subjects) { } + public AndWhichConstraint(TParent parent, TSubject subject) { } + public AndWhichConstraint(TParent parent, System.Collections.Generic.IEnumerable subjects, FluentAssertions.Execution.AssertionChain assertionChain, string pathPostfix) { } + public AndWhichConstraint(TParent parent, TSubject subject, FluentAssertions.Execution.AssertionChain assertionChain, string pathPostfix = "") { } + public TSubject Subject { get; } + public TSubject Which { get; } } public static class AssertionExtensions { @@ -65,7 +67,6 @@ namespace FluentAssertions public static FluentAssertions.Specialized.NonGenericAsyncFunctionAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Func action) { } public static FluentAssertions.Primitives.GuidAssertions Should(this System.Guid actualValue) { } public static FluentAssertions.Primitives.NullableGuidAssertions Should(this System.Guid? actualValue) { } - public static FluentAssertions.Streams.BufferedStreamAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.IO.BufferedStream actualValue) { } public static FluentAssertions.Streams.StreamAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.IO.Stream actualValue) { } public static FluentAssertions.Primitives.HttpResponseMessageAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Net.Http.HttpResponseMessage actualValue) { } public static FluentAssertions.Types.AssemblyAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Reflection.Assembly assembly) { } @@ -368,18 +369,18 @@ namespace FluentAssertions.Collections { public class GenericCollectionAssertions : FluentAssertions.Collections.GenericCollectionAssertions, T, FluentAssertions.Collections.GenericCollectionAssertions> { - public GenericCollectionAssertions(System.Collections.Generic.IEnumerable actualValue) { } + public GenericCollectionAssertions(System.Collections.Generic.IEnumerable actualValue, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class GenericCollectionAssertions : FluentAssertions.Collections.GenericCollectionAssertions> where TCollection : System.Collections.Generic.IEnumerable { - public GenericCollectionAssertions(TCollection actualValue) { } + public GenericCollectionAssertions(TCollection actualValue, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class GenericCollectionAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions where TCollection : System.Collections.Generic.IEnumerable where TAssertions : FluentAssertions.Collections.GenericCollectionAssertions { - public GenericCollectionAssertions(TCollection actualValue) { } + public GenericCollectionAssertions(TCollection actualValue, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint AllBeAssignableTo(System.Type expectedType, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint> AllBeAssignableTo(string because = "", params object[] becauseArgs) { } @@ -453,7 +454,7 @@ namespace FluentAssertions.Collections public FluentAssertions.AndConstraint NotBeSubsetOf(System.Collections.Generic.IEnumerable unexpectedSuperset, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotContain(System.Collections.Generic.IEnumerable unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotContain(System.Linq.Expressions.Expression> predicate, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint NotContain(T unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotContain(T unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotContainEquivalentOf(TExpectation unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotContainEquivalentOf(TExpectation unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotContainInConsecutiveOrder(params T[] unexpected) { } @@ -484,13 +485,13 @@ namespace FluentAssertions.Collections public class GenericDictionaryAssertions : FluentAssertions.Collections.GenericDictionaryAssertions> where TCollection : System.Collections.Generic.IEnumerable> { - public GenericDictionaryAssertions(TCollection keyValuePairs) { } + public GenericDictionaryAssertions(TCollection keyValuePairs, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class GenericDictionaryAssertions : FluentAssertions.Collections.GenericCollectionAssertions, TAssertions> where TCollection : System.Collections.Generic.IEnumerable> where TAssertions : FluentAssertions.Collections.GenericDictionaryAssertions { - public GenericDictionaryAssertions(TCollection keyValuePairs) { } + public GenericDictionaryAssertions(TCollection keyValuePairs, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint BeEquivalentTo(TExpectation expectation, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEquivalentTo(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } @@ -502,8 +503,8 @@ namespace FluentAssertions.Collections public FluentAssertions.AndConstraint ContainKeys(params TKey[] expected) { } public FluentAssertions.AndConstraint ContainKeys(System.Collections.Generic.IEnumerable expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint ContainValue(TValue expected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint ContainValues(params TValue[] expected) { } - public FluentAssertions.AndConstraint ContainValues(System.Collections.Generic.IEnumerable expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint> ContainValues(params TValue[] expected) { } + public FluentAssertions.AndWhichConstraint> ContainValues(System.Collections.Generic.IEnumerable expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Equal(T expected, string because = "", params object[] becauseArgs) where T : System.Collections.Generic.IEnumerable> { } public FluentAssertions.AndConstraint NotContain(params System.Collections.Generic.KeyValuePair[] items) { } @@ -521,18 +522,18 @@ namespace FluentAssertions.Collections } public class StringCollectionAssertions : FluentAssertions.Collections.StringCollectionAssertions> { - public StringCollectionAssertions(System.Collections.Generic.IEnumerable actualValue) { } + public StringCollectionAssertions(System.Collections.Generic.IEnumerable actualValue, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class StringCollectionAssertions : FluentAssertions.Collections.StringCollectionAssertions> where TCollection : System.Collections.Generic.IEnumerable { - public StringCollectionAssertions(TCollection actualValue) { } + public StringCollectionAssertions(TCollection actualValue, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class StringCollectionAssertions : FluentAssertions.Collections.GenericCollectionAssertions where TCollection : System.Collections.Generic.IEnumerable where TAssertions : FluentAssertions.Collections.StringCollectionAssertions { - public StringCollectionAssertions(TCollection actualValue) { } + public StringCollectionAssertions(TCollection actualValue, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint AllBe(string expectation, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint AllBe(string expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEquivalentTo(params string[] expectation) { } @@ -545,18 +546,18 @@ namespace FluentAssertions.Collections } public class SubsequentOrderingAssertions : FluentAssertions.Collections.SubsequentOrderingGenericCollectionAssertions, T, FluentAssertions.Collections.SubsequentOrderingAssertions> { - public SubsequentOrderingAssertions(System.Collections.Generic.IEnumerable actualValue, System.Linq.IOrderedEnumerable previousOrderedEnumerable) { } + public SubsequentOrderingAssertions(System.Collections.Generic.IEnumerable actualValue, System.Linq.IOrderedEnumerable previousOrderedEnumerable, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class SubsequentOrderingGenericCollectionAssertions : FluentAssertions.Collections.SubsequentOrderingGenericCollectionAssertions> where TCollection : System.Collections.Generic.IEnumerable { - public SubsequentOrderingGenericCollectionAssertions(TCollection actualValue, System.Linq.IOrderedEnumerable previousOrderedEnumerable) { } + public SubsequentOrderingGenericCollectionAssertions(TCollection actualValue, System.Linq.IOrderedEnumerable previousOrderedEnumerable, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class SubsequentOrderingGenericCollectionAssertions : FluentAssertions.Collections.GenericCollectionAssertions where TCollection : System.Collections.Generic.IEnumerable where TAssertions : FluentAssertions.Collections.SubsequentOrderingGenericCollectionAssertions { - public SubsequentOrderingGenericCollectionAssertions(TCollection actualValue, System.Linq.IOrderedEnumerable previousOrderedEnumerable) { } + public SubsequentOrderingGenericCollectionAssertions(TCollection actualValue, System.Linq.IOrderedEnumerable previousOrderedEnumerable, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint> ThenBeInAscendingOrder(System.Linq.Expressions.Expression> propertyExpression, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint> ThenBeInAscendingOrder(System.Linq.Expressions.Expression> propertyExpression, System.Collections.Generic.IComparer comparer, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint> ThenBeInDescendingOrder(System.Linq.Expressions.Expression> propertyExpression, string because = "", params object[] becauseArgs) { } @@ -697,7 +698,7 @@ namespace FluentAssertions.Equivalency { protected EquivalencyStep() { } public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } - protected abstract FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nested); + protected abstract FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nestedValidator); } public class EquivalencyValidationContext : FluentAssertions.Equivalency.IEquivalencyValidationContext { @@ -789,7 +790,7 @@ namespace FluentAssertions.Equivalency } public interface IMemberMatchingRule { - FluentAssertions.Equivalency.IMember Match(FluentAssertions.Equivalency.IMember expectedMember, object subject, FluentAssertions.Equivalency.INode parent, FluentAssertions.Equivalency.IEquivalencyOptions options); + FluentAssertions.Equivalency.IMember Match(FluentAssertions.Equivalency.IMember expectedMember, object subject, FluentAssertions.Equivalency.INode parent, FluentAssertions.Equivalency.IEquivalencyOptions options, FluentAssertions.Execution.AssertionChain assertionChain); } public interface IMemberSelectionRule { @@ -951,7 +952,7 @@ namespace FluentAssertions.Equivalency.Steps { public class AssertionRuleEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { - public AssertionRuleEquivalencyStep(System.Linq.Expressions.Expression> predicate, System.Action> assertion) { } + public AssertionRuleEquivalencyStep(System.Linq.Expressions.Expression> predicate, System.Action> assertionAction) { } public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } public override string ToString() { } } @@ -964,7 +965,7 @@ namespace FluentAssertions.Equivalency.Steps public class DictionaryEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep { public DictionaryEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nested) { } + protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nestedValidator) { } } public class EnumEqualityStep : FluentAssertions.Equivalency.IEquivalencyStep { @@ -1025,17 +1026,17 @@ namespace FluentAssertions.Equivalency.Steps public class XAttributeEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep { public XAttributeEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nested) { } + protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nestedValidator) { } } public class XDocumentEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep { public XDocumentEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nested) { } + protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nestedValidator) { } } public class XElementEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep { public XElementEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nested) { } + protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nestedValidator) { } } } namespace FluentAssertions.Equivalency.Tracing @@ -1063,76 +1064,63 @@ namespace FluentAssertions.Equivalency.Tracing } namespace FluentAssertions.Execution { + public sealed class AssertionChain + { + public string CallerIdentifier { get; } + public bool HasOverriddenCallerIdentifier { get; } + public bool Succeeded { get; } + public FluentAssertions.Execution.AssertionChain UsingLineBreaks { get; } + public void AddReportable(string key, System.Func getValue) { } + public void AddReportable(string key, string value) { } + public FluentAssertions.Execution.AssertionChain BecauseOf(FluentAssertions.Execution.Reason reason) { } + public FluentAssertions.Execution.AssertionChain BecauseOf(string because, params object[] becauseArgs) { } + public FluentAssertions.Execution.Continuation FailWith(System.Func getFailureReason) { } + public FluentAssertions.Execution.Continuation FailWith(string message) { } + public FluentAssertions.Execution.Continuation FailWith(string message, params System.Func[] argProviders) { } + public FluentAssertions.Execution.Continuation FailWith(string message, params object[] args) { } + public FluentAssertions.Execution.AssertionChain ForCondition(bool condition) { } + public FluentAssertions.Execution.AssertionChain ForConstraint(FluentAssertions.OccurrenceConstraint constraint, int actualOccurrences) { } + public FluentAssertions.Execution.GivenSelector Given(System.Func selector) { } + public void OverrideCallerIdentifier(System.Func getCallerIdentifier) { } + public void ReuseOnce() { } + public FluentAssertions.Execution.AssertionChain WithCallerPostfix(string postfix) { } + public FluentAssertions.Execution.AssertionChain WithCallerPrefix(string prefix) { } + public FluentAssertions.Execution.AssertionChain WithDefaultIdentifier(string identifier) { } + public FluentAssertions.Execution.Continuation WithExpectation(string message, System.Action chain) { } + public FluentAssertions.Execution.Continuation WithExpectation(string message, object arg1, System.Action chain) { } + public FluentAssertions.Execution.Continuation WithExpectation(string message, object arg1, object arg2, System.Action chain) { } + public FluentAssertions.Execution.AssertionChain WithReportable(string name, System.Func content) { } + public static FluentAssertions.Execution.AssertionChain GetOrCreate() { } + } [System.Serializable] public class AssertionFailedException : System.Exception { public AssertionFailedException(string message) { } protected AssertionFailedException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } } - public sealed class AssertionScope : FluentAssertions.Execution.IAssertionScope, System.IDisposable + public sealed class AssertionScope : System.IDisposable { public AssertionScope() { } public AssertionScope(FluentAssertions.Execution.IAssertionStrategy assertionStrategy) { } - public AssertionScope(System.Lazy context) { } - public AssertionScope(string context) { } - public string CallerIdentity { get; } - public System.Lazy Context { get; set; } + public AssertionScope(System.Func name) { } + public AssertionScope(string name) { } public FluentAssertions.Formatting.FormattingOptions FormattingOptions { get; } - public FluentAssertions.Execution.AssertionScope UsingLineBreaks { get; } + public System.Func Name { get; } public static FluentAssertions.Execution.AssertionScope Current { get; } - public void AddNonReportable(string key, object value) { } public void AddPreFormattedFailure(string formattedFailureMessage) { } - public void AddReportable(string key, System.Func valueFunc) { } - public void AddReportable(string key, string value) { } public void AppendTracing(string tracingBlock) { } - public void AssumeSingleCaller() { } - public FluentAssertions.Execution.AssertionScope BecauseOf(FluentAssertions.Execution.Reason reason) { } - public FluentAssertions.Execution.AssertionScope BecauseOf(string because, params object[] becauseArgs) { } - public FluentAssertions.Execution.Continuation ClearExpectation() { } public string[] Discard() { } public void Dispose() { } - public FluentAssertions.Execution.Continuation FailWith(System.Func failReasonFunc) { } - public FluentAssertions.Execution.Continuation FailWith(string message) { } - public FluentAssertions.Execution.Continuation FailWith(string message, params System.Func[] argProviders) { } - public FluentAssertions.Execution.Continuation FailWith(string message, params object[] args) { } - public FluentAssertions.Execution.AssertionScope ForCondition(bool condition) { } - public FluentAssertions.Execution.AssertionScope ForConstraint(FluentAssertions.OccurrenceConstraint constraint, int actualOccurrences) { } - public T Get(string key) { } - public FluentAssertions.Execution.GivenSelector Given(System.Func selector) { } public bool HasFailures() { } - public FluentAssertions.Execution.AssertionScope WithDefaultIdentifier(string identifier) { } - public FluentAssertions.Execution.AssertionScope WithExpectation(string message, params object[] args) { } } public class Continuation { - public FluentAssertions.Execution.IAssertionScope Then { get; } - public static bool op_Implicit(FluentAssertions.Execution.Continuation continuation) { } + public FluentAssertions.Execution.AssertionChain Then { get; } } public class ContinuationOfGiven { + public bool Succeeded { get; } public FluentAssertions.Execution.GivenSelector Then { get; } - public static bool op_Implicit(FluentAssertions.Execution.ContinuationOfGiven continuationOfGiven) { } - } - public sealed class ContinuedAssertionScope : FluentAssertions.Execution.IAssertionScope, System.IDisposable - { - public FluentAssertions.Execution.IAssertionScope UsingLineBreaks { get; } - public FluentAssertions.Execution.IAssertionScope BecauseOf(string because, params object[] becauseArgs) { } - public FluentAssertions.Execution.Continuation ClearExpectation() { } - public string[] Discard() { } - public void Dispose() { } - public FluentAssertions.Execution.Continuation FailWith(System.Func failReasonFunc) { } - public FluentAssertions.Execution.Continuation FailWith(string message) { } - public FluentAssertions.Execution.Continuation FailWith(string message, params System.Func[] argProviders) { } - public FluentAssertions.Execution.Continuation FailWith(string message, params object[] args) { } - public FluentAssertions.Execution.IAssertionScope ForCondition(bool condition) { } - public FluentAssertions.Execution.IAssertionScope ForConstraint(FluentAssertions.OccurrenceConstraint constraint, int actualOccurrences) { } - public FluentAssertions.Execution.GivenSelector Given(System.Func selector) { } - public FluentAssertions.Execution.IAssertionScope WithDefaultIdentifier(string identifier) { } - public FluentAssertions.Execution.IAssertionScope WithExpectation(string message, params object[] args) { } - } - public static class Execute - { - public static FluentAssertions.Execution.AssertionScope Assertion { get; } } public class FailReason { @@ -1142,29 +1130,13 @@ namespace FluentAssertions.Execution } public class GivenSelector { - public FluentAssertions.Execution.ContinuationOfGiven ClearExpectation() { } + public bool Succeeded { get; } public FluentAssertions.Execution.ContinuationOfGiven FailWith(string message) { } public FluentAssertions.Execution.ContinuationOfGiven FailWith(string message, params System.Func[] args) { } public FluentAssertions.Execution.ContinuationOfGiven FailWith(string message, params object[] args) { } public FluentAssertions.Execution.GivenSelector ForCondition(System.Func predicate) { } public FluentAssertions.Execution.GivenSelector Given(System.Func selector) { } } - public interface IAssertionScope : System.IDisposable - { - FluentAssertions.Execution.IAssertionScope UsingLineBreaks { get; } - FluentAssertions.Execution.IAssertionScope BecauseOf(string because, params object[] becauseArgs); - FluentAssertions.Execution.Continuation ClearExpectation(); - string[] Discard(); - FluentAssertions.Execution.Continuation FailWith(System.Func failReasonFunc); - FluentAssertions.Execution.Continuation FailWith(string message); - FluentAssertions.Execution.Continuation FailWith(string message, params System.Func[] argProviders); - FluentAssertions.Execution.Continuation FailWith(string message, params object[] args); - FluentAssertions.Execution.IAssertionScope ForCondition(bool condition); - FluentAssertions.Execution.IAssertionScope ForConstraint(FluentAssertions.OccurrenceConstraint constraint, int actualOccurrences); - FluentAssertions.Execution.GivenSelector Given(System.Func selector); - FluentAssertions.Execution.IAssertionScope WithDefaultIdentifier(string identifier); - FluentAssertions.Execution.IAssertionScope WithExpectation(string message, params object[] args); - } public interface IAssertionStrategy { System.Collections.Generic.IEnumerable FailureMessages { get; } @@ -1407,6 +1379,12 @@ namespace FluentAssertions.Formatting public MaxLinesExceededException(string message) { } public MaxLinesExceededException(string message, System.Exception innerException) { } } + public class MethodInfoFormatter : FluentAssertions.Formatting.IValueFormatter + { + public MethodInfoFormatter() { } + public bool CanHandle(object value) { } + public void Format(object value, FluentAssertions.Formatting.FormattedObjectGraph formattedGraph, FluentAssertions.Formatting.FormattingContext context, FluentAssertions.Formatting.FormatChild formatChild) { } + } public class MultidimensionalArrayFormatter : FluentAssertions.Formatting.IValueFormatter { public MultidimensionalArrayFormatter() { } @@ -1513,12 +1491,12 @@ namespace FluentAssertions.Numeric { public class ComparableTypeAssertions : FluentAssertions.Numeric.ComparableTypeAssertions> { - public ComparableTypeAssertions(System.IComparable value) { } + public ComparableTypeAssertions(System.IComparable value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class ComparableTypeAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions, TAssertions> where TAssertions : FluentAssertions.Numeric.ComparableTypeAssertions { - public ComparableTypeAssertions(System.IComparable value) { } + public ComparableTypeAssertions(System.IComparable value, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint Be(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEquivalentTo(TExpectation expectation, string because = "", params object[] becauseArgs) { } @@ -1538,13 +1516,13 @@ namespace FluentAssertions.Numeric public class NullableNumericAssertions : FluentAssertions.Numeric.NullableNumericAssertions> where T : struct, System.IComparable { - public NullableNumericAssertions(T? value) { } + public NullableNumericAssertions(T? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class NullableNumericAssertions : FluentAssertions.Numeric.NumericAssertions where T : struct, System.IComparable where TAssertions : FluentAssertions.Numeric.NullableNumericAssertions { - public NullableNumericAssertions(T? value) { } + public NullableNumericAssertions(T? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint BeNull(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveValue(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Match(System.Linq.Expressions.Expression> predicate, string because = "", params object[] becauseArgs) { } @@ -1554,13 +1532,14 @@ namespace FluentAssertions.Numeric public class NumericAssertions : FluentAssertions.Numeric.NumericAssertions> where T : struct, System.IComparable { - public NumericAssertions(T value) { } + public NumericAssertions(T value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class NumericAssertions where T : struct, System.IComparable where TAssertions : FluentAssertions.Numeric.NumericAssertions { - public NumericAssertions(T value) { } + public NumericAssertions(T value, FluentAssertions.Execution.AssertionChain assertionChain) { } + public FluentAssertions.Execution.AssertionChain CurrentAssertionChain { get; } public T? Subject { get; } public FluentAssertions.AndConstraint Be(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Be(T? expected, string because = "", params object[] becauseArgs) { } @@ -1586,12 +1565,12 @@ namespace FluentAssertions.Primitives { public class BooleanAssertions : FluentAssertions.Primitives.BooleanAssertions { - public BooleanAssertions(bool? value) { } + public BooleanAssertions(bool? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class BooleanAssertions where TAssertions : FluentAssertions.Primitives.BooleanAssertions { - public BooleanAssertions(bool? value) { } + public BooleanAssertions(bool? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public bool? Subject { get; } public FluentAssertions.AndConstraint Be(bool expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeFalse(string because = "", params object[] becauseArgs) { } @@ -1602,12 +1581,12 @@ namespace FluentAssertions.Primitives } public class DateTimeAssertions : FluentAssertions.Primitives.DateTimeAssertions { - public DateTimeAssertions(System.DateTime? value) { } + public DateTimeAssertions(System.DateTime? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class DateTimeAssertions where TAssertions : FluentAssertions.Primitives.DateTimeAssertions { - public DateTimeAssertions(System.DateTime? value) { } + public DateTimeAssertions(System.DateTime? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public System.DateTime? Subject { get; } public FluentAssertions.AndConstraint Be(System.DateTime expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Be(System.DateTime? expected, string because = "", params object[] becauseArgs) { } @@ -1652,12 +1631,12 @@ namespace FluentAssertions.Primitives } public class DateTimeOffsetAssertions : FluentAssertions.Primitives.DateTimeOffsetAssertions { - public DateTimeOffsetAssertions(System.DateTimeOffset? value) { } + public DateTimeOffsetAssertions(System.DateTimeOffset? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class DateTimeOffsetAssertions where TAssertions : FluentAssertions.Primitives.DateTimeOffsetAssertions { - public DateTimeOffsetAssertions(System.DateTimeOffset? value) { } + public DateTimeOffsetAssertions(System.DateTimeOffset? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public System.DateTimeOffset? Subject { get; } public FluentAssertions.AndConstraint Be(System.DateTimeOffset expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Be(System.DateTimeOffset? expected, string because = "", params object[] becauseArgs) { } @@ -1707,7 +1686,7 @@ namespace FluentAssertions.Primitives public class DateTimeOffsetRangeAssertions where TAssertions : FluentAssertions.Primitives.DateTimeOffsetAssertions { - protected DateTimeOffsetRangeAssertions(TAssertions parentAssertions, System.DateTimeOffset? subject, FluentAssertions.Primitives.TimeSpanCondition condition, System.TimeSpan timeSpan) { } + protected DateTimeOffsetRangeAssertions(TAssertions parentAssertions, FluentAssertions.Execution.AssertionChain assertionChain, System.DateTimeOffset? subject, FluentAssertions.Primitives.TimeSpanCondition condition, System.TimeSpan timeSpan) { } public FluentAssertions.AndConstraint After(System.DateTimeOffset target, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Before(System.DateTimeOffset target, string because = "", params object[] becauseArgs) { } public override bool Equals(object obj) { } @@ -1715,7 +1694,7 @@ namespace FluentAssertions.Primitives public class DateTimeRangeAssertions where TAssertions : FluentAssertions.Primitives.DateTimeAssertions { - protected DateTimeRangeAssertions(TAssertions parentAssertions, System.DateTime? subject, FluentAssertions.Primitives.TimeSpanCondition condition, System.TimeSpan timeSpan) { } + protected DateTimeRangeAssertions(TAssertions parentAssertions, FluentAssertions.Execution.AssertionChain assertionChain, System.DateTime? subject, FluentAssertions.Primitives.TimeSpanCondition condition, System.TimeSpan timeSpan) { } public FluentAssertions.AndConstraint After(System.DateTime target, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Before(System.DateTime target, string because = "", params object[] becauseArgs) { } public override bool Equals(object obj) { } @@ -1723,13 +1702,13 @@ namespace FluentAssertions.Primitives public class EnumAssertions : FluentAssertions.Primitives.EnumAssertions> where TEnum : struct, System.Enum { - public EnumAssertions(TEnum subject) { } + public EnumAssertions(TEnum subject, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class EnumAssertions where TEnum : struct, System.Enum where TAssertions : FluentAssertions.Primitives.EnumAssertions { - public EnumAssertions(TEnum subject) { } + public EnumAssertions(TEnum subject, FluentAssertions.Execution.AssertionChain assertionChain) { } public TEnum? Subject { get; } public FluentAssertions.AndConstraint Be(TEnum expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Be(TEnum? expected, string because = "", params object[] becauseArgs) { } @@ -1756,12 +1735,12 @@ namespace FluentAssertions.Primitives } public class GuidAssertions : FluentAssertions.Primitives.GuidAssertions { - public GuidAssertions(System.Guid? value) { } + public GuidAssertions(System.Guid? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class GuidAssertions where TAssertions : FluentAssertions.Primitives.GuidAssertions { - public GuidAssertions(System.Guid? value) { } + public GuidAssertions(System.Guid? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public System.Guid? Subject { get; } public FluentAssertions.AndConstraint Be(System.Guid expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Be(string expected, string because = "", params object[] becauseArgs) { } @@ -1773,12 +1752,12 @@ namespace FluentAssertions.Primitives } public class HttpResponseMessageAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions { - public HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { } + public HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class HttpResponseMessageAssertions : FluentAssertions.Primitives.ObjectAssertions where TAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions { - protected HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { } + protected HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint BeRedirection(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeSuccessful(string because = "", params object[] becauseArgs) { } @@ -1790,12 +1769,12 @@ namespace FluentAssertions.Primitives } public class NullableBooleanAssertions : FluentAssertions.Primitives.NullableBooleanAssertions { - public NullableBooleanAssertions(bool? value) { } + public NullableBooleanAssertions(bool? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class NullableBooleanAssertions : FluentAssertions.Primitives.BooleanAssertions where TAssertions : FluentAssertions.Primitives.NullableBooleanAssertions { - public NullableBooleanAssertions(bool? value) { } + public NullableBooleanAssertions(bool? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint Be(bool? expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeNull(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveValue(string because = "", params object[] becauseArgs) { } @@ -1807,12 +1786,12 @@ namespace FluentAssertions.Primitives } public class NullableDateTimeAssertions : FluentAssertions.Primitives.NullableDateTimeAssertions { - public NullableDateTimeAssertions(System.DateTime? expected) { } + public NullableDateTimeAssertions(System.DateTime? expected, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class NullableDateTimeAssertions : FluentAssertions.Primitives.DateTimeAssertions where TAssertions : FluentAssertions.Primitives.NullableDateTimeAssertions { - public NullableDateTimeAssertions(System.DateTime? expected) { } + public NullableDateTimeAssertions(System.DateTime? expected, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint BeNull(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveValue(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeNull(string because = "", params object[] becauseArgs) { } @@ -1820,12 +1799,12 @@ namespace FluentAssertions.Primitives } public class NullableDateTimeOffsetAssertions : FluentAssertions.Primitives.NullableDateTimeOffsetAssertions { - public NullableDateTimeOffsetAssertions(System.DateTimeOffset? expected) { } + public NullableDateTimeOffsetAssertions(System.DateTimeOffset? expected, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class NullableDateTimeOffsetAssertions : FluentAssertions.Primitives.DateTimeOffsetAssertions where TAssertions : FluentAssertions.Primitives.NullableDateTimeOffsetAssertions { - public NullableDateTimeOffsetAssertions(System.DateTimeOffset? expected) { } + public NullableDateTimeOffsetAssertions(System.DateTimeOffset? expected, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint BeNull(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveValue(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeNull(string because = "", params object[] becauseArgs) { } @@ -1834,13 +1813,13 @@ namespace FluentAssertions.Primitives public class NullableEnumAssertions : FluentAssertions.Primitives.NullableEnumAssertions> where TEnum : struct, System.Enum { - public NullableEnumAssertions(TEnum? subject) { } + public NullableEnumAssertions(TEnum? subject, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class NullableEnumAssertions : FluentAssertions.Primitives.EnumAssertions where TEnum : struct, System.Enum where TAssertions : FluentAssertions.Primitives.NullableEnumAssertions { - public NullableEnumAssertions(TEnum? subject) { } + public NullableEnumAssertions(TEnum? subject, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint BeNull(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint HaveValue(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint NotBeNull(string because = "", params object[] becauseArgs) { } @@ -1848,12 +1827,12 @@ namespace FluentAssertions.Primitives } public class NullableGuidAssertions : FluentAssertions.Primitives.NullableGuidAssertions { - public NullableGuidAssertions(System.Guid? value) { } + public NullableGuidAssertions(System.Guid? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class NullableGuidAssertions : FluentAssertions.Primitives.GuidAssertions where TAssertions : FluentAssertions.Primitives.NullableGuidAssertions { - public NullableGuidAssertions(System.Guid? value) { } + public NullableGuidAssertions(System.Guid? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint Be(System.Guid? expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeNull(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveValue(string because = "", params object[] becauseArgs) { } @@ -1862,12 +1841,12 @@ namespace FluentAssertions.Primitives } public class NullableSimpleTimeSpanAssertions : FluentAssertions.Primitives.NullableSimpleTimeSpanAssertions { - public NullableSimpleTimeSpanAssertions(System.TimeSpan? value) { } + public NullableSimpleTimeSpanAssertions(System.TimeSpan? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class NullableSimpleTimeSpanAssertions : FluentAssertions.Primitives.SimpleTimeSpanAssertions where TAssertions : FluentAssertions.Primitives.NullableSimpleTimeSpanAssertions { - public NullableSimpleTimeSpanAssertions(System.TimeSpan? value) { } + public NullableSimpleTimeSpanAssertions(System.TimeSpan? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint Be(System.TimeSpan? expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeNull(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveValue(string because = "", params object[] becauseArgs) { } @@ -1876,7 +1855,7 @@ namespace FluentAssertions.Primitives } public class ObjectAssertions : FluentAssertions.Primitives.ObjectAssertions { - public ObjectAssertions(object value) { } + public ObjectAssertions(object value, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint Be(TExpectation expected, System.Collections.Generic.IEqualityComparer comparer, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOneOf(System.Collections.Generic.IEnumerable validValues, System.Collections.Generic.IEqualityComparer comparer, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBe(TExpectation unexpected, System.Collections.Generic.IEqualityComparer comparer, string because = "", params object[] becauseArgs) { } @@ -1884,7 +1863,7 @@ namespace FluentAssertions.Primitives public class ObjectAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions where TAssertions : FluentAssertions.Primitives.ObjectAssertions { - public ObjectAssertions(TSubject value) { } + public ObjectAssertions(TSubject value, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint Be(TSubject expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Be(TSubject expected, System.Collections.Generic.IEqualityComparer comparer, string because = "", params object[] becauseArgs) { } @@ -1902,7 +1881,8 @@ namespace FluentAssertions.Primitives public abstract class ReferenceTypeAssertions where TAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions { - protected ReferenceTypeAssertions(TSubject subject) { } + protected ReferenceTypeAssertions(TSubject subject, FluentAssertions.Execution.AssertionChain assertionChain) { } + public FluentAssertions.Execution.AssertionChain CurrentAssertionChain { get; } protected abstract string Identifier { get; } public TSubject Subject { get; } public FluentAssertions.AndConstraint BeAssignableTo(System.Type type, string because = "", params object[] becauseArgs) { } @@ -1926,12 +1906,12 @@ namespace FluentAssertions.Primitives } public class SimpleTimeSpanAssertions : FluentAssertions.Primitives.SimpleTimeSpanAssertions { - public SimpleTimeSpanAssertions(System.TimeSpan? value) { } + public SimpleTimeSpanAssertions(System.TimeSpan? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class SimpleTimeSpanAssertions where TAssertions : FluentAssertions.Primitives.SimpleTimeSpanAssertions { - public SimpleTimeSpanAssertions(System.TimeSpan? value) { } + public SimpleTimeSpanAssertions(System.TimeSpan? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public System.TimeSpan? Subject { get; } public FluentAssertions.AndConstraint Be(System.TimeSpan expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeCloseTo(System.TimeSpan nearbyTime, System.TimeSpan precision, string because = "", params object[] becauseArgs) { } @@ -1947,12 +1927,12 @@ namespace FluentAssertions.Primitives } public class StringAssertions : FluentAssertions.Primitives.StringAssertions { - public StringAssertions(string value) { } + public StringAssertions(string value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class StringAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions where TAssertions : FluentAssertions.Primitives.StringAssertions { - public StringAssertions(string value) { } + public StringAssertions(string value, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint Be(string expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEmpty(string because = "", params object[] becauseArgs) { } @@ -2028,8 +2008,8 @@ namespace FluentAssertions.Specialized { public class ActionAssertions : FluentAssertions.Specialized.DelegateAssertions { - public ActionAssertions(System.Action subject, FluentAssertions.Specialized.IExtractExceptions extractor) { } - public ActionAssertions(System.Action subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Common.IClock clock) { } + public ActionAssertions(System.Action subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain) { } + public ActionAssertions(System.Action subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain, FluentAssertions.Common.IClock clock) { } protected override string Identifier { get; } protected override void InvokeSubject() { } public FluentAssertions.AndConstraint NotThrow(string because = "", params object[] becauseArgs) { } @@ -2039,7 +2019,7 @@ namespace FluentAssertions.Specialized where TTask : System.Threading.Tasks.Task where TAssertions : FluentAssertions.Specialized.AsyncFunctionAssertions { - protected AsyncFunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Common.IClock clock) { } + protected AsyncFunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain, FluentAssertions.Common.IClock clock) { } protected override string Identifier { get; } public System.Threading.Tasks.Task> NotCompleteWithinAsync(System.TimeSpan timeSpan, string because = "", params object[] becauseArgs) { } public System.Threading.Tasks.Task> NotThrowAsync(string because = "", params object[] becauseArgs) @@ -2065,7 +2045,7 @@ namespace FluentAssertions.Specialized where TDelegate : System.Delegate where TAssertions : FluentAssertions.Specialized.DelegateAssertions { - protected DelegateAssertions(TDelegate @delegate, FluentAssertions.Specialized.IExtractExceptions extractor) { } + protected DelegateAssertions(TDelegate @delegate, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain) { } protected abstract void InvokeSubject(); public FluentAssertions.AndConstraint NotThrow(string because = "", params object[] becauseArgs) where TException : System.Exception { } @@ -2077,7 +2057,7 @@ namespace FluentAssertions.Specialized public class ExceptionAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions, FluentAssertions.Specialized.ExceptionAssertions> where TException : System.Exception { - public ExceptionAssertions(System.Collections.Generic.IEnumerable exceptions) { } + public ExceptionAssertions(System.Collections.Generic.IEnumerable exceptions, FluentAssertions.Execution.AssertionChain assertionChain) { } public TException And { get; } protected override string Identifier { get; } public TException Which { get; } @@ -2099,7 +2079,7 @@ namespace FluentAssertions.Specialized } public class ExecutionTimeAssertions { - public ExecutionTimeAssertions(FluentAssertions.Specialized.ExecutionTime executionTime) { } + public ExecutionTimeAssertions(FluentAssertions.Specialized.ExecutionTime executionTime, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint BeCloseTo(System.TimeSpan expectedDuration, System.TimeSpan precision, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeGreaterThan(System.TimeSpan minDuration, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeGreaterThanOrEqualTo(System.TimeSpan minDuration, string because = "", params object[] becauseArgs) { } @@ -2109,8 +2089,8 @@ namespace FluentAssertions.Specialized } public class FunctionAssertions : FluentAssertions.Specialized.DelegateAssertions, FluentAssertions.Specialized.FunctionAssertions> { - public FunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor) { } - public FunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Common.IClock clock) { } + public FunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain) { } + public FunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain, FluentAssertions.Common.IClock clock) { } protected override string Identifier { get; } protected override void InvokeSubject() { } public FluentAssertions.AndWhichConstraint, T> NotThrow(string because = "", params object[] becauseArgs) { } @@ -2118,8 +2098,8 @@ namespace FluentAssertions.Specialized } public class GenericAsyncFunctionAssertions : FluentAssertions.Specialized.AsyncFunctionAssertions, FluentAssertions.Specialized.GenericAsyncFunctionAssertions> { - public GenericAsyncFunctionAssertions(System.Func> subject, FluentAssertions.Specialized.IExtractExceptions extractor) { } - public GenericAsyncFunctionAssertions(System.Func> subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Common.IClock clock) { } + public GenericAsyncFunctionAssertions(System.Func> subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain) { } + public GenericAsyncFunctionAssertions(System.Func> subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain, FluentAssertions.Common.IClock clock) { } public System.Threading.Tasks.Task, TResult>> CompleteWithinAsync(System.TimeSpan timeSpan, string because = "", params object[] becauseArgs) { } public System.Threading.Tasks.Task, TResult>> NotThrowAfterAsync(System.TimeSpan waitTime, System.TimeSpan pollInterval, string because = "", params object[] becauseArgs) { } public System.Threading.Tasks.Task, TResult>> NotThrowAsync(string because = "", params object[] becauseArgs) { } @@ -2135,8 +2115,8 @@ namespace FluentAssertions.Specialized } public class NonGenericAsyncFunctionAssertions : FluentAssertions.Specialized.AsyncFunctionAssertions { - public NonGenericAsyncFunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor) { } - public NonGenericAsyncFunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Common.IClock clock) { } + public NonGenericAsyncFunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain) { } + public NonGenericAsyncFunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain, FluentAssertions.Common.IClock clock) { } public System.Threading.Tasks.Task> CompleteWithinAsync(System.TimeSpan timeSpan, string because = "", params object[] becauseArgs) { } public System.Threading.Tasks.Task> NotThrowAfterAsync(System.TimeSpan waitTime, System.TimeSpan pollInterval, string because = "", params object[] becauseArgs) { } public System.Threading.Tasks.Task> NotThrowAsync(string because = "", params object[] becauseArgs) { } @@ -2148,33 +2128,23 @@ namespace FluentAssertions.Specialized } public class TaskCompletionSourceAssertions : FluentAssertions.Specialized.TaskCompletionSourceAssertionsBase { - public TaskCompletionSourceAssertions(System.Threading.Tasks.TaskCompletionSource tcs) { } - public TaskCompletionSourceAssertions(System.Threading.Tasks.TaskCompletionSource tcs, FluentAssertions.Common.IClock clock) { } + public TaskCompletionSourceAssertions(System.Threading.Tasks.TaskCompletionSource tcs, FluentAssertions.Execution.AssertionChain assertionChain) { } + public TaskCompletionSourceAssertions(System.Threading.Tasks.TaskCompletionSource tcs, FluentAssertions.Execution.AssertionChain assertionChain, FluentAssertions.Common.IClock clock) { } public System.Threading.Tasks.Task, T>> CompleteWithinAsync(System.TimeSpan timeSpan, string because = "", params object[] becauseArgs) { } public System.Threading.Tasks.Task>> NotCompleteWithinAsync(System.TimeSpan timeSpan, string because = "", params object[] becauseArgs) { } } } namespace FluentAssertions.Streams { - public class BufferedStreamAssertions : FluentAssertions.Streams.BufferedStreamAssertions - { - public BufferedStreamAssertions(System.IO.BufferedStream stream) { } - } - public class BufferedStreamAssertions : FluentAssertions.Streams.StreamAssertions - where TAssertions : FluentAssertions.Streams.BufferedStreamAssertions - { - public BufferedStreamAssertions(System.IO.BufferedStream stream) { } - protected override string Identifier { get; } - } public class StreamAssertions : FluentAssertions.Streams.StreamAssertions { - public StreamAssertions(System.IO.Stream stream) { } + public StreamAssertions(System.IO.Stream stream, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class StreamAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions where TSubject : System.IO.Stream where TAssertions : FluentAssertions.Streams.StreamAssertions { - public StreamAssertions(TSubject stream) { } + public StreamAssertions(TSubject stream, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint BeReadOnly(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeReadable(string because = "", params object[] becauseArgs) { } @@ -2200,7 +2170,7 @@ namespace FluentAssertions.Types } public class AssemblyAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions { - public AssemblyAssertions(System.Reflection.Assembly assembly) { } + public AssemblyAssertions(System.Reflection.Assembly assembly, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint BeSignedWithPublicKey(string publicKey, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeUnsigned(string because = "", params object[] becauseArgs) { } @@ -2210,15 +2180,17 @@ namespace FluentAssertions.Types } public class ConstructorInfoAssertions : FluentAssertions.Types.MethodBaseAssertions { - public ConstructorInfoAssertions(System.Reflection.ConstructorInfo constructorInfo) { } + public ConstructorInfoAssertions(System.Reflection.ConstructorInfo constructorInfo, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } + protected override string SubjectDescription { get; } } public abstract class MemberInfoAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions where TSubject : System.Reflection.MemberInfo where TAssertions : FluentAssertions.Types.MemberInfoAssertions { - protected MemberInfoAssertions(TSubject subject) { } + protected MemberInfoAssertions(TSubject subject, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } + protected virtual string SubjectDescription { get; } public FluentAssertions.AndWhichConstraint, TAttribute> BeDecoratedWith(string because = "", params object[] becauseArgs) where TAttribute : System.Attribute { } public FluentAssertions.AndWhichConstraint, TAttribute> BeDecoratedWith(System.Linq.Expressions.Expression> isMatchingAttributePredicate, string because = "", params object[] becauseArgs) @@ -2232,14 +2204,15 @@ namespace FluentAssertions.Types where TSubject : System.Reflection.MethodBase where TAssertions : FluentAssertions.Types.MethodBaseAssertions { - protected MethodBaseAssertions(TSubject subject) { } + protected MethodBaseAssertions(TSubject subject, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint HaveAccessModifier(FluentAssertions.Common.CSharpAccessModifier accessModifier, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveAccessModifier(FluentAssertions.Common.CSharpAccessModifier accessModifier, string because = "", params object[] becauseArgs) { } } public class MethodInfoAssertions : FluentAssertions.Types.MethodBaseAssertions { - public MethodInfoAssertions(System.Reflection.MethodInfo methodInfo) { } + public MethodInfoAssertions(System.Reflection.MethodInfo methodInfo, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } + protected override string SubjectDescription { get; } public FluentAssertions.AndConstraint BeAsync(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeVirtual(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeAsync(string because = "", params object[] becauseArgs) { } @@ -2282,7 +2255,7 @@ namespace FluentAssertions.Types } public class MethodInfoSelectorAssertions { - public MethodInfoSelectorAssertions(params System.Reflection.MethodInfo[] methods) { } + public MethodInfoSelectorAssertions(FluentAssertions.Execution.AssertionChain assertionChain, params System.Reflection.MethodInfo[] methods) { } protected string Context { get; } public System.Collections.Generic.IEnumerable SubjectMethods { get; } public FluentAssertions.AndConstraint Be(FluentAssertions.Common.CSharpAccessModifier accessModifier, string because = "", params object[] becauseArgs) { } @@ -2303,8 +2276,9 @@ namespace FluentAssertions.Types } public class PropertyInfoAssertions : FluentAssertions.Types.MemberInfoAssertions { - public PropertyInfoAssertions(System.Reflection.PropertyInfo propertyInfo) { } + public PropertyInfoAssertions(System.Reflection.PropertyInfo propertyInfo, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } + protected override string SubjectDescription { get; } public FluentAssertions.AndConstraint BeReadable(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeReadable(FluentAssertions.Common.CSharpAccessModifier accessModifier, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeVirtual(string because = "", params object[] becauseArgs) { } @@ -2345,7 +2319,7 @@ namespace FluentAssertions.Types } public class PropertyInfoSelectorAssertions { - public PropertyInfoSelectorAssertions(params System.Reflection.PropertyInfo[] properties) { } + public PropertyInfoSelectorAssertions(FluentAssertions.Execution.AssertionChain assertionChain, params System.Reflection.PropertyInfo[] properties) { } protected string Context { get; } public System.Collections.Generic.IEnumerable SubjectProperties { get; } public FluentAssertions.AndConstraint BeDecoratedWith(string because = "", params object[] becauseArgs) @@ -2360,7 +2334,7 @@ namespace FluentAssertions.Types } public class TypeAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions { - public TypeAssertions(System.Type type) { } + public TypeAssertions(System.Type type, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint Be(System.Type expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Be(string because = "", params object[] becauseArgs) { } @@ -2478,7 +2452,7 @@ namespace FluentAssertions.Types } public class TypeSelectorAssertions { - public TypeSelectorAssertions(params System.Type[] types) { } + public TypeSelectorAssertions(FluentAssertions.Execution.AssertionChain assertionChain, params System.Type[] types) { } public System.Collections.Generic.IEnumerable Subject { get; } public FluentAssertions.AndConstraint BeDecoratedWith(string because = "", params object[] becauseArgs) where TAttribute : System.Attribute { } @@ -2509,7 +2483,7 @@ namespace FluentAssertions.Xml { public class XAttributeAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions { - public XAttributeAssertions(System.Xml.Linq.XAttribute attribute) { } + public XAttributeAssertions(System.Xml.Linq.XAttribute attribute, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint Be(System.Xml.Linq.XAttribute expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveValue(string expected, string because = "", params object[] becauseArgs) { } @@ -2517,7 +2491,7 @@ namespace FluentAssertions.Xml } public class XDocumentAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions { - public XDocumentAssertions(System.Xml.Linq.XDocument document) { } + public XDocumentAssertions(System.Xml.Linq.XDocument document, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint Be(System.Xml.Linq.XDocument expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEquivalentTo(System.Xml.Linq.XDocument expected, string because = "", params object[] becauseArgs) { } @@ -2532,7 +2506,7 @@ namespace FluentAssertions.Xml } public class XElementAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions { - public XElementAssertions(System.Xml.Linq.XElement xElement) { } + public XElementAssertions(System.Xml.Linq.XElement xElement, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint Be(System.Xml.Linq.XElement expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEquivalentTo(System.Xml.Linq.XElement expected, string because = "", params object[] becauseArgs) { } @@ -2548,7 +2522,7 @@ namespace FluentAssertions.Xml } public class XmlElementAssertions : FluentAssertions.Xml.XmlNodeAssertions { - public XmlElementAssertions(System.Xml.XmlElement xmlElement) { } + public XmlElementAssertions(System.Xml.XmlElement xmlElement, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint HaveAttribute(string expectedName, string expectedValue, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveAttributeWithNamespace(string expectedName, string expectedNamespace, string expectedValue, string because = "", params object[] becauseArgs) { } @@ -2558,13 +2532,13 @@ namespace FluentAssertions.Xml } public class XmlNodeAssertions : FluentAssertions.Xml.XmlNodeAssertions { - public XmlNodeAssertions(System.Xml.XmlNode xmlNode) { } + public XmlNodeAssertions(System.Xml.XmlNode xmlNode, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class XmlNodeAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions where TSubject : System.Xml.XmlNode where TAssertions : FluentAssertions.Xml.XmlNodeAssertions { - public XmlNodeAssertions(TSubject xmlNode) { } + public XmlNodeAssertions(TSubject xmlNode, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint BeEquivalentTo(System.Xml.XmlNode expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEquivalentTo(System.Xml.XmlNode unexpected, string because = "", params object[] becauseArgs) { } diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.1.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.1.verified.txt index 24ab2600ca..560436a7d3 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.1.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.1.verified.txt @@ -15,12 +15,14 @@ namespace FluentAssertions public AndConstraint(TParent parent) { } public TParent And { get; } } - public class AndWhichConstraint : FluentAssertions.AndConstraint + public class AndWhichConstraint : FluentAssertions.AndConstraint { - public AndWhichConstraint(TParentConstraint parentConstraint, System.Collections.Generic.IEnumerable matchedConstraint) { } - public AndWhichConstraint(TParentConstraint parentConstraint, TMatchedElement matchedConstraint) { } - public TMatchedElement Subject { get; } - public TMatchedElement Which { get; } + public AndWhichConstraint(TParent parent, System.Collections.Generic.IEnumerable subjects) { } + public AndWhichConstraint(TParent parent, TSubject subject) { } + public AndWhichConstraint(TParent parent, System.Collections.Generic.IEnumerable subjects, FluentAssertions.Execution.AssertionChain assertionChain, string pathPostfix) { } + public AndWhichConstraint(TParent parent, TSubject subject, FluentAssertions.Execution.AssertionChain assertionChain, string pathPostfix = "") { } + public TSubject Subject { get; } + public TSubject Which { get; } } public static class AssertionExtensions { @@ -376,18 +378,18 @@ namespace FluentAssertions.Collections { public class GenericCollectionAssertions : FluentAssertions.Collections.GenericCollectionAssertions, T, FluentAssertions.Collections.GenericCollectionAssertions> { - public GenericCollectionAssertions(System.Collections.Generic.IEnumerable actualValue) { } + public GenericCollectionAssertions(System.Collections.Generic.IEnumerable actualValue, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class GenericCollectionAssertions : FluentAssertions.Collections.GenericCollectionAssertions> where TCollection : System.Collections.Generic.IEnumerable { - public GenericCollectionAssertions(TCollection actualValue) { } + public GenericCollectionAssertions(TCollection actualValue, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class GenericCollectionAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions where TCollection : System.Collections.Generic.IEnumerable where TAssertions : FluentAssertions.Collections.GenericCollectionAssertions { - public GenericCollectionAssertions(TCollection actualValue) { } + public GenericCollectionAssertions(TCollection actualValue, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint AllBeAssignableTo(System.Type expectedType, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint> AllBeAssignableTo(string because = "", params object[] becauseArgs) { } @@ -461,7 +463,7 @@ namespace FluentAssertions.Collections public FluentAssertions.AndConstraint NotBeSubsetOf(System.Collections.Generic.IEnumerable unexpectedSuperset, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotContain(System.Collections.Generic.IEnumerable unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotContain(System.Linq.Expressions.Expression> predicate, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint NotContain(T unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotContain(T unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotContainEquivalentOf(TExpectation unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotContainEquivalentOf(TExpectation unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotContainInConsecutiveOrder(params T[] unexpected) { } @@ -492,13 +494,13 @@ namespace FluentAssertions.Collections public class GenericDictionaryAssertions : FluentAssertions.Collections.GenericDictionaryAssertions> where TCollection : System.Collections.Generic.IEnumerable> { - public GenericDictionaryAssertions(TCollection keyValuePairs) { } + public GenericDictionaryAssertions(TCollection keyValuePairs, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class GenericDictionaryAssertions : FluentAssertions.Collections.GenericCollectionAssertions, TAssertions> where TCollection : System.Collections.Generic.IEnumerable> where TAssertions : FluentAssertions.Collections.GenericDictionaryAssertions { - public GenericDictionaryAssertions(TCollection keyValuePairs) { } + public GenericDictionaryAssertions(TCollection keyValuePairs, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint BeEquivalentTo(TExpectation expectation, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEquivalentTo(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } @@ -510,8 +512,8 @@ namespace FluentAssertions.Collections public FluentAssertions.AndConstraint ContainKeys(params TKey[] expected) { } public FluentAssertions.AndConstraint ContainKeys(System.Collections.Generic.IEnumerable expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint ContainValue(TValue expected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint ContainValues(params TValue[] expected) { } - public FluentAssertions.AndConstraint ContainValues(System.Collections.Generic.IEnumerable expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint> ContainValues(params TValue[] expected) { } + public FluentAssertions.AndWhichConstraint> ContainValues(System.Collections.Generic.IEnumerable expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Equal(T expected, string because = "", params object[] becauseArgs) where T : System.Collections.Generic.IEnumerable> { } public FluentAssertions.AndConstraint NotContain(params System.Collections.Generic.KeyValuePair[] items) { } @@ -529,18 +531,18 @@ namespace FluentAssertions.Collections } public class StringCollectionAssertions : FluentAssertions.Collections.StringCollectionAssertions> { - public StringCollectionAssertions(System.Collections.Generic.IEnumerable actualValue) { } + public StringCollectionAssertions(System.Collections.Generic.IEnumerable actualValue, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class StringCollectionAssertions : FluentAssertions.Collections.StringCollectionAssertions> where TCollection : System.Collections.Generic.IEnumerable { - public StringCollectionAssertions(TCollection actualValue) { } + public StringCollectionAssertions(TCollection actualValue, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class StringCollectionAssertions : FluentAssertions.Collections.GenericCollectionAssertions where TCollection : System.Collections.Generic.IEnumerable where TAssertions : FluentAssertions.Collections.StringCollectionAssertions { - public StringCollectionAssertions(TCollection actualValue) { } + public StringCollectionAssertions(TCollection actualValue, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint AllBe(string expectation, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint AllBe(string expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEquivalentTo(params string[] expectation) { } @@ -553,18 +555,18 @@ namespace FluentAssertions.Collections } public class SubsequentOrderingAssertions : FluentAssertions.Collections.SubsequentOrderingGenericCollectionAssertions, T, FluentAssertions.Collections.SubsequentOrderingAssertions> { - public SubsequentOrderingAssertions(System.Collections.Generic.IEnumerable actualValue, System.Linq.IOrderedEnumerable previousOrderedEnumerable) { } + public SubsequentOrderingAssertions(System.Collections.Generic.IEnumerable actualValue, System.Linq.IOrderedEnumerable previousOrderedEnumerable, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class SubsequentOrderingGenericCollectionAssertions : FluentAssertions.Collections.SubsequentOrderingGenericCollectionAssertions> where TCollection : System.Collections.Generic.IEnumerable { - public SubsequentOrderingGenericCollectionAssertions(TCollection actualValue, System.Linq.IOrderedEnumerable previousOrderedEnumerable) { } + public SubsequentOrderingGenericCollectionAssertions(TCollection actualValue, System.Linq.IOrderedEnumerable previousOrderedEnumerable, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class SubsequentOrderingGenericCollectionAssertions : FluentAssertions.Collections.GenericCollectionAssertions where TCollection : System.Collections.Generic.IEnumerable where TAssertions : FluentAssertions.Collections.SubsequentOrderingGenericCollectionAssertions { - public SubsequentOrderingGenericCollectionAssertions(TCollection actualValue, System.Linq.IOrderedEnumerable previousOrderedEnumerable) { } + public SubsequentOrderingGenericCollectionAssertions(TCollection actualValue, System.Linq.IOrderedEnumerable previousOrderedEnumerable, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint> ThenBeInAscendingOrder(System.Linq.Expressions.Expression> propertyExpression, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint> ThenBeInAscendingOrder(System.Linq.Expressions.Expression> propertyExpression, System.Collections.Generic.IComparer comparer, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint> ThenBeInDescendingOrder(System.Linq.Expressions.Expression> propertyExpression, string because = "", params object[] becauseArgs) { } @@ -705,7 +707,7 @@ namespace FluentAssertions.Equivalency { protected EquivalencyStep() { } public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } - protected abstract FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nested); + protected abstract FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nestedValidator); } public class EquivalencyValidationContext : FluentAssertions.Equivalency.IEquivalencyValidationContext { @@ -797,7 +799,7 @@ namespace FluentAssertions.Equivalency } public interface IMemberMatchingRule { - FluentAssertions.Equivalency.IMember Match(FluentAssertions.Equivalency.IMember expectedMember, object subject, FluentAssertions.Equivalency.INode parent, FluentAssertions.Equivalency.IEquivalencyOptions options); + FluentAssertions.Equivalency.IMember Match(FluentAssertions.Equivalency.IMember expectedMember, object subject, FluentAssertions.Equivalency.INode parent, FluentAssertions.Equivalency.IEquivalencyOptions options, FluentAssertions.Execution.AssertionChain assertionChain); } public interface IMemberSelectionRule { @@ -959,7 +961,7 @@ namespace FluentAssertions.Equivalency.Steps { public class AssertionRuleEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { - public AssertionRuleEquivalencyStep(System.Linq.Expressions.Expression> predicate, System.Action> assertion) { } + public AssertionRuleEquivalencyStep(System.Linq.Expressions.Expression> predicate, System.Action> assertionAction) { } public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } public override string ToString() { } } @@ -972,7 +974,7 @@ namespace FluentAssertions.Equivalency.Steps public class DictionaryEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep { public DictionaryEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nested) { } + protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nestedValidator) { } } public class EnumEqualityStep : FluentAssertions.Equivalency.IEquivalencyStep { @@ -1033,17 +1035,17 @@ namespace FluentAssertions.Equivalency.Steps public class XAttributeEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep { public XAttributeEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nested) { } + protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nestedValidator) { } } public class XDocumentEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep { public XDocumentEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nested) { } + protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nestedValidator) { } } public class XElementEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep { public XElementEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nested) { } + protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nestedValidator) { } } } namespace FluentAssertions.Equivalency.Tracing @@ -1073,7 +1075,7 @@ namespace FluentAssertions.Events { public class EventAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions> { - protected EventAssertions(FluentAssertions.Events.IMonitor monitor) { } + protected EventAssertions(FluentAssertions.Events.IMonitor monitor, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.Events.IMonitor Monitor { get; } public void NotRaise(string eventName, string because = "", params object[] becauseArgs) { } @@ -1119,76 +1121,63 @@ namespace FluentAssertions.Events } namespace FluentAssertions.Execution { + public sealed class AssertionChain + { + public string CallerIdentifier { get; } + public bool HasOverriddenCallerIdentifier { get; } + public bool Succeeded { get; } + public FluentAssertions.Execution.AssertionChain UsingLineBreaks { get; } + public void AddReportable(string key, System.Func getValue) { } + public void AddReportable(string key, string value) { } + public FluentAssertions.Execution.AssertionChain BecauseOf(FluentAssertions.Execution.Reason reason) { } + public FluentAssertions.Execution.AssertionChain BecauseOf(string because, params object[] becauseArgs) { } + public FluentAssertions.Execution.Continuation FailWith(System.Func getFailureReason) { } + public FluentAssertions.Execution.Continuation FailWith(string message) { } + public FluentAssertions.Execution.Continuation FailWith(string message, params System.Func[] argProviders) { } + public FluentAssertions.Execution.Continuation FailWith(string message, params object[] args) { } + public FluentAssertions.Execution.AssertionChain ForCondition(bool condition) { } + public FluentAssertions.Execution.AssertionChain ForConstraint(FluentAssertions.OccurrenceConstraint constraint, int actualOccurrences) { } + public FluentAssertions.Execution.GivenSelector Given(System.Func selector) { } + public void OverrideCallerIdentifier(System.Func getCallerIdentifier) { } + public void ReuseOnce() { } + public FluentAssertions.Execution.AssertionChain WithCallerPostfix(string postfix) { } + public FluentAssertions.Execution.AssertionChain WithCallerPrefix(string prefix) { } + public FluentAssertions.Execution.AssertionChain WithDefaultIdentifier(string identifier) { } + public FluentAssertions.Execution.Continuation WithExpectation(string message, System.Action chain) { } + public FluentAssertions.Execution.Continuation WithExpectation(string message, object arg1, System.Action chain) { } + public FluentAssertions.Execution.Continuation WithExpectation(string message, object arg1, object arg2, System.Action chain) { } + public FluentAssertions.Execution.AssertionChain WithReportable(string name, System.Func content) { } + public static FluentAssertions.Execution.AssertionChain GetOrCreate() { } + } [System.Serializable] public class AssertionFailedException : System.Exception { public AssertionFailedException(string message) { } protected AssertionFailedException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } } - public sealed class AssertionScope : FluentAssertions.Execution.IAssertionScope, System.IDisposable + public sealed class AssertionScope : System.IDisposable { public AssertionScope() { } public AssertionScope(FluentAssertions.Execution.IAssertionStrategy assertionStrategy) { } - public AssertionScope(System.Lazy context) { } - public AssertionScope(string context) { } - public string CallerIdentity { get; } - public System.Lazy Context { get; set; } + public AssertionScope(System.Func name) { } + public AssertionScope(string name) { } public FluentAssertions.Formatting.FormattingOptions FormattingOptions { get; } - public FluentAssertions.Execution.AssertionScope UsingLineBreaks { get; } + public System.Func Name { get; } public static FluentAssertions.Execution.AssertionScope Current { get; } - public void AddNonReportable(string key, object value) { } public void AddPreFormattedFailure(string formattedFailureMessage) { } - public void AddReportable(string key, System.Func valueFunc) { } - public void AddReportable(string key, string value) { } public void AppendTracing(string tracingBlock) { } - public void AssumeSingleCaller() { } - public FluentAssertions.Execution.AssertionScope BecauseOf(FluentAssertions.Execution.Reason reason) { } - public FluentAssertions.Execution.AssertionScope BecauseOf(string because, params object[] becauseArgs) { } - public FluentAssertions.Execution.Continuation ClearExpectation() { } public string[] Discard() { } public void Dispose() { } - public FluentAssertions.Execution.Continuation FailWith(System.Func failReasonFunc) { } - public FluentAssertions.Execution.Continuation FailWith(string message) { } - public FluentAssertions.Execution.Continuation FailWith(string message, params System.Func[] argProviders) { } - public FluentAssertions.Execution.Continuation FailWith(string message, params object[] args) { } - public FluentAssertions.Execution.AssertionScope ForCondition(bool condition) { } - public FluentAssertions.Execution.AssertionScope ForConstraint(FluentAssertions.OccurrenceConstraint constraint, int actualOccurrences) { } - public T Get(string key) { } - public FluentAssertions.Execution.GivenSelector Given(System.Func selector) { } public bool HasFailures() { } - public FluentAssertions.Execution.AssertionScope WithDefaultIdentifier(string identifier) { } - public FluentAssertions.Execution.AssertionScope WithExpectation(string message, params object[] args) { } } public class Continuation { - public FluentAssertions.Execution.IAssertionScope Then { get; } - public static bool op_Implicit(FluentAssertions.Execution.Continuation continuation) { } + public FluentAssertions.Execution.AssertionChain Then { get; } } public class ContinuationOfGiven { + public bool Succeeded { get; } public FluentAssertions.Execution.GivenSelector Then { get; } - public static bool op_Implicit(FluentAssertions.Execution.ContinuationOfGiven continuationOfGiven) { } - } - public sealed class ContinuedAssertionScope : FluentAssertions.Execution.IAssertionScope, System.IDisposable - { - public FluentAssertions.Execution.IAssertionScope UsingLineBreaks { get; } - public FluentAssertions.Execution.IAssertionScope BecauseOf(string because, params object[] becauseArgs) { } - public FluentAssertions.Execution.Continuation ClearExpectation() { } - public string[] Discard() { } - public void Dispose() { } - public FluentAssertions.Execution.Continuation FailWith(System.Func failReasonFunc) { } - public FluentAssertions.Execution.Continuation FailWith(string message) { } - public FluentAssertions.Execution.Continuation FailWith(string message, params System.Func[] argProviders) { } - public FluentAssertions.Execution.Continuation FailWith(string message, params object[] args) { } - public FluentAssertions.Execution.IAssertionScope ForCondition(bool condition) { } - public FluentAssertions.Execution.IAssertionScope ForConstraint(FluentAssertions.OccurrenceConstraint constraint, int actualOccurrences) { } - public FluentAssertions.Execution.GivenSelector Given(System.Func selector) { } - public FluentAssertions.Execution.IAssertionScope WithDefaultIdentifier(string identifier) { } - public FluentAssertions.Execution.IAssertionScope WithExpectation(string message, params object[] args) { } - } - public static class Execute - { - public static FluentAssertions.Execution.AssertionScope Assertion { get; } } public class FailReason { @@ -1198,29 +1187,13 @@ namespace FluentAssertions.Execution } public class GivenSelector { - public FluentAssertions.Execution.ContinuationOfGiven ClearExpectation() { } + public bool Succeeded { get; } public FluentAssertions.Execution.ContinuationOfGiven FailWith(string message) { } public FluentAssertions.Execution.ContinuationOfGiven FailWith(string message, params System.Func[] args) { } public FluentAssertions.Execution.ContinuationOfGiven FailWith(string message, params object[] args) { } public FluentAssertions.Execution.GivenSelector ForCondition(System.Func predicate) { } public FluentAssertions.Execution.GivenSelector Given(System.Func selector) { } } - public interface IAssertionScope : System.IDisposable - { - FluentAssertions.Execution.IAssertionScope UsingLineBreaks { get; } - FluentAssertions.Execution.IAssertionScope BecauseOf(string because, params object[] becauseArgs); - FluentAssertions.Execution.Continuation ClearExpectation(); - string[] Discard(); - FluentAssertions.Execution.Continuation FailWith(System.Func failReasonFunc); - FluentAssertions.Execution.Continuation FailWith(string message); - FluentAssertions.Execution.Continuation FailWith(string message, params System.Func[] argProviders); - FluentAssertions.Execution.Continuation FailWith(string message, params object[] args); - FluentAssertions.Execution.IAssertionScope ForCondition(bool condition); - FluentAssertions.Execution.IAssertionScope ForConstraint(FluentAssertions.OccurrenceConstraint constraint, int actualOccurrences); - FluentAssertions.Execution.GivenSelector Given(System.Func selector); - FluentAssertions.Execution.IAssertionScope WithDefaultIdentifier(string identifier); - FluentAssertions.Execution.IAssertionScope WithExpectation(string message, params object[] args); - } public interface IAssertionStrategy { System.Collections.Generic.IEnumerable FailureMessages { get; } @@ -1463,6 +1436,12 @@ namespace FluentAssertions.Formatting public MaxLinesExceededException(string message) { } public MaxLinesExceededException(string message, System.Exception innerException) { } } + public class MethodInfoFormatter : FluentAssertions.Formatting.IValueFormatter + { + public MethodInfoFormatter() { } + public bool CanHandle(object value) { } + public void Format(object value, FluentAssertions.Formatting.FormattedObjectGraph formattedGraph, FluentAssertions.Formatting.FormattingContext context, FluentAssertions.Formatting.FormatChild formatChild) { } + } public class MultidimensionalArrayFormatter : FluentAssertions.Formatting.IValueFormatter { public MultidimensionalArrayFormatter() { } @@ -1569,12 +1548,12 @@ namespace FluentAssertions.Numeric { public class ComparableTypeAssertions : FluentAssertions.Numeric.ComparableTypeAssertions> { - public ComparableTypeAssertions(System.IComparable value) { } + public ComparableTypeAssertions(System.IComparable value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class ComparableTypeAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions, TAssertions> where TAssertions : FluentAssertions.Numeric.ComparableTypeAssertions { - public ComparableTypeAssertions(System.IComparable value) { } + public ComparableTypeAssertions(System.IComparable value, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint Be(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEquivalentTo(TExpectation expectation, string because = "", params object[] becauseArgs) { } @@ -1594,13 +1573,13 @@ namespace FluentAssertions.Numeric public class NullableNumericAssertions : FluentAssertions.Numeric.NullableNumericAssertions> where T : struct, System.IComparable { - public NullableNumericAssertions(T? value) { } + public NullableNumericAssertions(T? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class NullableNumericAssertions : FluentAssertions.Numeric.NumericAssertions where T : struct, System.IComparable where TAssertions : FluentAssertions.Numeric.NullableNumericAssertions { - public NullableNumericAssertions(T? value) { } + public NullableNumericAssertions(T? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint BeNull(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveValue(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Match(System.Linq.Expressions.Expression> predicate, string because = "", params object[] becauseArgs) { } @@ -1610,13 +1589,14 @@ namespace FluentAssertions.Numeric public class NumericAssertions : FluentAssertions.Numeric.NumericAssertions> where T : struct, System.IComparable { - public NumericAssertions(T value) { } + public NumericAssertions(T value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class NumericAssertions where T : struct, System.IComparable where TAssertions : FluentAssertions.Numeric.NumericAssertions { - public NumericAssertions(T value) { } + public NumericAssertions(T value, FluentAssertions.Execution.AssertionChain assertionChain) { } + public FluentAssertions.Execution.AssertionChain CurrentAssertionChain { get; } public T? Subject { get; } public FluentAssertions.AndConstraint Be(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Be(T? expected, string because = "", params object[] becauseArgs) { } @@ -1642,12 +1622,12 @@ namespace FluentAssertions.Primitives { public class BooleanAssertions : FluentAssertions.Primitives.BooleanAssertions { - public BooleanAssertions(bool? value) { } + public BooleanAssertions(bool? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class BooleanAssertions where TAssertions : FluentAssertions.Primitives.BooleanAssertions { - public BooleanAssertions(bool? value) { } + public BooleanAssertions(bool? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public bool? Subject { get; } public FluentAssertions.AndConstraint Be(bool expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeFalse(string because = "", params object[] becauseArgs) { } @@ -1658,12 +1638,12 @@ namespace FluentAssertions.Primitives } public class DateTimeAssertions : FluentAssertions.Primitives.DateTimeAssertions { - public DateTimeAssertions(System.DateTime? value) { } + public DateTimeAssertions(System.DateTime? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class DateTimeAssertions where TAssertions : FluentAssertions.Primitives.DateTimeAssertions { - public DateTimeAssertions(System.DateTime? value) { } + public DateTimeAssertions(System.DateTime? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public System.DateTime? Subject { get; } public FluentAssertions.AndConstraint Be(System.DateTime expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Be(System.DateTime? expected, string because = "", params object[] becauseArgs) { } @@ -1708,12 +1688,12 @@ namespace FluentAssertions.Primitives } public class DateTimeOffsetAssertions : FluentAssertions.Primitives.DateTimeOffsetAssertions { - public DateTimeOffsetAssertions(System.DateTimeOffset? value) { } + public DateTimeOffsetAssertions(System.DateTimeOffset? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class DateTimeOffsetAssertions where TAssertions : FluentAssertions.Primitives.DateTimeOffsetAssertions { - public DateTimeOffsetAssertions(System.DateTimeOffset? value) { } + public DateTimeOffsetAssertions(System.DateTimeOffset? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public System.DateTimeOffset? Subject { get; } public FluentAssertions.AndConstraint Be(System.DateTimeOffset expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Be(System.DateTimeOffset? expected, string because = "", params object[] becauseArgs) { } @@ -1763,7 +1743,7 @@ namespace FluentAssertions.Primitives public class DateTimeOffsetRangeAssertions where TAssertions : FluentAssertions.Primitives.DateTimeOffsetAssertions { - protected DateTimeOffsetRangeAssertions(TAssertions parentAssertions, System.DateTimeOffset? subject, FluentAssertions.Primitives.TimeSpanCondition condition, System.TimeSpan timeSpan) { } + protected DateTimeOffsetRangeAssertions(TAssertions parentAssertions, FluentAssertions.Execution.AssertionChain assertionChain, System.DateTimeOffset? subject, FluentAssertions.Primitives.TimeSpanCondition condition, System.TimeSpan timeSpan) { } public FluentAssertions.AndConstraint After(System.DateTimeOffset target, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Before(System.DateTimeOffset target, string because = "", params object[] becauseArgs) { } public override bool Equals(object obj) { } @@ -1771,7 +1751,7 @@ namespace FluentAssertions.Primitives public class DateTimeRangeAssertions where TAssertions : FluentAssertions.Primitives.DateTimeAssertions { - protected DateTimeRangeAssertions(TAssertions parentAssertions, System.DateTime? subject, FluentAssertions.Primitives.TimeSpanCondition condition, System.TimeSpan timeSpan) { } + protected DateTimeRangeAssertions(TAssertions parentAssertions, FluentAssertions.Execution.AssertionChain assertionChain, System.DateTime? subject, FluentAssertions.Primitives.TimeSpanCondition condition, System.TimeSpan timeSpan) { } public FluentAssertions.AndConstraint After(System.DateTime target, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Before(System.DateTime target, string because = "", params object[] becauseArgs) { } public override bool Equals(object obj) { } @@ -1779,13 +1759,13 @@ namespace FluentAssertions.Primitives public class EnumAssertions : FluentAssertions.Primitives.EnumAssertions> where TEnum : struct, System.Enum { - public EnumAssertions(TEnum subject) { } + public EnumAssertions(TEnum subject, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class EnumAssertions where TEnum : struct, System.Enum where TAssertions : FluentAssertions.Primitives.EnumAssertions { - public EnumAssertions(TEnum subject) { } + public EnumAssertions(TEnum subject, FluentAssertions.Execution.AssertionChain assertionChain) { } public TEnum? Subject { get; } public FluentAssertions.AndConstraint Be(TEnum expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Be(TEnum? expected, string because = "", params object[] becauseArgs) { } @@ -1812,12 +1792,12 @@ namespace FluentAssertions.Primitives } public class GuidAssertions : FluentAssertions.Primitives.GuidAssertions { - public GuidAssertions(System.Guid? value) { } + public GuidAssertions(System.Guid? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class GuidAssertions where TAssertions : FluentAssertions.Primitives.GuidAssertions { - public GuidAssertions(System.Guid? value) { } + public GuidAssertions(System.Guid? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public System.Guid? Subject { get; } public FluentAssertions.AndConstraint Be(System.Guid expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Be(string expected, string because = "", params object[] becauseArgs) { } @@ -1829,12 +1809,12 @@ namespace FluentAssertions.Primitives } public class HttpResponseMessageAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions { - public HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { } + public HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class HttpResponseMessageAssertions : FluentAssertions.Primitives.ObjectAssertions where TAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions { - protected HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { } + protected HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint BeRedirection(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeSuccessful(string because = "", params object[] becauseArgs) { } @@ -1846,12 +1826,12 @@ namespace FluentAssertions.Primitives } public class NullableBooleanAssertions : FluentAssertions.Primitives.NullableBooleanAssertions { - public NullableBooleanAssertions(bool? value) { } + public NullableBooleanAssertions(bool? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class NullableBooleanAssertions : FluentAssertions.Primitives.BooleanAssertions where TAssertions : FluentAssertions.Primitives.NullableBooleanAssertions { - public NullableBooleanAssertions(bool? value) { } + public NullableBooleanAssertions(bool? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint Be(bool? expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeNull(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveValue(string because = "", params object[] becauseArgs) { } @@ -1863,12 +1843,12 @@ namespace FluentAssertions.Primitives } public class NullableDateTimeAssertions : FluentAssertions.Primitives.NullableDateTimeAssertions { - public NullableDateTimeAssertions(System.DateTime? expected) { } + public NullableDateTimeAssertions(System.DateTime? expected, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class NullableDateTimeAssertions : FluentAssertions.Primitives.DateTimeAssertions where TAssertions : FluentAssertions.Primitives.NullableDateTimeAssertions { - public NullableDateTimeAssertions(System.DateTime? expected) { } + public NullableDateTimeAssertions(System.DateTime? expected, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint BeNull(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveValue(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeNull(string because = "", params object[] becauseArgs) { } @@ -1876,12 +1856,12 @@ namespace FluentAssertions.Primitives } public class NullableDateTimeOffsetAssertions : FluentAssertions.Primitives.NullableDateTimeOffsetAssertions { - public NullableDateTimeOffsetAssertions(System.DateTimeOffset? expected) { } + public NullableDateTimeOffsetAssertions(System.DateTimeOffset? expected, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class NullableDateTimeOffsetAssertions : FluentAssertions.Primitives.DateTimeOffsetAssertions where TAssertions : FluentAssertions.Primitives.NullableDateTimeOffsetAssertions { - public NullableDateTimeOffsetAssertions(System.DateTimeOffset? expected) { } + public NullableDateTimeOffsetAssertions(System.DateTimeOffset? expected, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint BeNull(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveValue(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeNull(string because = "", params object[] becauseArgs) { } @@ -1890,13 +1870,13 @@ namespace FluentAssertions.Primitives public class NullableEnumAssertions : FluentAssertions.Primitives.NullableEnumAssertions> where TEnum : struct, System.Enum { - public NullableEnumAssertions(TEnum? subject) { } + public NullableEnumAssertions(TEnum? subject, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class NullableEnumAssertions : FluentAssertions.Primitives.EnumAssertions where TEnum : struct, System.Enum where TAssertions : FluentAssertions.Primitives.NullableEnumAssertions { - public NullableEnumAssertions(TEnum? subject) { } + public NullableEnumAssertions(TEnum? subject, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint BeNull(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint HaveValue(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint NotBeNull(string because = "", params object[] becauseArgs) { } @@ -1904,12 +1884,12 @@ namespace FluentAssertions.Primitives } public class NullableGuidAssertions : FluentAssertions.Primitives.NullableGuidAssertions { - public NullableGuidAssertions(System.Guid? value) { } + public NullableGuidAssertions(System.Guid? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class NullableGuidAssertions : FluentAssertions.Primitives.GuidAssertions where TAssertions : FluentAssertions.Primitives.NullableGuidAssertions { - public NullableGuidAssertions(System.Guid? value) { } + public NullableGuidAssertions(System.Guid? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint Be(System.Guid? expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeNull(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveValue(string because = "", params object[] becauseArgs) { } @@ -1918,12 +1898,12 @@ namespace FluentAssertions.Primitives } public class NullableSimpleTimeSpanAssertions : FluentAssertions.Primitives.NullableSimpleTimeSpanAssertions { - public NullableSimpleTimeSpanAssertions(System.TimeSpan? value) { } + public NullableSimpleTimeSpanAssertions(System.TimeSpan? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class NullableSimpleTimeSpanAssertions : FluentAssertions.Primitives.SimpleTimeSpanAssertions where TAssertions : FluentAssertions.Primitives.NullableSimpleTimeSpanAssertions { - public NullableSimpleTimeSpanAssertions(System.TimeSpan? value) { } + public NullableSimpleTimeSpanAssertions(System.TimeSpan? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint Be(System.TimeSpan? expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeNull(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveValue(string because = "", params object[] becauseArgs) { } @@ -1932,7 +1912,7 @@ namespace FluentAssertions.Primitives } public class ObjectAssertions : FluentAssertions.Primitives.ObjectAssertions { - public ObjectAssertions(object value) { } + public ObjectAssertions(object value, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint Be(TExpectation expected, System.Collections.Generic.IEqualityComparer comparer, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOneOf(System.Collections.Generic.IEnumerable validValues, System.Collections.Generic.IEqualityComparer comparer, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBe(TExpectation unexpected, System.Collections.Generic.IEqualityComparer comparer, string because = "", params object[] becauseArgs) { } @@ -1940,7 +1920,7 @@ namespace FluentAssertions.Primitives public class ObjectAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions where TAssertions : FluentAssertions.Primitives.ObjectAssertions { - public ObjectAssertions(TSubject value) { } + public ObjectAssertions(TSubject value, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint Be(TSubject expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Be(TSubject expected, System.Collections.Generic.IEqualityComparer comparer, string because = "", params object[] becauseArgs) { } @@ -1958,7 +1938,8 @@ namespace FluentAssertions.Primitives public abstract class ReferenceTypeAssertions where TAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions { - protected ReferenceTypeAssertions(TSubject subject) { } + protected ReferenceTypeAssertions(TSubject subject, FluentAssertions.Execution.AssertionChain assertionChain) { } + public FluentAssertions.Execution.AssertionChain CurrentAssertionChain { get; } protected abstract string Identifier { get; } public TSubject Subject { get; } public FluentAssertions.AndConstraint BeAssignableTo(System.Type type, string because = "", params object[] becauseArgs) { } @@ -1982,12 +1963,12 @@ namespace FluentAssertions.Primitives } public class SimpleTimeSpanAssertions : FluentAssertions.Primitives.SimpleTimeSpanAssertions { - public SimpleTimeSpanAssertions(System.TimeSpan? value) { } + public SimpleTimeSpanAssertions(System.TimeSpan? value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class SimpleTimeSpanAssertions where TAssertions : FluentAssertions.Primitives.SimpleTimeSpanAssertions { - public SimpleTimeSpanAssertions(System.TimeSpan? value) { } + public SimpleTimeSpanAssertions(System.TimeSpan? value, FluentAssertions.Execution.AssertionChain assertionChain) { } public System.TimeSpan? Subject { get; } public FluentAssertions.AndConstraint Be(System.TimeSpan expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeCloseTo(System.TimeSpan nearbyTime, System.TimeSpan precision, string because = "", params object[] becauseArgs) { } @@ -2003,12 +1984,12 @@ namespace FluentAssertions.Primitives } public class StringAssertions : FluentAssertions.Primitives.StringAssertions { - public StringAssertions(string value) { } + public StringAssertions(string value, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class StringAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions where TAssertions : FluentAssertions.Primitives.StringAssertions { - public StringAssertions(string value) { } + public StringAssertions(string value, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint Be(string expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEmpty(string because = "", params object[] becauseArgs) { } @@ -2084,8 +2065,8 @@ namespace FluentAssertions.Specialized { public class ActionAssertions : FluentAssertions.Specialized.DelegateAssertions { - public ActionAssertions(System.Action subject, FluentAssertions.Specialized.IExtractExceptions extractor) { } - public ActionAssertions(System.Action subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Common.IClock clock) { } + public ActionAssertions(System.Action subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain) { } + public ActionAssertions(System.Action subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain, FluentAssertions.Common.IClock clock) { } protected override string Identifier { get; } protected override void InvokeSubject() { } public FluentAssertions.AndConstraint NotThrow(string because = "", params object[] becauseArgs) { } @@ -2095,7 +2076,7 @@ namespace FluentAssertions.Specialized where TTask : System.Threading.Tasks.Task where TAssertions : FluentAssertions.Specialized.AsyncFunctionAssertions { - protected AsyncFunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Common.IClock clock) { } + protected AsyncFunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain, FluentAssertions.Common.IClock clock) { } protected override string Identifier { get; } public System.Threading.Tasks.Task> NotCompleteWithinAsync(System.TimeSpan timeSpan, string because = "", params object[] becauseArgs) { } public System.Threading.Tasks.Task> NotThrowAsync(string because = "", params object[] becauseArgs) @@ -2121,7 +2102,7 @@ namespace FluentAssertions.Specialized where TDelegate : System.Delegate where TAssertions : FluentAssertions.Specialized.DelegateAssertions { - protected DelegateAssertions(TDelegate @delegate, FluentAssertions.Specialized.IExtractExceptions extractor) { } + protected DelegateAssertions(TDelegate @delegate, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain) { } protected abstract void InvokeSubject(); public FluentAssertions.AndConstraint NotThrow(string because = "", params object[] becauseArgs) where TException : System.Exception { } @@ -2133,7 +2114,7 @@ namespace FluentAssertions.Specialized public class ExceptionAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions, FluentAssertions.Specialized.ExceptionAssertions> where TException : System.Exception { - public ExceptionAssertions(System.Collections.Generic.IEnumerable exceptions) { } + public ExceptionAssertions(System.Collections.Generic.IEnumerable exceptions, FluentAssertions.Execution.AssertionChain assertionChain) { } public TException And { get; } protected override string Identifier { get; } public TException Which { get; } @@ -2155,7 +2136,7 @@ namespace FluentAssertions.Specialized } public class ExecutionTimeAssertions { - public ExecutionTimeAssertions(FluentAssertions.Specialized.ExecutionTime executionTime) { } + public ExecutionTimeAssertions(FluentAssertions.Specialized.ExecutionTime executionTime, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint BeCloseTo(System.TimeSpan expectedDuration, System.TimeSpan precision, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeGreaterThan(System.TimeSpan minDuration, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeGreaterThanOrEqualTo(System.TimeSpan minDuration, string because = "", params object[] becauseArgs) { } @@ -2165,8 +2146,8 @@ namespace FluentAssertions.Specialized } public class FunctionAssertions : FluentAssertions.Specialized.DelegateAssertions, FluentAssertions.Specialized.FunctionAssertions> { - public FunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor) { } - public FunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Common.IClock clock) { } + public FunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain) { } + public FunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain, FluentAssertions.Common.IClock clock) { } protected override string Identifier { get; } protected override void InvokeSubject() { } public FluentAssertions.AndWhichConstraint, T> NotThrow(string because = "", params object[] becauseArgs) { } @@ -2174,8 +2155,8 @@ namespace FluentAssertions.Specialized } public class GenericAsyncFunctionAssertions : FluentAssertions.Specialized.AsyncFunctionAssertions, FluentAssertions.Specialized.GenericAsyncFunctionAssertions> { - public GenericAsyncFunctionAssertions(System.Func> subject, FluentAssertions.Specialized.IExtractExceptions extractor) { } - public GenericAsyncFunctionAssertions(System.Func> subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Common.IClock clock) { } + public GenericAsyncFunctionAssertions(System.Func> subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain) { } + public GenericAsyncFunctionAssertions(System.Func> subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain, FluentAssertions.Common.IClock clock) { } public System.Threading.Tasks.Task, TResult>> CompleteWithinAsync(System.TimeSpan timeSpan, string because = "", params object[] becauseArgs) { } public System.Threading.Tasks.Task, TResult>> NotThrowAfterAsync(System.TimeSpan waitTime, System.TimeSpan pollInterval, string because = "", params object[] becauseArgs) { } public System.Threading.Tasks.Task, TResult>> NotThrowAsync(string because = "", params object[] becauseArgs) { } @@ -2191,8 +2172,8 @@ namespace FluentAssertions.Specialized } public class NonGenericAsyncFunctionAssertions : FluentAssertions.Specialized.AsyncFunctionAssertions { - public NonGenericAsyncFunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor) { } - public NonGenericAsyncFunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Common.IClock clock) { } + public NonGenericAsyncFunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain) { } + public NonGenericAsyncFunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor, FluentAssertions.Execution.AssertionChain assertionChain, FluentAssertions.Common.IClock clock) { } public System.Threading.Tasks.Task> CompleteWithinAsync(System.TimeSpan timeSpan, string because = "", params object[] becauseArgs) { } public System.Threading.Tasks.Task> NotThrowAfterAsync(System.TimeSpan waitTime, System.TimeSpan pollInterval, string because = "", params object[] becauseArgs) { } public System.Threading.Tasks.Task> NotThrowAsync(string because = "", params object[] becauseArgs) { } @@ -2204,8 +2185,8 @@ namespace FluentAssertions.Specialized } public class TaskCompletionSourceAssertions : FluentAssertions.Specialized.TaskCompletionSourceAssertionsBase { - public TaskCompletionSourceAssertions(System.Threading.Tasks.TaskCompletionSource tcs) { } - public TaskCompletionSourceAssertions(System.Threading.Tasks.TaskCompletionSource tcs, FluentAssertions.Common.IClock clock) { } + public TaskCompletionSourceAssertions(System.Threading.Tasks.TaskCompletionSource tcs, FluentAssertions.Execution.AssertionChain assertionChain) { } + public TaskCompletionSourceAssertions(System.Threading.Tasks.TaskCompletionSource tcs, FluentAssertions.Execution.AssertionChain assertionChain, FluentAssertions.Common.IClock clock) { } public System.Threading.Tasks.Task, T>> CompleteWithinAsync(System.TimeSpan timeSpan, string because = "", params object[] becauseArgs) { } public System.Threading.Tasks.Task>> NotCompleteWithinAsync(System.TimeSpan timeSpan, string because = "", params object[] becauseArgs) { } } @@ -2214,25 +2195,25 @@ namespace FluentAssertions.Streams { public class BufferedStreamAssertions : FluentAssertions.Streams.BufferedStreamAssertions { - public BufferedStreamAssertions(System.IO.BufferedStream stream) { } + public BufferedStreamAssertions(System.IO.BufferedStream stream, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class BufferedStreamAssertions : FluentAssertions.Streams.StreamAssertions where TAssertions : FluentAssertions.Streams.BufferedStreamAssertions { - public BufferedStreamAssertions(System.IO.BufferedStream stream) { } + public BufferedStreamAssertions(System.IO.BufferedStream stream, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint HaveBufferSize(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveBufferSize(int unexpected, string because = "", params object[] becauseArgs) { } } public class StreamAssertions : FluentAssertions.Streams.StreamAssertions { - public StreamAssertions(System.IO.Stream stream) { } + public StreamAssertions(System.IO.Stream stream, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class StreamAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions where TSubject : System.IO.Stream where TAssertions : FluentAssertions.Streams.StreamAssertions { - public StreamAssertions(TSubject stream) { } + public StreamAssertions(TSubject stream, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint BeReadOnly(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeReadable(string because = "", params object[] becauseArgs) { } @@ -2258,7 +2239,7 @@ namespace FluentAssertions.Types } public class AssemblyAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions { - public AssemblyAssertions(System.Reflection.Assembly assembly) { } + public AssemblyAssertions(System.Reflection.Assembly assembly, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint BeSignedWithPublicKey(string publicKey, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeUnsigned(string because = "", params object[] becauseArgs) { } @@ -2268,15 +2249,17 @@ namespace FluentAssertions.Types } public class ConstructorInfoAssertions : FluentAssertions.Types.MethodBaseAssertions { - public ConstructorInfoAssertions(System.Reflection.ConstructorInfo constructorInfo) { } + public ConstructorInfoAssertions(System.Reflection.ConstructorInfo constructorInfo, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } + protected override string SubjectDescription { get; } } public abstract class MemberInfoAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions where TSubject : System.Reflection.MemberInfo where TAssertions : FluentAssertions.Types.MemberInfoAssertions { - protected MemberInfoAssertions(TSubject subject) { } + protected MemberInfoAssertions(TSubject subject, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } + protected virtual string SubjectDescription { get; } public FluentAssertions.AndWhichConstraint, TAttribute> BeDecoratedWith(string because = "", params object[] becauseArgs) where TAttribute : System.Attribute { } public FluentAssertions.AndWhichConstraint, TAttribute> BeDecoratedWith(System.Linq.Expressions.Expression> isMatchingAttributePredicate, string because = "", params object[] becauseArgs) @@ -2290,14 +2273,15 @@ namespace FluentAssertions.Types where TSubject : System.Reflection.MethodBase where TAssertions : FluentAssertions.Types.MethodBaseAssertions { - protected MethodBaseAssertions(TSubject subject) { } + protected MethodBaseAssertions(TSubject subject, FluentAssertions.Execution.AssertionChain assertionChain) { } public FluentAssertions.AndConstraint HaveAccessModifier(FluentAssertions.Common.CSharpAccessModifier accessModifier, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveAccessModifier(FluentAssertions.Common.CSharpAccessModifier accessModifier, string because = "", params object[] becauseArgs) { } } public class MethodInfoAssertions : FluentAssertions.Types.MethodBaseAssertions { - public MethodInfoAssertions(System.Reflection.MethodInfo methodInfo) { } + public MethodInfoAssertions(System.Reflection.MethodInfo methodInfo, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } + protected override string SubjectDescription { get; } public FluentAssertions.AndConstraint BeAsync(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeVirtual(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeAsync(string because = "", params object[] becauseArgs) { } @@ -2340,7 +2324,7 @@ namespace FluentAssertions.Types } public class MethodInfoSelectorAssertions { - public MethodInfoSelectorAssertions(params System.Reflection.MethodInfo[] methods) { } + public MethodInfoSelectorAssertions(FluentAssertions.Execution.AssertionChain assertionChain, params System.Reflection.MethodInfo[] methods) { } protected string Context { get; } public System.Collections.Generic.IEnumerable SubjectMethods { get; } public FluentAssertions.AndConstraint Be(FluentAssertions.Common.CSharpAccessModifier accessModifier, string because = "", params object[] becauseArgs) { } @@ -2361,8 +2345,9 @@ namespace FluentAssertions.Types } public class PropertyInfoAssertions : FluentAssertions.Types.MemberInfoAssertions { - public PropertyInfoAssertions(System.Reflection.PropertyInfo propertyInfo) { } + public PropertyInfoAssertions(System.Reflection.PropertyInfo propertyInfo, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } + protected override string SubjectDescription { get; } public FluentAssertions.AndConstraint BeReadable(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeReadable(FluentAssertions.Common.CSharpAccessModifier accessModifier, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeVirtual(string because = "", params object[] becauseArgs) { } @@ -2403,7 +2388,7 @@ namespace FluentAssertions.Types } public class PropertyInfoSelectorAssertions { - public PropertyInfoSelectorAssertions(params System.Reflection.PropertyInfo[] properties) { } + public PropertyInfoSelectorAssertions(FluentAssertions.Execution.AssertionChain assertionChain, params System.Reflection.PropertyInfo[] properties) { } protected string Context { get; } public System.Collections.Generic.IEnumerable SubjectProperties { get; } public FluentAssertions.AndConstraint BeDecoratedWith(string because = "", params object[] becauseArgs) @@ -2418,7 +2403,7 @@ namespace FluentAssertions.Types } public class TypeAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions { - public TypeAssertions(System.Type type) { } + public TypeAssertions(System.Type type, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint Be(System.Type expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Be(string because = "", params object[] becauseArgs) { } @@ -2536,7 +2521,7 @@ namespace FluentAssertions.Types } public class TypeSelectorAssertions { - public TypeSelectorAssertions(params System.Type[] types) { } + public TypeSelectorAssertions(FluentAssertions.Execution.AssertionChain assertionChain, params System.Type[] types) { } public System.Collections.Generic.IEnumerable Subject { get; } public FluentAssertions.AndConstraint BeDecoratedWith(string because = "", params object[] becauseArgs) where TAttribute : System.Attribute { } @@ -2567,7 +2552,7 @@ namespace FluentAssertions.Xml { public class XAttributeAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions { - public XAttributeAssertions(System.Xml.Linq.XAttribute attribute) { } + public XAttributeAssertions(System.Xml.Linq.XAttribute attribute, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint Be(System.Xml.Linq.XAttribute expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveValue(string expected, string because = "", params object[] becauseArgs) { } @@ -2575,7 +2560,7 @@ namespace FluentAssertions.Xml } public class XDocumentAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions { - public XDocumentAssertions(System.Xml.Linq.XDocument document) { } + public XDocumentAssertions(System.Xml.Linq.XDocument document, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint Be(System.Xml.Linq.XDocument expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEquivalentTo(System.Xml.Linq.XDocument expected, string because = "", params object[] becauseArgs) { } @@ -2590,7 +2575,7 @@ namespace FluentAssertions.Xml } public class XElementAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions { - public XElementAssertions(System.Xml.Linq.XElement xElement) { } + public XElementAssertions(System.Xml.Linq.XElement xElement, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint Be(System.Xml.Linq.XElement expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEquivalentTo(System.Xml.Linq.XElement expected, string because = "", params object[] becauseArgs) { } @@ -2606,7 +2591,7 @@ namespace FluentAssertions.Xml } public class XmlElementAssertions : FluentAssertions.Xml.XmlNodeAssertions { - public XmlElementAssertions(System.Xml.XmlElement xmlElement) { } + public XmlElementAssertions(System.Xml.XmlElement xmlElement, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint HaveAttribute(string expectedName, string expectedValue, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveAttributeWithNamespace(string expectedName, string expectedNamespace, string expectedValue, string because = "", params object[] becauseArgs) { } @@ -2616,13 +2601,13 @@ namespace FluentAssertions.Xml } public class XmlNodeAssertions : FluentAssertions.Xml.XmlNodeAssertions { - public XmlNodeAssertions(System.Xml.XmlNode xmlNode) { } + public XmlNodeAssertions(System.Xml.XmlNode xmlNode, FluentAssertions.Execution.AssertionChain assertionChain) { } } public class XmlNodeAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions where TSubject : System.Xml.XmlNode where TAssertions : FluentAssertions.Xml.XmlNodeAssertions { - public XmlNodeAssertions(TSubject xmlNode) { } + public XmlNodeAssertions(TSubject xmlNode, FluentAssertions.Execution.AssertionChain assertionChain) { } protected override string Identifier { get; } public FluentAssertions.AndConstraint BeEquivalentTo(System.Xml.XmlNode expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEquivalentTo(System.Xml.XmlNode unexpected, string because = "", params object[] becauseArgs) { } diff --git a/Tests/Benchmarks/Program.cs b/Tests/Benchmarks/Program.cs index 2dc62955e7..ff2fb9ab18 100644 --- a/Tests/Benchmarks/Program.cs +++ b/Tests/Benchmarks/Program.cs @@ -1,5 +1,4 @@ using System.Globalization; -using BenchmarkDotNet.Columns; using BenchmarkDotNet.Configs; using BenchmarkDotNet.Exporters.Csv; using BenchmarkDotNet.Reports; diff --git a/Tests/FluentAssertions.Equivalency.Specs/BasicSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/BasicSpecs.cs index 1936a02b89..b1c54dd353 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/BasicSpecs.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/BasicSpecs.cs @@ -599,8 +599,7 @@ public void When_asserting_equivalence_including_only_properties_it_should_not_m } [Fact] - public void - When_asserting_equivalence_of_objects_including_enumerables_it_should_print_the_failure_message_only_once() + public void When_asserting_equivalence_of_objects_including_enumerables_it_should_print_the_failure_message_only_once() { // Arrange var record = new { Member1 = "", Member2 = new[] { "", "" } }; diff --git a/Tests/FluentAssertions.Equivalency.Specs/CollectionSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/CollectionSpecs.cs index 6bff2fe4fb..2daadc159f 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/CollectionSpecs.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/CollectionSpecs.cs @@ -1467,8 +1467,7 @@ public void Can_force_strict_ordering_based_on_the_parent_type_of_an_unordered_c // Assert action.Should().Throw() - .WithMessage( - "*Expected*[0].UnorderedCollection*5 item(s)*empty collection*"); + .WithMessage("*Expected*[0].UnorderedCollection*5 item(s)*empty collection*"); } [Fact] diff --git a/Tests/FluentAssertions.Equivalency.Specs/DictionarySpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/DictionarySpecs.cs index ea5f509b56..131efc6be3 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/DictionarySpecs.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/DictionarySpecs.cs @@ -570,6 +570,29 @@ public void act.Should().Throw("the types have different properties"); } + [Fact] + public void Can_compare_non_generic_directories_without_recursing() + { + // Arrange + var expected = new NonGenericDictionary + { + ["Key2"] = "Value2", + ["Key1"] = "Value1" + }; + + var subject = new NonGenericDictionary + { + ["Key1"] = "Value1", + ["Key3"] = "Value2" + }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expected, options => options.ExcludingNestedObjects()); + + // Assert + act.Should().Throw().WithMessage("Expected subject[\"Key2\"] to be \"Value2\", but found *"); + } + [Fact] public void When_asserting_equivalence_of_dictionaries_it_should_respect_the_declared_type() { diff --git a/Tests/FluentAssertions.Equivalency.Specs/ExtensibilitySpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/ExtensibilitySpecs.cs index a4c7478d4a..af8beba7ce 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/ExtensibilitySpecs.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/ExtensibilitySpecs.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.Linq; using System.Reflection; +using FluentAssertions.Execution; using FluentAssertions.Extensions; using JetBrains.Annotations; using Xunit; @@ -141,7 +142,8 @@ public void When_a_matching_rule_is_added_it_should_appear_in_the_exception_mess internal class ForeignKeyMatchingRule : IMemberMatchingRule { - public IMember Match(IMember expectedMember, object subject, INode parent, IEquivalencyOptions options) + public IMember Match(IMember expectedMember, object subject, INode parent, IEquivalencyOptions options, + AssertionChain assertionChain) { string name = expectedMember.Name; @@ -296,7 +298,7 @@ public void When_equally_named_properties_are_both_incompatible_with_generic_typ // Assert act.Should().Throw() - .WithMessage("*Id*from subject*System.String*System.Double*Id*from expectation*System.String*System.Double*"); + .WithMessage("*Id*from subject*System.String*System.Double*"); } [Fact] @@ -335,13 +337,13 @@ public void When_property_of_subject_is_null_the_failure_message_should_not_comp Id = null as double?, }; - var other = new + var expectation = new { Id = "bar", }; // Act - Action act = () => subject.Should().BeEquivalentTo(other, + Action act = () => subject.Should().BeEquivalentTo(expectation, o => o .Using(c => c.Subject.Should().Be(c.Expectation)) .When(si => si.Path == "Id")); diff --git a/Tests/FluentAssertions.Specs/AssertionExtensions.cs b/Tests/FluentAssertions.Specs/AssertionExtensions.cs index 1e8936c211..add27b751a 100644 --- a/Tests/FluentAssertions.Specs/AssertionExtensions.cs +++ b/Tests/FluentAssertions.Specs/AssertionExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Threading.Tasks; using FluentAssertions.Common; +using FluentAssertions.Execution; using FluentAssertions.Specialized; namespace FluentAssertions.Specs; @@ -11,33 +12,33 @@ internal static class AssertionExtensions public static NonGenericAsyncFunctionAssertions Should(this Func action, IClock clock) { - return new NonGenericAsyncFunctionAssertions(action, Extractor, clock); + return new NonGenericAsyncFunctionAssertions(action, Extractor, AssertionChain.GetOrCreate(), clock); } public static GenericAsyncFunctionAssertions Should(this Func> action, IClock clock) { - return new GenericAsyncFunctionAssertions(action, Extractor, clock); + return new GenericAsyncFunctionAssertions(action, Extractor, AssertionChain.GetOrCreate(), clock); } public static ActionAssertions Should(this Action action, IClock clock) { - return new ActionAssertions(action, Extractor, clock); + return new ActionAssertions(action, Extractor, AssertionChain.GetOrCreate(), clock); } public static FunctionAssertions Should(this Func func, IClock clock) { - return new FunctionAssertions(func, Extractor, clock); + return new FunctionAssertions(func, Extractor, AssertionChain.GetOrCreate(), clock); } public static TaskCompletionSourceAssertions Should(this TaskCompletionSource tcs, IClock clock) { - return new TaskCompletionSourceAssertions(tcs, clock); + return new TaskCompletionSourceAssertions(tcs, AssertionChain.GetOrCreate(), clock); } #if NET6_0_OR_GREATER public static TaskCompletionSourceAssertions Should(this TaskCompletionSource tcs, IClock clock) { - return new TaskCompletionSourceAssertions(tcs, clock); + return new TaskCompletionSourceAssertions(tcs, AssertionChain.GetOrCreate(), clock); } #endif diff --git a/Tests/FluentAssertions.Specs/AssertionExtensionsSpecs.cs b/Tests/FluentAssertions.Specs/AssertionExtensionsSpecs.cs index 742f82cd18..8f4699cc9b 100644 --- a/Tests/FluentAssertions.Specs/AssertionExtensionsSpecs.cs +++ b/Tests/FluentAssertions.Specs/AssertionExtensionsSpecs.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Reflection; using FluentAssertions.Common; +using FluentAssertions.Execution; using FluentAssertions.Numeric; using FluentAssertions.Primitives; using FluentAssertions.Specialized; @@ -40,24 +41,24 @@ private static bool OverridesEquals(Type t) public static TheoryData ClassesWithGuardEquals => [ - new ObjectAssertions(default), - new BooleanAssertions(default), - new DateTimeAssertions(default), - new DateTimeRangeAssertions(default, default, default, default), - new DateTimeOffsetAssertions(default), - new DateTimeOffsetRangeAssertions(default, default, default, default), - new ExecutionTimeAssertions(new ExecutionTime(() => { }, () => new StopwatchTimer())), - new GuidAssertions(default), - new MethodInfoSelectorAssertions(), - new NumericAssertions>(default), - new PropertyInfoSelectorAssertions(), - new SimpleTimeSpanAssertions(default), - new TaskCompletionSourceAssertions(default), - new TypeSelectorAssertions(), - new EnumAssertions>(default), + new ObjectAssertions(default, AssertionChain.GetOrCreate()), + new BooleanAssertions(default, AssertionChain.GetOrCreate()), + new DateTimeAssertions(default, AssertionChain.GetOrCreate()), + new DateTimeRangeAssertions(default, AssertionChain.GetOrCreate(), default, default, default), + new DateTimeOffsetAssertions(default, AssertionChain.GetOrCreate()), + new DateTimeOffsetRangeAssertions(default, AssertionChain.GetOrCreate(), default, default, default), + new ExecutionTimeAssertions(new ExecutionTime(() => { }, () => new StopwatchTimer()), AssertionChain.GetOrCreate()), + new GuidAssertions(default, AssertionChain.GetOrCreate()), + new MethodInfoSelectorAssertions(AssertionChain.GetOrCreate()), + new NumericAssertions>(default, AssertionChain.GetOrCreate()), + new PropertyInfoSelectorAssertions(AssertionChain.GetOrCreate()), + new SimpleTimeSpanAssertions(default, AssertionChain.GetOrCreate()), + new TaskCompletionSourceAssertions(default, AssertionChain.GetOrCreate()), + new TypeSelectorAssertions(AssertionChain.GetOrCreate()), + new EnumAssertions>(default, AssertionChain.GetOrCreate()), #if NET6_0_OR_GREATER - new DateOnlyAssertions(default), - new TimeOnlyAssertions(default), + new DateOnlyAssertions(default, AssertionChain.GetOrCreate()), + new TimeOnlyAssertions(default, AssertionChain.GetOrCreate()), #endif ]; @@ -174,9 +175,9 @@ public void Should_methods_not_returning_reference_type_assertions_are_not_annot notNullAttribute.Should().BeNull(); } - public static TheoryData GetShouldMethods(bool referenceTypes) + public static IEnumerable GetShouldMethods(bool referenceTypes) { - return new(AllTypes.From(typeof(FluentAssertions.AssertionExtensions).Assembly) + return AllTypes.From(typeof(FluentAssertions.AssertionExtensions).Assembly) .ThatAreClasses() .ThatAreStatic() .Where(t => t.IsPublic) @@ -184,7 +185,8 @@ public static TheoryData GetShouldMethods(bool referenceTypes) .Where(m => m.Name == "Should" && !IsGuardOverload(m) && m.GetParameters().Length == 1 - && (referenceTypes ? ReturnsReferenceTypeAssertions(m) : !ReturnsReferenceTypeAssertions(m)))); + && (referenceTypes ? ReturnsReferenceTypeAssertions(m) : !ReturnsReferenceTypeAssertions(m))) + .Select(m => new object[] { m }); } private static bool ReturnsReferenceTypeAssertions(MethodInfo m) => diff --git a/Tests/FluentAssertions.Specs/AssertionFailureSpecs.cs b/Tests/FluentAssertions.Specs/AssertionFailureSpecs.cs index ea81a04d7a..857253db82 100644 --- a/Tests/FluentAssertions.Specs/AssertionFailureSpecs.cs +++ b/Tests/FluentAssertions.Specs/AssertionFailureSpecs.cs @@ -1,6 +1,5 @@ using System; using FluentAssertions.Execution; -using FluentAssertions.Primitives; using Xunit; using Xunit.Sdk; @@ -70,23 +69,15 @@ public void When_reason_does_not_start_with_because_but_is_prefixed_with_blanks_ .WithMessage("Expected it to fail\r\nbecause AssertionsTestSubClass should always fail."); } - internal class AssertionsTestSubClass : ReferenceTypeAssertions + internal class AssertionsTestSubClass { + private readonly AssertionChain assertionChain = AssertionChain.GetOrCreate(); + public void AssertFail(string because, params object[] becauseArgs) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Expected it to fail{reason}"); } - - protected override string Identifier - { - get { return "test"; } - } - - public AssertionsTestSubClass() - : base(null) - { - } } } diff --git a/Tests/FluentAssertions.Specs/AssertionOptionsSpecs.cs b/Tests/FluentAssertions.Specs/AssertionOptionsSpecs.cs index 0ffac09bcd..a931266d0b 100644 --- a/Tests/FluentAssertions.Specs/AssertionOptionsSpecs.cs +++ b/Tests/FluentAssertions.Specs/AssertionOptionsSpecs.cs @@ -421,7 +421,7 @@ internal class MyEquivalencyStep : IEquivalencyStep public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, IValidateChildNodeEquivalency valueChildNodes) { - Execute.Assertion.FailWith(GetType().FullName); + AssertionChain.GetOrCreate().For(context).FailWith(GetType().FullName); return EquivalencyResult.EquivalencyProven; } diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.BeEquivalentTo.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.BeEquivalentTo.cs index 6f4688bdce..60a28858db 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.BeEquivalentTo.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.BeEquivalentTo.cs @@ -363,11 +363,11 @@ public void When_asserting_collections_not_to_be_equivalent_with_options_but_sub public void Default_immutable_array_should_not_be_equivalent_to_initialized_immutable_array() { // Arrange - ImmutableArray collection = default; - ImmutableArray collection1 = ImmutableArray.Create("a", "b", "c"); + ImmutableArray subject = default; + ImmutableArray expectation = ImmutableArray.Create("a", "b", "c"); // Act / Assert - collection.Should().NotBeEquivalentTo(collection1); + subject.Should().NotBeEquivalentTo(expectation); } [Fact] diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.Contain.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.Contain.cs index 4c95e2abc2..4fb71e62ee 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.Contain.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.Contain.cs @@ -104,7 +104,8 @@ public void Action act = () => { using var _ = new AssertionScope(); - collection.Should().Contain([4]).And.Contain([5, 6]); + collection.Should().Contain([4]); + collection.Should().Contain([5, 6]); }; // Assert @@ -186,6 +187,20 @@ public void When_collection_does_contain_an_expected_item_matching_a_predicate_i "Expected*greater*4*2*"); } + [Fact] + public void Can_chain_another_assertion_on_the_single_result() + { + // Arrange + IEnumerable collection = [1, 2, 3]; + + // Act + Action act = () => collection.Should().Contain(item => item == 2).Which.Should().BeGreaterThan(4); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection[1]*greater*4*2*"); + } + [Fact] public void When_collection_does_contain_an_expected_item_matching_a_predicate_it_should_not_throw() { @@ -395,7 +410,7 @@ public void When_collection_contains_unexpected_items_it_should_throw() } [Fact] - public void When_asserting_multiple_collection_in_assertion_scope_all_should_be_reported() + public void Assertion_scopes_do_not_affect_chained_calls() { // Arrange int[] collection = [1, 2, 3]; @@ -409,7 +424,7 @@ public void When_asserting_multiple_collection_in_assertion_scope_all_should_be_ // Assert act.Should().Throw().WithMessage( - "*to not contain {1, 2}*to not contain 3*"); + "*but found {1, 2}."); } [Fact] diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainEquivalentOf.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainEquivalentOf.cs index 1a07b2b20a..b8f3028b4b 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainEquivalentOf.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainEquivalentOf.cs @@ -45,6 +45,21 @@ public void When_character_collection_does_contain_equivalent_it_should_succeed( collection.Should().ContainEquivalentOf(item); } + [Fact] + public void Can_chain_a_successive_assertion_on_the_matching_item() + { + // Arrange + char[] collection = "abc123ab".ToCharArray(); + char item = 'c'; + + // Act + var act = () => collection.Should().ContainEquivalentOf(item).Which.Should().Be('C'); + + // Assert + act.Should().Throw() + .WithMessage("Expected collection[2] to be equal to C, but found c."); + } + [Fact] public void When_string_collection_does_contain_same_string_with_other_case_it_should_throw() { @@ -398,17 +413,17 @@ public void When_asserting_collection_to_not_contain_equivalent_it_should_allow_ // Act Action act = () => { - using (new AssertionScope()) - { - collection.Should().NotContainEquivalentOf(another, "because we want to test {0}", "first message") - .And - .HaveCount(4, "because we want to test {0}", "second message"); - } + using var _ = new AssertionScope(); + + collection.Should() + .NotContainEquivalentOf(another, "because we want to test {0}", "first message") + .And + .HaveCount(4, "because we want to test {0}", "second message"); }; // Assert - act.Should().Throw().WithMessage("Expected collection*not to contain*first message*but*.\n" + - "Expected*4 item(s)*because*second message*but*."); + act.Should().Throw() + .WithMessage("Expected collection*not to contain*first message*but found one at index 2.*"); } } } diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainInOrder.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainInOrder.cs index da7dafbc61..0efa455e49 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainInOrder.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainInOrder.cs @@ -90,7 +90,7 @@ public void When_a_collection_does_not_contain_an_ordered_item_it_should_throw_w } [Fact] - public void When_a_collection_does_not_contain_items_with_assertion_scope_all_items_are_reported() + public void Even_with_an_assertion_scope_only_the_first_failure_in_a_chained_assertion_is_reported() { // Act Action act = () => @@ -100,8 +100,7 @@ public void When_a_collection_does_not_contain_items_with_assertion_scope_all_it }; // Assert - act.Should().Throw().WithMessage( - "*but 4 (index 0)*but 5 (index 0)*"); + act.Should().Throw().WithMessage("*but 4 (index 0) did not appear (in the right order)."); } [Fact] diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainSingle.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainSingle.cs index 260662bc6e..15bf529510 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainSingle.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainSingle.cs @@ -33,11 +33,8 @@ public void When_a_collection_contains_a_single_item_matching_a_predicate_it_sho IEnumerable collection = [1, 2, 3]; Expression> expression = item => item == 2; - // Act - Action act = () => collection.Should().ContainSingle(expression); - - // Assert - act.Should().NotThrow(); + // Act / Assert + collection.Should().ContainSingle(expression); } [Fact] @@ -122,7 +119,27 @@ public void When_single_item_matching_a_predicate_is_found_it_should_allow_conti Action act = () => collection.Should().ContainSingle(item => item == 2).Which.Should().BeGreaterThan(4); // Assert - act.Should().Throw().WithMessage("Expected*greater*4*2*"); + act.Should() + .Throw() + .WithMessage("Expected collection[0]*greater*4*2*"); + } + + [Fact] + public void Chained_assertions_are_never_called_when_the_initial_assertion_failed() + { + // Arrange + IEnumerable collection = [1, 2, 3]; + + // Act + Action act = () => + { + using var _ = new AssertionScope(); + collection.Should().ContainSingle(item => item == 4).Which.Should().BeGreaterThan(4); + }; + + // Assert + act.Should().Throw() + .WithMessage("Expected collection to contain a single item matching (item == 4), but no such item was found."); } [Fact] @@ -239,9 +256,8 @@ public void When_single_item_is_found_it_should_allow_continuation() Action act = () => collection.Should().ContainSingle().Which.Should().BeGreaterThan(4); // Assert - const string expectedMessage = "Expected collection to be greater than 4, but found 3."; - - act.Should().Throw().WithMessage(expectedMessage); + act.Should().Throw() + .WithMessage("Expected collection[0] to be greater than 4, but found 3."); } [Fact] diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveCount.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveCount.cs index bc0022bbdc..91f58b5fc1 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveCount.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveCount.cs @@ -62,7 +62,7 @@ public void When_collection_has_a_count_larger_than_the_minimum_it_should_not_th } [Fact] - public void When_asserting_a_collection_with_incorrect_predicates_in_assertion_scope_all_are_reported() + public void Even_with_an_assertion_scope_only_the_first_failure_in_a_chained_call_is_reported() { // Arrange int[] collection = [1, 2, 3]; @@ -75,8 +75,7 @@ public void When_asserting_a_collection_with_incorrect_predicates_in_assertion_s }; // Assert - act.Should().Throw().WithMessage( - "*to have a count (c > 3)*to have a count (c < 3)*"); + act.Should().Throw().WithMessage("*count (c > 3), but count is 3: {1, 2, 3}."); } [Fact] diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveElementAt.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveElementAt.cs index efad04183c..fa9c3c0a6e 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveElementAt.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveElementAt.cs @@ -22,6 +22,20 @@ public void When_collection_has_expected_element_at_specific_index_it_should_not collection.Should().HaveElementAt(1, 2); } + [Fact] + public void Can_chain_another_assertion_on_the_selected_element() + { + // Arrange + int[] collection = [1, 2, 3]; + + // Act + var act = () => collection.Should().HaveElementAt(index: 1, element: 2).Which.Should().Be(3); + + // Assert + act.Should().Throw() + .WithMessage("Expected collection[1] to be 3, but found 2."); + } + [Fact] public void When_collection_does_not_have_the_expected_element_at_specific_index_it_should_throw() { diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.OnlyHaveUniqueItems.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.OnlyHaveUniqueItems.cs index 6c86ba71c4..b07d84886b 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.OnlyHaveUniqueItems.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.OnlyHaveUniqueItems.cs @@ -177,7 +177,7 @@ public void When_a_collection_contains_multiple_duplicate_items_with_a_predicate } [Fact] - public void When_a_collection_contains_multiple_duplicates_on_different_properties_all_should_be_reported() + public void Only_the_first_failing_assertion_in_a_chain_is_reported() { // Arrange IEnumerable collection = @@ -198,7 +198,7 @@ public void When_a_collection_contains_multiple_duplicates_on_different_properti // Assert act.Should().Throw().WithMessage( - "*have unique items on e.Text*have unique items on e.Number*"); + "*have unique items on e.Text*"); } [Fact] diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.cs index b908e6669c..ee26a1a148 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.cs @@ -12,8 +12,21 @@ namespace FluentAssertions.Specs.Collections; /// public partial class CollectionAssertionSpecs { - public class Chainings + public class Chaining { + [Fact] + public void Chaining_something_should_do_something() + { + // Act + var languages = new[] { "C#" }; + + var act = () => languages.Should().ContainSingle() + .Which.Should().EndWith("script"); + + // Assert + act.Should().Throw().WithMessage("Expected languages[0]*"); + } + [Fact] public void Should_support_chaining_constraints_with_and() { @@ -71,7 +84,7 @@ public void When_the_collection_is_not_ordered_according_to_the_subsequent_ascen // Assert action.Should().Throw() - .WithMessage("Expected collection * to be ordered \"by Item2\"*"); + .WithMessage("Expected collection*to be ordered \"by Item2\"*"); } [Fact] @@ -163,7 +176,7 @@ public void When_the_collection_is_not_ordered_according_to_the_subsequent_desce // Assert action.Should().Throw() - .WithMessage("Expected collection * to be ordered \"by Item2\"*"); + .WithMessage("Expected collection*to be ordered \"by Item2\"*"); } [Fact] diff --git a/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.ContainMatch.cs b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.ContainMatch.cs index d274fe904f..6901a1e685 100644 --- a/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.ContainMatch.cs +++ b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.ContainMatch.cs @@ -37,7 +37,21 @@ public void When_collection_contains_multiple_matches_it_should_not_throw() } [Fact] - public void When_collection_contains_multiple_matches_which_should_throw() + public void Can_chain_another_assertion_if_a_single_string_matches_the_pattern() + { + // Arrange + IEnumerable collection = ["build succeeded", "test succeeded", "pack failed"]; + + // Act + Action action = () => collection.Should().ContainMatch("*failed*").Which.Should().StartWith("test"); + + // Assert + action.Should().Throw() + .WithMessage("Expected collection[2] to start with*test*pack failed*"); + } + + [Fact] + public void Cannot_chain_another_assertion_if_multiple_strings_match_the_pattern() { // Arrange IEnumerable collection = ["build succeded", "test failed", "pack failed"]; diff --git a/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.ContainValue.cs b/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.ContainValue.cs index 639b329436..3cfd9ab9d9 100644 --- a/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.ContainValue.cs +++ b/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.ContainValue.cs @@ -27,6 +27,23 @@ public void When_dictionary_contains_expected_value_it_should_succeed() act.Should().NotThrow(); } + [Fact] + public void Can_continue_asserting_on_a_single_matching_item() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act + Action act = () => dictionary.Should().ContainValue("One").Which.Should().Be("Two"); + + // Assert + act.Should().Throw().WithMessage("*Expected dictionary[1] to be*Two*, but*One*differs*"); + } + [Fact] public void Null_dictionaries_do_not_contain_any_values() { @@ -82,30 +99,6 @@ public void When_the_specified_value_exists_it_should_allow_continuation_using_t act.Should().Throw().WithMessage("Expected*greater*0*0*"); } - [Fact] - public void When_multiple_matches_for_the_specified_value_exist_continuation_using_the_matched_value_should_fail() - { - // Arrange - var myClass = new MyClass { SomeProperty = 0 }; - - var dictionary = new Dictionary - { - [1] = myClass, - [2] = new() { SomeProperty = 0 } - }; - - // Act - Action act = - () => - dictionary.Should() - .ContainValue(new MyClass { SomeProperty = 0 }) - .Which.Should() - .BeSameAs(myClass); - - // Assert - act.Should().Throw(); - } - [Fact] public void When_a_dictionary_does_not_contain_single_value_it_should_throw_with_clear_explanation() { diff --git a/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.ContainValues.cs b/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.ContainValues.cs index ec0247fb4d..07f898177b 100644 --- a/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.ContainValues.cs +++ b/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.ContainValues.cs @@ -42,7 +42,7 @@ public void When_a_dictionary_does_not_contain_a_number_of_values_it_should_thro // Assert act.Should().Throw().WithMessage( - "Expected dictionary {[1] = \"One\", [2] = \"Two\"} to contain value {\"Two\", \"Three\"} because we do, but could not find {\"Three\"}."); + "Expected dictionary {[1] = \"One\", [2] = \"Two\"} to contain values {\"Two\", \"Three\"} because we do, but could not find \"Three\"."); } [Fact] @@ -65,6 +65,23 @@ public void } } + [Fact] + public void Can_run_another_assertion_on_the_result() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act + Action act = () => dictionary.Should().ContainValues("Two", "One").Which.Should().Contain("Three"); + + // Assert + act.Should().Throw().WithMessage("Expected dictionary[1 and 2]*to contain*Three*"); + } + public class NotContainValues { [Fact] diff --git a/Tests/FluentAssertions.Specs/Exceptions/FunctionExceptionAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Exceptions/FunctionExceptionAssertionSpecs.cs index 9933c52a5c..69d8e077c3 100644 --- a/Tests/FluentAssertions.Specs/Exceptions/FunctionExceptionAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Exceptions/FunctionExceptionAssertionSpecs.cs @@ -435,29 +435,6 @@ public void .WithMessage("*no*exception*that's what he told me*but*ArgumentNullException*"); } - [Fact] - public void When_an_assertion_fails_on_NotThrow_succeeding_message_should_be_included() - { - // Arrange - Func throwingFunction = () => throw new Exception(); - - // Act - Action act = () => - { - using var _ = new AssertionScope(); - - throwingFunction.Should().NotThrow() - .And.BeNull(); - }; - - // Assert - act.Should().Throw() - .WithMessage( - "*Did not expect any exception*" + - "*to be *" - ); - } - #endregion #region NotThrowAfter @@ -594,10 +571,10 @@ public void When_no_exception_should_be_thrown_after_wait_time_the_func_result_s // Act Action act = () => throwShorterThanWaitTime.Should(clock).NotThrowAfter(waitTime, pollInterval) - .Which.Should().Be(42); + .Which.Should().Be(43); // Assert - act.Should().NotThrow(); + act.Should().Throw().WithMessage("Expected throwShorterThanWaitTime.Result to be 43*"); } [Fact] @@ -619,10 +596,7 @@ public void When_an_assertion_fails_on_NotThrowAfter_succeeding_message_should_b // Assert act.Should().Throw() - .WithMessage( - "*Did not expect any exceptions after*" + - "*to be *" - ); + .WithMessage("*Did not expect any exceptions after*"); } #endregion diff --git a/Tests/FluentAssertions.Specs/Execution/AssertionChainSpecs.Chaining.cs b/Tests/FluentAssertions.Specs/Execution/AssertionChainSpecs.Chaining.cs new file mode 100644 index 0000000000..11e959587d --- /dev/null +++ b/Tests/FluentAssertions.Specs/Execution/AssertionChainSpecs.Chaining.cs @@ -0,0 +1,599 @@ +using System; +using FluentAssertions.Execution; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Execution; + +/// +/// The chaining API specs. +/// +public partial class AssertionChainSpecs +{ + public class Chaining + { + [Fact] + public void A_successful_assertion_does_not_affect_the_chained_failing_assertion() + { + // Act + Action act = () => AssertionChain.GetOrCreate() + .ForCondition(condition: true) + .FailWith("First assertion") + .Then + .FailWith("Second assertion"); + + // Arrange + act.Should().Throw().WithMessage("*Second assertion*"); + } + + [Fact] + public void When_the_previous_assertion_succeeded_it_should_not_affect_the_next_one_with_arguments() + { + // Act + Action act = () => AssertionChain.GetOrCreate() + .ForCondition(true) + .FailWith("First assertion") + .Then + .FailWith("Second {0}", "assertion"); + + // Assert + act.Should().Throw() + .WithMessage("Second \"assertion\""); + } + + [Fact] + public void When_the_previous_assertion_succeeded_it_should_not_affect_the_next_one_with_argument_providers() + { + // Act + Action act = () => AssertionChain.GetOrCreate() + .ForCondition(true) + .FailWith("First assertion") + .Then + .FailWith("Second {0}", () => "assertion"); + + // Assert + act.Should().Throw() + .WithMessage("Second \"assertion\""); + } + + [Fact] + public void When_the_previous_assertion_succeeded_it_should_not_affect_the_next_one_with_a_fail_reason_function() + { + // Act + Action act = () => AssertionChain.GetOrCreate() + .ForCondition(true) + .FailWith("First assertion") + .Then + .FailWith(() => new FailReason("Second {0}", "assertion")); + + // Assert + act.Should().Throw() + .WithMessage("Second \"assertion\""); + } + + [Fact] + public void When_continuing_an_assertion_chain_the_reason_should_be_part_of_consecutive_failures() + { + // Act + Action act = () => AssertionChain.GetOrCreate() + .ForCondition(true) + .FailWith("First assertion") + .Then + .BecauseOf("because reasons") + .FailWith("Expected{reason}"); + + // Assert + act.Should().Throw() + .WithMessage("Expected because reasons"); + } + + [Fact] + public void When_continuing_an_assertion_chain_the_reason_with_arguments_should_be_part_of_consecutive_failures() + { + // Act + Action act = () => AssertionChain.GetOrCreate() + .ForCondition(true) + .FailWith("First assertion") + .Then + .BecauseOf("because {0}", "reasons") + .FailWith("Expected{reason}"); + + // Assert + act.Should().Throw() + .WithMessage("Expected because reasons"); + } + + [Fact] + public void Passing_a_null_value_as_reason_does_not_fail() + { + // Act + Action act = () => AssertionChain.GetOrCreate() + .BecauseOf(null, "only because for method disambiguity") + .ForCondition(false) + .FailWith("First assertion"); + + // Assert + act.Should().Throw() + .WithMessage("First assertion"); + } + + [Fact] + public void When_a_given_is_used_before_an_assertion_then_the_result_should_be_available_for_evaluation() + { + // Act + Action act = () => AssertionChain.GetOrCreate() + .Given(() => new[] { "a", "b" }) + .ForCondition(collection => collection.Length > 0) + .FailWith("First assertion"); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_the_previous_assertion_failed_it_should_not_evaluate_the_succeeding_given_statement() + { + // Arrange + using var _ = new AssertionScope(new IgnoringFailuresAssertionStrategy()); + + // Act / Assert + AssertionChain.GetOrCreate() + .ForCondition(false) + .FailWith("First assertion") + .Then + .Given(() => throw new InvalidOperationException()); + } + + [Fact] + public void When_the_previous_assertion_failed_it_should_not_evaluate_the_succeeding_condition() + { + // Arrange + bool secondConditionEvaluated = false; + + try + { + using var _ = new AssertionScope(); + + // Act + AssertionChain.GetOrCreate() + .Given(() => (string)null) + .ForCondition(s => s is not null) + .FailWith("but is was null") + .Then + .ForCondition(_ => secondConditionEvaluated = true) + .FailWith("it should be 42"); + } + catch + { + // Ignore + } + + // Assert + secondConditionEvaluated.Should().BeFalse("because the 2nd condition should not be invoked"); + } + + [Fact] + public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_failure() + { + // Arrange + var scope = new AssertionScope(); + + // Act + AssertionChain.GetOrCreate() + .ForCondition(false) + .FailWith("First assertion") + .Then + .ForCondition(false) + .FailWith("Second assertion"); + + string[] failures = scope.Discard(); + scope.Dispose(); + + Assert.Single(failures); + Assert.Contains("First assertion", failures); + } + + [Fact] + public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_failure_with_arguments() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(); + + AssertionChain.GetOrCreate() + .ForCondition(false) + .FailWith("First assertion") + .Then + .FailWith("Second {0}", "assertion"); + }; + + // Assert + act.Should().Throw() + .WithMessage("First assertion"); + } + + [Fact] + public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_failure_with_argument_providers() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(); + + AssertionChain.GetOrCreate() + .ForCondition(false) + .FailWith("First assertion") + .Then + .FailWith("Second {0}", () => "assertion"); + }; + + // Assert + act.Should().Throw() + .WithMessage("First assertion"); + } + + [Fact] + public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_failure_with_a_fail_reason_function() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(); + + AssertionChain.GetOrCreate() + .ForCondition(false) + .FailWith("First assertion") + .Then + .FailWith(() => new FailReason("Second {0}", "assertion")); + }; + + // Assert + act.Should().Throw() + .WithMessage("First assertion"); + } + + [Fact] + public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_expectation() + { + // Act + Action act = () => + { + using var scope = new AssertionScope(); + + AssertionChain.GetOrCreate() + .WithExpectation("Expectations are the root ", c => c + .ForCondition(false) + .FailWith("of disappointment") + .Then + .WithExpectation("Assumptions are the root ", c2 => c2 + .FailWith("of all evil"))); + }; + + // Assert + act.Should().Throw() + .WithMessage("Expectations are the root of disappointment"); + } + + [Fact] + public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_expectation_with_arguments() + { + // Act + Action act = () => + { + using var scope = new AssertionScope(); + + AssertionChain.GetOrCreate() + .WithExpectation("Expectations are the {0} ", "root", c => c + .ForCondition(false) + .FailWith("of disappointment") + .Then + .WithExpectation("Assumptions are the {0} ", "root", c2 => c2 + .FailWith("of all evil"))); + }; + + // Assert + act.Should().Throw() + .WithMessage("Expectations are the \"root\" of disappointment"); + } + + [Fact] + public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_default_identifier() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(); + + AssertionChain.GetOrCreate() + .WithDefaultIdentifier("identifier") + .ForCondition(false) + .FailWith("Expected {context}") + .Then + .WithDefaultIdentifier("other") + .FailWith("Expected {context}"); + }; + + // Assert + act.Should().Throw() + .WithMessage("Expected identifier"); + } + + [Fact] + public void When_continuing_a_failed_assertion_chain_consecutive_reasons_are_ignored() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(); + + AssertionChain.GetOrCreate() + .BecauseOf("because {0}", "whatever") + .ForCondition(false) + .FailWith("Expected{reason}") + .Then + .BecauseOf("because reasons") + .FailWith("Expected{reason}"); + }; + + // Assert + act.Should().Throw() + .WithMessage("Expected because whatever"); + } + + [Fact] + public void When_continuing_a_failed_assertion_chain_consecutive_reasons_with_arguments_are_ignored() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(); + + AssertionChain.GetOrCreate() + .BecauseOf("because {0}", "whatever") + .ForCondition(false) + .FailWith("Expected{reason}") + .Then + .BecauseOf("because {0}", "reasons") + .FailWith("Expected{reason}"); + }; + + // Assert + act.Should().Throw() + .WithMessage("Expected because whatever"); + } + + [Fact] + public void When_the_previous_assertion_succeeded_it_should_evaluate_the_succeeding_given_statement() + { + // Act + Action act = () => AssertionChain.GetOrCreate() + .ForCondition(true) + .FailWith("First assertion") + .Then + .Given(() => throw new InvalidOperationException()); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_the_previous_assertion_succeeded_it_should_not_affect_the_succeeding_expectation() + { + // Act + Action act = () => + { + AssertionChain.GetOrCreate() + .WithExpectation("Expectations are the root ", chain => chain + .ForCondition(true) + .FailWith("of disappointment") + .Then + .WithExpectation("Assumptions are the root ", innerChain => innerChain + .FailWith("of all evil"))); + }; + + // Assert + act.Should().Throw() + .WithMessage("Assumptions are the root of all evil"); + } + + [Fact] + public void When_the_previous_assertion_succeeded_it_should_not_affect_the_succeeding_expectation_with_arguments() + { + // Act + Action act = () => + { + AssertionChain.GetOrCreate() + .WithExpectation("Expectations are the {0} ", "root", c => c + .ForCondition(true) + .FailWith("of disappointment") + .Then + .WithExpectation("Assumptions are the {0} ", "root", c2 => c2 + .FailWith("of all evil"))); + }; + + // Assert + act.Should().Throw() + .WithMessage("Assumptions are the \"root\" of all evil"); + } + + [Fact] + public void When_the_previous_assertion_succeeded_it_should_not_affect_the_succeeding_default_identifier() + { + // Act + Action act = () => + { + AssertionChain.GetOrCreate() + .WithDefaultIdentifier("identifier") + .ForCondition(true) + .FailWith("Expected {context}") + .Then + .WithDefaultIdentifier("other") + .FailWith("Expected {context}"); + }; + + // Assert + act.Should().Throw() + .WithMessage("Expected other"); + } + + [Fact] + public void Continuing_an_assertion_with_occurrence() + { + // Act + Action act = () => + { + AssertionChain.GetOrCreate() + .ForCondition(true) + .FailWith("First assertion") + .Then + .WithExpectation("{expectedOccurrence} ", c => c + .ForConstraint(Exactly.Once(), 2) + .FailWith("Second {0}", "assertion")); + }; + + // Assert + act.Should().Throw() + .WithMessage("Exactly 1 time Second \"assertion\"*"); + } + + [Fact] + public void Continuing_an_assertion_with_occurrence_will_not_be_executed_when_first_assertion_fails() + { + // Act + Action act = () => + { + AssertionChain.GetOrCreate() + .ForCondition(false) + .FailWith("First assertion") + .Then + .WithExpectation("{expectedOccurrence} ", c => c + .ForConstraint(Exactly.Once(), 2) + .FailWith("Second {0}", "assertion")); + }; + + // Assert + act.Should().Throw() + .WithMessage("First assertion"); + } + + [Fact] + public void Continuing_an_assertion_with_occurrence_overrides_the_previous_defined_expectations() + { + // Act + Action act = () => + { + AssertionChain.GetOrCreate() + .WithExpectation("First expectation", c => c + .ForCondition(true) + .FailWith("First assertion") + .Then + .WithExpectation("{expectedOccurrence} ", c2 => c2 + .ForConstraint(Exactly.Once(), 2) + .FailWith("Second {0}", "assertion"))); + }; + + // Assert + act.Should().Throw() + .WithMessage("Exactly 1 time Second \"assertion\"*"); + } + + [Fact] + public void Continuing_an_assertion_after_occurrence_check_works() + { + // Act + Action act = () => + { + AssertionChain.GetOrCreate() + .WithExpectation("{expectedOccurrence} ", c => c + .ForConstraint(Exactly.Once(), 1) + .FailWith("First assertion") + .Then + .WithExpectation("Second expectation ", c2 => c2 + .ForCondition(false) + .FailWith("Second {0}", "assertion"))); + }; + + // Assert + act.Should().Throw() + .WithMessage("Second expectation Second \"assertion\"*"); + } + + [Fact] + public void Continuing_an_assertion_with_occurrence_check_before_defining_expectation_works() + { + // Act + Action act = () => + { + AssertionChain.GetOrCreate() + .ForCondition(true) + .FailWith("First assertion") + .Then + .ForConstraint(Exactly.Once(), 2) + .WithExpectation("Second expectation ", c => c + .FailWith("Second {0}", "assertion")); + }; + + // Assert + act.Should().Throw() + .WithMessage("Second expectation Second \"assertion\"*"); + } + + [Fact] + public void Does_not_continue_a_chained_assertion_after_the_first_one_failed_the_occurrence_check() + { + // Arrange + var scope = new AssertionScope(); + + // Act + AssertionChain.GetOrCreate() + .ForConstraint(Exactly.Once(), 2) + .FailWith("First {0}", "assertion") + .Then + .ForConstraint(Exactly.Once(), 2) + .FailWith("Second {0}", "assertion"); + + string[] failures = scope.Discard(); + + // Assert + Assert.Single(failures); + Assert.Contains("First \"assertion\"", failures); + } + + [Fact] + public void Discard_a_scope_after_continuing_chained_assertion() + { + // Arrange + using var scope = new AssertionScope(); + + // Act + AssertionChain.GetOrCreate() + .ForConstraint(Exactly.Once(), 2) + .FailWith("First {0}", "assertion"); + + var failures = scope.Discard(); + + // Assert + Assert.Single(failures); + Assert.Contains("First \"assertion\"", failures); + } + + // [Fact] + // public void Get_info_about_line_breaks_from_parent_scope_after_continuing_chained_assertion() + // { + // // Arrange + // using var scope = new AssertionScope(); + // scope.FormattingOptions.UseLineBreaks = true; + // + // // Act + // var innerScope = AssertionChain.GetOrCreate() + // .ForConstraint(Exactly.Once(), 1) + // .FailWith("First {0}", "assertion") + // .Then + // .UsingLineBreaks; + // + // // Assert + // innerScope.UsingLineBreaks.Should().Be(scope.UsingLineBreaks); + // } + } +} diff --git a/Tests/FluentAssertions.Specs/Execution/AssertionChainSpecs.MessageFormating.cs b/Tests/FluentAssertions.Specs/Execution/AssertionChainSpecs.MessageFormating.cs new file mode 100644 index 0000000000..63fc8e684e --- /dev/null +++ b/Tests/FluentAssertions.Specs/Execution/AssertionChainSpecs.MessageFormating.cs @@ -0,0 +1,390 @@ +using System; +using System.Collections.Generic; +using FluentAssertions.Execution; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Execution; + +/// +/// The message formatting specs. +/// +public partial class AssertionChainSpecs +{ + public class MessageFormatting + { + [Fact] + public void Multiple_assertions_in_an_assertion_scope_are_all_reported() + { + // Arrange + var scope = new AssertionScope(); + + AssertionChain.GetOrCreate().FailWith("Failure"); + AssertionChain.GetOrCreate().FailWith("Failure"); + + using (new AssertionScope()) + { + AssertionChain.GetOrCreate().FailWith("Failure"); + AssertionChain.GetOrCreate().FailWith("Failure"); + } + + // Act + Action act = scope.Dispose; + + // Assert + act.Should().Throw() + .Which.Message.Should().Contain("Failure", Exactly.Times(4)); + } + + [InlineData("foo")] + [InlineData("{}")] + [Theory] + public void The_failure_message_uses_the_name_of_the_scope_as_context(string context) + { + // Act + Action act = () => + { + using var _ = new AssertionScope(context); + new[] { 1, 2, 3 }.Should().Equal(3, 2, 1); + }; + + // Assert + act.Should().Throw() + .WithMessage($"Expected {context} to be equal to*"); + } + + [Fact] + public void The_failure_message_uses_the_lazy_name_of_the_scope_as_context() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(() => "lazy foo"); + new[] { 1, 2, 3 }.Should().Equal(3, 2, 1); + }; + + // Assert + act.Should().Throw() + .WithMessage("Expected lazy foo to be equal to*"); + } + + [Fact] + public void The_failure_message_includes_all_failures() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(); + var values = new Dictionary(); + values.Should().ContainKey(0); + values.Should().ContainKey(1); + }; + + // Assert + act.Should().Throw() + .WithMessage( + "Expected * to contain key 0.\nExpected * to contain key 1.\n"); + } + + [Fact] + public void The_failure_message_includes_all_failures_as_well() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(); + var values = new List(); + values.Should().ContainSingle(); + values.Should().ContainSingle(); + }; + + // Assert + act.Should().Throw() + .WithMessage( + "Expected * to contain a single item, but the collection is empty.\n" + + "Expected * to contain a single item, but the collection is empty.\n"); + } + + [Fact] + public void The_reason_can_contain_parentheses() + { + // Act + Action act = () => 1.Should().Be(2, "can't use these in becauseArgs: {0} {1}", "{", "}"); + + // Assert + act.Should().Throw() + .WithMessage("*because can't use these in becauseArgs: { }*"); + } + + [Fact] + public void Because_reason_should_ignore_undefined_arguments() + { + // Act + object[] becauseArgs = null; + Action act = () => 1.Should().Be(2, "it should still work", becauseArgs); + + // Assert + act.Should().Throw() + .WithMessage("*because it should still work*"); + } + + [Fact] + public void Because_reason_should_threat_parentheses_as_literals_if_no_arguments_are_defined() + { + // Act +#pragma warning disable CA2241 + // ReSharper disable once FormatStringProblem + Action act = () => 1.Should().Be(2, "use of {} is okay if there are no because arguments"); +#pragma warning restore CA2241 + + // Assert + act.Should().Throw() + .WithMessage("*because use of {} is okay if there are no because arguments*"); + } + + [Fact] + public void Because_reason_should_inform_about_invalid_parentheses_with_a_default_message() + { + // Act +#pragma warning disable CA2241 + // ReSharper disable once FormatStringProblem + Action act = () => 1.Should().Be(2, "use of {} is considered invalid in because parameter with becauseArgs", + "additional becauseArgs argument"); +#pragma warning restore CA2241 + + // Assert + act.Should().Throw() + .WithMessage( + "*because message 'use of {} is considered invalid in because parameter with becauseArgs' could not be formatted with string.Format*"); + } + + [Fact] + public void Message_should_keep_parentheses_in_literal_values() + { + // Act + Action act = () => "{foo}".Should().Be("{bar}"); + + // Assert + act.Should().Throw() + .WithMessage("Expected string to be \"{bar}\", but \"{foo}\" differs near*"); + } + + [Fact] + public void Message_should_contain_literal_value_if_marked_with_double_parentheses() + { + // Act + Action act = () => AssertionChain.GetOrCreate().FailWith("{{empty}}"); + + // Assert + act.Should().ThrowExactly() + .WithMessage("{empty}*"); + } + + [InlineData("\r")] + [InlineData("\\r")] + [InlineData("\\\r")] + [InlineData("\\\\r")] + [InlineData("\\\\\r")] + [InlineData("\n")] + [InlineData("\\n")] + [InlineData("\\\n")] + [InlineData("\\\\n")] + [InlineData("\\\\\n")] + [Theory] + public void Message_should_not_have_modified_carriage_return_or_line_feed_control_characters(string str) + { + // Act + Action act = () => AssertionChain.GetOrCreate().FailWith(str); + + // Assert + act.Should().ThrowExactly() + .WithMessage(str); + } + + [InlineData("\r")] + [InlineData("\\r")] + [InlineData("\\\r")] + [InlineData(@"\\r")] + [InlineData("\\\\\r")] + [InlineData("\n")] + [InlineData("\\n")] + [InlineData("\\\n")] + [InlineData(@"\\n")] + [InlineData("\\\\\n")] + [Theory] + public void Message_should_not_have_modified_carriage_return_or_line_feed_control_characters_in_supplied_arguments( + string str) + { + // Act + Action act = () => AssertionChain.GetOrCreate().FailWith(@"\{0}\A", str); + + // Assert + act.Should().ThrowExactly() + .WithMessage("\\\"" + str + "\"\\A*"); + } + + [Fact] + public void Message_should_not_have_trailing_backslashes_removed_from_subject() + { + // Arrange / Act + Action act = () => "A\\".Should().Be("A"); + + // Assert + act.Should().Throw() + .WithMessage("""* near "\" *"""); + } + + [Fact] + public void Message_should_not_have_trailing_backslashes_removed_from_expectation() + { + // Arrange / Act + Action act = () => "A".Should().Be("A\\"); + + // Assert + act.Should().Throw() + .WithMessage("""* to be "A\" *"""); + } + + [Fact] + public void Message_should_have_reportable_values_appended_at_the_end() + { + // Arrange + var scope = new AssertionScope(); + scope.AddReportable("SomeKey", "SomeValue"); + scope.AddReportable("AnotherKey", "AnotherValue"); + + AssertionChain.GetOrCreate().FailWith("{SomeKey}{AnotherKey}"); + + // Act + Action act = scope.Dispose; + + // Assert + act.Should().ThrowExactly() + .WithMessage("*With SomeKey:\nSomeValue\nWith AnotherKey:\nAnotherValue"); + } + + [Fact] + public void Deferred_reportable_values_should_not_be_calculated_in_absence_of_failures() + { + // Arrange + var scope = new AssertionScope(); + var deferredValueInvoked = false; + + scope.AddReportable("MyKey", () => + { + deferredValueInvoked = true; + + return "MyValue"; + }); + + // Act + scope.Dispose(); + + // Assert + deferredValueInvoked.Should().BeFalse(); + } + + [Fact] + public void Message_should_start_with_the_defined_expectation() + { + // Act + Action act = () => + { + var assertion = AssertionChain.GetOrCreate(); + + assertion + .WithExpectation("Expectations are the root ", chain => chain + .ForCondition(false) + .FailWith("of disappointment")); + }; + + // Assert + act.Should().Throw() + .WithMessage("Expectations are the root of disappointment"); + } + + [Fact] + public void Message_should_start_with_the_defined_expectation_and_arguments() + { + // Act + Action act = () => + { + var assertion = AssertionChain.GetOrCreate(); + + assertion + .WithExpectation("Expectations are the {0} ", "root", chain => chain.ForCondition(false) + .FailWith("of disappointment")); + }; + + // Assert + act.Should().Throw() + .WithMessage("Expectations are the \"root\" of disappointment"); + } + + [Fact] + public void Message_should_contain_object_as_context_if_identifier_can_not_be_resolved() + { + // Act + Action act = () => AssertionChain.GetOrCreate() + .ForCondition(false) + .FailWith("Expected {context}"); + + // Assert + act.Should().Throw() + .WithMessage("Expected object"); + } + + [Fact] + public void Message_should_contain_the_fallback_value_as_context_if_identifier_can_not_be_resolved() + { + // Act + Action act = () => AssertionChain.GetOrCreate() + .ForCondition(false) + .FailWith("Expected {context:fallback}"); + + // Assert + act.Should().Throw() + .WithMessage("Expected fallback"); + } + + [Fact] + public void Message_should_contain_the_default_identifier_as_context_if_identifier_can_not_be_resolved() + { + // Act + Action act = () => AssertionChain.GetOrCreate() + .WithDefaultIdentifier("identifier") + .ForCondition(false) + .FailWith("Expected {context}"); + + // Assert + act.Should().Throw() + .WithMessage("Expected identifier"); + } + + [Fact] + public void Message_should_contain_the_reason_as_defined() + { + // Act + Action act = () => AssertionChain.GetOrCreate() + .BecauseOf("because reasons") + .FailWith("Expected{reason}"); + + // Assert + act.Should().Throw() + .WithMessage("Expected because reasons"); + } + + [Fact] + public void Message_should_contain_the_reason_as_defined_with_arguments() + { + // Act + Action act = () => AssertionChain.GetOrCreate() + .BecauseOf("because {0}", "reasons") + .FailWith("Expected{reason}"); + + // Assert + act.Should().Throw() + .WithMessage("Expected because reasons"); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Execution/AssertionScope.ChainingApiSpecs.cs b/Tests/FluentAssertions.Specs/Execution/AssertionScope.ChainingApiSpecs.cs deleted file mode 100644 index 11f21eae89..0000000000 --- a/Tests/FluentAssertions.Specs/Execution/AssertionScope.ChainingApiSpecs.cs +++ /dev/null @@ -1,588 +0,0 @@ -using System; -using FluentAssertions.Execution; -using Xunit; -using Xunit.Sdk; - -namespace FluentAssertions.Specs.Execution; - -/// -/// The chaining API specs. -/// -public partial class AssertionScopeSpecs -{ - [Fact] - public void When_the_previous_assertion_succeeded_it_should_not_affect_the_next_one() - { - bool succeeded = false; - - // Act - try - { - Execute.Assertion - .ForCondition(condition: true) - .FailWith("First assertion") - .Then - .FailWith("Second assertion"); - } - catch (Exception e) - { - // Assert - succeeded = e is XunitException xUnitException && xUnitException.Message.Contains("Second"); - } - - if (!succeeded) - { - throw new XunitException("Expected the second assertion to fail"); - } - } - - [Fact] - public void When_the_previous_assertion_succeeded_it_should_not_affect_the_next_one_with_arguments() - { - // Act - Action act = () => Execute.Assertion - .ForCondition(true) - .FailWith("First assertion") - .Then - .FailWith("Second {0}", "assertion"); - - // Assert - act.Should().Throw() - .WithMessage("Second \"assertion\""); - } - - [Fact] - public void When_the_previous_assertion_succeeded_it_should_not_affect_the_next_one_with_argument_providers() - { - // Act - Action act = () => Execute.Assertion - .ForCondition(true) - .FailWith("First assertion") - .Then - .FailWith("Second {0}", () => "assertion"); - - // Assert - act.Should().Throw() - .WithMessage("Second \"assertion\""); - } - - [Fact] - public void When_the_previous_assertion_succeeded_it_should_not_affect_the_next_one_with_a_fail_reason_function() - { - // Act - Action act = () => Execute.Assertion - .ForCondition(true) - .FailWith("First assertion") - .Then - .FailWith(() => new FailReason("Second {0}", "assertion")); - - // Assert - act.Should().Throw() - .WithMessage("Second \"assertion\""); - } - - [Fact] - public void When_continuing_an_assertion_chain_the_reason_should_be_part_of_consecutive_failures() - { - // Act - Action act = () => Execute.Assertion - .ForCondition(true) - .FailWith("First assertion") - .Then - .BecauseOf("because reasons") - .FailWith("Expected{reason}"); - - // Assert - act.Should().Throw() - .WithMessage("Expected because reasons"); - } - - [Fact] - public void When_continuing_an_assertion_chain_the_reason_with_arguments_should_be_part_of_consecutive_failures() - { - // Act - Action act = () => Execute.Assertion - .ForCondition(true) - .FailWith("First assertion") - .Then - .BecauseOf("because {0}", "reasons") - .FailWith("Expected{reason}"); - - // Assert - act.Should().Throw() - .WithMessage("Expected because reasons"); - } - - [Fact] - public void Passing_a_null_value_as_reason_does_not_fail() - { - // Act - Action act = () => Execute.Assertion - .BecauseOf(null, "only because for method disambiguity") - .ForCondition(false) - .FailWith("First assertion"); - - // Assert - act.Should().Throw() - .WithMessage("First assertion"); - } - - [Fact] - public void When_a_given_is_used_before_an_assertion_then_the_result_should_be_available_for_evaluation() - { - // Act - Action act = () => Execute.Assertion - .Given(() => new[] { "a", "b" }) - .ForCondition(collection => collection.Length > 0) - .FailWith("First assertion"); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_the_previous_assertion_failed_it_should_not_evaluate_the_succeeding_given_statement() - { - // Arrange - using var _ = new AssertionScope(new IgnoringFailuresAssertionStrategy()); - - // Act / Assert - Execute.Assertion - .ForCondition(false) - .FailWith("First assertion") - .Then - .Given(() => throw new InvalidOperationException()); - } - - [Fact] - public void When_the_previous_assertion_failed_it_should_not_evaluate_the_succeeding_condition() - { - // Arrange - bool secondConditionEvaluated = false; - - try - { - using var _ = new AssertionScope(); - - // Act - Execute.Assertion - .Given(() => (string)null) - .ForCondition(s => s is not null) - .FailWith("but is was null") - .Then - .ForCondition(_ => secondConditionEvaluated = true) - .FailWith("it should be 42"); - } - catch - { - // Ignore - } - - // Assert - secondConditionEvaluated.Should().BeFalse("because the 2nd condition should not be invoked"); - } - - [Fact] - public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_failure() - { - // Arrange - var scope = new AssertionScope(); - - // Act - Execute.Assertion - .ForCondition(false) - .FailWith("First assertion") - .Then - .ForCondition(false) - .FailWith("Second assertion"); - - string[] failures = scope.Discard(); - scope.Dispose(); - - Assert.Single(failures); - Assert.Contains("First assertion", failures); - } - - [Fact] - public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_failure_with_arguments() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(); - - Execute.Assertion - .ForCondition(false) - .FailWith("First assertion") - .Then - .FailWith("Second {0}", "assertion"); - }; - - // Assert - act.Should().Throw() - .WithMessage("First assertion"); - } - - [Fact] - public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_failure_with_argument_providers() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(); - - Execute.Assertion - .ForCondition(false) - .FailWith("First assertion") - .Then - .FailWith("Second {0}", () => "assertion"); - }; - - // Assert - act.Should().Throw() - .WithMessage("First assertion"); - } - - [Fact] - public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_failure_with_a_fail_reason_function() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(); - - Execute.Assertion - .ForCondition(false) - .FailWith("First assertion") - .Then - .FailWith(() => new FailReason("Second {0}", "assertion")); - }; - - // Assert - act.Should().Throw() - .WithMessage("First assertion"); - } - - [Fact] - public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_expectation() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(); - - Execute.Assertion - .WithExpectation("Expectations are the root ") - .ForCondition(false) - .FailWith("of disappointment") - .Then - .WithExpectation("Assumptions are the root ") - .FailWith("of all evil"); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expectations are the root of disappointment"); - } - - [Fact] - public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_expectation_with_arguments() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(); - - Execute.Assertion - .WithExpectation("Expectations are the {0} ", "root") - .ForCondition(false) - .FailWith("of disappointment") - .Then - .WithExpectation("Assumptions are the {0} ", "root") - .FailWith("of all evil"); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expectations are the \"root\" of disappointment"); - } - - [Fact] - public void When_the_previous_assertion_failed_it_should_not_execute_the_succeeding_default_identifier() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(); - - Execute.Assertion - .WithDefaultIdentifier("identifier") - .ForCondition(false) - .FailWith("Expected {context}") - .Then - .WithDefaultIdentifier("other") - .FailWith("Expected {context}"); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expected identifier"); - } - - [Fact] - public void When_continuing_a_failed_assertion_chain_consecutive_resons_are_ignored() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(); - - Execute.Assertion - .BecauseOf("because {0}", "whatever") - .ForCondition(false) - .FailWith("Expected{reason}") - .Then - .BecauseOf("because reasons") - .FailWith("Expected{reason}"); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expected because whatever"); - } - - [Fact] - public void When_continuing_a_failed_assertion_chain_consecutive_resons_with_arguments_are_ignored() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(); - - Execute.Assertion - .BecauseOf("because {0}", "whatever") - .ForCondition(false) - .FailWith("Expected{reason}") - .Then - .BecauseOf("because {0}", "reasons") - .FailWith("Expected{reason}"); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expected because whatever"); - } - - [Fact] - public void When_the_previous_assertion_succeeded_it_should_evaluate_the_succeeding_given_statement() - { - // Act - Action act = () => Execute.Assertion - .ForCondition(true) - .FailWith("First assertion") - .Then - .Given(() => throw new InvalidOperationException()); - - // Assert - Assert.Throws(act); - } - - [Fact] - public void When_the_previous_assertion_succeeded_it_should_not_affect_the_succeeding_expectation() - { - // Act - Action act = () => Execute.Assertion - .WithExpectation("Expectations are the root ") - .ForCondition(true) - .FailWith("of disappointment") - .Then - .WithExpectation("Assumptions are the root ") - .FailWith("of all evil"); - - // Assert - act.Should().Throw() - .WithMessage("Assumptions are the root of all evil"); - } - - [Fact] - public void When_the_previous_assertion_succeeded_it_should_not_affect_the_succeeding_expectation_with_arguments() - { - // Act - Action act = () => Execute.Assertion - .WithExpectation("Expectations are the {0} ", "root") - .ForCondition(true) - .FailWith("of disappointment") - .Then - .WithExpectation("Assumptions are the {0} ", "root") - .FailWith("of all evil"); - - // Assert - act.Should().Throw() - .WithMessage("Assumptions are the \"root\" of all evil"); - } - - [Fact] - public void When_the_previous_assertion_succeeded_it_should_not_affect_the_succeeding_default_identifier() - { - // Act - Action act = () => - { - Execute.Assertion - .WithDefaultIdentifier("identifier") - .ForCondition(true) - .FailWith("Expected {context}") - .Then - .WithDefaultIdentifier("other") - .FailWith("Expected {context}"); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expected other"); - } - - [Fact] - public void Continuing_an_assertion_with_occurrence() - { - // Act - Action act = () => Execute.Assertion - .ForCondition(true) - .FailWith("First assertion") - .Then - .WithExpectation("{expectedOccurrence} ") - .ForConstraint(Exactly.Once(), 2) - .FailWith("Second {0}", "assertion"); - - // Assert - act.Should().Throw() - .WithMessage("Exactly 1 time Second \"assertion\"*"); - } - - [Fact] - public void Continuing_an_assertion_with_occurrence_will_not_be_executed_when_first_assertion_fails() - { - // Act - Action act = () => Execute.Assertion - .ForCondition(false) - .FailWith("First assertion") - .Then - .WithExpectation("{expectedOccurrence} ") - .ForConstraint(Exactly.Once(), 2) - .FailWith("Second {0}", "assertion"); - - // Assert - act.Should().Throw() - .WithMessage("First assertion"); - } - - [Fact] - public void Continuing_an_assertion_with_occurrence_overrides_the_previous_defined_expectations() - { - // Act - Action act = () => Execute.Assertion - .WithExpectation("First expectation") - .ForCondition(true) - .FailWith("First assertion") - .Then - .WithExpectation("{expectedOccurrence} ") - .ForConstraint(Exactly.Once(), 2) - .FailWith("Second {0}", "assertion"); - - // Assert - act.Should().Throw() - .WithMessage("Exactly 1 time Second \"assertion\"*"); - } - - [Fact] - public void Continuing_an_assertion_after_occurrence_check_works() - { - // Act - Action act = () => Execute.Assertion - .WithExpectation("{expectedOccurrence} ") - .ForConstraint(Exactly.Once(), 1) - .FailWith("First assertion") - .Then - .WithExpectation("Second expectation ") - .ForCondition(false) - .FailWith("Second {0}", "assertion"); - - // Assert - act.Should().Throw() - .WithMessage("Second expectation Second \"assertion\"*"); - } - - [Fact] - public void Continuing_an_assertion_with_occurrence_check_before_defining_expectation_works() - { - // Act - Action act = () => Execute.Assertion - .ForCondition(true) - .FailWith("First assertion") - .Then - .ForConstraint(Exactly.Once(), 2) - .WithExpectation("Second expectation ") - .FailWith("Second {0}", "assertion"); - - // Assert - act.Should().Throw() - .WithMessage("Second expectation Second \"assertion\"*"); - } - - [Fact] - public void Does_not_continue_a_chained_assertion_after_the_first_one_failed_the_occurrence_check() - { - // Arrange - var scope = new AssertionScope(); - - // Act - Execute.Assertion - .ForConstraint(Exactly.Once(), 2) - .FailWith("First {0}", "assertion") - .Then - .ForConstraint(Exactly.Once(), 2) - .FailWith("Second {0}", "assertion"); - - string[] failures = scope.Discard(); - scope.Dispose(); - - // Assert - Assert.Single(failures); - Assert.Contains("First \"assertion\"", failures); - } - - [Fact] - public void Discard_a_scope_after_continuing_chained_assertion() - { - // Arrange - using var scope = new AssertionScope(); - - // Act - var failures = Execute.Assertion - .ForConstraint(Exactly.Once(), 2) - .FailWith("First {0}", "assertion") - .Then - .Discard(); - - // Assert - Assert.Single(failures); - Assert.Contains("First \"assertion\"", failures); - } - - [Fact] - public void Get_info_about_line_breaks_from_parent_scope_after_continuing_chained_assertion() - { - // Arrange - using var scope = new AssertionScope(); - scope.FormattingOptions.UseLineBreaks = true; - - // Act - var innerScope = Execute.Assertion - .ForConstraint(Exactly.Once(), 1) - .FailWith("First {0}", "assertion") - .Then - .UsingLineBreaks; - - // Assert - innerScope.UsingLineBreaks.Should().Be(scope.UsingLineBreaks); - } -} diff --git a/Tests/FluentAssertions.Specs/Execution/AssertionScope.ContextDataSpecs.cs b/Tests/FluentAssertions.Specs/Execution/AssertionScope.ContextDataSpecs.cs deleted file mode 100644 index 1a36fb62bc..0000000000 --- a/Tests/FluentAssertions.Specs/Execution/AssertionScope.ContextDataSpecs.cs +++ /dev/null @@ -1,68 +0,0 @@ -using FluentAssertions.Execution; -using Xunit; - -namespace FluentAssertions.Specs.Execution; - -/// -/// The chaining API specs. -/// -public partial class AssertionScopeSpecs -{ - [Fact] - public void Get_value_when_key_is_present() - { - // Arrange - var scope = new AssertionScope(); - scope.AddNonReportable("SomeKey", "SomeValue"); - scope.AddNonReportable("SomeOtherKey", "SomeOtherValue"); - - // Act - var value = scope.Get("SomeKey"); - - // Assert - value.Should().Be("SomeValue"); - } - - [Fact] - public void Get_default_value_when_key_is_not_present() - { - // Arrange - var scope = new AssertionScope(); - - // Act - var value = scope.Get("SomeKey"); - - // Assert - value.Should().Be(0); - } - - [Fact] - public void Get_default_value_when_nullable_value_is_null() - { - // Arrange - var scope = new AssertionScope(); - - int? someValue = null; - scope.AddNonReportable("SomeKey", someValue); - - // Act - var value = scope.Get("SomeKey"); - - // Assert - value.Should().Be(0); - } - - [Fact] - public void Value_should_be_of_requested_type() - { - // Arrange - var scope = new AssertionScope(); - scope.AddNonReportable("SomeKey", "SomeValue"); - - // Act - var value = scope.Get("SomeKey"); - - // Assert - value.Should().BeOfType(); - } -} diff --git a/Tests/FluentAssertions.Specs/Execution/AssertionScope.MessageFormatingSpecs.cs b/Tests/FluentAssertions.Specs/Execution/AssertionScope.MessageFormatingSpecs.cs deleted file mode 100644 index 4dcd1d7af9..0000000000 --- a/Tests/FluentAssertions.Specs/Execution/AssertionScope.MessageFormatingSpecs.cs +++ /dev/null @@ -1,522 +0,0 @@ -using System; -using System.Collections.Generic; -using FluentAssertions.Execution; -using Xunit; -using Xunit.Sdk; - -namespace FluentAssertions.Specs.Execution; - -/// -/// The message formatting specs. -/// -public partial class AssertionScopeSpecs -{ - [Fact] - public void When_the_same_failure_is_handled_twice_or_more_it_should_still_report_it_once() - { - // Arrange - var scope = new AssertionScope(); - - AssertionScope.Current.FailWith("Failure"); - AssertionScope.Current.FailWith("Failure"); - - using (var nestedScope = new AssertionScope()) - { - nestedScope.FailWith("Failure"); - nestedScope.FailWith("Failure"); - } - - // Act - Action act = scope.Dispose; - - // Assert - act.Should().Throw() - .Which.Message.Should().Contain("Failure", Exactly.Times(4)); - } - - [InlineData("foo")] - [InlineData("{}")] - [Theory] - public void Message_should_use_the_name_of_the_scope_as_context(string context) - { - // Act - Action act = () => - { - using var _ = new AssertionScope(context); - new[] { 1, 2, 3 }.Should().Equal(3, 2, 1); - }; - - // Assert - act.Should().Throw() - .WithMessage($"Expected {context} to be equal to*"); - } - - [Fact] - public void Message_should_use_the_lazy_name_of_the_scope_as_context() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(new Lazy(() => "lazy foo")); - new[] { 1, 2, 3 }.Should().Equal(3, 2, 1); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expected lazy foo to be equal to*"); - } - - [Fact] - public void Nested_scopes_use_the_name_of_their_outer_scope_as_context() - { - // Act - Action act = () => - { - using var outerScope = new AssertionScope("outer"); - using var innerScope = new AssertionScope("inner"); - new[] { 1, 2, 3 }.Should().Equal(3, 2, 1); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expected outer/inner to be equal to*"); - } - - [Fact] - public void The_inner_scope_is_used_when_the_outer_scope_does_not_have_a_context() - { - // Act - Action act = () => - { - using var outerScope = new AssertionScope(); - using var innerScope = new AssertionScope("inner"); - new[] { 1, 2, 3 }.Should().Equal(3, 2, 1); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expected inner to be equal to*"); - } - - [Fact] - public void Message_should_contain_each_unique_failed_assertion_seperately() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(); - var values = new Dictionary(); - values.Should().ContainKey(0); - values.Should().ContainKey(1); - }; - - // Assert - act.Should().Throw() - .WithMessage( - "Expected * to contain key 0.\n" + - "Expected * to contain key 1.\n"); - } - - [Fact] - public void Message_should_contain_the_same_failed_assertion_seperately_if_called_multiple_times() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(); - var values = new List(); - values.Should().ContainSingle(); - values.Should().ContainSingle(); - }; - - // Assert - act.Should().Throw() - .WithMessage( - "Expected * to contain a single item, but the collection is empty.\n" + - "Expected * to contain a single item, but the collection is empty.\n"); - } - - [Fact] - public void Because_reason_should_keep_parentheses_in_arguments_as_literals() - { - // Act - Action act = () => 1.Should().Be(2, "can't use these in becauseArgs: {0} {1}", "{", "}"); - - // Assert - act.Should().Throw() - .WithMessage("*because can't use these in becauseArgs: { }*"); - } - - [Fact] - public void Because_reason_should_ignore_undefined_arguments() - { - // Act - object[] becauseArgs = null; - - // ReSharper disable once FormatStringProblem - Action act = () => 1.Should().Be(2, "it should still work", becauseArgs); - - // Assert - act.Should().Throw() - .WithMessage("*because it should still work*"); - } - - [Fact] - public void Because_reason_should_threat_parentheses_as_literals_if_no_arguments_are_defined() - { - // Act -#pragma warning disable CA2241 - // ReSharper disable once FormatStringProblem - Action act = () => 1.Should().Be(2, "use of {} is okay if there are no because arguments"); -#pragma warning restore CA2241 - - // Assert - act.Should().Throw() - .WithMessage("*because use of {} is okay if there are no because arguments*"); - } - - [Fact] - public void Because_reason_should_inform_about_invalid_parentheses_with_a_default_message() - { - // Act -#pragma warning disable CA2241 - // ReSharper disable once FormatStringProblem - Action act = () => 1.Should().Be(2, "use of {} is considered invalid in because parameter with becauseArgs", - "additional becauseArgs argument"); -#pragma warning restore CA2241 - - // Assert - act.Should().Throw() - .WithMessage( - "*because message 'use of {} is considered invalid in because parameter with becauseArgs' could not be formatted with string.Format*"); - } - - [Fact] - public void Message_should_keep_parentheses_in_literal_values() - { - // Act - Action act = () => "{foo}".Should().Be("{bar}"); - - // Assert - act.Should().Throw() - .WithMessage("Expected string to be \"{bar}\", but \"{foo}\" differs near*"); - } - - [Fact] - public void Message_should_contain_literal_value_if_marked_with_double_parentheses() - { - // Arrange - var scope = new AssertionScope("context"); - - AssertionScope.Current.FailWith("{{empty}}"); - - // Act - Action act = scope.Dispose; - - // Assert - act.Should().ThrowExactly() - .WithMessage("{empty}*"); - } - - [InlineData("\r")] - [InlineData("\\r")] - [InlineData("\\\r")] - [InlineData("\\\\r")] - [InlineData("\\\\\r")] - [InlineData("\n")] - [InlineData("\\n")] - [InlineData("\\\n")] - [InlineData("\\\\n")] - [InlineData("\\\\\n")] - [Theory] - public void Message_should_not_have_modified_carriage_return_or_line_feed_control_characters(string str) - { - // Arrange - var scope = new AssertionScope(); - - AssertionScope.Current.FailWith(str); - - // Act - Action act = scope.Dispose; - - // Assert - act.Should().ThrowExactly() - .WithMessage(str); - } - - [InlineData("\r")] - [InlineData("\\r")] - [InlineData("\\\r")] - [InlineData(@"\\r")] - [InlineData("\\\\\r")] - [InlineData("\n")] - [InlineData("\\n")] - [InlineData("\\\n")] - [InlineData(@"\\n")] - [InlineData("\\\\\n")] - [Theory] - public void Message_should_not_have_modified_carriage_return_or_line_feed_control_characters_in_supplied_arguments(string str) - { - // Arrange - var scope = new AssertionScope(); - - AssertionScope.Current.FailWith(@"\{0}\A", str); - - // Act - Action act = scope.Dispose; - - // Assert - act.Should().ThrowExactly() - .WithMessage("\\\"" + str + "\"\\A*"); - } - - [Fact] - public void Message_should_not_have_trailing_backslashes_removed_from_subject() - { - // Arrange / Act - Action act = () => "A\\".Should().Be("A"); - - // Assert - act.Should().Throw() - .WithMessage("""* near "\" *"""); - } - - [Fact] - public void Message_should_not_have_trailing_backslashes_removed_from_expectation() - { - // Arrange / Act - Action act = () => "A".Should().Be("A\\"); - - // Assert - act.Should().Throw() - .WithMessage("""* to be "A\" *"""); - } - - [Fact] - public void Message_should_have_named_placeholder_be_replaced_by_reportable_value() - { - // Arrange - var scope = new AssertionScope(); - scope.AddReportable("MyKey", "MyValue"); - - AssertionScope.Current.FailWith("{MyKey}"); - - // Act - Action act = scope.Dispose; - - // Assert - act.Should().ThrowExactly() - .WithMessage("MyValue*"); - } - - [Fact] - public void Message_should_have_named_placeholders_be_replaced_by_reportable_values() - { - // Arrange - var scope = new AssertionScope(); - scope.AddReportable("SomeKey", "SomeValue"); - scope.AddReportable("AnotherKey", "AnotherValue"); - - AssertionScope.Current.FailWith("{SomeKey}{AnotherKey}"); - - // Act - Action act = scope.Dispose; - - // Assert - act.Should().ThrowExactly() - .WithMessage("SomeValueAnotherValue*"); - } - - [Fact] - public void Message_should_have_reportable_values_appended_at_the_end() - { - // Arrange - var scope = new AssertionScope(); - scope.AddReportable("SomeKey", "SomeValue"); - scope.AddReportable("AnotherKey", "AnotherValue"); - - AssertionScope.Current.FailWith("{SomeKey}{AnotherKey}"); - - // Act - Action act = scope.Dispose; - - // Assert - act.Should().ThrowExactly() - .WithMessage("*With SomeKey:\nSomeValue\nWith AnotherKey:\nAnotherValue"); - } - - [Fact] - public void Message_should_not_have_nonreportable_values_appended_at_the_end() - { - // Arrange - var scope = new AssertionScope(); - scope.AddNonReportable("SomeKey", "SomeValue"); - - AssertionScope.Current.FailWith("{SomeKey}"); - - // Act - Action act = scope.Dispose; - - // Assert - act.Should().ThrowExactly() - .Which.Message.Should().NotContain("With SomeKey:\nSomeValue"); - } - - [Fact] - public void Message_should_have_named_placeholder_be_replaced_by_nonreportable_value() - { - // Arrange - var scope = new AssertionScope(); - scope.AddNonReportable("SomeKey", "SomeValue"); - - AssertionScope.Current.FailWith("{SomeKey}"); - - // Act - Action act = scope.Dispose; - - // Assert - act.Should().ThrowExactly() - .WithMessage("SomeValue"); - } - - [Fact] - public void Deferred_reportable_values_should_not_be_calculated_in_absence_of_failures() - { - // Arrange - var scope = new AssertionScope(); - var deferredValueInvoked = false; - - scope.AddReportable("MyKey", () => - { - deferredValueInvoked = true; - - return "MyValue"; - }); - - // Act - scope.Dispose(); - - // Assert - deferredValueInvoked.Should().BeFalse(); - } - - [Fact] - public void Message_should_have_named_placeholder_be_replaced_by_defered_reportable_value() - { - // Arrange - var scope = new AssertionScope(); - var deferredValueInvoked = false; - - scope.AddReportable("MyKey", () => - { - deferredValueInvoked = true; - - return "MyValue"; - }); - - AssertionScope.Current.FailWith("{MyKey}"); - - // Act - Action act = scope.Dispose; - - // Assert - act.Should().ThrowExactly() - .WithMessage("MyValue*\n\nWith MyKey:\nMyValue\n"); - - deferredValueInvoked.Should().BeTrue(); - } - - [Fact] - public void Message_should_start_with_the_defined_expectation() - { - // Act - Action act = () => Execute.Assertion - .WithExpectation("Expectations are the root ") - .ForCondition(false) - .FailWith("of disappointment"); - - // Assert - act.Should().Throw() - .WithMessage("Expectations are the root of disappointment"); - } - - [Fact] - public void Message_should_start_with_the_defined_expectation_and_arguments() - { - // Act - Action act = () => Execute.Assertion - .WithExpectation("Expectations are the {0} ", "root") - .ForCondition(false) - .FailWith("of disappointment"); - - // Assert - act.Should().Throw() - .WithMessage("Expectations are the \"root\" of disappointment"); - } - - [Fact] - public void Message_should_contain_object_as_context_if_identifier_can_not_be_resolved() - { - // Act - Action act = () => Execute.Assertion - .ForCondition(false) - .FailWith("Expected {context}"); - - // Assert - act.Should().Throw() - .WithMessage("Expected object"); - } - - [Fact] - public void Message_should_contain_the_fallback_value_as_context_if_identifier_can_not_be_resolved() - { - // Act - Action act = () => Execute.Assertion - .ForCondition(false) - .FailWith("Expected {context:fallback}"); - - // Assert - act.Should().Throw() - .WithMessage("Expected fallback"); - } - - [Fact] - public void Message_should_contain_the_default_identifier_as_context_if_identifier_can_not_be_resolved() - { - // Act - Action act = () => Execute.Assertion - .WithDefaultIdentifier("identifier") - .ForCondition(false) - .FailWith("Expected {context}"); - - // Assert - act.Should().Throw() - .WithMessage("Expected identifier"); - } - - [Fact] - public void Message_should_contain_the_reason_as_defined() - { - // Act - Action act = () => Execute.Assertion - .BecauseOf("because reasons") - .FailWith("Expected{reason}"); - - // Assert - act.Should().Throw() - .WithMessage("Expected because reasons"); - } - - [Fact] - public void Message_should_contain_the_reason_as_defined_with_arguments() - { - // Act - Action act = () => Execute.Assertion - .BecauseOf("because {0}", "reasons") - .FailWith("Expected{reason}"); - - // Assert - act.Should().Throw() - .WithMessage("Expected because reasons"); - } -} diff --git a/Tests/FluentAssertions.Specs/Execution/AssertionScope.ScopedFormatters.cs b/Tests/FluentAssertions.Specs/Execution/AssertionScopeSpecs.ScopedFormatters.cs similarity index 100% rename from Tests/FluentAssertions.Specs/Execution/AssertionScope.ScopedFormatters.cs rename to Tests/FluentAssertions.Specs/Execution/AssertionScopeSpecs.ScopedFormatters.cs diff --git a/Tests/FluentAssertions.Specs/Execution/AssertionScopeSpecs.cs b/Tests/FluentAssertions.Specs/Execution/AssertionScopeSpecs.cs index 931c352e65..440efa3152 100644 --- a/Tests/FluentAssertions.Specs/Execution/AssertionScopeSpecs.cs +++ b/Tests/FluentAssertions.Specs/Execution/AssertionScopeSpecs.cs @@ -24,7 +24,7 @@ public void When_disposed_it_should_throw_any_failures() // Arrange var scope = new AssertionScope(); - AssertionScope.Current.FailWith("Failure1"); + AssertionChain.GetOrCreate().FailWith("Failure1"); // Act Action act = scope.Dispose; @@ -46,7 +46,7 @@ public void When_disposed_it_should_throw_any_failures_and_properly_format_using // Arrange var scope = new AssertionScope(); - AssertionScope.Current.FailWith("Failure{0}", 1); + AssertionChain.GetOrCreate().FailWith("Failure{0}", 1); // Act Action act = scope.Dispose; @@ -69,7 +69,7 @@ public void When_lazy_version_is_not_disposed_it_should_not_execute_fail_reason_ var scope = new AssertionScope(); bool failReasonCalled = false; - AssertionScope.Current + AssertionChain.GetOrCreate() .ForCondition(true) .FailWith(() => { @@ -91,7 +91,9 @@ public void When_lazy_version_is_disposed_it_should_throw_any_failures_and_prope // Arrange var scope = new AssertionScope(); - AssertionScope.Current.FailWith(() => new FailReason("Failure{0}", 1)); + AssertionChain + .GetOrCreate() + .FailWith(() => new FailReason("Failure{0}", 1)); // Act Action act = scope.Dispose; @@ -113,14 +115,14 @@ public void When_multiple_scopes_are_nested_it_should_throw_all_failures_from_th // Arrange var scope = new AssertionScope(); - AssertionScope.Current.FailWith("Failure1"); + AssertionChain.GetOrCreate().FailWith("Failure1"); - using (var nestedScope = new AssertionScope()) + using (new AssertionScope()) { - nestedScope.FailWith("Failure2"); + AssertionChain.GetOrCreate().FailWith("Failure2"); using var deeplyNestedScope = new AssertionScope(); - deeplyNestedScope.FailWith("Failure3"); + AssertionChain.GetOrCreate().FailWith("Failure3"); } // Act @@ -143,14 +145,14 @@ public void When_a_nested_scope_is_discarded_its_failures_should_also_be_discard // Arrange var scope = new AssertionScope(); - AssertionScope.Current.FailWith("Failure1"); + AssertionChain.GetOrCreate().FailWith("Failure1"); - using (var nestedScope = new AssertionScope()) + using (new AssertionScope()) { - nestedScope.FailWith("Failure2"); + AssertionChain.GetOrCreate().FailWith("Failure2"); using var deeplyNestedScope = new AssertionScope(); - deeplyNestedScope.FailWith("Failure3"); + AssertionChain.GetOrCreate().FailWith("Failure3"); deeplyNestedScope.Discard(); } @@ -170,7 +172,7 @@ public void When_a_nested_scope_is_discarded_its_failures_should_also_be_discard } [Fact] - public async Task When_using_AssertionScope_across_thread_boundaries_it_should_work() + public async Task When_using_a_scope_across_thread_boundaries_it_should_work() { using var semaphore = new SemaphoreSlim(0, 1); await Task.WhenAll(SemaphoreYieldAndWait(semaphore), SemaphoreYieldAndRelease(semaphore)); @@ -196,10 +198,10 @@ private static async Task SemaphoreYieldAndRelease(SemaphoreSlim semaphore) public void When_custom_strategy_used_respect_its_behavior() { // Arrange - var scope = new AssertionScope(new FailWithStupidMessageAssertionStrategy()); + using var _ = new AssertionScope(new FailWithStupidMessageAssertionStrategy()); // Act - Action act = () => scope.FailWith("Failure 1"); + Action act = () => AssertionChain.GetOrCreate().FailWith("Failure 1"); // Assert act.Should().ThrowExactly() @@ -240,7 +242,7 @@ public void When_using_a_custom_strategy_it_should_include_failure_messages_of_a public void When_nested_scope_is_disposed_it_passes_reports_to_parent_scope() { // Arrange/Act - using var outerScope = new AssertionScope(); + var outerScope = new AssertionScope(); outerScope.AddReportable("outerReportable", "foo"); using (var innerScope = new AssertionScope()) @@ -248,8 +250,13 @@ public void When_nested_scope_is_disposed_it_passes_reports_to_parent_scope() innerScope.AddReportable("innerReportable", "bar"); } + AssertionChain.GetOrCreate().FailWith("whatever reason"); + + Action act = () => outerScope.Dispose(); + // Assert - outerScope.Get("innerReportable").Should().Be("bar"); + act.Should().Throw() + .Which.Message.Should().Match("Whatever reason*outerReportable*foo*innerReportable*bar*"); } [Fact] @@ -286,6 +293,25 @@ public void Formatting_options_passed_to_inner_assertion_scopes() .Which.Should().Contain("Maximum recursion depth of 1 was reached"); } + [Fact] + public void Multiple_named_scopes_will_prefix_the_caller_identifier() + { + // Arrange + var nonEmptyList = new List([1, 2]); + + // Act + Action act = () => + { + using var scope1 = new AssertionScope("Test1"); + using var scope2 = new AssertionScope("Test2"); + nonEmptyList.Should().BeEmpty(); + }; + + // Assert + act.Should().Throw() + .WithMessage("Expected Test1/Test2/nonEmptyList to be empty*"); + } + public class CustomAssertionStrategy : IAssertionStrategy { private readonly List failureMessages = []; @@ -326,12 +352,12 @@ public void HandleFailure(string message) internal class FailWithStupidMessageAssertionStrategy : IAssertionStrategy { - public IEnumerable FailureMessages => new string[0]; + public IEnumerable FailureMessages => Array.Empty(); public void HandleFailure(string message) => Services.ThrowException("Good luck with understanding what's going on!"); - public IEnumerable DiscardFailures() => new string[0]; + public IEnumerable DiscardFailures() => Array.Empty(); public void ThrowIfAny(IDictionary context) { diff --git a/Tests/FluentAssertions.Specs/Execution/CallerIdentifierSpecs.cs b/Tests/FluentAssertions.Specs/Execution/CallerIdentificationSpecs.cs similarity index 93% rename from Tests/FluentAssertions.Specs/Execution/CallerIdentifierSpecs.cs rename to Tests/FluentAssertions.Specs/Execution/CallerIdentificationSpecs.cs index c508ade7c9..235b19d327 100644 --- a/Tests/FluentAssertions.Specs/Execution/CallerIdentifierSpecs.cs +++ b/Tests/FluentAssertions.Specs/Execution/CallerIdentificationSpecs.cs @@ -14,36 +14,37 @@ // ReSharper disable RedundantStringInterpolation namespace FluentAssertions.Specs.Execution { - public class CallerIdentifierSpecs + public class CallerIdentificationSpecs { [Fact] - public void When_namespace_is_exactly_System_caller_should_be_unknown() + public void Types_in_the_system_namespace_are_excluded_from_identification() { // Act - Action act = () => SystemNamespaceClass.DetermineCallerIdentityInNamespace(); + Action act = () => SystemNamespaceClass.AssertAgainstFailure(); // Assert - act.Should().Throw().WithMessage("Expected function to be*"); + act.Should().Throw().WithMessage("Expected object*", + "because a subject in a system namespace should not be ignored by caller identification"); } [Fact] - public void When_namespace_is_nested_under_System_caller_should_be_unknown() + public void Types_in_a_namespace_nested_under_system_are_excluded_from_identification() { // Act - Action act = () => System.Data.NestedSystemNamespaceClass.DetermineCallerIdentityInNamespace(); + Action act = () => System.Data.NestedSystemNamespaceClass.AssertAgainstFailure(); // Assert - act.Should().Throw().WithMessage("Expected function to be*"); + act.Should().Throw().WithMessage("Expected object*"); } [Fact] - public void When_namespace_is_prefixed_with_System_caller_should_be_known() + public void Types_in_a_namespace_prefixed_with_system_are_excluded_from_identification() { // Act - Action act = () => SystemPrefixed.SystemPrefixedNamespaceClass.DetermineCallerIdentityInNamespace(); + Action act = () => SystemPrefixed.SystemPrefixedNamespaceClass.AssertAgainstFailure(); // Assert - act.Should().Throw().WithMessage("Expected actualCaller to be*"); + act.Should().Throw().WithMessage("Expected actualCaller*"); } [Fact] @@ -545,7 +546,7 @@ public void An_object_initializer_preceding_an_assertion_is_not_an_identifier() // Act Action act = () => new { Property = "blah" }.Should().BeNull(); - // Assert + // Assertl act.Should().Throw() .WithMessage("Expected object to be*"); } @@ -570,7 +571,7 @@ 5. Test } [CustomAssertion] - private string GetSubjectId() => AssertionScope.Current.CallerIdentity; + private string GetSubjectId() => AssertionChain.GetOrCreate().CallerIdentifier; } #pragma warning disable IDE0060, RCS1163 // Remove unused parameter @@ -605,10 +606,10 @@ namespace System { public static class SystemNamespaceClass { - public static void DetermineCallerIdentityInNamespace() + public static void AssertAgainstFailure() { - Func actualCaller = () => AssertionScope.Current.CallerIdentity; - actualCaller.Should().BeNull("we want this check to fail for the test"); + object actualCaller = null; + actualCaller.Should().NotBeNull("because we want this to fail and not return the name of the subject"); } } } @@ -617,10 +618,10 @@ namespace SystemPrefixed { public static class SystemPrefixedNamespaceClass { - public static void DetermineCallerIdentityInNamespace() + public static void AssertAgainstFailure() { - Func actualCaller = () => AssertionScope.Current.CallerIdentity; - actualCaller.Should().BeNull("we want this check to fail for the test"); + object actualCaller = null; + actualCaller.Should().NotBeNull("because we want this to fail and return the name of the subject"); } } } @@ -629,10 +630,10 @@ namespace System.Data { public static class NestedSystemNamespaceClass { - public static void DetermineCallerIdentityInNamespace() + public static void AssertAgainstFailure() { - Func actualCaller = () => AssertionScope.Current.CallerIdentity; - actualCaller.Should().BeNull("we want this check to fail for the test"); + object actualCaller = null; + actualCaller.Should().NotBeNull("because we want this to fail and not return the name of the subject"); } } } diff --git a/Tests/FluentAssertions.Specs/Execution/GivenSelectorSpecs.cs b/Tests/FluentAssertions.Specs/Execution/GivenSelectorSpecs.cs index d9808ab3f2..3209ccddbb 100644 --- a/Tests/FluentAssertions.Specs/Execution/GivenSelectorSpecs.cs +++ b/Tests/FluentAssertions.Specs/Execution/GivenSelectorSpecs.cs @@ -14,7 +14,7 @@ public void A_consecutive_subject_should_be_selected() string value = string.Empty; // Act - Execute.Assertion + AssertionChain.GetOrCreate() .ForCondition(true) .Given(() => "First selector") .Given(_ => value = "Second selector"); @@ -30,7 +30,7 @@ public void After_a_failed_condition_a_consecutive_subject_should_be_ignored() string value = string.Empty; // Act - Execute.Assertion + AssertionChain.GetOrCreate() .ForCondition(false) .Given(() => "First selector") .Given(_ => value = "Second selector"); @@ -43,7 +43,7 @@ public void After_a_failed_condition_a_consecutive_subject_should_be_ignored() public void A_consecutive_condition_should_be_evaluated() { // Act / Assert - Execute.Assertion + AssertionChain.GetOrCreate() .ForCondition(true) .Given(() => "Subject") .ForCondition(_ => true) @@ -54,7 +54,7 @@ public void A_consecutive_condition_should_be_evaluated() public void After_a_failed_condition_a_consecutive_condition_should_be_ignored() { // Act - Action act = () => Execute.Assertion + Action act = () => AssertionChain.GetOrCreate() .ForCondition(false) .Given(() => "Subject") .ForCondition(_ => throw new ApplicationException()) @@ -68,7 +68,7 @@ public void After_a_failed_condition_a_consecutive_condition_should_be_ignored() public void When_continuing_an_assertion_chain_it_fails_with_a_message_after_selecting_the_subject() { // Act - Action act = () => Execute.Assertion + Action act = () => AssertionChain.GetOrCreate() .ForCondition(true) .Given(() => "First") .FailWith("First selector") @@ -85,7 +85,7 @@ public void When_continuing_an_assertion_chain_it_fails_with_a_message_after_sel public void When_continuing_an_assertion_chain_it_fails_with_a_message_with_arguments_after_selecting_the_subject() { // Act - Action act = () => Execute.Assertion + Action act = () => AssertionChain.GetOrCreate() .ForCondition(true) .Given(() => "First") .FailWith("{0} selector", "First") @@ -102,7 +102,7 @@ public void When_continuing_an_assertion_chain_it_fails_with_a_message_with_argu public void When_continuing_an_assertion_chain_it_fails_with_a_message_with_argument_selectors_after_selecting_the_subject() { // Act - Action act = () => Execute.Assertion + Action act = () => AssertionChain.GetOrCreate() .ForCondition(true) .Given(() => "First") .FailWith("{0} selector", _ => "First") @@ -123,7 +123,7 @@ public void When_continuing_a_failed_assertion_chain_consecutive_failure_message { using var _ = new AssertionScope(); - Execute.Assertion + AssertionChain.GetOrCreate() .Given(() => "First") .FailWith("First selector") .Then @@ -143,7 +143,7 @@ public void When_continuing_a_failed_assertion_chain_consecutive_failure_message { using var _ = new AssertionScope(); - Execute.Assertion + AssertionChain.GetOrCreate() .Given(() => "First") .FailWith("{0} selector", "First") .Then @@ -163,7 +163,7 @@ public void When_continuing_a_failed_assertion_chain_consecutive_failure_message { using var _ = new AssertionScope(); - Execute.Assertion + AssertionChain.GetOrCreate() .Given(() => "First") .FailWith("{0} selector", _ => "First") .Then @@ -179,10 +179,13 @@ public void When_continuing_a_failed_assertion_chain_consecutive_failure_message public void The_failure_message_should_be_preceded_by_the_expectation_after_selecting_a_subject() { // Act - Action act = () => Execute.Assertion - .WithExpectation("Expectation ") - .Given(() => "Subject") - .FailWith("Failure"); + Action act = () => + { + AssertionChain.GetOrCreate() + .WithExpectation("Expectation ", chain => chain + .Given(() => "Subject") + .FailWith("Failure")); + }; // Assert act.Should().Throw() @@ -194,12 +197,14 @@ public void The_failure_message_should_not_be_preceded_by_the_expectation_after_selecting_a_subject_and_clearing_the_expectation() { // Act - Action act = () => Execute.Assertion - .WithExpectation("Expectation ") - .Given(() => "Subject") - .ClearExpectation() - .Then - .FailWith("Failure"); + Action act = () => + { + AssertionChain.GetOrCreate() + .WithExpectation("Expectation ", chain => chain + .Given(() => "Subject")) + .Then + .FailWith("Failure"); + }; // Assert act.Should().Throw() @@ -210,38 +215,15 @@ public void public void Clearing_the_expectation_does_not_affect_a_successful_assertion() { // Act - bool result = Execute.Assertion - .WithExpectation("Expectation ") - .Given(() => "Don't care") - .ForCondition(_ => true) - .FailWith("Should not fail") - .Then - .ClearExpectation(); - - // Assert - result.Should().BeTrue(); - } - - [Fact] - public void Clearing_the_expectation_does_not_affect_a_failed_assertion() - { - // Act - using var scope = new AssertionScope(); + var assertionChain = AssertionChain.GetOrCreate(); - bool result = Execute.Assertion - .WithExpectation("Expectation ") - .Given(() => "Don't care") - .ForCondition(_ => false) - .FailWith("Should fail") - .Then - .ClearExpectation(); - - scope.Discard(); + assertionChain + .WithExpectation("Expectation ", chain => chain + .Given(() => "Don't care") + .ForCondition(_ => true) + .FailWith("Should not fail")); // Assert - if (result) - { - throw new XunitException("the assertion failed and should return false"); - } + assertionChain.Succeeded.Should().BeTrue(); } } diff --git a/Tests/FluentAssertions.Specs/Formatting/FormatterSpecs.cs b/Tests/FluentAssertions.Specs/Formatting/FormatterSpecs.cs index 4d6853ee72..e38556fc56 100644 --- a/Tests/FluentAssertions.Specs/Formatting/FormatterSpecs.cs +++ b/Tests/FluentAssertions.Specs/Formatting/FormatterSpecs.cs @@ -211,32 +211,32 @@ public void When_the_object_is_a_generic_type_without_custom_string_representati act.Should().Throw() .WithMessage( """ - Expected stuff to be equal to + Expected stuff to be equal to { FluentAssertions.Specs.Formatting.FormatterSpecs+Stuff`1[[System.Int32*]] { - Children = {1, 2, 3, 4}, - Description = "Stuff_1", + Children = {1, 2, 3, 4}, + Description = "Stuff_1", StuffId = 1 - }, + }, FluentAssertions.Specs.Formatting.FormatterSpecs+Stuff`1[[System.Int32*]] { - Children = {1, 2, 3, 4}, - Description = "WRONG_DESCRIPTION", + Children = {1, 2, 3, 4}, + Description = "WRONG_DESCRIPTION", StuffId = 2 } - }, but + }, but { FluentAssertions.Specs.Formatting.FormatterSpecs+Stuff`1[[System.Int32*]] { - Children = {1, 2, 3, 4}, - Description = "Stuff_1", + Children = {1, 2, 3, 4}, + Description = "Stuff_1", StuffId = 1 - }, + }, FluentAssertions.Specs.Formatting.FormatterSpecs+Stuff`1[[System.Int32*]] { - Children = {1, 2, 3, 4}, - Description = "Stuff_2", + Children = {1, 2, 3, 4}, + Description = "Stuff_2", StuffId = 2 } } differs at index 1. @@ -254,13 +254,13 @@ public void When_the_object_is_a_user_defined_type_it_should_show_the_name_on_th // Assert act.Should().Throw() - .Which.Message.Should().Be( + .Which.Message.Should().Match( """ Expected stuff to be , but found FluentAssertions.Specs.Formatting.FormatterSpecs+StuffRecord { - RecordChildren = {10, 20, 30, 40}, - RecordDescription = "description", - RecordId = 42, + RecordChildren = {10, 20, 30, 40},* + RecordDescription = "description",* + RecordId = 42,* SingleChild = FluentAssertions.Specs.Formatting.FormatterSpecs+ChildRecord { ChildRecordId = 24 @@ -293,18 +293,18 @@ public void When_the_object_is_an_anonymous_type_it_should_show_the_properties_r act.Should().Throw() .Which.Message.Should().Be( """ - Expected stuff to be + Expected stuff to be { - Children = {10, 20, 30, 40}, - SingleChild = + Children = {10, 20, 30, 40}, + SingleChild = { ChildId = 4 } - }, but found + }, but found { - Children = {1, 2, 3, 4}, - Description = "absent", - SingleChild = + Children = {1, 2, 3, 4}, + Description = "absent", + SingleChild = { ChildId = 8 } @@ -345,13 +345,13 @@ public void When_the_object_is_a_list_of_anonymous_type_it_should_show_the_prope // Assert act.Should().Throw() - .Which.Message.Should().StartWith( + .Which.Message.Should().Match( """ - Expected stuff to be a collection with 1 item(s), but + Expected stuff to be a collection with 1 item(s), but* { { Description = "absent" - }, + },* { Description = "absent" } @@ -360,17 +360,17 @@ contains 1 item(s) more than { { - ComplexChildren = + ComplexChildren =* { { Property = "hello" - }, + },* { Property = "goodbye" } } } - }. + }.* """); } @@ -403,19 +403,19 @@ public void When_the_object_is_a_tuple_it_should_show_the_properties_recursively // Assert act.Should().Throw() - .Which.Message.Should().Be( + .Which.Message.Should().Match( """ - Expected stuff to be equal to + Expected stuff to be equal to* { - Item1 = 2, - Item2 = "WRONG_DESCRIPTION", + Item1 = 2,* + Item2 = "WRONG_DESCRIPTION",* Item3 = {4, 5, 6, 7} - }, but found + }, but found* { - Item1 = 1, - Item2 = "description", + Item1 = 1,* + Item2 = "description",* Item3 = {1, 2, 3, 4} - }. + }.* """); } @@ -439,16 +439,16 @@ public void When_the_object_is_a_record_it_should_show_the_properties_recursivel // Assert act.Should().Throw() - .Which.Message.Should().Be( + .Which.Message.Should().Match( """ - Expected stuff to be + Expected stuff to be* { RecordDescription = "WRONG_DESCRIPTION" }, but found FluentAssertions.Specs.Formatting.FormatterSpecs+StuffRecord { - RecordChildren = {4, 5, 6, 7}, - RecordDescription = "descriptive", - RecordId = 9, + RecordChildren = {4, 5, 6, 7},* + RecordDescription = "descriptive",* + RecordId = 9,* SingleChild = FluentAssertions.Specs.Formatting.FormatterSpecs+ChildRecord { ChildRecordId = 80 @@ -1193,7 +1193,7 @@ public void When_defining_a_custom_enumerable_value_formatter_it_should_respect_ str.Should().Match(Environment.NewLine + "{*FluentAssertions*FormatterSpecs+CustomClass" + Environment.NewLine + " {" + Environment.NewLine + - " IntProperty = 1, " + Environment.NewLine + + " IntProperty = 1," + Environment.NewLine + " StringProperty = " + Environment.NewLine + " },*…1 more…*}*"); } diff --git a/Tests/FluentAssertions.Specs/OccurrenceConstraintSpecs.cs b/Tests/FluentAssertions.Specs/OccurrenceConstraintSpecs.cs index 307099e068..7024a699dd 100644 --- a/Tests/FluentAssertions.Specs/OccurrenceConstraintSpecs.cs +++ b/Tests/FluentAssertions.Specs/OccurrenceConstraintSpecs.cs @@ -49,7 +49,7 @@ public class OccurrenceConstraintSpecs public void Occurrence_constraint_passes(OccurrenceConstraint constraint, int occurrences) { // Act / Assert - Execute.Assertion + AssertionChain.GetOrCreate() .ForConstraint(constraint, occurrences) .FailWith(""); } @@ -96,7 +96,7 @@ public void Occurrence_constraint_passes(OccurrenceConstraint constraint, int oc public void Occurrence_constraint_fails(OccurrenceConstraint constraint, int occurrences) { // Act - Action act = () => Execute.Assertion + Action act = () => AssertionChain.GetOrCreate() .ForConstraint(constraint, occurrences) .FailWith($"Expected occurrence to be {constraint.Mode} {constraint.ExpectedCount}, but it was {occurrences}"); diff --git a/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.cs index e83f7a0394..e57f61e5d8 100644 --- a/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using FluentAssertions.Execution; using FluentAssertions.Primitives; using Xunit; @@ -84,7 +85,7 @@ public bool Equals(SomeClass x, SomeClass y) internal class SomeClassAssertions : ObjectAssertions { public SomeClassAssertions(SomeClass value) - : base(value) + : base(value, AssertionChain.GetOrCreate()) { } } diff --git a/Tests/FluentAssertions.Specs/Primitives/ReferenceTypeAssertionsSpecs.cs b/Tests/FluentAssertions.Specs/Primitives/ReferenceTypeAssertionsSpecs.cs index 191ea2e41e..54f2c74587 100644 --- a/Tests/FluentAssertions.Specs/Primitives/ReferenceTypeAssertionsSpecs.cs +++ b/Tests/FluentAssertions.Specs/Primitives/ReferenceTypeAssertionsSpecs.cs @@ -43,10 +43,10 @@ public void When_two_different_objects_are_expected_to_be_the_same_it_should_fai .Should().Throw() .WithMessage( """ - Expected subject to refer to + Expected subject to refer to { UserName = "JohnDoe" - } because they are the same, but found + } because they are the same, but found { Name = "John Doe" }. @@ -61,7 +61,7 @@ public void When_a_derived_class_has_longer_formatting_than_the_base_class() act.Should().Throw() .WithMessage( """ - Expected subject to be empty, but found at least one item + Expected subject to be empty, but found at least one item { FluentAssertions.Specs.Primitives.Complex { @@ -454,7 +454,7 @@ public void Should_throw_a_helpful_error_when_accidentally_using_equals() public class ReferenceTypeAssertionsDummy : ReferenceTypeAssertions { public ReferenceTypeAssertionsDummy(object subject) - : base(subject) + : base(subject, AssertionChain.GetOrCreate()) { } diff --git a/Tests/FluentAssertions.Specs/Primitives/SimpleTimeSpanAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Primitives/SimpleTimeSpanAssertionSpecs.cs index 1e2a8a56d8..0cf1b14ae1 100644 --- a/Tests/FluentAssertions.Specs/Primitives/SimpleTimeSpanAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Primitives/SimpleTimeSpanAssertionSpecs.cs @@ -1,4 +1,5 @@ using System; +using FluentAssertions.Execution; using FluentAssertions.Extensions; using FluentAssertions.Primitives; using Xunit; @@ -165,7 +166,7 @@ public void When_asserting_value_to_be_equal_to_different_value_it_should_fail() public void A_null_is_not_equal_to_another_value() { // Arrange - var subject = new SimpleTimeSpanAssertions(null); + var subject = new SimpleTimeSpanAssertions(null, AssertionChain.GetOrCreate()); TimeSpan expected = 2.Seconds(); // Act diff --git a/Tests/FluentAssertions.Specs/Primitives/StringComparisonSpecs.cs b/Tests/FluentAssertions.Specs/Primitives/StringComparisonSpecs.cs index 5ece34c048..abbc42f90b 100644 --- a/Tests/FluentAssertions.Specs/Primitives/StringComparisonSpecs.cs +++ b/Tests/FluentAssertions.Specs/Primitives/StringComparisonSpecs.cs @@ -264,15 +264,11 @@ public void When_comparing_strings_for_not_containing_any_equals_it_should_ignor [CulturedFact("tr-TR")] public void When_formatting_reason_arguments_it_should_ignore_culture() { - // Arrange - var scope = new AssertionScope(); - // Act - scope.BecauseOf("{0}", 1.234) - .FailWith("{reason}"); + Action act = () => 1.Should().Be(2, "{0}", 1.234); // Assert - scope.Invoking(e => e.Dispose()).Should().Throw() + act.Should().Throw() .WithMessage("*1.234*", "it should always use . as decimal separator"); } diff --git a/Tests/FluentAssertions.Specs/Specialized/AssemblyAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Specialized/AssemblyAssertionSpecs.cs index 50ec7d4f01..debf5e2c06 100644 --- a/Tests/FluentAssertions.Specs/Specialized/AssemblyAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Specialized/AssemblyAssertionSpecs.cs @@ -165,7 +165,7 @@ public void When_an_assembly_is_referencing_null_it_should_throw() public class DefineType { [Fact] - public void When_an_assembly_defines_a_type_and_Should_DefineType_is_asserted_it_should_succeed() + public void Can_find_a_specific_type() { // Arrange var thisAssembly = GetType().Assembly; @@ -179,6 +179,22 @@ public void When_an_assembly_defines_a_type_and_Should_DefineType_is_asserted_it act.Should().NotThrow(); } + [Fact] + public void Can_continue_assertions_on_the_found_type() + { + // Arrange + var thisAssembly = GetType().Assembly; + + // Act + Action act = () => thisAssembly + .Should().DefineType(GetType().Namespace, typeof(WellKnownClassWithAttribute).Name) + .Which.Should().BeDecoratedWith(); + + // Assert + act.Should().Throw() + .WithMessage("Expected*WellKnownClassWithAttribute*decorated*SerializableAttribute*not found."); + } + [Fact] public void When_an_assembly_does_not_define_a_type_and_Should_DefineType_is_asserted_it_should_fail_with_a_useful_message() diff --git a/Tests/FluentAssertions.Specs/Specialized/ExecutionTimeAssertionsSpecs.cs b/Tests/FluentAssertions.Specs/Specialized/ExecutionTimeAssertionsSpecs.cs index d1a591ba61..d0f632baa4 100644 --- a/Tests/FluentAssertions.Specs/Specialized/ExecutionTimeAssertionsSpecs.cs +++ b/Tests/FluentAssertions.Specs/Specialized/ExecutionTimeAssertionsSpecs.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using FluentAssertions.Execution; using FluentAssertions.Extensions; using FluentAssertions.Specialized; using Xunit; @@ -580,7 +581,7 @@ public void When_asserting_on_null_execution_it_should_throw() ExecutionTime executionTime = null; // Act - Func act = () => new ExecutionTimeAssertions(executionTime); + Func act = () => new ExecutionTimeAssertions(executionTime, AssertionChain.GetOrCreate()); // Assert act.Should().Throw() diff --git a/Tests/FluentAssertions.Specs/Specialized/TaskCompletionSourceAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Specialized/TaskCompletionSourceAssertionSpecs.cs index 087c9b2d51..2d5a53994f 100644 --- a/Tests/FluentAssertions.Specs/Specialized/TaskCompletionSourceAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Specialized/TaskCompletionSourceAssertionSpecs.cs @@ -201,7 +201,7 @@ public async Task When_it_completes_in_time_and_async_result_is_not_expected_it_ // Assert await action.Should().ThrowAsync() - .WithMessage("Expected testSubject to be 42, but found 99."); + .WithMessage("Expected testSubject.Result to be 42, but found 99."); } [Fact] diff --git a/Tests/FluentAssertions.Specs/Specialized/TaskOfTAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Specialized/TaskOfTAssertionSpecs.cs index df75d2f527..5ea7158732 100644 --- a/Tests/FluentAssertions.Specs/Specialized/TaskOfTAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Specialized/TaskOfTAssertionSpecs.cs @@ -76,6 +76,29 @@ public async Task When_task_completes_fast_it_should_succeed() await action.Should().NotThrowAsync(); } + [Fact] + public async Task Can_chain_another_assertion_on_the_result_of_the_async_operation() + { + // Arrange + var timer = new FakeClock(); + var taskFactory = new TaskCompletionSource(); + + // Act + Func action = async () => + { + Func> func = () => taskFactory.Task; + + (await func.Should(timer).CompleteWithinAsync(100.Milliseconds())) + .Which.Should().Be(42); + }; + + taskFactory.SetResult(42); + timer.Complete(); + + // Assert + await action.Should().NotThrowAsync(); + } + [Fact] public async Task When_task_completes_and_result_is_not_expected_it_should_fail() { @@ -120,7 +143,7 @@ public async Task When_task_completes_and_async_result_is_not_expected_it_should timer.Complete(); // Assert - await action.Should().ThrowAsync().WithMessage("Expected funcSubject to be 42, but found 99."); + await action.Should().ThrowAsync().WithMessage("Expected funcSubject.Result to be 42, but found 99."); } [Fact] @@ -185,7 +208,7 @@ public async Task When_task_does_not_complete_the_result_extension_does_not_hang // Assert var assertionTask = action.Should().ThrowAsync() - .WithMessage("Expected*to complete within 100ms.*Expected*to be 2, but found 0."); + .WithMessage("Expected*to complete within 100ms."); await Awaiting(() => assertionTask).Should().CompleteWithinAsync(200.Seconds()); } @@ -261,6 +284,29 @@ public async Task When_task_does_not_throw_it_should_succeed() await action.Should().NotThrowAsync(); } + [Fact] + public async Task Can_chain_another_assertion_on_the_result_of_the_async_operation() + { + // Arrange + var timer = new FakeClock(); + var taskFactory = new TaskCompletionSource(); + + // Act + Func action = async () => + { + Func> func = () => taskFactory.Task; + + (await func.Should(timer).NotThrowAsync()) + .Which.Should().Be(10); + }; + + taskFactory.SetResult(20); + timer.Complete(); + + // Assert + await action.Should().ThrowAsync().WithMessage("*func.Result to be 10*"); + } + [Fact] public async Task When_task_throws_it_should_fail() { diff --git a/Tests/FluentAssertions.Specs/Types/MethodBaseAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Types/MethodBaseAssertionSpecs.cs index 84a2bf8dc2..997f09fb29 100644 --- a/Tests/FluentAssertions.Specs/Types/MethodBaseAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Types/MethodBaseAssertionSpecs.cs @@ -389,7 +389,7 @@ public void When_asserting_a_private_member_is_protected_it_throws_with_a_useful // Assert act.Should().Throw() .WithMessage( - "Expected method PrivateMethod to be Protected because we want to test the error message, but it is " + + "Expected method TestClass.PrivateMethod to be Protected because we want to test the error message, but it is " + "Private."); } @@ -426,7 +426,7 @@ public void When_asserting_a_protected_member_is_public_it_throws_with_a_useful_ // Assert act.Should().Throw() .WithMessage( - "Expected method set_ProtectedSetProperty to be Public because we want to test the error message, but it" + + "Expected method TestClass.set_ProtectedSetProperty to be Public because we want to test the error message, but it" + " is Protected."); } @@ -463,7 +463,7 @@ public void When_asserting_a_public_member_is_internal_it_throws_with_a_useful_m // Assert act.Should().Throw() .WithMessage( - "Expected method get_PublicGetProperty to be Internal because we want to test the error message, but it" + + "Expected method TestClass.get_PublicGetProperty to be Internal because we want to test the error message, but it" + " is Public."); } @@ -495,7 +495,7 @@ public void When_asserting_an_internal_member_is_protectedInternal_it_throws_wit // Assert act.Should().Throw() .WithMessage( - "Expected method InternalMethod to be ProtectedInternal because we want to test the error message, but" + + "Expected method TestClass.InternalMethod to be ProtectedInternal because we want to test the error message, but" + " it is Internal."); } @@ -526,7 +526,7 @@ public void When_asserting_a_protected_internal_member_is_private_it_throws_with // Assert act.Should().Throw() .WithMessage( - "Expected method ProtectedInternalMethod to be Private because we want to test the error message, but it is " + + "Expected method TestClass.ProtectedInternalMethod to be Private because we want to test the error message, but it is " + "ProtectedInternal."); } @@ -604,7 +604,7 @@ public void When_asserting_a_private_member_is_not_private_it_throws_with_a_usef // Assert act.Should().Throw() - .WithMessage("Expected method PrivateMethod not to be Private*because we want to test the error message*"); + .WithMessage("Expected method TestClass.PrivateMethod not to be Private*because we want to test the error message*"); } [Fact] @@ -638,7 +638,7 @@ public void When_asserting_a_protected_member_is_not_protected_it_throws_with_a_ // Assert act.Should().Throw() .WithMessage( - "Expected method set_ProtectedSetProperty not to be Protected*because we want to test the error message*"); + "Expected method TestClass.set_ProtectedSetProperty not to be Protected*because we want to test the error message*"); } [Fact] @@ -686,7 +686,7 @@ public void When_asserting_a_public_member_is_not_public_it_throws_with_a_useful // Assert act.Should().Throw() - .WithMessage("Expected method get_PublicGetProperty not to be Public*because we want to test the error message*"); + .WithMessage("Expected method TestClass.get_PublicGetProperty not to be Public*because we want to test the error message*"); } [Fact] @@ -716,7 +716,7 @@ public void When_asserting_an_internal_member_is_not_internal_it_throws_with_a_u // Assert act.Should().Throw() - .WithMessage("Expected method InternalMethod not to be Internal*because we want to test the error message*"); + .WithMessage("Expected method TestClass.InternalMethod not to be Internal*because we want to test the error message*"); } [Fact] @@ -747,7 +747,7 @@ public void When_asserting_a_protected_internal_member_is_not_protected_internal // Assert act.Should().Throw() .WithMessage( - "Expected method ProtectedInternalMethod not to be ProtectedInternal*because we want to test the error message*"); + "Expected method TestClass.ProtectedInternalMethod not to be ProtectedInternal*because we want to test the error message*"); } [Fact] diff --git a/Tests/FluentAssertions.Specs/Types/PropertyInfoAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Types/PropertyInfoAssertionSpecs.cs index e4479a345e..5d247992e8 100644 --- a/Tests/FluentAssertions.Specs/Types/PropertyInfoAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Types/PropertyInfoAssertionSpecs.cs @@ -19,8 +19,7 @@ public void When_asserting_that_a_property_is_virtual_and_it_is_then_it_succeeds PropertyInfo propertyInfo = typeof(ClassWithAllPropertiesVirtual).GetRuntimeProperty("PublicVirtualProperty"); // Act - Action act = () => - propertyInfo.Should().BeVirtual(); + Action act = () => propertyInfo.Should().BeVirtual(); // Assert act.Should().NotThrow(); @@ -34,13 +33,12 @@ public void When_asserting_that_a_property_is_virtual_and_it_is_not_then_it_fail typeof(ClassWithNonVirtualPublicProperties).GetRuntimeProperty("PublicNonVirtualProperty"); // Act - Action act = () => - propertyInfo.Should().BeVirtual("we want to test the error {0}", "message"); + Action act = () => propertyInfo.Should().BeVirtual("we want to test the error {0}", "message"); // Assert act.Should().Throw() .WithMessage( - "Expected property String FluentAssertions*ClassWithNonVirtualPublicProperties.PublicNonVirtualProperty" + + "Expected property ClassWithNonVirtualPublicProperties.PublicNonVirtualProperty" + " to be virtual because we want to test the error message," + " but it is not."); } @@ -173,8 +171,8 @@ public void When_asserting_a_property_is_decorated_with_attribute_and_it_is_not_ // Assert act.Should().Throw() - .WithMessage("Expected property String " + - "FluentAssertions*ClassWithPropertiesThatAreNotDecoratedWithDummyAttribute.PublicProperty to be decorated with " + + .WithMessage("Expected property " + + "ClassWithPropertiesThatAreNotDecoratedWithDummyAttribute.PublicProperty to be decorated with " + "FluentAssertions*DummyPropertyAttribute because we want to test the error message, but that attribute was not found."); } @@ -194,7 +192,7 @@ public void // Assert act.Should().Throw() .WithMessage( - "Expected property String FluentAssertions*ClassWithPropertiesThatAreNotDecoratedWithDummyAttribute.PublicProperty to be decorated with " + + "Expected property ClassWithPropertiesThatAreNotDecoratedWithDummyAttribute.PublicProperty to be decorated with " + "FluentAssertions*DummyPropertyAttribute because we want to test the error message," + " but that attribute was not found."); } @@ -295,7 +293,7 @@ public void When_asserting_a_readonly_property_is_writable_it_fails_with_useful_ action .Should().Throw() .WithMessage( - "Expected propertyInfo ReadOnlyProperty to have a setter because we want to test the error message."); + "Expected property ClassWithProperties.ReadOnlyProperty to have a setter because we want to test the error message."); } [Fact] @@ -381,7 +379,7 @@ public void When_asserting_a_writeonly_property_is_readable_it_fails_with_useful action .Should().Throw() .WithMessage( - "Expected property WriteOnlyProperty to have a getter because we want to test the error message, but it does not."); + "Expected property *WriteOnlyProperty to have a getter because we want to test the error message, but it does not."); } [Fact] @@ -427,8 +425,8 @@ public void When_asserting_a_readwrite_property_is_not_writable_it_fails_with_us // Assert action .Should().Throw() - .WithMessage( - "Expected propertyInfo ReadWriteProperty not to have a setter because we want to test the error message."); + .WithMessage("Did not expect property ClassWithReadOnlyProperties.ReadWriteProperty" + + " to have a setter because we want to test the error message."); } [Fact] @@ -443,8 +441,8 @@ public void When_asserting_a_writeonly_property_is_not_writable_it_fails_with_us // Assert action .Should().Throw() - .WithMessage( - "Expected propertyInfo WriteOnlyProperty not to have a setter because we want to test the error message."); + .WithMessage("Did not expect property ClassWithProperties.WriteOnlyProperty" + + " to have a setter because we want to test the error message."); } [Fact] @@ -459,7 +457,7 @@ public void When_subject_is_null_not_be_writable_should_fail() // Assert act.Should().Throw() - .WithMessage("Expected property not to have a setter *failure message*, but propertyInfo is ."); + .WithMessage("Expected propertyInfo not to have a setter *failure message*, but it is ."); } } @@ -477,8 +475,8 @@ public void When_asserting_a_readonly_property_is_not_readable_it_fails_with_use // Assert action .Should().Throw() - .WithMessage( - "Expected propertyInfo ReadOnlyProperty not to have a getter because we want to test the error message."); + .WithMessage("Did not expect property ClassWithReadOnlyProperties.ReadOnlyProperty " + + "to have a getter because we want to test the error message."); } [Fact] @@ -493,8 +491,8 @@ public void When_asserting_a_readwrite_property_is_not_readable_it_fails_with_us // Assert action .Should().Throw() - .WithMessage( - "Expected propertyInfo ReadWriteProperty not to have a getter because we want to test the error message."); + .WithMessage("Did not expect property ClassWithReadOnlyProperties.ReadWriteProperty " + + "to have a getter because we want to test the error message."); } [Fact] @@ -553,8 +551,8 @@ public void When_asserting_a_private_read_public_write_property_is_public_readab // Assert action.Should().Throw() - .WithMessage( - "Expected method get_WritePrivateReadProperty to be Public because we want to test the error message, but it is Private."); + .WithMessage("Expected getter of property ClassWithProperties.WritePrivateReadProperty " + + "to be Public because we want to test the error message, but it is Private*"); } [Fact] @@ -572,7 +570,7 @@ public void Do_not_the_check_access_modifier_when_the_property_is_not_readable() // Assert action.Should().Throw() - .WithMessage("Expected property WriteOnlyProperty to have a getter, but it does not."); + .WithMessage("Expected property ClassWithProperties.WriteOnlyProperty to have a getter, but it does not."); } [Fact] @@ -587,7 +585,7 @@ public void When_subject_is_null_be_readable_with_accessmodifier_should_fail() // Assert act.Should().Throw() - .WithMessage("Expected property to be Public *failure message*, but propertyInfo is ."); + .WithMessage("Expected propertyInfo to be Public *failure message*, but it is ."); } [Fact] @@ -633,8 +631,8 @@ public void When_asserting_a_private_write_public_read_property_is_public_writab // Assert action.Should().Throw() - .WithMessage( - "Expected method set_ReadPrivateWriteProperty to be Public because we want to test the error message, but it is Private."); + .WithMessage("Expected setter of property ClassWithProperties.ReadPrivateWriteProperty " + + "to be Public because we want to test the error message, but it is Private."); } [Fact] @@ -652,7 +650,7 @@ public void Do_not_the_check_access_modifier_when_the_property_is_not_writable() // Assert action.Should().Throw() - .WithMessage("Expected propertyInfo ReadOnlyProperty to have a setter."); + .WithMessage("Expected property ClassWithProperties.ReadOnlyProperty to have a setter."); } [Fact] @@ -667,7 +665,7 @@ public void When_subject_is_null_be_writable_with_accessmodifier_should_fail() // Assert act.Should().Throw() - .WithMessage("Expected property to be Public *failure message*, but propertyInfo is ."); + .WithMessage("Expected propertyInfo to be Public *failure message*, but it is ."); } [Fact] @@ -712,8 +710,8 @@ public void When_asserting_a_String_property_returns_an_Int32_it_throw_with_a_us // Assert action.Should().Throw() - .WithMessage("Expected Type of property StringProperty to be System.Int32 because we want to test the error " + - "message, but it is System.String."); + .WithMessage("Expected type of property ClassWithProperties.StringProperty" + + " to be System.Int32 because we want to test the error message, but it is System.String."); } [Fact] @@ -773,7 +771,7 @@ public void When_asserting_a_String_property_returnsOfT_an_Int32_it_throw_with_a // Assert action.Should().Throw() - .WithMessage("Expected Type of property StringProperty to be System.Int32 because we want to test the error " + + .WithMessage("Expected type of property ClassWithProperties.StringProperty to be System.Int32 because we want to test the error " + "message, but it is System.String."); } } @@ -804,8 +802,8 @@ public void When_asserting_a_String_property_does_not_return_a_String_it_throw_w // Assert action.Should().Throw() - .WithMessage("Expected Type of property StringProperty not to be*String*because we want to test the error " + - "message, but it is."); + .WithMessage("Expected type of property ClassWithProperties.StringProperty" + + " not to be System.String because we want to test the error message, but it is."); } [Fact] @@ -842,7 +840,7 @@ public void When_asserting_property_type_is_not_null_it_should_throw() public class NotReturnOfT { [Fact] - public void When_asserting_a_String_property_does_not_returnOfT_an_Int32_it_succeeds() + public void Can_validate_the_type_of_a_property() { // Arrange PropertyInfo propertyInfo = typeof(ClassWithProperties).GetRuntimeProperty("StringProperty"); @@ -855,7 +853,7 @@ public void When_asserting_a_String_property_does_not_returnOfT_an_Int32_it_succ } [Fact] - public void When_asserting_a_String_property_does_not_returnsOfT_a_String_it_throw_with_a_useful_message() + public void When_asserting_a_string_property_does_not_returnsOfT_a_String_it_throw_with_a_useful_message() { // Arrange PropertyInfo propertyInfo = typeof(ClassWithProperties).GetRuntimeProperty("StringProperty"); @@ -865,7 +863,7 @@ public void When_asserting_a_String_property_does_not_returnsOfT_a_String_it_thr // Assert action.Should().Throw() - .WithMessage("Expected Type of property StringProperty not to be*String*because we want to test the error " + + .WithMessage("Expected type of property ClassWithProperties.StringProperty not to be*String*because we want to test the error " + "message, but it is."); } } diff --git a/Tests/FluentAssertions.Specs/Types/PropertyInfoSelectorAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Types/PropertyInfoSelectorAssertionSpecs.cs index cf3dca9c6c..650b56fd00 100644 --- a/Tests/FluentAssertions.Specs/Types/PropertyInfoSelectorAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Types/PropertyInfoSelectorAssertionSpecs.cs @@ -1,4 +1,5 @@ using System; +using FluentAssertions.Execution; using FluentAssertions.Types; using Xunit; using Xunit.Sdk; @@ -53,9 +54,9 @@ public void .WithMessage("Expected all selected properties" + " to be virtual because we want to test the error message," + " but the following properties are not virtual:*" + - "String FluentAssertions*ClassWithNonVirtualPublicProperties.PublicNonVirtualProperty*" + - "String FluentAssertions*ClassWithNonVirtualPublicProperties.InternalNonVirtualProperty*" + - "String FluentAssertions*ClassWithNonVirtualPublicProperties.ProtectedNonVirtualProperty"); + "ClassWithNonVirtualPublicProperties.PublicNonVirtualProperty*" + + "ClassWithNonVirtualPublicProperties.InternalNonVirtualProperty*" + + "ClassWithNonVirtualPublicProperties.ProtectedNonVirtualProperty"); } } @@ -159,9 +160,9 @@ public void .WithMessage("Expected all selected properties to be decorated with" + " FluentAssertions*DummyPropertyAttribute because we want to test the error message," + " but the following properties are not:*" + - "String FluentAssertions*ClassWithPropertiesThatAreNotDecoratedWithDummyAttribute.PublicProperty*" + - "String FluentAssertions*ClassWithPropertiesThatAreNotDecoratedWithDummyAttribute.InternalProperty*" + - "String FluentAssertions*ClassWithPropertiesThatAreNotDecoratedWithDummyAttribute.ProtectedProperty"); + "ClassWithPropertiesThatAreNotDecoratedWithDummyAttribute.PublicProperty*" + + "ClassWithPropertiesThatAreNotDecoratedWithDummyAttribute.InternalProperty*" + + "ClassWithPropertiesThatAreNotDecoratedWithDummyAttribute.ProtectedProperty"); } } @@ -236,8 +237,8 @@ public void When_a_read_only_property_is_expected_to_be_writable_it_should_throw .WithMessage( "Expected all selected properties to have a setter because we want to test the error message, " + "but the following properties do not:*" + - "String FluentAssertions*ClassWithReadOnlyProperties.ReadOnlyProperty*" + - "String FluentAssertions*ClassWithReadOnlyProperties.ReadOnlyProperty2"); + "ClassWithReadOnlyProperties.ReadOnlyProperty*" + + "ClassWithReadOnlyProperties.ReadOnlyProperty2"); } [Fact] @@ -271,8 +272,8 @@ public void When_a_writable_property_is_expected_to_be_read_only_it_should_throw .WithMessage( "Expected selected properties to not have a setter because we want to test the error message, " + "but the following properties do:*" + - "String FluentAssertions*ClassWithWritableProperties.ReadWriteProperty*" + - "String FluentAssertions*ClassWithWritableProperties.ReadWriteProperty2"); + "ClassWithWritableProperties.ReadWriteProperty*" + + "ClassWithWritableProperties.ReadWriteProperty2"); } [Fact] @@ -295,7 +296,7 @@ public class Miscellaneous public void When_accidentally_using_equals_it_should_throw_a_helpful_error() { // Arrange - var someObject = new PropertyInfoSelectorAssertions(); + var someObject = new PropertyInfoSelectorAssertions(AssertionChain.GetOrCreate()); // Act var action = () => someObject.Equals(null); diff --git a/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveExplicitConversionOperator.cs b/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveExplicitConversionOperator.cs index 5eedb90a94..89fa79cdd3 100644 --- a/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveExplicitConversionOperator.cs +++ b/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveExplicitConversionOperator.cs @@ -1,4 +1,5 @@ using System; +using FluentAssertions.Common; using Xunit; using Xunit.Sdk; @@ -19,15 +20,30 @@ public void When_asserting_a_type_has_an_explicit_conversion_operator_which_it_d var sourceType = typeof(TypeWithConversionOperators); var targetType = typeof(byte); + // Act / Assert + type.Should() + .HaveExplicitConversionOperator(sourceType, targetType) + .Which.Should() + .NotBeNull(); + } + + [Fact] + public void Can_chain_an_additional_assertion_on_the_implicit_conversion_operator() + { + // Arrange + var type = typeof(TypeWithConversionOperators); + var sourceType = typeof(TypeWithConversionOperators); + var targetType = typeof(byte); + // Act - Action act = () => - type.Should() - .HaveExplicitConversionOperator(sourceType, targetType) - .Which.Should() - .NotBeNull(); + Action act = () => type + .Should().HaveExplicitConversionOperator(sourceType, targetType) + .Which.Should().HaveAccessModifier(CSharpAccessModifier.Private); // Assert - act.Should().NotThrow(); + act.Should().Throw() + .WithMessage( + "Expected method explicit operator Byte(TypeWithConversionOperators) to be Private, but it is Public."); } [Fact] diff --git a/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveImplicitConversionOperator.cs b/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveImplicitConversionOperator.cs index d5ef12601b..f3194619e9 100644 --- a/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveImplicitConversionOperator.cs +++ b/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveImplicitConversionOperator.cs @@ -1,4 +1,5 @@ using System; +using FluentAssertions.Common; using Xunit; using Xunit.Sdk; @@ -118,6 +119,24 @@ public void When_asserting_a_type_has_an_implicit_conversion_operatorOfT_which_i act.Should().NotThrow(); } + [Fact] + public void Can_chain_an_additional_assertion_on_the_implicit_conversion_operator() + { + // Arrange + var type = typeof(TypeWithConversionOperators); + + // Act + Action act = () => + type.Should() + .HaveImplicitConversionOperator() + .Which.Should() + .HaveAccessModifier(CSharpAccessModifier.Internal); + + // Assert + act.Should().Throw() + .WithMessage("Expected method implicit operator Int32(TypeWithConversionOperators) to be Internal, but it is Public."); + } + [Fact] public void When_asserting_a_type_has_an_implicit_conversion_operatorOfT_which_it_does_not_it_fails() { diff --git a/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveProperty.cs b/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveProperty.cs index fe7f4cbb6a..543f439ff3 100644 --- a/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveProperty.cs +++ b/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveProperty.cs @@ -30,6 +30,21 @@ public void When_asserting_a_type_has_a_property_which_it_does_then_it_succeeds( act.Should().NotThrow(); } + [Fact] + public void The_name_of_the_property_is_passed_to_the_chained_assertion() + { + // Arrange + var type = typeof(ClassWithMembers); + + // Act + Action act = () => type + .Should().HaveProperty(typeof(string), "PrivateWriteProtectedReadProperty") + .Which.Should().NotBeWritable(); + + // Assert + act.Should().Throw("Expected property PrivateWriteProtectedReadProperty not to have a setter."); + } + [Fact] public void When_asserting_a_type_has_a_property_which_it_does_not_it_fails() { @@ -42,7 +57,7 @@ public void When_asserting_a_type_has_a_property_which_it_does_not_it_fails() // Assert act.Should().Throw() - .WithMessage("Expected String *ClassWithNoMembers.PublicProperty to exist *failure message*, but it does not."); + .WithMessage("Expected ClassWithNoMembers to have a property PublicProperty of type String because we want to test the failure message, but it does not."); } [Fact] @@ -58,9 +73,8 @@ public void When_asserting_a_type_has_a_property_which_it_has_with_a_different_t // Assert act.Should().Throw() - .WithMessage( - "Expected String *.ClassWithMembers.PrivateWriteProtectedReadProperty to be of type System.Int32 " + - "*failure message*, but it is not."); + .WithMessage("Expected property PrivateWriteProtectedReadProperty " + + "to be of type System.Int32 because we want to test the failure message, but it is not."); } [Fact] @@ -75,7 +89,7 @@ public void When_subject_is_null_have_property_should_fail() // Assert act.Should().Throw() - .WithMessage("Expected String type.PublicProperty to exist *failure message*, but type is ."); + .WithMessage("Cannot determine if a type has a property named PublicProperty if the type is ."); } [Fact] @@ -184,8 +198,7 @@ public void When_asserting_a_type_does_not_have_a_property_which_it_does_not_it_ var type = typeof(ClassWithoutMembers); // Act - Action act = () => - type.Should().NotHaveProperty("Property"); + Action act = () => type.Should().NotHaveProperty("Property"); // Assert act.Should().NotThrow(); @@ -203,9 +216,7 @@ public void When_asserting_a_type_does_not_have_a_property_which_it_does_it_fail // Assert act.Should().Throw() - .WithMessage( - "Expected String *.ClassWithMembers.PrivateWriteProtectedReadProperty to not exist *failure message*" + - ", but it does."); + .WithMessage("Did not expect ClassWithMembers to have a property PrivateWriteProtectedReadProperty because we want to test the failure message, but it does."); } [Fact] @@ -220,7 +231,7 @@ public void When_subject_is_null_not_have_property_should_fail() // Assert act.Should().Throw() - .WithMessage("Expected type.PublicProperty to not exist *failure message*, but type is ."); + .WithMessage("Cannot determine if a type has an unexpected property named PublicProperty if the type is ."); } [Fact] diff --git a/Tests/FluentAssertions.Specs/Xml/XDocumentAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Xml/XDocumentAssertionSpecs.cs index f09023363c..0c7270b0b0 100644 --- a/Tests/FluentAssertions.Specs/Xml/XDocumentAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Xml/XDocumentAssertionSpecs.cs @@ -990,6 +990,25 @@ public void When_asserting_document_has_root_element_with_ns_but_it_does_not_it_ "Expected theDocument to have root element \"{http://www.example.com/2012/test}unknown\", but found ."); } + [Fact] + public void Can_chain_another_assertion_on_the_root_element() + { + // Arrange + var theDocument = XDocument.Parse( + """ + + + + """); + + // Act + Action act = () => theDocument.Should().HaveRoot("parent").Which.Should().HaveElement("unknownChild"); + + // Assert + act.Should().Throw().WithMessage( + "Expected theDocument/parent to have child element*unknownChild*"); + } + [Fact] public void When_asserting_document_has_root_element_with_ns_but_it_does_not_it_should_fail_with_descriptive_message() { @@ -1036,6 +1055,24 @@ public void When_document_has_the_expected_child_element_it_should_not_throw_and element.Should().BeSameAs(document.Element("parent").Element("child")); } + [Fact] + public void Can_chain_another_assertion_on_the_root_element() + { + // Arrange + var document = XDocument.Parse( + """ + + + + """); + + // Act + var act = () => document.Should().HaveElement("child").Which.Should().HaveElement("grandChild"); + + // Assert + act.Should().Throw().WithMessage("Expected document/child to have child element*grandChild*"); + } + [Fact] public void When_asserting_document_has_root_with_child_element_but_it_does_not_it_should_fail() { diff --git a/docs/_pages/exceptions.md b/docs/_pages/exceptions.md index 48570bd9d0..d2447987fa 100644 --- a/docs/_pages/exceptions.md +++ b/docs/_pages/exceptions.md @@ -28,7 +28,7 @@ act.Should().Throw() Notice that the example also verifies that the exception has a particular inner exception with a specific message. In fact, you can even check the individual properties of the exception instance using the And property. ```csharp -Action act = () => subject.Foo(null); +var act = () => subject.Foo(null); act.Should().Throw() .WithParameterName("message"); @@ -37,7 +37,7 @@ act.Should().Throw() An alternative syntax for doing the same is by chaining one or more calls to the `Where()` method: ```csharp -Action act = () => subject.Foo(null); +var act = () => subject.Foo(null); act.Should().Throw().Where(e => e.Message.StartsWith("did")); ``` @@ -53,7 +53,7 @@ The following wildcard specifiers are permitted in the pattern: | ? (question mark) | Exactly one character in that position. | ```csharp -Action act = () => subject.Foo(null); +var act = () => subject.Foo(null); act .Should().Throw() .WithMessage("?did*"); @@ -62,7 +62,7 @@ act On the other hand, you may want to verify that no exceptions were thrown. ```csharp -Action act = () => subject.Foo("Hello"); +var act = () => subject.Foo("Hello"); act.Should().NotThrow(); ``` @@ -71,14 +71,14 @@ We know that a unit test will fail anyhow if an exception was thrown, but this s If you want to verify that a specific exception is not thrown, and want to ignore others, you can do that using an overload: ```csharp -Action act = () => subject.Foo("Hello"); +var act = () => subject.Foo("Hello"); act.Should().NotThrow(); ``` Sometimes you may want to retry an assertion until it either succeeds or a given time elapses. For instance, you could be testing a network service which should become available after a certain time, say, 10 seconds: ```csharp -Action act = () => service.IsReady().Should().BeTrue(); +var act = () => service.IsReady().Should().BeTrue(); act.Should().NotThrowAfter(10.Seconds(), 100.Milliseconds()); ``` @@ -107,7 +107,7 @@ However, if you really want to be explicit about the exact type of exception, yo Talking about the `async` keyword, you can also verify that an asynchronously executed method throws or doesn't throw an exception: ```csharp -Func act = () => asyncObject.ThrowAsync(); +var act = () => asyncObject.ThrowAsync(); await act.Should().ThrowAsync(); await act.Should().NotThrowAsync(); ``` @@ -115,7 +115,7 @@ await act.Should().NotThrowAsync(); Alternatively, you can use the `Awaiting` method like this: ```csharp -Func act = asyncObject.Awaiting(x => x.ThrowAsync()); +var act = asyncObject.Awaiting(x => x.ThrowAsync()); await act.Should().ThrowAsync(); ``` @@ -125,7 +125,7 @@ As for synchronous methods, you can also check that an asynchronously executed m ```csharp Stopwatch watch = Stopwatch.StartNew(); -Func act = async () => +var act = async () => { if (watch.ElapsedMilliseconds <= 1000) { diff --git a/docs/_pages/executiontime.md b/docs/_pages/executiontime.md index 41551dfc3f..2a398b2bf2 100644 --- a/docs/_pages/executiontime.md +++ b/docs/_pages/executiontime.md @@ -34,7 +34,7 @@ subject.ExecutionTimeOf(s => s.ExpensiveMethod()).Should().BeLessThanOrEqualTo(5 Alternatively, to verify the execution time of an arbitrary action, use this syntax: ```csharp -Action someAction = () => Thread.Sleep(100); +var someAction = () => Thread.Sleep(100); someAction.ExecutionTime().Should().BeLessThanOrEqualTo(200.Milliseconds()); ``` @@ -52,7 +52,7 @@ someAction.ExecutionTime().Should().BeCloseTo(150.Milliseconds(), 50.Millisecond If you're dealing with a `Task`, you can also assert that it completed within a specified period of time or not completed: ```csharp -Func someAsyncWork = () => SomethingReturningATask(); +var someAsyncWork = () => SomethingReturningATask(); await someAsyncWork.Should().CompleteWithinAsync(100.Milliseconds()); await someAsyncWork.Should().NotCompleteWithinAsync(100.Milliseconds()); await someAsyncWork.Should().ThrowWithinAsync(100.Milliseconds()); diff --git a/docs/_pages/extensibility.md b/docs/_pages/extensibility.md index 52f8692230..e92aa32d5c 100644 --- a/docs/_pages/extensibility.md +++ b/docs/_pages/extensibility.md @@ -18,20 +18,27 @@ public static class DirectoryInfoExtensions { public static DirectoryInfoAssertions Should(this DirectoryInfo instance) { - return new DirectoryInfoAssertions(instance); + return new DirectoryInfoAssertions(instance, AssertionChain.GetOrCreate()); } } ``` -It's the returned assertions class that provides the actual assertion methods. You don't need to, but if you sub-class the self-referencing generic class `ReferenceTypeAssertions`, you'll already get methods like `BeNull`, `BeSameAs` and `Match` for free. Assuming you did, and you provided an override of the `Identifier` property so that these methods know that we're dealing with a directory, it's time for the the next step. Let's add an extension that allows you to assert that the involved directory contains a particular file. +It's the returned assertions class that provides the actual assertion methods. The `AssertionChain.GetOrCreate()` method is used to start a potential chain of assertions that can be used to support chained constructs using `Which`. + +You don't need to, but if you sub-class the self-referencing generic class `ReferenceTypeAssertions`, you'll already get methods like `BeNull`, `BeSameAs` and `Match` for free. Assuming you did, and you provided an override of the `Identifier` property so that these methods know that we're dealing with a directory, it's time for the the next step. + +Let's add an extension that allows you to assert that the involved directory contains a particular file. ```csharp public class DirectoryInfoAssertions : ReferenceTypeAssertions { - public DirectoryInfoAssertions(DirectoryInfo instance) + private AssertionChain chain; + + public DirectoryInfoAssertions(DirectoryInfo instance, AssertionChain chain) : base(instance) { + this.chain = chain; } protected override string Identifier => "directory"; @@ -40,7 +47,7 @@ public class DirectoryInfoAssertions : public AndConstraint ContainFile( string filename, string because = "", params object[] becauseArgs) { - Execute.Assertion + chain .BecauseOf(because, becauseArgs) .ForCondition(!string.IsNullOrEmpty(filename)) .FailWith("You can't assert a file exist if you don't pass a proper name") @@ -59,13 +66,46 @@ This is quite an elaborate example which shows some of the more advanced extensi * The `Subject` property is used to give the base-class extensions access to the current `DirectoryInfo` object. * `[CustomAssertion]` attribute enables correct subject identification, allowing Fluent Assertions to render more meaningful test fail messages -* `Execute.Assertion` is the point of entrance into the internal fluent assertion API. +* The variable `chain` of type `AssertionChain` is the point of entrance into the internal fluent API. * The optional `because` parameter can contain `string.Format` style place holders which will be filled using the values provided to the `becauseArgs`. They can be used by the caller to provide a reason why the assertion should succeed. By passing those into the `BecauseOf` method, you can refer to the expanded result using the `{reason}` tag in the `FailWith` method. -* The `Then` property is just there to chain multiple assertions together. You can have more than one. +* The `Then` property is just there to chain multiple assertions together. You can have more than one. However, if the first assertion fails, then the successive assertions will not be evaluated anymore. * The `Given` method allows you to perform a lazily evaluated projection on whatever you want. In this case I use it to get a list of `FileInfo` objects from the current directory. Notice that the resulting expression is not evaluated until the final call to `FailWith`. * `FailWith` will evaluate the condition, and raise the appropriate exception specific for the detected test framework. It again can contain numbered placeholders as well as the special named placeholders `{context}` and `{reason}`. I'll explain the former in a minute, but suffice to say that it displays the text "directory" at that point. The remainder of the place holders will be filled by applying the appropriate type-specific value formatter for the provided arguments. If those arguments involve a non-primitive type such as a collection or complex type, the formatters will use recursion to always use the appropriate formatter. * Since we used the `Given` construct to create a projection, the parameters of `FailWith` are formed by a `params` array of `Func` that give you access to the projection (such as the `FileInfo[]` in this particular case). But normally, it's just a `params array` of objects. +## Supporting chaining +Imagine that you want to have your custom `ContainFile` assertion support chaining additional assertions like this: + +```csharp +directoryInfo.Should().ContainFile("trace.dmp").Which.Path.Should().BeginWith("c:\\files"); +``` + +You can support that by replacing the `AndConstraint` your assertion returns by a generic `AndWhichConstraint` and passing the `FileInfo` instance that was found by `ContainFile`, as well as the `AssertionChain` to its constructor. `AndWhichConstraint` has a property called `Which` which (no pun intended) will provide access to the `FileInfo` instance. + +Then, if the first part succeeds, but the second part fails, you'll get something like: + + Expected directoryInfo.Path to begin with "c:\files", but it didn't. + +Notice the `directoryInfo` part that the caller identification logic picks up? You can overrule that by using `AssertionChain.OverrideCallerIdentifier` before you pass it to the constructor of `AndWhichConstraint`. Alternatively, you can also use `WithCallerPostFix`. Check out `GenericCollectionAssertions.Contain` for an example of that. + +## Reusing expectations +A common situation is that you want to execute more than one assertion in a chain, where the first part of the failure message is the same. Instead of repeating this first part, you can use `WithExpectation` to reuse that message in all nested calls to `FailWith` like this: + +```csharp +assertionChain + .BecauseOf(because, becauseArgs) + .WithExpectation("Expected the month part of {context:the date} to be {0}{reason}", expected, chain => chain + .ForCondition(Subject.HasValue) + .FailWith(", but found a DateOnly.") + .Then + .ForCondition(Subject.Value.Month == expected) + .FailWith(", but found {0}.", Subject.Value.Month)); +``` + +If the first assertion fails, you'll get something like this: + + Expected the month part of the date to be January, but found a DateOnly. + ## Scoping your extensions Now what if you want to reuse your newly created extension method within some other extension method? For instance, what if you want to apply that assertion on a collection of directories? Wouldn't it be cool if you can tell your extension method about the current directory? This is where the `AssertionScope` comes into place. @@ -86,12 +126,14 @@ public AndConstraint ContainFileInAllSubdirectories( } ``` -Whatever you pass into its constructor will be used to overwrite the default `{context}` passed to `FailWith`. +Whatever you pass into its constructor will be used to overwrite the default `{context}` passed to `FailWith`: ```csharp .FailWith("Expected {context:directory} to contain {0}{reason}, but found {1}.", ``` +If you don't, `{context}` will be replaced with the variable on which `Should()` is invoked, or, if caller identification failed somehow, with `"directory"`. + So in this case, our nicely created `ContainFile` extension method will display the directory that it used to assert that file existed. You can do a lot more advanced stuff if you want. Just check out the code that is used by the structural equivalency API. ## Rendering objects with beauty @@ -171,7 +213,7 @@ class EnumerableCustomClassFormatter : EnumerableValueFormatter } ``` -### Scoped `IValueFormatter`s +## Scoped `IValueFormatter`s You can add a custom value formatter inside a scope to selectively customize formatting of an object based on the context of the test. To achieve that, you can do following: diff --git a/docs/_pages/introduction.md b/docs/_pages/introduction.md index 178df091bd..0bdd3eb5b3 100644 --- a/docs/_pages/introduction.md +++ b/docs/_pages/introduction.md @@ -36,31 +36,39 @@ numbers.Should().OnlyContain(n => n > 0); numbers.Should().HaveCount(4, "because we thought we put four items in the collection"); ``` -The nice thing about the second failing example is that it will throw an exception with the message +The nice thing about the second failing example is that it will throw an exception with the message (and notice that it uses the name of the variable `numbers`): -> "Expected numbers to contain 4 item(s) because we thought we put four items in the collection, but found 3." + Expected numbers to contain 4 item(s) because we thought we put four items in the collection, but found 3. -To verify that a particular business rule is enforced using exceptions. +To verify that a particular business rule is enforced using exceptions: ```csharp var recipe = new RecipeBuilder() - .With(new IngredientBuilder().For("Milk").WithQuantity(200, Unit.Milliliters)) - .Build(); -Action action = () => recipe.AddIngredient("Milk", 100, Unit.Spoon); + .With(new IngredientBuilder().For("Milk").WithQuantity(200, Unit.Milliliters)) + .Build(); + +var action = () => recipe.AddIngredient("Milk", 100, Unit.Spoon); + action - .Should().Throw() - .WithMessage("*change the unit of an existing ingredient*") - .And.Violations.Should().Contain(BusinessRule.CannotChangeIngredientQuantity); + .Should().Throw() + .WithMessage("*change the unit of an existing ingredient*") + .And.Violations.Should().Contain(BusinessRule.CannotChangeIngredientQuantity); ``` One neat feature is the ability to chain a specific assertion on top of an assertion that acts on a collection or graph of objects. ```csharp dictionary.Should().ContainValue(myClass).Which.SomeProperty.Should().BeGreaterThan(0); + someObject.Should().BeOfType().Which.Message.Should().Be("Other Message"); + xDocument.Should().HaveElement("child").Which.Should().BeOfType().And.HaveAttribute("attr", "1"); ``` +If the first one fails, you will get a message like: + + Expected dictionary["key"].SomeProperty to be greater than 0, but found -2 + This chaining can make your unit tests a lot easier to read. ## Global Configurations @@ -108,7 +116,7 @@ username.Should().Be("jonas"); This will throw a test framework-specific exception with the following message: -`Expected username to be "jonas" with a length of 5, but "dennis" has a length of 6, differs near "den" (index 0).` + Expected username to be "jonas" with a length of 5, but "dennis" has a length of 6, differs near "den" (index 0). The way this works is that Fluent Assertions will try to traverse the current stack trace to find the line and column numbers as well as the full path to the source file. Since it needs the debug symbols for that, this will require you to compile the unit test projects in debug mode, even on your build servers. Also, this does not work with [`PathMap`](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-options/advanced#pathmap) for unit test projects as it assumes that source files are present on the path returned from [`StackFrame.GetFileName()`](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.stackframe.getfilename). @@ -145,7 +153,7 @@ Alternatively, you can add the `[assembly:CustomAssertionsAssembly]` attribute t ## Assertion Scopes -You can batch multiple assertions into an `AssertionScope` so that FluentAssertions throws one exception at the end of the scope with all failures. +You can batch multiple assertions into an `AssertionScope` so that Fluent Assertions throws one exception at the end of the scope with all failures. E.g. @@ -162,13 +170,39 @@ The above will batch the two failures, and throw an exception at the point of di E.g. Exception thrown at point of dispose contains: ```text -Expected value to be 10, but found 5. +Expected value to be 100, but found 95 (difference of -5). Expected string to be "Expected" with a length of 8, but "Actual" has a length of 6, differs near "Act" (index 0). +``` + +You can even nest two of those scopes and give them suitable names: + +```csharp +using var outerScope = new AssertionScope("Test1"); +using var innerScope = new AssertionScope("Test2"); +nonEmptyList.Should().BeEmpty(); +``` - at........ +This will give you: + + Expected Test1/Test2/nonEmptyList to be empty, but found at least one item {1}. + + +In more sophisticated scenarios, you might want to intercept the assertion raised within an `AssertionScope` and prevent it from throwing an exception. + +```csharp +using (var scope = new AssertionScope()) +{ + 5.Should().Be(10); + // other assertion left out for brevity... + + // Collect all the failure messages that occurred up to this point + string[] failures = scope.Discard(); + + // The closing brace will not throw any exceptions anymore +} ``` -For more information take a look at the [AssertionScopeSpecs.cs](https://github.com/fluentassertions/fluentassertions/blob/master/Tests/FluentAssertions.Specs/Execution/AssertionScopeSpecs.cs) in Unit Tests. +For more examples take a look at the [AssertionScopeSpecs.cs](https://github.com/fluentassertions/fluentassertions/blob/master/Tests/FluentAssertions.Specs/Execution/AssertionScopeSpecs.cs) in Unit Tests. ### Scoped `IValueFormatter`s diff --git a/docs/_pages/releases.md b/docs/_pages/releases.md index 26bc190692..2700b36e91 100644 --- a/docs/_pages/releases.md +++ b/docs/_pages/releases.md @@ -28,6 +28,8 @@ sidebar: * You can mark all assertions in an assembly as custom assertions using the `[CustomAssertionsAssembly]` attribute - [#2389](https://github.com/fluentassertions/fluentassertions/pull/2389) * Improve `BeEmpty()` and `BeNullOrEmpty()` performance for `IEnumerable`, by materializing only the first item - [#2530](https://github.com/fluentassertions/fluentassertions/pull/2530) * All `Should()` methods on reference types are now annotated with the `[NotNull]` attribute for a better Fluent Assertions experience when nullable reference types are enabled - [#2380](https://github.com/fluentassertions/fluentassertions/pull/2380) +* All assertions that support chaining using the `.Which` construct will now amend the caller identifier - [2539](https://github.com/fluentassertions/pull/2539) +* Introduced a `MethodInfoFormatter` and improved the `PropertyInfoFormatter` - [2539](https://github.com/fluentassertions/pull/2539) ### Fixes * Fixed formatting error when checking nullable `DateTimeOffset` with @@ -53,6 +55,7 @@ sidebar: * Fixed `RaisePropertyChangeFor` to return a filtered list of events - [#2677](https://github.com/fluentassertions/fluentassertions/pull/2677) ### Breaking Changes (for users) +* Replaced `Execute.Assertion` with `AssertionChain.GetOrCreate()` - [2539](https://github.com/fluentassertions/pull/2539) * Moved support for `DataSet`, `DataTable`, `DataRow` and `DataColumn` into a new package `FluentAssertions.DataSet` - [#2267](https://github.com/fluentassertions/fluentassertions/pull/2267) * Removed obsolete `...OrEqualTo` methods - [#2269](https://github.com/fluentassertions/fluentassertions/pull/2269) * `GenericCollectionAssertions` @@ -83,6 +86,9 @@ sidebar: * The semantics of `BeLowerCased`/`BeUpperCased` have been changed to align with the behavior of `ToLower`/`ToUpper` - [#2660](https://github.com/fluentassertions/fluentassertions/pull/2660) ### Breaking Changes (for extensions) +* Introduced a new `AssertionChain` class which `GetOrCreate` is used to replace `Execute.Assertion` when writing custom assertions - [2539](https://github.com/fluentassertions/fluentassertions/pull/2539) +* Removed `ClearExpectation` and made `WithExpectation` a scoped operation that takes a nested `AssertionChain` - [2539](https://github.com/fluentassertions/fluentassertions/pull/2539) +* `AndWhichConstraint` now takes an `AssertionChain` instance and a postfix to improve the caller identifier in chained constructs - [2539](https://github.com/fluentassertions/pull/2539) * Add `ForConstraint` to `IAssertionsScope` to support chaining `.ForConstraint()` after `.Then` - [#2324](https://github.com/fluentassertions/fluentassertions/pull/2324) * Refactored `AsyncFunctionAssertions` into real base class - [#2359](https://github.com/fluentassertions/fluentassertions/pull/2359) * Its constructor has been made `protected`. @@ -93,6 +99,7 @@ sidebar: * Renamed `IEquivalencyValidator` to `IValidateChildNodeEquivalency`, and its method `RecursivelyAssertEquality` to `AssertEquivalencyOf` - [#2745](https://github.com/fluentassertions/fluentassertions/pull/2745) * Made `Node`, `Property` and `Field` internal - [#2745](https://github.com/fluentassertions/fluentassertions/pull/2745) + ## 6.12.0 ### What's new diff --git a/docs/_pages/upgradingtov7.md b/docs/_pages/upgradingtov7.md index b6e69bb169..1248cfdd36 100644 --- a/docs/_pages/upgradingtov7.md +++ b/docs/_pages/upgradingtov7.md @@ -10,3 +10,142 @@ sidebar: ## Dropping support for .NET Core 2.x and .NET Core 3.x. As of v7, we've decided to no longer directly target .NET Core. All versions of .NET Core, including .NET 5 are [already out of support by Microsoft](https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core). But we still support .NET Standard 2.0 and 2.1, so Fluent Assertions will still work with those deprecated frameworks. + +## From `Execute.Assertion` to `AssertionChain` + +We've made quite some changes to the API that you use to build your own assertions. For example, the `BooleanAssertions` class was instantiated in `AssertionExtensions` like this: + +```csharp +public static BooleanAssertions Should(this bool actualValue) +{ + return new BooleanAssertions(actualValue); +} +``` + +On turn, the `BooleanAssertions` would expose a `BeTrue` method + +```csharp +public AndConstraint BeTrue(string because = "", params object[] becauseArgs) +{ + Execute.Assertion + .ForCondition(Subject == true) + .BecauseOf(because, becauseArgs) + .FailWith("Expected {context:boolean} to be {0}{reason}, but found {1}.", true, Subject); + + return new AndConstraint((TAssertions)this); +} +``` + +To be able to support chaining multiple assertions where the chained assertion can extend the caller identification, we introduced an `AssertionChain` class which instance can flow from one assertion to another. Because of that, the above code changed to: + +```csharp +public static BooleanAssertions Should(this bool actualValue) +{ + return new BooleanAssertions(actualValue, AssertionChain.GetOrCreate()); +} +``` + +Notice how we pass the call to `AssertionChain.GetOrCreate` to the assertions class? By default `GetOrCreate` will create a new instance of `AssertionChain`. But if the previous assertion method uses `AssertionChain.ReuseOnce`, `GetOrCreate` will return that reused instance only once. + +The new `BeTrue` now looks like: + +```csharp +public AndConstraint BeTrue(string because = "", params object[] becauseArgs) +{ + assertionChain + .ForCondition(Subject == true) + .BecauseOf(because, becauseArgs) + .FailWith("Expected {context:boolean} to be {0}{reason}, but found {1}.", true, Subject); + + return new AndConstraint((TAssertions)this); +} +``` + +So all of the methods to build an assertion that used to live on the `AssertionScope` (which is what `Execute.Assertion` returned), have now moved to `AssertionChain`. This is great because it allows the second assertion to get access to the state of the first assertion. For instance, if the first assertion failed, any successive attempts to call `FailWith` will not do anything. + +## No more `ClearExpectation` + +If you wanted to reuse the first part of the failure message across multiple failures, you could use the following construct (example taken from `TimeOnlyAssertions.BeCloseTo`): + +```csharp +Execute.Assertion + .BecauseOf(because, becauseArgs) + .WithExpectation("Expected {context:the time} to be within {0} from {1}{reason}, ", precision, nearbyTime) + .ForCondition(Subject is not null) + .FailWith("but found .") + .Then + .ForCondition(Subject?.IsCloseTo(nearbyTime, precision) == true) + .FailWith("but {0} was off by {1}.", Subject, difference) + .Then + .ClearExpectation(); +``` + +When using an `using new AssertionScope()` construct to wrap multiple assertions, all assertions executed within that scope will reuse the same instance of `AssertionScope` (which is what `Execute.Assertion` returned). The problem was that you had to explicitly call `ClearExpectation` to prevent the failure message passed to `WithExpectation` to leak into the next assertion within that scope. People often forgot that. + +We solved this in v7, by making `WithExpectation` use a nested construct. This is what it now looks like: + +```csharp +assertionChain + .BecauseOf(because, becauseArgs) + .WithExpectation("Expected {context:the time} to be within {0} from {1}{reason}, ", precision, nearbyTime, chain => chain + .ForCondition(Subject is not null) + .FailWith("but found .") + .Then + .ForCondition(Subject?.IsCloseTo(nearbyTime, precision) == true) + .FailWith("but {0} was off by {1}.", Subject, difference) + ); +``` + +All the code nested within the `WithExpectation` will share the first part of the failure message, and there's no need to explicitly clear it anymore. + +## Amending caller identifiers with `WithPostfix` + +Imagine the following chained assertion + +```csharp +var element = XElement.Parse( + """ + + + + + """); + + +element.Should().HaveElement("child", AtLeast.Twice()).Which.Should().HaveCount(1); +``` + +Prior to version 7, if the `HaveElement` assertion succeeded, but the `NotBeNull` failed, you would get the following exception: + + Expected element to contain 1 item(s), but found 3: {, , }. + +Now, in v7, it'll will return the following: + + Expected element/child to contain 1 item(s), but found 3: {, , }. + +This is possible because `HaveElement` will pass the `AssertionChain` through `ReuseOnce` to the succeeding `HaveCount()` _and_ amend the automatically detected caller identifier `element` (the part on which the first `Should` is invoked) with `"/child"` using `WithCallerPostfix`. Since this is a common thing in v7, the `AndWhichConstraint` has a constructor that does most of that automatically. + +This is what `HaveElement` looks like (with some details left out): + +```csharp +public AndWhichConstraint HaveElement(XName expected, + string because = "", params object[] becauseArgs) +{ + xElement = Subject!.Element(expected); + + assertionChain + .ForCondition(xElement is not null) + .BecauseOf(because, becauseArgs) + .FailWith( + "Expected {context:subject} to have child element {0}{reason}, but no such child element was found.", + expected.ToString().EscapePlaceholders()); + + return new AndWhichConstraint(this, xElement, assertionChain, "/" + expected); +} +``` + +Notice the last argument to the `AndWhichConstraint` constructor. + +## Other breaking changes + +Check out the [release notes](releases.md) for other changes that might affect the upgrade to v7. \ No newline at end of file diff --git a/qodana.yaml b/qodana.yaml index 3f248f5a33..c4d518d378 100644 --- a/qodana.yaml +++ b/qodana.yaml @@ -33,6 +33,7 @@ exclude: - name: SwitchExpressionHandlesSomeKnownEnumValuesWithExceptionInDefault - name: UnusedMemberInSuper.Global - name: ArrangeAccessorOwnerBody + - name: ParameterHidesMember - name: CollectionNeverUpdated.Local paths: - Tests