diff --git a/.editorconfig b/.editorconfig index dde9336287..09ea95a062 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,9 +1,10 @@ root = true # EditorConfig is awesome: http://EditorConfig.org -# top-most EditorConfig file +#################################################################### +## Global settings +#################################################################### -# Global settings [*] end_of_line = crlf insert_final_newline = true @@ -110,13 +111,11 @@ csharp_space_after_dot = false csharp_space_after_keywords_in_control_flow_statements = true csharp_space_after_semicolon_in_for_statement = true csharp_space_around_binary_operators = before_and_after -csharp_space_around_declaration_statements = do_not_ignore csharp_space_before_colon_in_inheritance_clause = true csharp_space_before_comma = false csharp_space_before_dot = false csharp_space_before_open_square_brackets = false csharp_space_before_semicolon_in_for_statement = false -csharp_space_between_attribute_sections = false csharp_space_between_empty_square_brackets = false csharp_space_between_method_call_empty_parameter_list_parentheses = false csharp_space_between_method_call_name_and_opening_parenthesis = false @@ -127,279 +126,283 @@ csharp_space_between_method_declaration_parameter_list_parentheses = false csharp_space_between_parentheses = false csharp_space_between_square_brackets = false -# FxCop Analyzers -dotnet_diagnostic.CA1030.severity = none -dotnet_diagnostic.CA1034.severity = none -dotnet_diagnostic.CA1062.severity = suggestion -dotnet_code_quality.CA1062.exclude_extension_method_this_parameter = true -dotnet_code_quality.exclude_extension_method_this_parameter = true -dotnet_code_quality.null_check_validation_methods = ThrowIfArgumentIsNull -# CA1031: Do not catch general exception types -dotnet_diagnostic.CA1031.severity = none -# CA1303: Do not pass literals as localized parameters -dotnet_diagnostic.CA1303.severity = none -# CA1304: Specify CultureInfo -dotnet_diagnostic.CA1304.severity = error -# CA1307: Specify StringComparison for clarity -dotnet_diagnostic.CA1307.severity = error -# CA1308: Normalize strings to uppercase -dotnet_diagnostic.CA1308.severity = error -# CA1309: Use ordinal StringComparison -dotnet_diagnostic.CA1309.severity = error -# CA1724: Type names should not match namespaces -dotnet_diagnostic.CA1724.severity = none -# CA1819: Properties should not return arrays -dotnet_diagnostic.CA1819.severity = none -# CA1851: Possible multiple enumerations of IEnumerable collection. Related to GH-issue #2000 -dotnet_diagnostic.CA1851.severity = suggestion -# CA1860: Avoid using 'Enumerable.Any()' extension method -dotnet_diagnostic.CA1860.severity = warning -# CA1861: Avoid constant arrays as arguments -dotnet_diagnostic.CA1861.severity = none -# CA2007: Do not directly await a Task -dotnet_diagnostic.CA2007.severity = none -# CA2225: Operator overloads have named alternates -dotnet_diagnostic.CA2225.severity = none -# CA3075: Insecure DTD Processing -dotnet_diagnostic.CA3075.severity = none -# CA5369: Use XmlReader for Deserialize -dotnet_diagnostic.CA5369.severity = none +#################################################################### +## Rider/ReSharper Settings +#################################################################### -# Banned API Analyzers -dotnet_diagnostic.RS0030.severity = error +# Keep enough blank lines between code blocks to make it readable. +resharper_blank_lines_after_multiline_statements = 1 +resharper_blank_lines_around_single_line_auto_property = 1 +resharper_blank_lines_before_case = 1 -# IDE0004: Remove unnecessary cast -dotnet_diagnostic.IDE0004.severity = error +# Purpose: Don't put elements of an array on new lines, unless the length exceeds the maximum line length. +resharper_wrap_array_initializer_style = chop_always -# IDE0005: Remove unnecessary usings/imports -dotnet_diagnostic.IDE0005.severity = suggestion +# Purpose: Always put elements of an object initializer on new lines, unless the length exceeds the maximum line length. +resharper_wrap_object_and_collection_initializer_style = chop_always -# IDE0051: Remove unused private members (no reads or writes) -dotnet_diagnostic.IDE0051.severity = error +resharper_unused_parameter_local_highlighting = error +resharper_not_accessed_positional_property_global_highlighting = error -# IDE0052: Remove unread private members (writes but no reads) -dotnet_diagnostic.IDE0052.severity = error +# Do not remove redundant else blocks +resharper_redundant_if_else_block_highlighting = none -# IDE0055: Fix formatting -dotnet_diagnostic.IDE0055.severity = error +#################################################################### +## Roslyn Analyzers and Code Fixes +#################################################################### -# CS1574: XML comment on 'construct' has syntactically incorrect cref attribute 'name' -dotnet_diagnostic.CS1574.severity = error +# Purpose: Field contains the word 'and', which suggests doing multiple thing +# Rationale: We do not want to enforce this rule in our codebase. +dotnet_diagnostic.AV1115.severity = suggestion -# StyleCop -# SA1028: Code should not contain trailing whitespace -dotnet_diagnostic.SA1028.severity = suggestion -# SA1101: Prefix local calls with this -dotnet_diagnostic.SA1101.severity = none -# SA1116: The parameters should begin on the line after the declaration, whenever the parameter span across multiple lines -dotnet_diagnostic.SA1116.severity = none -# SA1117: The parameters should all be placed on the same line or each parameter should be placed on its own line. -dotnet_diagnostic.SA1117.severity = none -# SA1200: Using directive should appear within a namespace declaration -dotnet_diagnostic.SA1200.severity = none -# SA1124: Do not use regions -dotnet_diagnostic.SA1124.severity = none -# SA1201: A property should not follow a method -dotnet_diagnostic.SA1201.severity = none -# SA1202: 'public' members should come before 'private' members -dotnet_diagnostic.SA1202.severity = none -# SA1204: Static members should appear before non-static members -dotnet_diagnostic.SA1204.severity = none -# SA1312: variable should begin with lower-case letter -dotnet_diagnostic.SA1312.severity = suggestion # re-enable if using statements can be discarded -# SA1313: parameter should begin with lower-case letter -dotnet_diagnostic.SA1313.severity = suggestion # re-enable when parameters discards are available -# SA1316: Tuple element names should use correct casing -dotnet_diagnostic.SA1316.severity = none -# SA1402: File may only contain a single type. Handled better by AV1507 -dotnet_diagnostic.SA1402.severity = none -# SA1404: Code analysis suppression should have justification -dotnet_diagnostic.SA1404.severity = none -# SA1413: Use trailing comma in multi-line initializers -dotnet_diagnostic.SA1413.severity = none -# SA1600: Elements should be documented -dotnet_diagnostic.SA1600.severity = suggestion -# SA1602: Enumeration items should be documented -dotnet_diagnostic.SA1602.severity = suggestion -# SA1611: The documentation for parameter is missing -dotnet_diagnostic.SA1611.severity = suggestion -# SA1612: The parameter documentation for parameter should be at position -dotnet_diagnostic.SA1612.severity = suggestion -# SA1614: Element parameter documentation should have text -dotnet_diagnostic.SA1614.severity = suggestion -# SA1615: Element return value should be documented -dotnet_diagnostic.SA1615.severity = none -# SA1616: Element return value documentation should have text -dotnet_diagnostic.SA1616.severity = suggestion -# SA1618: The documentation for type parameter is missing -dotnet_diagnostic.SA1618.severity = suggestion -# SA1623: The property's documentation summary text should begin with: 'Gets' -dotnet_diagnostic.SA1623.severity = none -# SA1629: Documentation text should end with a period -dotnet_diagnostic.SA1629.severity = none -# SA1633: The file header is missing or not located at the top of the file -dotnet_diagnostic.SA1633.severity = none -# SA1642: Constructor summary documentation should begin with standard text -dotnet_diagnostic.SA1642.severity = suggestion +# Purpose: Return interfaces to unchangeable collections +# Rationale: We do not want to enforce this rule in our codebase. +dotnet_diagnostic.AV1130.severity = suggestion -# CSharpGuidelines -dotnet_diagnostic.AV1561.max_parameter_count = 5 -# AV1008: Class should be non-static or its name should be suffixed with Extensions -dotnet_diagnostic.AV1008.severity = none -# AV1010: Type hides inherited member -dotnet_diagnostic.AV1010.severity = none -# AV1115: Member or local function contains the word 'and', which suggests doing multiple things -dotnet_diagnostic.AV1115.severity = suggestion -# AV1130: Return type in signature for Type should be a collection interface instead of a concrete type -dotnet_diagnostic.AV1130.severity = none -# AV1135: null is returned from method which has return type of string, collection or task -dotnet_diagnostic.AV1135.severity = none # re-enable if we can distinguish between string, collection and task -# AV1210: Catch a specific exception instead of Exception, SystemException or ApplicationException -dotnet_diagnostic.AV1210.severity = none -# AV1250: Evaluate LINQ query before returning it -dotnet_diagnostic.AV1250.severity = suggestion -# AV1500: Method 'CallerIdentifier.DetermineCallerIdentity()' contains 10 statements, which exceeds the maximum of 7 statements -dotnet_diagnostic.AV1500.severity = none -# AV1532: Loop statement contains nested loop +# Purpose: null is returned from method which has return type of string, collection or task +# Rationale: It's a good suggestion, but can be hard to implement. +dotnet_diagnostic.AV1135.severity = suggestion + +# Purpose contains x statements, which exceeds the maximum of 7 statements +# Rationale: 7 is the ideal Clean Code value, 15 is what we think is reasonable, 40 is the absolute maximum. +dotnet_diagnostic.AV1500.max_statement_count = 40 +dotnet_diagnostic.AV1500.severity = error + +# Purpose: Loop statement contains nested loop +# Rationale: We don't want to completely prevent this. dotnet_diagnostic.AV1532.severity = suggestion -# AV1535: Missing block in case or default clause of switch statement -dotnet_diagnostic.AV1535.severity = none # re-enable if we can adjust the formatting to not indent the scope braces -# AV1537: If-else-if construct should end with an unconditional else clause -dotnet_diagnostic.AV1537.severity = suggestion -# AV1551: Method overload with the most parameters should be virtual + +# Purpose: Overloaded method should call another overload +# Rationale: Does often lead to more complex code dotnet_diagnostic.AV1551.severity = none -# AV1555: Avoid using non-(nullable-)boolean named arguments -dotnet_diagnostic.AV1555.severity = suggestion -# AV1561: Method contains 5 parameters, which exceeds the maximum of 3 parameters -dotnet_diagnostic.AV1561.severity = suggestion -# AV1564: Parameter in public or internal member is of type bool or bool? -dotnet_diagnostic.AV1564.severity = suggestion -# AV1554: Do not use optional parameters in interface methods or their concrete implementations + +# Purpose: Method contains optional parameter 'because' +# Rationale: This is good advice, but in our case the entire purpose dotnet_diagnostic.AV1554.severity = none -# AV1580: Argument for parameter calls nested method + +# Parameter is invoked with a named argument +# Rationale: We do not want to enforce this rule in our codebase. +dotnet_diagnostic.AV1555.severity = none + +# Purpose: Don’t declare signatures with more than 3 parameters +# Rationale: 3 is the ideal number of parameters for a method, but more than 5 in a constructor is a problem +dotnet_diagnostic.AV1561.max_parameter_count = 5 +dotnet_diagnostic.AV1561.max_constructor_parameter_count = 8 +dotnet_diagnostic.AV1561.severity = error + +# Purpose: Argument for parameter calls nested method +# Rationale: Modern debuggers allow stepping into nested calls, so no need to avoid them. dotnet_diagnostic.AV1580.severity = none -# AV1706: Parameter 'p' should have a more descriptive name -dotnet_diagnostic.AV1706.severity = suggestion -# AV1708: Type name contains term that should be avoided -dotnet_diagnostic.AV1708.severity = suggestion -# AV1710: Field contains the name of its containing type -dotnet_diagnostic.AV1710.severity = none -# AV2202: Replace call to Nullable.HasValue with null check -dotnet_diagnostic.AV2202.severity = none -# AV2305: Missing XML comment for internally visible type or member + +# Purpose: Parameter 'x' should have a more descriptive name +# Rationale: Not always useful +dotnet_diagnostic.AV1706.severity = none + +# Purpose: Field contains the name of its containing type +# Rationale: It's not smart enough to understand that CustomFormatters is not the same as Formatter +dotnet_diagnostic.AV1710.severity = suggestion + +# Purpose: Name of async method +# Rationale: We prefer to only use the "Async" suffix if a synchronous and asynchronous version of the method exists. +dotnet_diagnostic.AV1755.severity = none + +# Purpose: Replace call to Nullable.HasValue with null check +# Rationale: Should be a suggestion, not an error +dotnet_diagnostic.AV2202.severity = suggestion + +# Purpose: Missing XML comment for internally visible type or member +# Rationale: We do not want to enforce this rule in our codebase. dotnet_diagnostic.AV2305.severity = none -# AV2407: Region should be removed -dotnet_diagnostic.AV2407.severity = none -# Convert lambda expression to method group -resharper_convert_closure_to_method_group_highlighting = none +# Purpose: Consider making 'RaiseXXX' an event +# Rationale: Should only be a suggestion +dotnet_diagnostic.CA1030.severity = suggestion -# Start each element in a object or collection initializer on a new line -resharper_wrap_object_and_collection_initializer_style = chop_always +# Purpose: Modify 'WriteMemberValueTextFor' to catch a more specific allowed exception type +# Rationale: We know what we are doing, so we don't want to enforce this. +dotnet_diagnostic.CA1031.severity = none -# Force an empty line -resharper_blank_lines_after_multiline_statements = 1 +# Purpose: Change the type of property 'from 'string' to 'System.Uri' +# Rationale: Not a bad idea, but not always practical in existing codebases. +dotnet_diagnostic.CA1056.severity = suggestion -# Don't remove existing line breaks -resharper_keep_existing_initializer_arrangement = true -resharper_keep_existing_arrangement = true +# Purpose: In externally visible method validate parameter 'formattedGraph' is non-null before using it. +# Rationale: Not a bad idea, but not always practical in existing codebases. +dotnet_diagnostic.CA1062.severity = suggestion -# We care about that extra else after an else-if -resharper_redundant_if_else_block_highlighting = none +# Purpose: Method passes a literal string as parameter +# Rationale: Very specific rule for localizable applications +dotnet_diagnostic.CA1303.severity = suggestion -# Don't remove explicit default cases in switch statements -resharper_redundant_empty_switch_section_highlighting = none +# Purpose: Rename virtual/interface member so that it no longer conflicts with the reserved language keyword 'Throw'. +# Rationale: We only support C# +dotnet_diagnostic.CA1716.severity = none -resharper_align_multiline_binary_expressions_chain = false +# Purpose: Properties should not return arrays +# Rationale: Although returning arrays has some performance implications, we don't want to force this. +dotnet_diagnostic.CA1819.severity = suggestion -# Only use new() when the type is obvious -resharper_object_creation_when_type_not_evident = explicitly_typed -resharper_object_creation_when_type_evident = target_typed +# Purpose: Use concrete types when possible for improved performance +# Rationale: Does not need to be enforced in all cases, especially when using interfaces creates less coupling +dotnet_diagnostic.CA1859.severity = suggestion -# Indent 4 spaces per necessary indention -resharper_continuous_indent_multiplier = 1 +# Purpose: Use Task.ConfigureAwait(false) if the current SynchronizationContext is not needed +# Rationale: Not relevant for .NET Core web applications. +dotnet_diagnostic.CA2007.severity = none -# Avoid breaking a generic definition -resharper_wrap_before_extends_colon = true +# Purpose: Pass System.Uri objects instead of strings +# Rationale: Although nice to have, this rule is not always practical in existing codebases. +dotnet_diagnostic.CA2234.severity = suggestion -resharper_blank_lines_before_multiline_statements = 1 +# Purpose: Use string.Equals instead of Equals operator +# Rationale: Does not improve readability +dotnet_diagnostic.MA0006.severity = none -resharper_parentheses_non_obvious_operations = arithmetic, multiplicative, equality, relational, additive -resharper_parentheses_redundancy_style = remove_if_not_clarifies_precedence +# Purpose: Regular expressions should not be vulnerable to Denial of Service attacks +# Rationale: See https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0009.md +dotnet_diagnostic.MA0009.severity = suggestion -dotnet_analyzer_diagnostic.category-roslynator.severity = error +# Purpose: Use an overload of 'System.ArgumentException' with the parameter name +# Rationale: We don't want to force this +dotnet_diagnostic.MA0015.severity = suggestion -# Purpose: Remove trailing white-space -# Reason: It also complains about this when pressing enter to start a new empty line. -dotnet_diagnostic.RCS1037.severity = none +# Purpose: Use an explicit StringComparer to compute hash codes +# Rationale: Too farfetched +dotnet_diagnostic.MA0021.severity = suggestion -# Remove suffix 'Async' from non-asynchronous method name. Disabled because we like that suffix for now. -dotnet_diagnostic.RCS1047.severity = none +# Purpose: Closing parenthesis should not be preceded by a space +# Rationale: This conflicts with record constructors +dotnet_diagnostic.SA1009.severity = none -# Combine 'Enumerable.Where' method chain. It doesn't make it more readable in all cases. -dotnet_diagnostic.RCS1112.severity = suggestion +# Purpose: Code should not contain trailing whitespace +# Rationale: We don't want to force this. +dotnet_diagnostic.SA1028.severity = none -# Inline local variable. -dotnet_diagnostic.RCS1124.severity = suggestion +# Purpose: Prefix local calls with this +# Rationale: We don't want to force this +dotnet_diagnostic.SA1101.severity = none -# Add exception to documentation comment. Nice suggestion, but we don't want to document exceptions for internal code. -dotnet_diagnostic.RCS1140.severity = suggestion +# Purpose: Generic type constraints should be on their own line +# Rationale: We don't want to force this +dotnet_diagnostic.SA1127.severity = none -# Missing documentation -dotnet_diagnostic.RCS1141.severity = suggestion -dotnet_diagnostic.RCS1142.severity = suggestion +# Purpose: Ordering rules +# Rationale: We prefer to order members so we can read code like a book +dotnet_diagnostic.SA1201.severity = none +dotnet_diagnostic.SA1202.severity = none +dotnet_diagnostic.SA1204.severity = none -# Use conditional access. Suggestion because it doesn't always improve readability -dotnet_diagnostic.RCS1146.severity = suggestion +# Purpose: The parameters should begin on the line after the declaration, whenever the parameter span across multiple lines +# Rationale: We don't want to force this +dotnet_diagnostic.SA1116.severity = none -# Enum should declare explicit values. Disabled because we're not storing them. -dotnet_diagnostic.RCS1161.severity = none +# Purpose: The parameters should all be placed on the same line or each parameter should be placed on its own lin +# Rationale: We don't want to force this +dotnet_diagnostic.SA1117.severity = none -# Static member in generic type should use a type parameter. Disabled because it's not always applicable. -dotnet_diagnostic.RCS1158.severity = none +# Purpose: Use string.Empty for empty string +# Rationale: There is no performance difference in modern .NET versions, so no need to enforce this. +dotnet_diagnostic.SA1122.severity = none -# Add region name to #endregion. -dotnet_diagnostic.RCS1189.severity = none +# Purpose: Do not use region +# Rationale: Is up to the developer to decide if they want to use regions or not. +dotnet_diagnostic.SA1124.severity = none -# Convert comment to documentation comment. Disabled because it also complains about SMELL/REFACTOR comments -dotnet_diagnostic.RCS1181.severity = none +# Purpose: Variable '_' should begin with lower-case lette +# Rationale: Does not understand discard parameters +dotnet_diagnostic.SA1312.severity = none -# Use Regex instance instead of static method. Disabled because it's not always worth it. -dotnet_diagnostic.RCS1186.severity = none +# Purpose: Parameter '_' should begin with lower-case lette +# Rationale: Does not understand discard parameters +dotnet_diagnostic.SA1313.severity = none -# Use bit shift operator. -dotnet_diagnostic.RCS1237.severity = none +# Purpose: File may only contain a single type +# Rationale: Although we prefer to have one type per file, we don't want to force this. +dotnet_diagnostic.SA1402.severity = suggestion -# RCS1228: Unused element in documentation comment. (Equivalent to SA1614) -dotnet_diagnostic.RCS1228.severity = suggestion +# Purpose: Code analysis suppression should have justification +# Rationale: If we do it, we have reasons for it. +dotnet_diagnostic.SA1404.severity = none -# Purpose: Use element access -# Reason: Don't want to force this in this branch -dotnet_diagnostic.RCS1246.severity = suggestion +# Purpose: Use trailing comma in multi-line initialize +# Rationale: We don't want to force this +dotnet_diagnostic.SA1413.severity = none -# Use an overload that has a IEqualityComparer or IComparer parameter -dotnet_diagnostic.MA0002.severity = suggestion +# Purpose: A closing brace should not be preceded by a blank line +# Rationale: We don't care +dotnet_diagnostic.SA1508.severity = none -# Use Task.ConfigureAwait(false) as the current SynchronizationContext is not needed -dotnet_diagnostic.MA0004.severity = none +# Purpose: File is required to end with a single newline character ( +# Rationale: We don't care +dotnet_diagnostic.SA1518.severity = none -# Use string.Equals instead of Equals operator -dotnet_diagnostic.MA0006.severity = none +# Purpose: Elements should be documented +# Rationale: We don't want to force documentation for all elements +dotnet_diagnostic.SA1600.severity = suggestion -# Add regex evaluation timeout -dotnet_diagnostic.MA0009.severity = none +# Purpose: Partial elements should be documented +# Rationale: We don't want to force this +dotnet_diagnostic.SA1601.severity = suggestion -# Use an overload of 'ToString' that has a 'System.IFormatProvider' parameter. Already caught by CA1305. -dotnet_diagnostic.MA0011.severity = none +# Purpose: Enumeration items should be documented +# Rationale: We don't want to force this +dotnet_diagnostic.SA1602.severity = suggestion -# Use an overload of 'System.ArgumentException' with the parameter name. Just a suggestion since we have a bunch of justified exceptions. -dotnet_diagnostic.MA0015.severity = suggestion +# Purpose: Element parameters should be documented +# Rationale: We don't want to force this +dotnet_diagnostic.SA1611.severity = suggestion + +# Purpose: The parameter documentation for 'because' should be at position 3 +# Rationale: We don't want to force this +dotnet_diagnostic.SA1612.severity = suggestion + +# Purpose: Element return value should be documented +# Rationale: We don't want to force this +dotnet_diagnostic.SA1615.severity = none + +# Purpose: Generic type parameters should be documented +# Rationale: We don't want to force this +dotnet_diagnostic.SA1618.severity = suggestion + +# Purpose: Element return value should be documented +# Rationale: We don't want to force this +dotnet_diagnostic.sa1622.severity = suggestion + +# Purpose: The property's documentation summary text should begin with: 'Gets or sets' +# Rationale: We don't want to force this +dotnet_diagnostic.SA1623.severity = none + +# Purpose: Documentation text should end with a period +# Rationale: We don't want to force this +dotnet_diagnostic.SA1629.severity = none + +# Purpose: The file header is missing or not located at the top of the file +# Rationale: We don't want to force this +dotnet_diagnostic.SA1633.severity = none + +# Purpose: Constructor summary documentation should begin with standard text +# Rationale: We don't want to force this +dotnet_diagnostic.SA1642.severity = none + +# Purpose: File name should match first type name +# Rationale: To keep the content-only package simple, we keep everything together +dotnet_diagnostic.SA1649.severity = none + +# Purpose: Use file header +# Rationale: We don't want to force this +dotnet_diagnostic.SA1633.severity = none -# Use an explicit StringComparer to compute hash codes -dotnet_diagnostic.MA0021.severity = none +#################################################################### +## Duplicate rules +#################################################################### -# Declare types in namespaces. Already caught by CA1050 -dotnet_diagnostic.MA0047.severity = none +dotnet_diagnostic.MA0004.severity = none # duplicate of CA2007 +dotnet_diagnostic.AV2407.severity = none # duplicate of SA1124 +dotnet_diagnostic.RCS1102.severity = none # duplicate of CA1052 +dotnet_diagnostic.SA1401.severity = none # duplicate of CA1051 +dotnet_diagnostic.MA0069.severity = none # duplicate of CA2211 +dotnet_diagnostic.AV1210.severity = none # duplicate of CA1031 +dotnet_diagnostic.MA0051.severity = none # duplicate of AV1500 +dotnet_diagnostic.MA0011.severity = none # duplicate of CA1305 +dotnet_diagnostic.CA1050.severity = none # duplicate of MA0047 (which is more explanatory) -# Use an overload of 'GetHashCode' that has a StringComparison parameter -dotnet_diagnostic.MA0074.severity = none diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 7ab3cb088b..0000000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,6 +0,0 @@ -# These are supported funding model platforms - -github: fluentassertions -ko_fi: dennisdoomen -patreon: fluentassertions -custom: ["https://paypal.me/fluentassertions"] diff --git a/.github/ISSUE_TEMPLATE/01_bug_report.yml b/.github/ISSUE_TEMPLATE/01_bug_report.yml index 062e893d55..834f8bf8f8 100644 --- a/.github/ISSUE_TEMPLATE/01_bug_report.yml +++ b/.github/ISSUE_TEMPLATE/01_bug_report.yml @@ -1,19 +1,19 @@ name: 🐞 Bug Report -description: Create a report to help us improve +description: Create a report to help us improve labels: ["bug", "triage"] body: - type: markdown attributes: value: | We welcome bug reports! Please see our [contribution guidelines](https://github.com/fluentassertions/fluentassertions/blob/develop/CONTRIBUTING.md#writing-a-good-bug-report) for more information on writing a good bug report. - + **Before continuing, have you:** * Tried upgrading to newest version of Fluent Assertions, to see if your issue has already been resolved and released? * Checked existing open *and* closed [issues](https://github.com/fluentassertions/fluentassertions/issues?utf8=%E2%9C%93&q=is%3Aissue), to see if the issue has already been reported? * Tried reproducing your problem in a new isolated project? * Read the [documentation](https://fluentassertions.com/introduction)? - * Searched the two [test](https://github.com/fluentassertions/fluentassertions/tree/develop/Tests/FluentAssertions.Specs) [suites](https://github.com/fluentassertions/fluentassertions/tree/develop/Tests/FluentAssertions.Equivalency.Specs) if there is a test documenting the expected behavior? + * Searched the two [test](https://github.com/fluentassertions/fluentassertions/tree/main/Tests/FluentAssertions.Specs) [suites](https://github.com/fluentassertions/fluentassertions/tree/main/Tests/FluentAssertions.Equivalency.Specs) if there is a test documenting the expected behavior? * Considered if this is a general question and not a bug? For general questions please use [Stack Overflow](https://stackoverflow.com/questions/tagged/fluent-assertions?mixed=1). - type: textarea id: background @@ -88,7 +88,7 @@ body: description: | Please provide more information on your .NET configuration: * Which version of Fluent Assertions are you using? - * Which .NET runtime and version are you targeting? E.g. .NET framework 4.7.1 or .NET 6. + * Which .NET runtime and version are you targeting? E.g. .NET framework 4.7.2 or .NET 6.0. placeholder: Configuration validations: required: false @@ -104,10 +104,11 @@ body: - type: dropdown id: pull-request attributes: - label: Could you help with a pull-request? - description: Make sure you have read the sections about [contributing changes](https://github.com/fluentassertions/fluentassertions/blob/develop/CONTRIBUTING.md#contributing-changes) and [dos and don'ts](https://github.com/fluentassertions/fluentassertions/blob/develop/CONTRIBUTING.md#dos-and-donts). + label: Are you willing to help with a pull-request? + description: | + Make sure you have read the sections about [contributing changes](https://github.com/fluentassertions/fluentassertions/blob/develop/CONTRIBUTING.md#contributing-changes) and [dos and don'ts](https://github.com/fluentassertions/fluentassertions/blob/develop/CONTRIBUTING.md#dos-and-donts). options: - "No" - - "Yes" + - "Yes, please assign this issue to me." validations: required: true diff --git a/.github/ISSUE_TEMPLATE/02_api_proposal.yml b/.github/ISSUE_TEMPLATE/02_api_proposal.yml index 882af545ad..375ae1a78c 100644 --- a/.github/ISSUE_TEMPLATE/02_api_proposal.yml +++ b/.github/ISSUE_TEMPLATE/02_api_proposal.yml @@ -70,10 +70,12 @@ body: - type: dropdown id: pull-request attributes: - label: Could you help with a proof-of-concept (as PR in that or a separate repo) first and as pull-request later on? - description: This is mainly to help demonstrate your suggestion. Please also make sure you have read the sections about [contributing changes](https://github.com/fluentassertions/fluentassertions/blob/develop/CONTRIBUTING.md#contributing-changes) and [dos and don'ts](https://github.com/fluentassertions/fluentassertions/blob/develop/CONTRIBUTING.md#dos-and-donts). + label: Are you willing to help with a proof-of-concept (as PR in that or a separate repo) first and as pull-request later on? + description: | + This is mainly to help demonstrate your suggestion. + Please also make sure you have read the sections about [contributing changes](https://github.com/fluentassertions/fluentassertions/blob/develop/CONTRIBUTING.md#contributing-changes) and [dos and don'ts](https://github.com/fluentassertions/fluentassertions/blob/develop/CONTRIBUTING.md#dos-and-donts). options: - "No" - - "Yes" + - "Yes, please assign this issue to me." validations: required: true \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/03_general_feature.yml b/.github/ISSUE_TEMPLATE/03_general_feature.yml index 6a6471b8cb..211c325acd 100644 --- a/.github/ISSUE_TEMPLATE/03_general_feature.yml +++ b/.github/ISSUE_TEMPLATE/03_general_feature.yml @@ -27,10 +27,11 @@ body: - type: dropdown id: pull-request attributes: - label: Could you help with a pull-request? - description: Make sure you have read the sections about [contributing changes](https://github.com/fluentassertions/fluentassertions/blob/develop/CONTRIBUTING.md#contributing-changes) and [dos and don'ts](https://github.com/fluentassertions/fluentassertions/blob/develop/CONTRIBUTING.md#dos-and-donts). + label: Are you willing help with a pull-request? + description: | + Make sure you have read the sections about [contributing changes](https://github.com/fluentassertions/fluentassertions/blob/develop/CONTRIBUTING.md#contributing-changes) and [dos and don'ts](https://github.com/fluentassertions/fluentassertions/blob/develop/CONTRIBUTING.md#dos-and-donts). options: - "No" - - "Yes" + - "Yes, please assign this issue to me." validations: required: true \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 04e2a43489..8497d08c65 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -10,3 +10,14 @@ * [ ] If the PR changes the public API the changes needs to be included by running [AcceptApiChanges.ps1](../tree/develop/AcceptApiChanges.ps1) or [AcceptApiChanges.sh](../tree/develop/AcceptApiChanges.sh). * [ ] If the PR affects [the documentation](../tree/develop/docs/_pages), please include your changes in this pull request so the documentation will appear on the [website](https://www.fluentassertions.com/introduction). * [ ] Please also run `./build.sh --target spellcheck` or `.\build.ps1 --target spellcheck` before pushing and check the good outcome + +## CONTRIBUTOR LICENSE GRANT + +By submitting this contribution, the contributor hereby irrevocably grants to the project owners and maintainers a perpetual, worldwide, royalty-free, irrevocable license to use, reproduce, modify, distribute, sublicense, and create derivative works of the contribution for any purpose and under any terms, including proprietary licensing. + +The contributor waives any moral rights in the contribution to the extent permitted by law and agrees not to assert any claim of authorship or control over the contribution. The contributor represents that they are the sole author of the contribution and that it is provided free of any third-party claims. + +The contributor understands and agrees that the maintainers may, at their sole discretion, use, license, or redistribute the contribution as part of any work and under any terms they choose, without further permission or attribution. + +* [ ] I have read the Contributor License Grant and accept the conditions +* [ ] I'm interested in a free license for Fluent Assertions and will share my email address through sales@xceed.com \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..4cff2d053e --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,50 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + time: "16:00" + timezone: "Europe/Copenhagen" + + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + time: "16:00" + timezone: "Europe/Copenhagen" + + - package-ecosystem: "bundler" + directory: "/docs" + schedule: + interval: "weekly" + day: "monday" + time: "16:00" + timezone: "Europe/Copenhagen" + + - package-ecosystem: "nuget" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + time: "16:00" + timezone: "Europe/Copenhagen" + ignore: + - dependency-name: "System.Collections.Immutable" + - dependency-name: "System.Threading.Tasks.Extensions" + groups: + xunit: + patterns: + - "xunit*" + - "Verify*" + mstest: + patterns: + - "MSTest*" + nuke: + patterns: + - "Nuke*" + nunit: + patterns: + - "NUnit*" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 08caf2633d..e97fb5f22f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,37 +1,44 @@ -name: build +name: Build on: [push, pull_request] jobs: build: - + name: "Build, Test, Analyze and Publish" runs-on: windows-latest env: DOTNET_NOLOGO: true steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 with: fetch-depth: 0 - name: Setup .NET SDKs - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v5 with: dotnet-version: | - 2.1.x - 3.1.x 6.0.x - 7.0.x + 8.0.x + 9.0.x + + - name: Cache .nuke/temp + uses: actions/cache@v4 + with: + path: | + .nuke/temp + key: ${{ runner.os }}-${{ hashFiles('NodeVersion') }} - name: Run NUKE run: ./build.ps1 env: NuGetApiKey: ${{ secrets.NUGETAPIKEY }} + GitHubApiKey: ${{ secrets.GITHUB_TOKEN }} - name: Check for 'lcov.info' existence id: check_files - uses: andstor/file-existence-action@v2 + uses: andstor/file-existence-action@v3 with: files: "TestResults/reports/lcov.info" @@ -43,30 +50,63 @@ jobs: file: TestResults/reports/lcov.info - name: Upload artifacts - uses: actions/upload-artifact@v3 + if: always() + uses: actions/upload-artifact@v4 with: - path: ./Artifacts/* - only-unit-tests: + name: windows-artifacts + path: | + ./Artifacts/* + ./TestResults/*.trx + only-unit-tests: + name: "Run Unit Tests Only" strategy: matrix: - os: [ubuntu-22.04, macos-12] + os: [ubuntu-24.04, macos-15] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 with: fetch-depth: 0 - name: Setup .NET SDKs - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v5 with: dotnet-version: | - 2.1.x - 3.1.x 6.0.x - 7.0.x + 8.0.x + 9.0.x - name: Run NUKE run: ./build.sh UnitTests + + - name: Upload artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: ${{ runner.os }}-artifacts + path: | + ./TestResults/*.trx + + publish-test-results: + name: "Publish Tests Results" + needs: [ build, only-unit-tests ] + runs-on: ubuntu-latest + permissions: + checks: write + pull-requests: write + if: always() + + steps: + - name: Download Artifacts + uses: actions/download-artifact@v5 + with: + path: artifacts + + - name: Publish Test Results + uses: EnricoMi/publish-unit-test-result-action@v2 + with: + comment_mode: always + files: "artifacts/**/**/*.trx" diff --git a/.github/workflows/code_quality.yml b/.github/workflows/code_quality.yml index e743e206dc..9e5c7b0b6e 100644 --- a/.github/workflows/code_quality.yml +++ b/.github/workflows/code_quality.yml @@ -13,12 +13,12 @@ jobs: name: Qodana Scan runs-on: windows-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 - name: 'Qodana Scan' - uses: JetBrains/qodana-action@v2024.2 + uses: JetBrains/qodana-action@v2025.2 with: upload-result: ${{ github.ref_name == 'main' || github.ref_name == 'develop' }} args: --baseline,qodana.sarif.json,--ide,QDNET diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000000..ac55636fe2 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,57 @@ +name: "CodeQL" + +on: + push: + branches: [ "develop", "main", "release*" ] + pull_request: + branches: [ "develop", "main", "release*" ] + schedule: + - cron: '00 15 * * 1' + +jobs: + analyze: + name: Analyze + runs-on: 'ubuntu-latest' + timeout-minutes: 360 + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'csharp' ] + + steps: + - name: Setup .NET SDKs + uses: actions/setup-dotnet@v5 + with: + dotnet-version: | + 9.0.x + + - name: Checkout repository + + uses: actions/checkout@v5 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + - name: Build + run: | + dotnet restore FluentAssertions.sln --configfile nuget.config + dotnet build FluentAssertions.sln --configuration CI --no-restore + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json index 50c8444452..91b6d5c140 100644 --- a/.nuke/build.schema.json +++ b/.nuke/build.schema.json @@ -29,14 +29,18 @@ "Clean", "CodeCoverage", "Compile", + "InstallNode", "Pack", "Push", "Restore", + "ScanPackages", "SpellCheck", "TestFrameworks", + "TestingPlatformFrameworks", "UnitTests", - "UnitTestsNetCore", - "UnitTestsNetFramework" + "UnitTestsNet47", + "UnitTestsNet6OrGreater", + "VSTestFrameworks" ] }, "Verbosity": { @@ -110,6 +114,14 @@ "allOf": [ { "properties": { + "Configuration": { + "type": "string", + "description": "The solution configuration to build. Default is 'Debug' (local) or 'CI' (server)", + "enum": [ + "CI", + "Debug" + ] + }, "GenerateBinLog": { "type": [ "boolean", @@ -117,6 +129,11 @@ ], "description": "Use this parameter if you encounter build problems in any way, to generate a .binlog file which holds some useful information" }, + "GitHubApiKey": { + "type": "string", + "description": "The key to use for scanning packages on GitHub", + "default": "Secrets must be entered via 'nuke :secrets [profile]'" + }, "NuGetApiKey": { "type": "string", "description": "The key to push to Nuget", diff --git a/.packageguard/cache.bin b/.packageguard/cache.bin new file mode 100644 index 0000000000..94b0ea828e Binary files /dev/null and b/.packageguard/cache.bin differ diff --git a/.packageguard/config.json b/.packageguard/config.json new file mode 100644 index 0000000000..e88cc9e6b1 --- /dev/null +++ b/.packageguard/config.json @@ -0,0 +1,18 @@ +{ + "Settings": { + "Allow": { + "Licenses": [ + "MIT", + "Apache-2.0", + "BSD-3-Clause", + "BSD-2-Clause", + "Microsoft .NET Library License", + "MS-PL" + ], + "Packages": [ + "NETStandard.Library", + "NUnit" + ] + } + } +} diff --git a/Build/Build.cs b/Build/Build.cs index 3b206c57ee..87d8bb32de 100644 --- a/Build/Build.cs +++ b/Build/Build.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using LibGit2Sharp; using Nuke.Common; @@ -13,14 +12,14 @@ using Nuke.Common.Tools.GitVersion; using Nuke.Common.Tools.ReportGenerator; using Nuke.Common.Tools.Xunit; +using Nuke.Common.Utilities; using Nuke.Common.Utilities.Collections; using Nuke.Components; -using static Nuke.Common.IO.FileSystemTasks; -using static Nuke.Common.IO.PathConstruction; using static Nuke.Common.Tools.DotNet.DotNetTasks; using static Nuke.Common.Tools.ReportGenerator.ReportGeneratorTasks; using static Nuke.Common.Tools.Xunit.XunitTasks; using static Serilog.Log; +using static CustomNpmTasks; [UnsetVisualStudioEnvironmentVariables] [DotNetVerbosityMapping] @@ -43,6 +42,9 @@ class Build : NukeBuild string PullRequestBase => GitHubActions?.BaseRef; + [Parameter("The solution configuration to build. Default is 'Debug' (local) or 'CI' (server).")] + readonly Configuration Configuration = IsLocalBuild ? Configuration.Debug : Configuration.CI; + [Parameter("Use this parameter if you encounter build problems in any way, " + "to generate a .binlog file which holds some useful information.")] readonly bool? GenerateBinLog; @@ -51,25 +53,23 @@ class Build : NukeBuild [Secret] readonly string NuGetApiKey; + [Parameter("The key to use for scanning packages on GitHub")] + [Secret] + readonly string GitHubApiKey; + [Solution(GenerateProjects = true)] readonly Solution Solution; - [GitVersion(Framework = "net6.0")] + [Required] + [GitVersion(Framework = "net9.0", NoCache = true, NoFetch = true)] readonly GitVersion GitVersion; + [Required] [GitRepository] readonly GitRepository GitRepository; -#if OS_WINDOWS - [NuGetPackage("Node.js.redist", "node.exe", Version = "16.20.0", Framework = "win-x64")] -#elif OS_MAC - [NuGetPackage("Node.js.redist", "node", Version = "16.20.0", Framework = "osx-x64")] -#else - [NuGetPackage("Node.js.redist", "node", Version = "16.20.0", Framework = "linux-x64")] -#endif - Tool Node; - - string YarnCli => $"{NuGetToolPathResolver.GetPackageExecutable("Yarn.MSBuild", "yarn.js", "1.22.19")}"; + [NuGetPackage("PackageGuard", "PackageGuard.dll")] + Tool PackageGuard; AbsolutePath ArtifactsDirectory => RootDirectory / "Artifacts"; @@ -90,13 +90,14 @@ class Build : NukeBuild .Executes(() => { SemVer = GitVersion.SemVer; + if (IsPullRequest) { Information( "Branch spec {branchspec} is a pull request. Adding build number {buildnumber}", BranchSpec, BuildNumber); - SemVer = string.Join('.', GitVersion.SemVer.Split('.').Take(3).Union(new[] { BuildNumber })); + SemVer = string.Join('.', GitVersion.SemVer.Split('.').Take(3).Union([BuildNumber])); } Information("SemVer = {semver}", SemVer); @@ -117,21 +118,23 @@ class Build : NukeBuild Target Compile => _ => _ .DependsOn(Restore) + .DependsOn(CalculateNugetVersion) .OnlyWhenDynamic(() => RunAllTargets || HasSourceChanges) .Executes(() => { ReportSummary(s => s - .WhenNotNull(GitVersion, (_, o) => _ - .AddPair("Version", o.SemVer))); + .WhenNotNull(SemVer, (summary, semVer) => summary + .AddPair("Version", semVer))); DotNetBuild(s => s .SetProjectFile(Solution) - .SetConfiguration(Configuration.CI) - .When(GenerateBinLog is true, _ => _ + .SetConfiguration(Configuration) + .When(_ => GenerateBinLog is true, c => c .SetBinaryLog(ArtifactsDirectory / $"{Solution.Core.FluentAssertions.Name}.binlog") ) .EnableNoLogo() .EnableNoRestore() + .SetVersion(SemVer) .SetAssemblyVersion(GitVersion.AssemblySemVer) .SetFileVersion(GitVersion.AssemblySemFileVer) .SetInformationalVersion(GitVersion.InformationalVersion)); @@ -145,35 +148,34 @@ class Build : NukeBuild Project project = Solution.Specs.Approval_Tests; DotNetTest(s => s - .SetConfiguration(Configuration.Release) + .SetConfiguration(Configuration == Configuration.Debug ? "Debug" : "Release") .SetProcessEnvironmentVariable("DOTNET_CLI_UI_LANGUAGE", "en-US") .EnableNoBuild() .SetResultsDirectory(TestResultsDirectory) .CombineWith(cc => cc .SetProjectFile(project) .AddLoggers($"trx;LogFileName={project.Name}.trx")), completeOnFailure: true); - - ReportTestOutcome(globFilters: $"*{project.Name}.trx"); }); - Project[] Projects => new[] - { + Project[] Projects => + [ Solution.Specs.FluentAssertions_Specs, Solution.Specs.FluentAssertions_Equivalency_Specs, + Solution.Specs.FluentAssertions_Extensibility_Specs, Solution.Specs.FSharp_Specs, Solution.Specs.VB_Specs - }; + ]; - Target UnitTestsNetFramework => _ => _ + Target UnitTestsNet47 => _ => _ .Unlisted() .DependsOn(Compile) .OnlyWhenDynamic(() => EnvironmentInfo.IsWin && (RunAllTargets || HasSourceChanges)) .Executes(() => { string[] testAssemblies = Projects - .SelectMany(project => project.Directory.GlobFiles("bin/Debug/net47/*.Specs.dll")) - .Select(_ => _.ToString()) - .ToArray(); + .SelectMany(project => project.Directory.GlobFiles("bin/Debug/net47/*.Specs.dll")) + .Select(p => p.ToString()) + .ToArray(); Assert.NotEmpty(testAssemblies.ToList()); @@ -183,64 +185,40 @@ class Build : NukeBuild ); }); - Target UnitTestsNetCore => _ => _ + Target UnitTestsNet6OrGreater => _ => _ .Unlisted() .DependsOn(Compile) .OnlyWhenDynamic(() => RunAllTargets || HasSourceChanges) .Executes(() => { - const string NET47 = "net47"; + const string net47 = "net47"; DotNetTest(s => s - .SetConfiguration(Configuration.Debug) - .SetProcessEnvironmentVariable("DOTNET_CLI_UI_LANGUAGE", "en-US") - .EnableNoBuild() - .SetDataCollector("XPlat Code Coverage") - .SetResultsDirectory(TestResultsDirectory) - .AddRunSetting( - "DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.DoesNotReturnAttribute", - "DoesNotReturnAttribute") - .CombineWith( - Projects, - (_, project) => _ - .SetProjectFile(project) - .CombineWith( - project.GetTargetFrameworks().Except(new[] { NET47 }), - (_, framework) => _ - .SetFramework(framework) - .AddLoggers($"trx;LogFileName={project.Name}_{framework}.trx") - ) - ), completeOnFailure: true + .SetConfiguration(Configuration.Debug) + .SetProcessEnvironmentVariable("DOTNET_CLI_UI_LANGUAGE", "en-US") + .EnableNoBuild() + .SetDataCollector("XPlat Code Coverage") + .SetResultsDirectory(TestResultsDirectory) + .AddRunSetting( + "DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.DoesNotReturnAttribute", + "DoesNotReturnAttribute") + .CombineWith( + Projects, + (settings, project) => settings + .SetProjectFile(project) + .CombineWith( + project.GetTargetFrameworks().Except([net47]), + (frameworkSettings, framework) => frameworkSettings + .SetFramework(framework) + .AddLoggers($"trx;LogFileName={project.Name}_{framework}.trx") + ) + ), completeOnFailure: true ); - - ReportTestOutcome(globFilters: $"*[!*{NET47}].trx"); }); Target UnitTests => _ => _ - .DependsOn(UnitTestsNetFramework) - .DependsOn(UnitTestsNetCore); - - static string[] Outcomes(AbsolutePath path) - => XmlTasks.XmlPeek( - path, - "/xn:TestRun/xn:Results/xn:UnitTestResult/@outcome", - ("xn", "http://microsoft.com/schemas/VisualStudio/TeamTest/2010")).ToArray(); - - void ReportTestOutcome(params string[] globFilters) - { - var resultFiles = TestResultsDirectory.GlobFiles(globFilters); - var outcomes = resultFiles.SelectMany(Outcomes).ToList(); - var passedTests = outcomes.Count(outcome => outcome is "Passed"); - var failedTests = outcomes.Count(outcome => outcome is "Failed"); - var skippedTests = outcomes.Count(outcome => outcome is "NotExecuted"); - - ReportSummary(_ => _ - .When(failedTests > 0, _ => _ - .AddPair("Failed", failedTests.ToString())) - .AddPair("Passed", passedTests.ToString()) - .When(skippedTests > 0, _ => _ - .AddPair("Skipped", skippedTests.ToString()))); - } + .DependsOn(UnitTestsNet47) + .DependsOn(UnitTestsNet6OrGreater); Target CodeCoverage => _ => _ .DependsOn(TestFrameworks) @@ -249,7 +227,8 @@ void ReportTestOutcome(params string[] globFilters) .Executes(() => { ReportGenerator(s => s - .SetProcessToolPath(NuGetToolPathResolver.GetPackageExecutable("ReportGenerator", "ReportGenerator.dll", framework: "net6.0")) + .SetProcessToolPath(NuGetToolPathResolver.GetPackageExecutable("ReportGenerator", "ReportGenerator.dll", + framework: "net9.0")) .SetTargetDirectory(TestResultsDirectory / "reports") .AddReports(TestResultsDirectory / "**/coverage.cobertura.xml") .AddReportTypes( @@ -263,23 +242,26 @@ void ReportTestOutcome(params string[] globFilters) Information($"Code coverage report: \x1b]8;;file://{link.Replace('\\', '/')}\x1b\\{link}\x1b]8;;\x1b\\"); }); - Target TestFrameworks => _ => _ + Target VSTestFrameworks => _ => _ .DependsOn(Compile) .OnlyWhenDynamic(() => RunAllTargets || HasSourceChanges) .Executes(() => { - var projects = new[] - { + Project[] projects = + [ Solution.TestFrameworks.MSpec_Specs, Solution.TestFrameworks.MSTestV2_Specs, Solution.TestFrameworks.NUnit3_Specs, - Solution.TestFrameworks.XUnit2_Specs - }; + Solution.TestFrameworks.NUnit4_Specs, + Solution.TestFrameworks.XUnit2_Specs, + Solution.TestFrameworks.XUnit3_Specs, + Solution.TestFrameworks.XUnit3Core_Specs, + ]; var testCombinations = from project in projects let frameworks = project.GetTargetFrameworks() - let supportedFrameworks = EnvironmentInfo.IsWin ? frameworks : frameworks.Except(new[] { "net47" }) + let supportedFrameworks = EnvironmentInfo.IsWin ? frameworks : frameworks.Except(["net47"]) from framework in supportedFrameworks select new { project, framework }; @@ -294,31 +276,77 @@ from framework in supportedFrameworks "DoesNotReturnAttribute") .CombineWith( testCombinations, - (_, v) => _ + (settings, v) => settings .SetProjectFile(v.project) .SetFramework(v.framework) .AddLoggers($"trx;LogFileName={v.project.Name}_{v.framework}.trx")), completeOnFailure: true); + }); + + Target TestingPlatformFrameworks => _ => _ + .DependsOn(Compile) + .OnlyWhenDynamic(() => RunAllTargets || HasSourceChanges) + .Executes(() => + { + Project[] projects = + [ + Solution.TestFrameworks.TUnit_Specs + ]; + + var testCombinations = + from project in projects + let frameworks = project.GetTargetFrameworks() + from framework in frameworks + select new { project, framework }; - ReportTestOutcome(projects.Select(p => $"*{p.Name}*.trx").ToArray()); + DotNetTest(s => s + .SetConfiguration(Configuration.Debug) + .SetProcessEnvironmentVariable("DOTNET_CLI_UI_LANGUAGE", "en-US") + .EnableNoBuild() + .CombineWith( + testCombinations, + (settings, v) => settings + .SetProjectFile(v.project) + .SetFramework(v.framework) + .SetProcessAdditionalArguments( + "--", + "--coverage", + "--report-trx", + $"--report-trx-filename {v.project.Name}_{v.framework}.trx", + $"--results-directory {TestResultsDirectory}" + ) + ) + ); + }); + + Target TestFrameworks => _ => _ + .DependsOn(VSTestFrameworks) + .DependsOn(TestingPlatformFrameworks); + + Target ScanPackages => _ => _ + .DependsOn(Restore) + .Executes(() => + { + Environment.SetEnvironmentVariable("GITHUB_API_KEY", GitHubApiKey); + PackageGuard($"--config-path={RootDirectory / ".packageguard" / "config.json"} --use-caching {RootDirectory}"); }); Target Pack => _ => _ .DependsOn(ApiChecks) .DependsOn(TestFrameworks) + .DependsOn(ScanPackages) .DependsOn(UnitTests) .DependsOn(CodeCoverage) - .DependsOn(CalculateNugetVersion) .OnlyWhenDynamic(() => RunAllTargets || HasSourceChanges) .Executes(() => { ReportSummary(s => s - .WhenNotNull(SemVer, (_, semVer) => _ + .WhenNotNull(SemVer, (c, semVer) => c .AddPair("Packed version", semVer))); DotNetPack(s => s .SetProject(Solution.Core.FluentAssertions) .SetOutputDirectory(ArtifactsDirectory) - .SetConfiguration(Configuration.Release) + .SetConfiguration(Configuration == Configuration.Debug ? "Debug" : "Release") .EnableNoLogo() .EnableNoRestore() .EnableContinuousIntegrationBuild() // Necessary for deterministic builds @@ -345,13 +373,33 @@ from framework in supportedFrameworks }); Target SpellCheck => _ => _ + .OnlyWhenDynamic(() => RunAllTargets || HasDocumentationChanges) + .DependsOn(InstallNode) + .ProceedAfterFailure() + .Executes(() => + { + NpmInstall(silent: true, workingDirectory: RootDirectory); + NpmRun("cspell", silent: true); + }); + + Target InstallNode => _ => _ .OnlyWhenDynamic(() => RunAllTargets || HasDocumentationChanges) .ProceedAfterFailure() .Executes(() => { - Node($"{YarnCli} --silent install", workingDirectory: RootDirectory); - Node($"{YarnCli} --silent run cspell", workingDirectory: RootDirectory, - logger: (_, msg) => Error(msg)); + Initialize(RootDirectory); + + NpmFetchRuntime(); + + ReportSummary(conf => + { + if (HasCachedNodeModules) + { + conf.AddPair("Skipped", "Downloading and extracting"); + } + + return conf; + }); }); bool HasDocumentationChanges => Changes.Any(x => IsDocumentation(x)); @@ -364,6 +412,7 @@ static bool IsDocumentation(string x) => x.StartsWith("cSpell.json") || x.StartsWith("LICENSE") || x.StartsWith("package.json") || + x.StartsWith("package-lock.json") || x.StartsWith("README.md"); string[] Changes => diff --git a/Build/CompressionExtensions.cs b/Build/CompressionExtensions.cs new file mode 100644 index 0000000000..334960480d --- /dev/null +++ b/Build/CompressionExtensions.cs @@ -0,0 +1,28 @@ +using Nuke.Common.IO; +using SharpCompress.Common; +using SharpCompress.Readers; +using System.IO; + +public static class CompressionExtensions +{ + public static void UnTarXzTo(this AbsolutePath archive, AbsolutePath directory) + { + using Stream stream = File.OpenRead(archive); + + using var reader = ReaderFactory.Open(stream); + + while (reader.MoveToNextEntry()) + { + if (reader.Entry.IsDirectory) + { + continue; + } + + reader.WriteEntryToDirectory(directory, new ExtractionOptions + { + ExtractFullPath = true, + Overwrite = true + }); + } + } +} diff --git a/Build/Configuration.cs b/Build/Configuration.cs index 63b8965fad..3de0a1c880 100644 --- a/Build/Configuration.cs +++ b/Build/Configuration.cs @@ -4,11 +4,9 @@ [TypeConverter(typeof(TypeConverter))] public class Configuration : Enumeration { - public static Configuration Debug { get; } = new() { Value = nameof(Debug) }; + public static readonly Configuration Debug = new() { Value = nameof(Debug) }; - public static Configuration Release { get; } = new() { Value = nameof(Release) }; - - public static Configuration CI { get; } = new() { Value = nameof(CI) }; + public static readonly Configuration CI = new() { Value = nameof(CI) }; public static implicit operator string(Configuration configuration) { diff --git a/Build/CustomNpmTasks.cs b/Build/CustomNpmTasks.cs new file mode 100644 index 0000000000..840bf988ea --- /dev/null +++ b/Build/CustomNpmTasks.cs @@ -0,0 +1,223 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Nuke.Common; +using Nuke.Common.IO; +using Nuke.Common.Tooling; +using Nuke.Common.Utilities; +using Nuke.Common.Utilities.Collections; +using NukeDictionaryExtensions = Nuke.Common.Utilities.Collections.DictionaryExtensions; +using static Serilog.Log; + +public static class CustomNpmTasks +{ + static AbsolutePath RootDirectory; + static AbsolutePath TempDir; + static AbsolutePath NodeDir; + static AbsolutePath NodeDirPerOs; + static AbsolutePath WorkingDirectory; + + static IReadOnlyDictionary NpmEnvironmentVariables; + + static Tool Npm; + + static string Version; + + static Func GetCachedNodeModules; + + public static bool HasCachedNodeModules; + + public static void Initialize(AbsolutePath root) + { + RootDirectory = root; + NodeDir = RootDirectory / ".nuke" / "temp"; + + Version = (RootDirectory / "NodeVersion").ReadAllText().Trim(); + GetCachedNodeModules = os => NodeDir.GlobFiles($"node*{Version}-{os}*/**/node*", $"node*{Version}-{os}*/**/npm*").Count != 0; + } + + public static void NpmFetchRuntime() + { + DownloadNodeArchive().ExtractNodeArchive(); + + LinkTools(); + } + + static AbsolutePath DownloadNodeArchive() + { + AbsolutePath archive = NodeDir; + string os = null; + string archiveType = null; + + if (EnvironmentInfo.IsWin) + { + os = "win"; + archiveType = ".zip"; + } + else if (EnvironmentInfo.IsOsx) + { + os = "darwin"; + archiveType = ".tar.gz"; + } + else if (EnvironmentInfo.IsLinux) + { + os = "linux"; + archiveType = ".tar.xz"; + } + + os.NotNull(); + archiveType.NotNull(); + + string architecture = + EnvironmentInfo.IsArm64 ? "arm64" : + EnvironmentInfo.Is64Bit ? "x64" : "x86"; + + os = $"{os}-{architecture}"; + + HasCachedNodeModules = GetCachedNodeModules(os); + + if (!HasCachedNodeModules) + { + Information($"Fetching node.js ({Version}) for {os}"); + + string downloadUrl = $"https://nodejs.org/dist/v{Version}/node-v{Version}-{os}{archiveType}"; + archive = NodeDir / $"node{archiveType}"; + + HttpTasks.HttpDownloadFile(downloadUrl, archive, clientConfigurator: c => + { + c.Timeout = TimeSpan.FromSeconds(50); + + return c; + }); + } + else + { + Information("Skipping archive download due to cache"); + } + + NodeDirPerOs = NodeDir / $"node-v{Version}-{os}"; + WorkingDirectory = NodeDirPerOs; + + return archive; + } + + static void ExtractNodeArchive(this AbsolutePath archive) + { + if (HasCachedNodeModules) + { + Information("Skipping archive extraction due to cache"); + + return; + } + + Information($"Extracting node.js binary archive ({archive}) to {NodeDir}"); + + if (EnvironmentInfo.IsWin) + { + archive.UnZipTo(NodeDir); + } + else if (EnvironmentInfo.IsOsx) + { + archive.UnTarGZipTo(NodeDir); + } + else if (EnvironmentInfo.IsLinux) + { + archive.UnTarXzTo(NodeDir); + } + } + + static void LinkTools() + { + AbsolutePath npmExecutable; + + if (EnvironmentInfo.IsWin) + { + npmExecutable = NodeDirPerOs / "npm.cmd"; + } + else + { + WorkingDirectory /= "bin"; + + var nodeExecutable = WorkingDirectory / "node"; + var npmNodeModules = NodeDirPerOs / "lib" / "node_modules"; + npmExecutable = npmNodeModules / "npm" / "bin" / "npm"; + + Information($"Set execution permissions for {nodeExecutable}..."); + nodeExecutable.SetExecutable(); + + Information($"Set execution permissions for {npmExecutable}..."); + npmExecutable.SetExecutable(); + + Information("Linking binaries..."); + npmExecutable.AddUnixSymlink(WorkingDirectory / "npm", force: true); + npmNodeModules.AddUnixSymlink(WorkingDirectory / "node_modules", force: true); + + npmExecutable = WorkingDirectory / "npm"; + } + + Information("Resolve tool npm..."); + npmExecutable.NotNull(); + Npm = ToolResolver.GetTool(npmExecutable); + + NpmConfig("set update-notifier false"); + NpmVersion(); + + SetEnvVars(); + } + + static void SetEnvVars() + { + NpmEnvironmentVariables = EnvironmentInfo.Variables + .ToDictionary(x => x.Key, x => x.Value) + .SetKeyValue("path", WorkingDirectory) + .AsReadOnly(); + } + + public static void NpmInstall(bool silent = false, string workingDirectory = null) + { + Npm($"install {(silent ? "--silent" : "")}", + workingDirectory, + logger: NpmLogger); + } + + public static void NpmRun(string args, bool silent = false) + { + Npm($"run {(silent ? "--silent" : "")} {args}".TrimMatchingDoubleQuotes(), + environmentVariables: NpmEnvironmentVariables, + logger: NpmLogger); + } + + static void NpmVersion() + { + Npm("--version", + workingDirectory: WorkingDirectory, + logInvocation: false, + logger: NpmLogger, + environmentVariables: NpmEnvironmentVariables); + } + + static void NpmConfig(string args) + { + Npm($"config {args}".TrimMatchingDoubleQuotes(), + workingDirectory: WorkingDirectory, + logInvocation: false, + logOutput: false, + logger: NpmLogger, + environmentVariables: NpmEnvironmentVariables); + } + + static readonly Action NpmLogger = (outputType, msg) => + { + if (EnvironmentInfo.IsWsl && msg.Contains("wslpath")) + return; + + if (outputType == OutputType.Err) + { + Error(msg); + + return; + } + + Information(msg); + }; +} diff --git a/Build/_build.csproj b/Build/_build.csproj index 6ecfc4325b..f6891e6651 100644 --- a/Build/_build.csproj +++ b/Build/_build.csproj @@ -1,29 +1,23 @@ - + Exe - net8.0 + net9.0 - CS0649;CS0169;NU1903 + CS0649;CS0169 ..\ ..\ - - - OS_WINDOWS - - - OS_LINUX - - - OS_MAC + 9.0.4 + 1 - - - - - - - - + + + + + + + + + diff --git a/Build/_build.csproj.DotSettings b/Build/_build.csproj.DotSettings index 28494fb0c6..7348ae5acb 100644 --- a/Build/_build.csproj.DotSettings +++ b/Build/_build.csproj.DotSettings @@ -13,8 +13,8 @@ False <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></Policy> - <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></Policy> + <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> + <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> True True True diff --git a/Build/_build.v3.ncrunchproject b/Build/_build.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/Build/_build.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4e7c5110ab..8b15ce80d3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -67,6 +67,7 @@ Contributions must also satisfy the other published guidelines defined in this d Please do: * Target the [Pull Request](https://help.github.com/articles/using-pull-requests) at the `develop` branch. +* Prefer rebase over merge when updating your local branch. * Follow the style presented in the [Coding Guidelines for C#](https://csharpcodingguidelines.com/). * Align with the [Design Principles](https://github.com/fluentassertions/fluentassertions/issues/1340) * Ensure that changes are covered by a new or existing set of unit tests which follow the Arrange-Act-Assert syntax such as is used [in this example](https://github.com/fluentassertions/fluentassertions/blob/daaf35b9b59b622c96d0c034e8972a020b2bee55/Tests/FluentAssertions.Shared.Specs/BasicEquivalencySpecs.cs#L33). diff --git a/Directory.Build.props b/Directory.Build.props index ec349586de..79f0d8f4e9 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,8 +1,7 @@ - 12.0 + 13.0 false - $(WarningsNotAsErrors);NU1902;NU1903 true @@ -14,12 +13,12 @@ true - 7.0 + 9.0 All true - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -31,11 +30,11 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/FluentAssertions.sln b/FluentAssertions.sln index 31ee369de2..842d66c8b0 100644 --- a/FluentAssertions.sln +++ b/FluentAssertions.sln @@ -7,9 +7,9 @@ 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 EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "Build\_build.csproj", "{364DD16C-D759-49DC-A04A-64D40205295B}" @@ -35,6 +35,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluentAssertions", "Src\Flu EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NUnit3.Specs", "Tests\TestFrameworks\NUnit3.Specs\NUnit3.Specs.csproj", "{C8335EA3-C6CE-47C9-AB4F-CE37157E1EB2}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NUnit4.Specs", "Tests\TestFrameworks\NUnit4.Specs\NUnit4.Specs.csproj", "{23891E7D-05CE-4893-AC38-A95CC9D3603E}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MSpec.Specs", "Tests\TestFrameworks\MSpec.Specs\MSpec.Specs.csproj", "{4F210C41-7E8E-424A-B956-FC1AA47663C9}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarks", "Tests\Benchmarks\Benchmarks.csproj", "{FCAFB0F1-79EA-4D49-813B-188D4BC4BE71}" @@ -51,6 +53,16 @@ Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "VB.Specs", "Tests\VB.Specs\ EndProject Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Specs", "Tests\FSharp.Specs\FSharp.Specs.fsproj", "{0A69DC62-CA14-44E5-BAF9-2EB2E2E2CADF}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExampleExtensions", "Tests\ExampleExtensions\ExampleExtensions.csproj", "{8DF4A6FE-AAD0-41E5-B2F4-34166D1B139C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluentAssertions.Extensibility.Specs", "Tests\FluentAssertions.Extensibility.Specs\FluentAssertions.Extensibility.Specs.csproj", "{450FC408-A4E2-4483-B064-2007024D6CF1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TUnit.Specs", "Tests\TestFrameworks\TUnit.Specs\TUnit.Specs.csproj", "{6540564E-9B6E-4E1E-8881-6F0DD0F35576}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XUnit3.Specs", "Tests\TestFrameworks\XUnit3.Specs\XUnit3.Specs.csproj", "{AF1479D8-519F-4C6B-9E8C-5A84A6D5549A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XUnit3Core.Specs", "Tests\TestFrameworks\XUnit3Core.Specs\XUnit3Core.Specs.csproj", "{77BB9496-169D-43DA-BCED-7DB3ACD9179A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution CI|Any CPU = CI|Any CPU @@ -97,6 +109,12 @@ Global {C8335EA3-C6CE-47C9-AB4F-CE37157E1EB2}.Debug|Any CPU.Build.0 = Debug|Any CPU {C8335EA3-C6CE-47C9-AB4F-CE37157E1EB2}.Release|Any CPU.ActiveCfg = Debug|Any CPU {C8335EA3-C6CE-47C9-AB4F-CE37157E1EB2}.Release|Any CPU.Build.0 = Debug|Any CPU + {23891E7D-05CE-4893-AC38-A95CC9D3603E}.CI|Any CPU.ActiveCfg = Debug|Any CPU + {23891E7D-05CE-4893-AC38-A95CC9D3603E}.CI|Any CPU.Build.0 = Debug|Any CPU + {23891E7D-05CE-4893-AC38-A95CC9D3603E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {23891E7D-05CE-4893-AC38-A95CC9D3603E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {23891E7D-05CE-4893-AC38-A95CC9D3603E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {23891E7D-05CE-4893-AC38-A95CC9D3603E}.Release|Any CPU.Build.0 = Release|Any CPU {4F210C41-7E8E-424A-B956-FC1AA47663C9}.CI|Any CPU.ActiveCfg = Debug|Any CPU {4F210C41-7E8E-424A-B956-FC1AA47663C9}.CI|Any CPU.Build.0 = Debug|Any CPU {4F210C41-7E8E-424A-B956-FC1AA47663C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -141,6 +159,36 @@ Global {0A69DC62-CA14-44E5-BAF9-2EB2E2E2CADF}.Debug|Any CPU.Build.0 = Debug|Any CPU {0A69DC62-CA14-44E5-BAF9-2EB2E2E2CADF}.Release|Any CPU.ActiveCfg = Release|Any CPU {0A69DC62-CA14-44E5-BAF9-2EB2E2E2CADF}.Release|Any CPU.Build.0 = Release|Any CPU + {8DF4A6FE-AAD0-41E5-B2F4-34166D1B139C}.CI|Any CPU.ActiveCfg = Debug|Any CPU + {8DF4A6FE-AAD0-41E5-B2F4-34166D1B139C}.CI|Any CPU.Build.0 = Debug|Any CPU + {8DF4A6FE-AAD0-41E5-B2F4-34166D1B139C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8DF4A6FE-AAD0-41E5-B2F4-34166D1B139C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8DF4A6FE-AAD0-41E5-B2F4-34166D1B139C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8DF4A6FE-AAD0-41E5-B2F4-34166D1B139C}.Release|Any CPU.Build.0 = Release|Any CPU + {450FC408-A4E2-4483-B064-2007024D6CF1}.CI|Any CPU.ActiveCfg = Debug|Any CPU + {450FC408-A4E2-4483-B064-2007024D6CF1}.CI|Any CPU.Build.0 = Debug|Any CPU + {450FC408-A4E2-4483-B064-2007024D6CF1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {450FC408-A4E2-4483-B064-2007024D6CF1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {450FC408-A4E2-4483-B064-2007024D6CF1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {450FC408-A4E2-4483-B064-2007024D6CF1}.Release|Any CPU.Build.0 = Release|Any CPU + {6540564E-9B6E-4E1E-8881-6F0DD0F35576}.CI|Any CPU.ActiveCfg = Debug|Any CPU + {6540564E-9B6E-4E1E-8881-6F0DD0F35576}.CI|Any CPU.Build.0 = Debug|Any CPU + {6540564E-9B6E-4E1E-8881-6F0DD0F35576}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6540564E-9B6E-4E1E-8881-6F0DD0F35576}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6540564E-9B6E-4E1E-8881-6F0DD0F35576}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6540564E-9B6E-4E1E-8881-6F0DD0F35576}.Release|Any CPU.Build.0 = Release|Any CPU + {AF1479D8-519F-4C6B-9E8C-5A84A6D5549A}.CI|Any CPU.ActiveCfg = Debug|Any CPU + {AF1479D8-519F-4C6B-9E8C-5A84A6D5549A}.CI|Any CPU.Build.0 = Debug|Any CPU + {AF1479D8-519F-4C6B-9E8C-5A84A6D5549A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AF1479D8-519F-4C6B-9E8C-5A84A6D5549A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AF1479D8-519F-4C6B-9E8C-5A84A6D5549A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AF1479D8-519F-4C6B-9E8C-5A84A6D5549A}.Release|Any CPU.Build.0 = Release|Any CPU + {77BB9496-169D-43DA-BCED-7DB3ACD9179A}.CI|Any CPU.ActiveCfg = Debug|Any CPU + {77BB9496-169D-43DA-BCED-7DB3ACD9179A}.CI|Any CPU.Build.0 = Debug|Any CPU + {77BB9496-169D-43DA-BCED-7DB3ACD9179A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {77BB9496-169D-43DA-BCED-7DB3ACD9179A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {77BB9496-169D-43DA-BCED-7DB3ACD9179A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {77BB9496-169D-43DA-BCED-7DB3ACD9179A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -152,6 +200,7 @@ Global {A4E37052-5581-4E70-A9C3-FF8364B2F332} = {4D8FA213-8724-4C43-B68A-F018148D238C} {34E3713D-C02F-4868-BBE7-47DAD2C7F03D} = {31891850-3EDC-480A-9B6C-F60540E9C90F} {C8335EA3-C6CE-47C9-AB4F-CE37157E1EB2} = {4D8FA213-8724-4C43-B68A-F018148D238C} + {23891E7D-05CE-4893-AC38-A95CC9D3603E} = {4D8FA213-8724-4C43-B68A-F018148D238C} {4F210C41-7E8E-424A-B956-FC1AA47663C9} = {4D8FA213-8724-4C43-B68A-F018148D238C} {FCAFB0F1-79EA-4D49-813B-188D4BC4BE71} = {963262D0-9FD5-4741-8C0E-E2F34F110EF3} {F5115158-A038-4D14-A04E-46E7863E40B9} = {963262D0-9FD5-4741-8C0E-E2F34F110EF3} @@ -160,6 +209,11 @@ Global {A946043D-D3F8-46A4-B485-A88412C417FE} = {963262D0-9FD5-4741-8C0E-E2F34F110EF3} {0C0211B6-D185-4518-A15A-38AC092EDC50} = {963262D0-9FD5-4741-8C0E-E2F34F110EF3} {0A69DC62-CA14-44E5-BAF9-2EB2E2E2CADF} = {963262D0-9FD5-4741-8C0E-E2F34F110EF3} + {8DF4A6FE-AAD0-41E5-B2F4-34166D1B139C} = {963262D0-9FD5-4741-8C0E-E2F34F110EF3} + {450FC408-A4E2-4483-B064-2007024D6CF1} = {963262D0-9FD5-4741-8C0E-E2F34F110EF3} + {6540564E-9B6E-4E1E-8881-6F0DD0F35576} = {4D8FA213-8724-4C43-B68A-F018148D238C} + {AF1479D8-519F-4C6B-9E8C-5A84A6D5549A} = {4D8FA213-8724-4C43-B68A-F018148D238C} + {77BB9496-169D-43DA-BCED-7DB3ACD9179A} = {4D8FA213-8724-4C43-B68A-F018148D238C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {75DDA3D8-9D6F-4865-93F4-DDE11DEE8290} diff --git a/FluentAssertions.sln.DotSettings b/FluentAssertions.sln.DotSettings index c20c8d78f0..14861b12e8 100644 --- a/FluentAssertions.sln.DotSettings +++ b/FluentAssertions.sln.DotSettings @@ -1,4 +1,7 @@  + Default + Inherit + ReturnDefaultValue True True False @@ -104,8 +107,8 @@ UseExplicitType <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb" /></Policy> - <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> @@ -145,6 +148,7 @@ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> OUTLINE + Minimal SOLUTION_FOLDER True True @@ -162,8 +166,6 @@ False True False - True - 0 False @@ -188,4 +190,5 @@ public void $Fact$() 2.0 True True - True + True + True diff --git a/FluentAssertions.v3.ncrunchsolution b/FluentAssertions.v3.ncrunchsolution new file mode 100644 index 0000000000..fe9b0700d2 --- /dev/null +++ b/FluentAssertions.v3.ncrunchsolution @@ -0,0 +1,8 @@ + + + True + False + True + True + + \ No newline at end of file diff --git a/GitVersion.yml b/GitVersion.yml index 99fad930de..0270a49790 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -1,13 +1,13 @@ -next-version: 6.0 +workflow: GitFlow/v1 +commit-message-incrementing: Disabled branches: release: - regex: releases?[/-] - tag: rc + label: rc + prevent-increment: + when-current-commit-tagged: true pull-request: - mode: ContinuousDeployment regex: ((pull|pull\-requests|pr)[/-]|[/-](merge)) - tag: pr - tag-number-pattern: '[/-]?(?\d+)' - prevent-increment-of-merged-branch-version: false -ignore: - sha: [] + label: pr + hotfix: + prevent-increment: + when-current-commit-tagged: true \ No newline at end of file diff --git a/LICENSE b/LICENSE index 95178297b1..09889e4680 100644 --- a/LICENSE +++ b/LICENSE @@ -1,191 +1,63 @@ -Apache License -Version 2.0, January 2004 -http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - -"License" shall mean the terms and conditions for use, reproduction, and -distribution as defined by Sections 1 through 9 of this document. - -"Licensor" shall mean the copyright owner or entity authorized by the copyright -owner that is granting the License. - -"Legal Entity" shall mean the union of the acting entity and all other entities -that control, are controlled by, or are under common control with that entity. -For the purposes of this definition, "control" means (i) the power, direct or -indirect, to cause the direction or management of such entity, whether by -contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the -outstanding shares, or (iii) beneficial ownership of such entity. - -"You" (or "Your") shall mean an individual or Legal Entity exercising -permissions granted by this License. - -"Source" form shall mean the preferred form for making modifications, including -but not limited to software source code, documentation source, and configuration -files. - -"Object" form shall mean any form resulting from mechanical transformation or -translation of a Source form, including but not limited to compiled object code, -generated documentation, and conversions to other media types. - -"Work" shall mean the work of authorship, whether in Source or Object form, made -available under the License, as indicated by a copyright notice that is included -in or attached to the work (an example is provided in the Appendix below). - -"Derivative Works" shall mean any work, whether in Source or Object form, that -is based on (or derived from) the Work and for which the editorial revisions, -annotations, elaborations, or other modifications represent, as a whole, an -original work of authorship. For the purposes of this License, Derivative Works -shall not include works that remain separable from, or merely link (or bind by -name) to the interfaces of, the Work and Derivative Works thereof. - -"Contribution" shall mean any work of authorship, including the original version -of the Work and any modifications or additions to that Work or Derivative Works -thereof, that is intentionally submitted to Licensor for inclusion in the Work -by the copyright owner or by an individual or Legal Entity authorized to submit -on behalf of the copyright owner. For the purposes of this definition, -"submitted" means any form of electronic, verbal, or written communication sent -to the Licensor or its representatives, including but not limited to -communication on electronic mailing lists, source code control systems, and -issue tracking systems that are managed by, or on behalf of, the Licensor for -the purpose of discussing and improving the Work, but excluding communication -that is conspicuously marked or otherwise designated in writing by the copyright -owner as "Not a Contribution." - -"Contributor" shall mean Licensor and any individual or Legal Entity on behalf -of whom a Contribution has been received by Licensor and subsequently -incorporated within the Work. - -2. Grant of Copyright License. - -Subject to the terms and conditions of this License, each Contributor hereby -grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, -irrevocable copyright license to reproduce, prepare Derivative Works of, -publicly display, publicly perform, sublicense, and distribute the Work and such -Derivative Works in Source or Object form. - -3. Grant of Patent License. - -Subject to the terms and conditions of this License, each Contributor hereby -grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, -irrevocable (except as stated in this section) patent license to make, have -made, use, offer to sell, sell, import, and otherwise transfer the Work, where -such license applies only to those patent claims licensable by such Contributor -that are necessarily infringed by their Contribution(s) alone or by combination -of their Contribution(s) with the Work to which such Contribution(s) was -submitted. If You institute patent litigation against any entity (including a -cross-claim or counterclaim in a lawsuit) alleging that the Work or a -Contribution incorporated within the Work constitutes direct or contributory -patent infringement, then any patent licenses granted to You under this License -for that Work shall terminate as of the date such litigation is filed. - -4. Redistribution. - -You may reproduce and distribute copies of the Work or Derivative Works thereof -in any medium, with or without modifications, and in Source or Object form, -provided that You meet the following conditions: - -You must give any other recipients of the Work or Derivative Works a copy of -this License; and -You must cause any modified files to carry prominent notices stating that You -changed the files; and -You must retain, in the Source form of any Derivative Works that You distribute, -all copyright, patent, trademark, and attribution notices from the Source form -of the Work, excluding those notices that do not pertain to any part of the -Derivative Works; and -If the Work includes a "NOTICE" text file as part of its distribution, then any -Derivative Works that You distribute must include a readable copy of the -attribution notices contained within such NOTICE file, excluding those notices -that do not pertain to any part of the Derivative Works, in at least one of the -following places: within a NOTICE text file distributed as part of the -Derivative Works; within the Source form or documentation, if provided along -with the Derivative Works; or, within a display generated by the Derivative -Works, if and wherever such third-party notices normally appear. The contents of -the NOTICE file are for informational purposes only and do not modify the -License. You may add Your own attribution notices within Derivative Works that -You distribute, alongside or as an addendum to the NOTICE text from the Work, -provided that such additional attribution notices cannot be construed as -modifying the License. -You may add Your own copyright statement to Your modifications and may provide -additional or different license terms and conditions for use, reproduction, or -distribution of Your modifications, or for any such Derivative Works as a whole, -provided Your use, reproduction, and distribution of the Work otherwise complies -with the conditions stated in this License. - -5. Submission of Contributions. - -Unless You explicitly state otherwise, any Contribution intentionally submitted -for inclusion in the Work by You to the Licensor shall be under the terms and -conditions of this License, without any additional terms or conditions. -Notwithstanding the above, nothing herein shall supersede or modify the terms of -any separate license agreement you may have executed with Licensor regarding -such Contributions. - -6. Trademarks. - -This License does not grant permission to use the trade names, trademarks, -service marks, or product names of the Licensor, except as required for -reasonable and customary use in describing the origin of the Work and -reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. - -Unless required by applicable law or agreed to in writing, Licensor provides the -Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, -including, without limitation, any warranties or conditions of TITLE, -NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are -solely responsible for determining the appropriateness of using or -redistributing the Work and assume any risks associated with Your exercise of -permissions under this License. - -8. Limitation of Liability. - -In no event and under no legal theory, whether in tort (including negligence), -contract, or otherwise, unless required by applicable law (such as deliberate -and grossly negligent acts) or agreed to in writing, shall any Contributor be -liable to You for damages, including any direct, indirect, special, incidental, -or consequential damages of any character arising as a result of this License or -out of the use or inability to use the Work (including but not limited to -damages for loss of goodwill, work stoppage, computer failure or malfunction, or -any and all other commercial damages or losses), even if such Contributor has -been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. - -While redistributing the Work or Derivative Works thereof, You may choose to -offer, and charge a fee for, acceptance of support, warranty, indemnity, or -other liability obligations and/or rights consistent with this License. However, -in accepting such obligations, You may act only on Your own behalf and on Your -sole responsibility, not on behalf of any other Contributor, and only if You -agree to indemnify, defend, and hold each Contributor harmless for any liability -incurred by, or claims asserted against, such Contributor by reason of your -accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work - -To apply the Apache License to your work, attach the following boilerplate -notice, with the fields enclosed by brackets "[]" replaced with your own -identifying information. (Don't include the brackets!) The text should be -enclosed in the appropriate comment syntax for the file format. We also -recommend that a file or class name and description of purpose be included on -the same "printed page" as the copyright notice for easier identification within -third-party archives. - - Copyright [2010-2021] [Dennis Doomen] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +XCEED SOFTWARE INC. + +COMMUNITY LICENSE AGREEMENT + +FLUENT ASSERTIONS + +(for Non-Commercial Use) + +GENERAL + +This Community License Agreement (the "Agreement") is a legal agreement between you ("Licensee") and Xceed Software Inc. ("Xceed"), whereby Licensee wishes to use Fluent Assertions (the "Software"), an Xceed product, for Non-Commercial Use purposes. Xceed agrees to license its software to developers as along as all terms & conditions set forth herein are respected. The Software is provided under a license; it is not sold in any manner. By installing, copying or otherwise using the Software, you confirm your agreement to the terms and conditions expressed in this Agreement. If you do not agree, you are not authorized to use the Software. + +DEFINITION OF NON-COMMERCIAL USE + +For the purposes of this Agreement, Non-Commercial Use refers to any use of the Software where the primary objective is not to gain commercial advantage or monetary compensation or any other type of compensation or consideration. This includes, but is not limited to, the following: + +The Software is used in developing or testing open-source software or open-source projects. + +The Software is used for developing or testing personal, or experimental projects. + +The Software is not being used by or for an organisation, group of persons, legal entity, or company, that charges fees or earns revenues with or without the intention to make profit. + +The resulting work created or tested using the Software is not sold, leased, or sublicensed. + +Any use outside of these parameters requires a paid Commercial License from Xceed. + +LICENSE GRANT + +Subject to compliance with the conditions set out below, Xceed grants to Licensee a non-exclusive and perpetual right (unless/until revoked by Xceed at its sole discretion) to install and use the Software for designing, building, testing (an application, system or program for Non-Commercial Use only, as defined above. + +Would Licensee need to use the Software for any purpose that is not strictly Non-Commercial Use, Licensee must acquire a paid Commercial License. + +The license granted under this Agreement is conditional on Licensee complying at all times with the following conditions: + +All of the Agreement's terms & conditions are strictly complied with by the Licensee; + +The Software is used for Non-Commercial Use only; + +The Software cannot be resold, licensed, sublicensed or distributed by Licensee in any manner other than as specified above; + +Licensee does not create or contribute to a competitive software product based on the Software; + +SUPPORT + +Xceed support is not included with the Community License. The Software is provided on an "as is" basis only. Licensee can send requests to Xceed's technical support team only if a Commercial license has been obtained. Bug fixes and new release updates are performed at Xceed's discretion. Pull requests can be made via GitHub for community license users and will be attended to on a best effort basis. + +WARRANTY + +Xceed clearly states that this Community License includes no warranty of any type. Xceed software is provided on an "as is" basis. In no case shall Xceed be responsible or liable for any direct or indirect, or consequential damages whatsoever (including, without limitation, any damages for loss of revenues, of business profits, business interruption, reputational impact, or loss of business information, or any other type of loss or damages) arising out of the use of the Software even if Xceed may have been advised of such potential damages or loss. + +OTHER + +Xceed does not allow Community Licensees to publish results from benchmarks or performance comparison tests (with other products) without advance written permission by Xceed. Licensee is not authorized to use Xceed's name, tradenames and trademarks without Xceed's written permission. + +GOVERNING LAW + +This Agreement shall be governed by the laws of the Province of Quebec, Canada. Any claim, dispute or problem arising out of this Agreement shall be deemed non-receivable or may be settled or disposed of at Xceed's discretion. + +Xceed reserves the right to settle any action before an arbitration board in Quebec, Canada as per generally accepted, international rules of arbitration prevailing in Quebec, Canada. + +Xceed reserves the right to modify this Agreement at all times without notice. + +(C) Copyright. Xceed Software Inc. - 2025. All rights reserved. \ No newline at end of file diff --git a/NodeVersion b/NodeVersion new file mode 100644 index 0000000000..3516580bbb --- /dev/null +++ b/NodeVersion @@ -0,0 +1 @@ +20.17.0 diff --git a/README.md b/README.md index 2551f8e5a3..9ef506eeac 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,15 @@ - +

+
+ + +Extension methods to fluently assert the outcome of .NET tests +
+

+ +
-# Extension methods to fluently assert the outcome of .NET tests [![](https://img.shields.io/github/actions/workflow/status/fluentassertions/fluentassertions/build.yml?branch=develop)](https://github.com/fluentassertions/fluentassertions/actions?query=branch%3Adevelop) -[![Coveralls branch](https://img.shields.io/coverallsCoverage/github/fluentassertions/fluentassertions?branch=develop)](https://coveralls.io/github/fluentassertions/fluentassertions?branch=develop) +[![Coveralls branch](https://img.shields.io/coverallsCoverage/github/fluentassertions/fluentassertions?branch=main)](https://coveralls.io/github/fluentassertions/fluentassertions?branch=main) [![qodana](https://github.com/fluentassertions/fluentassertions/actions/workflows/code_quality.yml/badge.svg)](https://github.com/fluentassertions/fluentassertions/actions/workflows/code_quality.yml) [![](https://img.shields.io/github/release/FluentAssertions/FluentAssertions.svg?label=latest%20release&color=007edf)](https://github.com/FluentAssertions/FluentAssertions/releases/latest) [![](https://img.shields.io/nuget/dt/FluentAssertions.svg?label=downloads&color=007edf&logo=nuget)](https://www.nuget.org/packages/FluentAssertions) @@ -14,17 +21,22 @@ [![open issues](https://img.shields.io/github/issues/fluentassertions/fluentassertions)](https://github.com/fluentassertions/fluentassertions/issues) ![](https://img.shields.io/badge/release%20strategy-githubflow-orange.svg) -A very extensive set of extension methods that allow you to more naturally specify the expected outcome of a TDD or BDD-style unit tests. Targets .NET Framework 4.7, as well as .NET 6, .NET Standard 2.0 and 2.1. +
+ +Fluent Assertions provides a comprehensive set of extension methods that enable developers to express the expected outcomes of TDD (Test-Driven Development) and BDD (Behavior-Driven Development) unit tests in a natural, readable style. It is compatible with .NET Standard 2.0+, .NET Framework 4.7+, and .NET 6+. + +Versions 8 and beyond are free for open-source projects and non-commercial use, but commercial use requires [a paid license](https://xceed.com/products/unit-testing/fluent-assertions/). +Check out the [license page](LICENSE) for more information. Version 7 +will remain fully open-source indefinitely and receive bugfixes and other important corrections. -See https://www.fluentassertions.com for [background information](https://fluentassertions.com/about/), [usage documentation](https://fluentassertions.com/introduction), an [extensibility guide](https://fluentassertions.com/extensibility/), support information and more [tips & tricks](https://fluentassertions.com/tips/). -![](https://repobeats.axiom.co/api/embed/282ed7bca0ede1ac7751ebde6b3ef091a0c6c52d.svg) +Visit https://www.fluentassertions.com for [background information](https://fluentassertions.com/about/), [usage documentation](https://fluentassertions.com/introduction), an [extensibility guide](https://fluentassertions.com/extensibility/), support information and more [tips & tricks](https://fluentassertions.com/tips/). -# Who created this? -Originally authored by Dennis Doomen with Jonas Nyrup as the productive side-kick. Notable contributions were provided by Artur Krajewski, Lukas Grützmacher and David Omid. +# Xceed Partnership FAQ +Originally authored by Dennis Doomen with Jonas Nyrup as the productive side-kick. Xceed is now an official Partner to Fluent Assertions! [Learn what this partnership means for our users](https://xceed.com/fluent-assertions-faq/). After extensive discussions with the Fluent Assertions team, we are thrilled about the future of the product and look forward to its continued growth and development. # How do I build this? -Install Visual Studio 2022 17.0+ or JetBrains Rider 2021.3 as well as the Build Tools 2022 (including the Universal Windows Platform build tools). You will also need to have .NET Framework 4.7 SDK and .NET 7.0 SDK installed. Check [global.json](global.json) for the current minimum required version. +Install Visual Studio 2022 17.14+ or JetBrains Rider 2024.3 as well as the Build Tools 2022 (including the Universal Windows Platform build tools). You will also need to have .NET Framework 4.7 SDK and .NET 9.0 SDK installed. Check the [global.json](global.json) for the current minimum required version. # What are these Approval.Tests? This is a special set of tests that use the [Verify](https://github.com/VerifyTests/Verify) project to verify whether you've introduced any breaking changes in the public API of the library. @@ -32,13 +44,4 @@ This is a special set of tests that use the [Verify](https://github.com/VerifyTe If you've verified the changes and decided they are valid, you can accept them using `AcceptApiChanges.ps1` or `AcceptApiChanges.sh`. Alternatively, you can use the [Verify Support](https://plugins.jetbrains.com/plugin/17240-verify-support) plug-in to compare the changes and accept them right from inside Rider. See also the [Contribution Guidelines](CONTRIBUTING.md). # Powered By -  -  - -With support from the following public [sponsors](https://github.com/sponsors/fluentassertions) - - - - - - + diff --git a/Src/FluentAssertions.png b/Src/FluentAssertions.png index 216d3a3f1b..94c749a655 100644 Binary files a/Src/FluentAssertions.png and b/Src/FluentAssertions.png differ diff --git a/Src/FluentAssertions/AggregateExceptionExtractor.cs b/Src/FluentAssertions/AggregateExceptionExtractor.cs index b94e03e7fc..9f697b2776 100644 --- a/Src/FluentAssertions/AggregateExceptionExtractor.cs +++ b/Src/FluentAssertions/AggregateExceptionExtractor.cs @@ -13,7 +13,7 @@ public IEnumerable OfType(Exception actualException) { if (typeof(T).IsSameOrInherits(typeof(AggregateException))) { - return actualException is T exception ? new[] { exception } : Enumerable.Empty(); + return actualException is T exception ? [exception] : []; } return GetExtractedExceptions(actualException); diff --git a/Src/FluentAssertions/AndConstraint.cs b/Src/FluentAssertions/AndConstraint.cs index 00d94c8410..796336bd30 100644 --- a/Src/FluentAssertions/AndConstraint.cs +++ b/Src/FluentAssertions/AndConstraint.cs @@ -3,15 +3,15 @@ namespace FluentAssertions; [DebuggerNonUserCode] -public class AndConstraint +public class AndConstraint { - public T And { get; } + public TParent And { get; } /// /// Initializes a new instance of the class. /// - public AndConstraint(T parentConstraint) + public AndConstraint(TParent parent) { - And = parentConstraint; + And = parent; } } diff --git a/Src/FluentAssertions/AndWhichConstraint.cs b/Src/FluentAssertions/AndWhichConstraint.cs index a30126ba2f..37d6183a47 100644 --- a/Src/FluentAssertions/AndWhichConstraint.cs +++ b/Src/FluentAssertions/AndWhichConstraint.cs @@ -1,51 +1,106 @@ using System; 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. + /// + // REFACTOR: In a next major version, we need to remove this overload and make the AssertionChain required + 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 . + /// + /// + /// If contains more than one object, a clear exception is thrown. + /// + public AndWhichConstraint(TParent parent, IEnumerable subjects, AssertionChain assertionChain) + : base(parent) { - TMatchedElement[] matchedElements = matchedConstraint.ToArray(); + getSubject = new Lazy(() => Single(subjects)); + + this.assertionChain = assertionChain; + } + + /// + /// 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) + { + 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}"; - Services.ThrowException(message); + AssertionEngine.TestFramework.Throw(message); } return matchedElements.Single(); @@ -54,13 +109,30 @@ 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(); + } + else + { + // Make sure the caller identification restarts with the code following the Which property. + assertionChain?.AdvanceToNextIdentifier(); + assertionChain?.ReuseOnce(); + } + + return getSubject.Value; + } + } } diff --git a/Src/FluentAssertions/AssertionConfiguration.cs b/Src/FluentAssertions/AssertionConfiguration.cs new file mode 100644 index 0000000000..82a2833716 --- /dev/null +++ b/Src/FluentAssertions/AssertionConfiguration.cs @@ -0,0 +1,11 @@ +using FluentAssertions.Configuration; + +namespace FluentAssertions; + +/// +/// Provides access to the global configuration and options to customize the behavior of FluentAssertions. +/// +public static class AssertionConfiguration +{ + public static GlobalConfiguration Current => AssertionEngine.Configuration; +} diff --git a/Src/FluentAssertions/AssertionEngine.cs b/Src/FluentAssertions/AssertionEngine.cs new file mode 100644 index 0000000000..ef04504101 --- /dev/null +++ b/Src/FluentAssertions/AssertionEngine.cs @@ -0,0 +1,154 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using FluentAssertions.Configuration; +using FluentAssertions.Execution; +using FluentAssertions.Extensibility; +using JetBrains.Annotations; + +namespace FluentAssertions; + +/// +/// Represents the run-time configuration of the assertion library. +/// +public static class AssertionEngine +{ + private static readonly object Lockable = new(); + private static ITestFramework testFramework; + private static bool isInitialized; + + static AssertionEngine() + { + EnsureInitialized(); + } + + /// + /// Gets or sets the run-time test framework used for throwing assertion exceptions. + /// + public static ITestFramework TestFramework + { + get + { + if (testFramework is not null) + { + return testFramework; + } + + lock (Lockable) + { +#pragma warning disable CA1508 + if (testFramework is null) +#pragma warning restore CA1508 + { + testFramework = TestFrameworkFactory.GetFramework(Configuration.TestFramework); + } + } + + return testFramework; + } + set => testFramework = value; + } + + /// + /// Provides access to the global configuration and options to customize the behavior of FluentAssertions. + /// + public static GlobalConfiguration Configuration { get; private set; } = new(); + + /// + /// Resets the configuration to its default state and forces the engine to reinitialize the next time it is used. + /// + [PublicAPI] + public static void ResetToDefaults() + { + isInitialized = false; + Configuration = new GlobalConfiguration(); + testFramework = null; + EnsureInitialized(); + } + + internal static void EnsureInitialized() + { + if (isInitialized) + { + return; + } + + lock (Lockable) + { + if (!isInitialized) + { + ExecuteCustomInitializers(); + + if (!License.Accepted) + { + const string softWarning = + """ + Warning: + The component "Fluent Assertions" is governed by the rules defined in the Xceed License Agreement and + the Xceed Fluent Assertions Community License. You may use Fluent Assertions free of charge for + non-commercial use only. An active subscription is required to use Fluent Assertions for commercial use. + + Please contact Xceed Sales mailto:sales@xceed.com to acquire a subscription at a very low cost. + + A paid commercial license supports the development and continued increasing support of + Fluent Assertions users under both commercial and community licenses. Help us + keep Fluent Assertions at the forefront of unit testing. + + For more information, visit https://xceed.com/products/unit-testing/fluent-assertions/ + """; + + Console.WriteLine(softWarning); + Trace.WriteLine(softWarning); + } + + isInitialized = true; + } + } + } + + private static void ExecuteCustomInitializers() + { + var currentAssembly = Assembly.GetExecutingAssembly(); + var currentAssemblyName = currentAssembly.GetName(); + + AssertionEngineInitializerAttribute[] attributes = []; + + try + { + attributes = AppDomain.CurrentDomain + .GetAssemblies() + .Where(assembly => assembly != currentAssembly && !assembly.IsDynamic && !IsFramework(assembly)) + .Where(a => a.GetReferencedAssemblies().Any(r => r.FullName == currentAssemblyName.FullName)) + .SelectMany(a => a.GetCustomAttributes()) + .ToArray(); + } + catch + { + // Just ignore any exceptions that might happen while trying to find the attributes + } + + foreach (var attribute in attributes) + { + try + { + attribute.Initialize(); + } + catch + { + // Just ignore any exceptions that might happen while trying to find the attributes + } + } + } + + private static bool IsFramework(Assembly assembly) + { +#if NET6_0_OR_GREATER + return assembly!.FullName?.StartsWith("Microsoft.", StringComparison.OrdinalIgnoreCase) == true || + assembly.FullName?.StartsWith("System.", StringComparison.OrdinalIgnoreCase) == true; +#else + return assembly.FullName.StartsWith("Microsoft.", StringComparison.OrdinalIgnoreCase) || + assembly.FullName.StartsWith("System.", StringComparison.OrdinalIgnoreCase); +#endif + } +} diff --git a/Src/FluentAssertions/AssertionExtensions.cs b/Src/FluentAssertions/AssertionExtensions.cs index d16c01d5d7..624adec0b6 100644 --- a/Src/FluentAssertions/AssertionExtensions.cs +++ b/Src/FluentAssertions/AssertionExtensions.cs @@ -1,26 +1,24 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; -using System.Data; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq.Expressions; -using System.Net.Http; using System.Reflection; using System.Threading.Tasks; using System.Xml.Linq; using FluentAssertions.Collections; using FluentAssertions.Common; -using FluentAssertions.Data; +using FluentAssertions.Execution; using FluentAssertions.Numeric; using FluentAssertions.Primitives; -using FluentAssertions.Reflection; using FluentAssertions.Specialized; using FluentAssertions.Streams; using FluentAssertions.Types; using FluentAssertions.Xml; using JetBrains.Annotations; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; #if !NETSTANDARD2_0 using FluentAssertions.Events; #endif @@ -35,6 +33,11 @@ public static class AssertionExtensions { private static readonly AggregateExceptionExtractor Extractor = new(); + static AssertionExtensions() + { + AssertionEngine.EnsureInitialized(); + } + /// /// Invokes the specified action on a subject so that you can chain it /// with any of the assertions from @@ -146,7 +149,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. /// @@ -164,7 +166,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()); } /// @@ -172,9 +174,9 @@ public static ExecutionTimeAssertions Should(this ExecutionTime executionTime) /// current . /// [Pure] - public static AssemblyAssertions Should(this Assembly assembly) + public static AssemblyAssertions Should([NotNull] this Assembly assembly) { - return new AssemblyAssertions(assembly); + return new AssemblyAssertions(assembly, AssertionChain.GetOrCreate()); } /// @@ -182,9 +184,9 @@ public static AssemblyAssertions Should(this Assembly assembly) /// current . /// [Pure] - public static XDocumentAssertions Should(this XDocument actualValue) + public static XDocumentAssertions Should([NotNull] this XDocument actualValue) { - return new XDocumentAssertions(actualValue); + return new XDocumentAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -192,9 +194,9 @@ public static XDocumentAssertions Should(this XDocument actualValue) /// current . /// [Pure] - public static XElementAssertions Should(this XElement actualValue) + public static XElementAssertions Should([NotNull] this XElement actualValue) { - return new XElementAssertions(actualValue); + return new XElementAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -202,9 +204,9 @@ public static XElementAssertions Should(this XElement actualValue) /// current . /// [Pure] - public static XAttributeAssertions Should(this XAttribute actualValue) + public static XAttributeAssertions Should([NotNull] this XAttribute actualValue) { - return new XAttributeAssertions(actualValue); + return new XAttributeAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -212,9 +214,9 @@ public static XAttributeAssertions Should(this XAttribute actualValue) /// current . /// [Pure] - public static StreamAssertions Should(this Stream actualValue) + public static StreamAssertions Should([NotNull] this Stream actualValue) { - return new StreamAssertions(actualValue); + return new StreamAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -222,9 +224,9 @@ public static StreamAssertions Should(this Stream actualValue) /// current . /// [Pure] - public static BufferedStreamAssertions Should(this BufferedStream actualValue) + public static BufferedStreamAssertions Should([NotNull] this BufferedStream actualValue) { - return new BufferedStreamAssertions(actualValue); + return new BufferedStreamAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -279,11 +281,13 @@ private static void ForceEnumeration(T subject, Func enumerab /// current . /// [Pure] - public static ObjectAssertions Should(this object actualValue) + public static ObjectAssertions Should([NotNull] this object actualValue) { - return new ObjectAssertions(actualValue); + return new ObjectAssertions(actualValue, AssertionChain.GetOrCreate()); } +#pragma warning disable AV1564 // Avoid signatures that take a bool parameter + /// /// Returns an object that can be used to assert the /// current . @@ -291,7 +295,7 @@ public static ObjectAssertions Should(this object actualValue) [Pure] public static BooleanAssertions Should(this bool actualValue) { - return new BooleanAssertions(actualValue); + return new BooleanAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -299,20 +303,12 @@ public static BooleanAssertions Should(this bool actualValue) /// current nullable . /// [Pure] - public static NullableBooleanAssertions Should(this bool? actualValue) + public static NullableBooleanAssertions Should([NotNull] this bool? actualValue) { - return new NullableBooleanAssertions(actualValue); + return new NullableBooleanAssertions(actualValue, AssertionChain.GetOrCreate()); } - /// - /// Returns an object that can be used to assert the - /// current . - /// - [Pure] - public static HttpResponseMessageAssertions Should(this HttpResponseMessage actualValue) - { - return new HttpResponseMessageAssertions(actualValue); - } +#pragma warning restore AV1564 /// /// Returns an object that can be used to assert the @@ -321,7 +317,7 @@ public static HttpResponseMessageAssertions Should(this HttpResponseMessage actu [Pure] public static GuidAssertions Should(this Guid actualValue) { - return new GuidAssertions(actualValue); + return new GuidAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -329,9 +325,9 @@ public static GuidAssertions Should(this Guid actualValue) /// current nullable . /// [Pure] - public static NullableGuidAssertions Should(this Guid? actualValue) + public static NullableGuidAssertions Should([NotNull] this Guid? actualValue) { - return new NullableGuidAssertions(actualValue); + return new NullableGuidAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -339,9 +335,9 @@ public static NullableGuidAssertions Should(this Guid? actualValue) /// current . /// [Pure] - public static GenericCollectionAssertions Should(this IEnumerable actualValue) + public static GenericCollectionAssertions Should([NotNull] this IEnumerable actualValue) { - return new GenericCollectionAssertions(actualValue); + return new GenericCollectionAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -349,9 +345,9 @@ public static GenericCollectionAssertions Should(this IEnumerable actua /// current . /// [Pure] - public static StringCollectionAssertions Should(this IEnumerable @this) + public static StringCollectionAssertions Should([NotNull] this IEnumerable @this) { - return new StringCollectionAssertions(@this); + return new StringCollectionAssertions(@this, AssertionChain.GetOrCreate()); } /// @@ -360,9 +356,10 @@ public static StringCollectionAssertions Should(this IEnumerable @this) /// [Pure] public static GenericDictionaryAssertions, TKey, TValue> Should( - this IDictionary actualValue) + [NotNull] this IDictionary actualValue) { - return new GenericDictionaryAssertions, TKey, TValue>(actualValue); + return new GenericDictionaryAssertions, TKey, TValue>(actualValue, + AssertionChain.GetOrCreate()); } /// @@ -371,9 +368,10 @@ public static GenericDictionaryAssertions, TKey, TValu /// [Pure] public static GenericDictionaryAssertions>, TKey, TValue> Should( - this IEnumerable> actualValue) + [NotNull] this IEnumerable> actualValue) { - return new GenericDictionaryAssertions>, TKey, TValue>(actualValue); + return new GenericDictionaryAssertions>, TKey, TValue>(actualValue, + AssertionChain.GetOrCreate()); } /// @@ -382,50 +380,10 @@ public static GenericDictionaryAssertions /// [Pure] public static GenericDictionaryAssertions Should( - this TCollection actualValue) + [NotNull] this TCollection actualValue) where TCollection : IEnumerable> { - return new GenericDictionaryAssertions(actualValue); - } - - /// - /// Returns an assertions object that provides methods for asserting the state of a . - /// - [Pure] - public static GenericCollectionAssertions Should(this DataTableCollection actualValue) - { - return new GenericCollectionAssertions( - ReadOnlyNonGenericCollectionWrapper.Create(actualValue)); - } - - /// - /// Returns an assertions object that provides methods for asserting the state of a . - /// - [Pure] - public static GenericCollectionAssertions Should(this DataColumnCollection actualValue) - { - return new GenericCollectionAssertions( - ReadOnlyNonGenericCollectionWrapper.Create(actualValue)); - } - - /// - /// Returns an assertions object that provides methods for asserting the state of a . - /// - [Pure] - public static GenericCollectionAssertions Should(this DataRowCollection actualValue) - { - return new GenericCollectionAssertions( - ReadOnlyNonGenericCollectionWrapper.Create(actualValue)); - } - - /// - /// Returns a object that can be used to assert the - /// current . - /// - [Pure] - public static DataColumnAssertions Should(this DataColumn actualValue) - { - return new DataColumnAssertions(actualValue); + return new GenericDictionaryAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -435,7 +393,7 @@ public static DataColumnAssertions Should(this DataColumn actualValue) [Pure] public static DateTimeAssertions Should(this DateTime actualValue) { - return new DateTimeAssertions(actualValue); + return new DateTimeAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -445,7 +403,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()); } /// @@ -453,9 +411,9 @@ public static DateTimeOffsetAssertions Should(this DateTimeOffset actualValue) /// current nullable . /// [Pure] - public static NullableDateTimeAssertions Should(this DateTime? actualValue) + public static NullableDateTimeAssertions Should([NotNull] this DateTime? actualValue) { - return new NullableDateTimeAssertions(actualValue); + return new NullableDateTimeAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -463,9 +421,9 @@ public static NullableDateTimeAssertions Should(this DateTime? actualValue) /// current nullable . /// [Pure] - public static NullableDateTimeOffsetAssertions Should(this DateTimeOffset? actualValue) + public static NullableDateTimeOffsetAssertions Should([NotNull] this DateTimeOffset? actualValue) { - return new NullableDateTimeOffsetAssertions(actualValue); + return new NullableDateTimeOffsetAssertions(actualValue, AssertionChain.GetOrCreate()); } #if NET6_0_OR_GREATER @@ -476,7 +434,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()); } /// @@ -484,9 +442,9 @@ public static DateOnlyAssertions Should(this DateOnly actualValue) /// current nullable . /// [Pure] - public static NullableDateOnlyAssertions Should(this DateOnly? actualValue) + public static NullableDateOnlyAssertions Should([NotNull] this DateOnly? actualValue) { - return new NullableDateOnlyAssertions(actualValue); + return new NullableDateOnlyAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -496,7 +454,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()); } /// @@ -504,9 +462,9 @@ public static TimeOnlyAssertions Should(this TimeOnly actualValue) /// current nullable . /// [Pure] - public static NullableTimeOnlyAssertions Should(this TimeOnly? actualValue) + public static NullableTimeOnlyAssertions Should([NotNull] this TimeOnly? actualValue) { - return new NullableTimeOnlyAssertions(actualValue); + return new NullableTimeOnlyAssertions(actualValue, AssertionChain.GetOrCreate()); } #endif @@ -516,9 +474,9 @@ public static NullableTimeOnlyAssertions Should(this TimeOnly? actualValue) /// current . /// [Pure] - public static ComparableTypeAssertions Should(this IComparable comparableValue) + public static ComparableTypeAssertions Should([NotNull] this IComparable comparableValue) { - return new ComparableTypeAssertions(comparableValue); + return new ComparableTypeAssertions(comparableValue, AssertionChain.GetOrCreate()); } /// @@ -528,7 +486,7 @@ public static ComparableTypeAssertions Should(this IComparable comparab [Pure] public static NumericAssertions Should(this int actualValue) { - return new Int32Assertions(actualValue); + return new Int32Assertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -536,9 +494,9 @@ public static NumericAssertions Should(this int actualValue) /// current nullable . /// [Pure] - public static NullableNumericAssertions Should(this int? actualValue) + public static NullableNumericAssertions Should([NotNull] this int? actualValue) { - return new NullableInt32Assertions(actualValue); + return new NullableInt32Assertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -548,7 +506,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()); } /// @@ -556,9 +514,9 @@ public static NumericAssertions Should(this uint actualValue) /// current nullable . /// [Pure] - public static NullableNumericAssertions Should(this uint? actualValue) + public static NullableNumericAssertions Should([NotNull] this uint? actualValue) { - return new NullableUInt32Assertions(actualValue); + return new NullableUInt32Assertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -568,7 +526,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()); } /// @@ -576,9 +534,9 @@ public static NumericAssertions Should(this decimal actualValue) /// current nullable . /// [Pure] - public static NullableNumericAssertions Should(this decimal? actualValue) + public static NullableNumericAssertions Should([NotNull] this decimal? actualValue) { - return new NullableDecimalAssertions(actualValue); + return new NullableDecimalAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -588,7 +546,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()); } /// @@ -596,9 +554,9 @@ public static NumericAssertions Should(this byte actualValue) /// current nullable . /// [Pure] - public static NullableNumericAssertions Should(this byte? actualValue) + public static NullableNumericAssertions Should([NotNull] this byte? actualValue) { - return new NullableByteAssertions(actualValue); + return new NullableByteAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -608,7 +566,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()); } /// @@ -616,9 +574,9 @@ public static NumericAssertions Should(this sbyte actualValue) /// current nullable . /// [Pure] - public static NullableNumericAssertions Should(this sbyte? actualValue) + public static NullableNumericAssertions Should([NotNull] this sbyte? actualValue) { - return new NullableSByteAssertions(actualValue); + return new NullableSByteAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -628,7 +586,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()); } /// @@ -636,9 +594,9 @@ public static NumericAssertions Should(this short actualValue) /// current nullable . /// [Pure] - public static NullableNumericAssertions Should(this short? actualValue) + public static NullableNumericAssertions Should([NotNull] this short? actualValue) { - return new NullableInt16Assertions(actualValue); + return new NullableInt16Assertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -648,7 +606,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()); } /// @@ -656,9 +614,9 @@ public static NumericAssertions Should(this ushort actualValue) /// current nullable . /// [Pure] - public static NullableNumericAssertions Should(this ushort? actualValue) + public static NullableNumericAssertions Should([NotNull] this ushort? actualValue) { - return new NullableUInt16Assertions(actualValue); + return new NullableUInt16Assertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -668,7 +626,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()); } /// @@ -676,9 +634,9 @@ public static NumericAssertions Should(this long actualValue) /// current nullable . /// [Pure] - public static NullableNumericAssertions Should(this long? actualValue) + public static NullableNumericAssertions Should([NotNull] this long? actualValue) { - return new NullableInt64Assertions(actualValue); + return new NullableInt64Assertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -688,7 +646,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()); } /// @@ -696,9 +654,9 @@ public static NumericAssertions Should(this ulong actualValue) /// current nullable . /// [Pure] - public static NullableNumericAssertions Should(this ulong? actualValue) + public static NullableNumericAssertions Should([NotNull] this ulong? actualValue) { - return new NullableUInt64Assertions(actualValue); + return new NullableUInt64Assertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -708,7 +666,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()); } /// @@ -716,9 +674,9 @@ public static NumericAssertions Should(this float actualValue) /// current nullable . /// [Pure] - public static NullableNumericAssertions Should(this float? actualValue) + public static NullableNumericAssertions Should([NotNull] this float? actualValue) { - return new NullableSingleAssertions(actualValue); + return new NullableSingleAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -728,7 +686,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()); } /// @@ -736,9 +694,9 @@ public static NumericAssertions Should(this double actualValue) /// current nullable . /// [Pure] - public static NullableNumericAssertions Should(this double? actualValue) + public static NullableNumericAssertions Should([NotNull] this double? actualValue) { - return new NullableDoubleAssertions(actualValue); + return new NullableDoubleAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -746,9 +704,9 @@ public static NullableNumericAssertions Should(this double? actualValue) /// current . /// [Pure] - public static StringAssertions Should(this string actualValue) + public static StringAssertions Should([NotNull] this string actualValue) { - return new StringAssertions(actualValue); + return new StringAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -758,7 +716,7 @@ public static StringAssertions Should(this string actualValue) [Pure] public static SimpleTimeSpanAssertions Should(this TimeSpan actualValue) { - return new SimpleTimeSpanAssertions(actualValue); + return new SimpleTimeSpanAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -766,9 +724,9 @@ public static SimpleTimeSpanAssertions Should(this TimeSpan actualValue) /// current nullable . /// [Pure] - public static NullableSimpleTimeSpanAssertions Should(this TimeSpan? actualValue) + public static NullableSimpleTimeSpanAssertions Should([NotNull] this TimeSpan? actualValue) { - return new NullableSimpleTimeSpanAssertions(actualValue); + return new NullableSimpleTimeSpanAssertions(actualValue, AssertionChain.GetOrCreate()); } /// @@ -776,9 +734,9 @@ public static NullableSimpleTimeSpanAssertions Should(this TimeSpan? actualValue /// current . /// [Pure] - public static TypeAssertions Should(this Type subject) + public static TypeAssertions Should([NotNull] this Type subject) { - return new TypeAssertions(subject); + return new TypeAssertions(subject, AssertionChain.GetOrCreate()); } /// @@ -791,7 +749,7 @@ public static TypeSelectorAssertions Should(this TypeSelector typeSelector) { Guard.ThrowIfArgumentIsNull(typeSelector); - return new TypeSelectorAssertions(typeSelector.ToArray()); + return new TypeSelectorAssertions(AssertionChain.GetOrCreate(), typeSelector.ToArray()); } /// @@ -800,9 +758,9 @@ public static TypeSelectorAssertions Should(this TypeSelector typeSelector) /// /// [Pure] - public static ConstructorInfoAssertions Should(this ConstructorInfo constructorInfo) + public static ConstructorInfoAssertions Should([NotNull] this ConstructorInfo constructorInfo) { - return new ConstructorInfoAssertions(constructorInfo); + return new ConstructorInfoAssertions(constructorInfo, AssertionChain.GetOrCreate()); } /// @@ -810,9 +768,9 @@ public static ConstructorInfoAssertions Should(this ConstructorInfo constructorI /// /// [Pure] - public static MethodInfoAssertions Should(this MethodInfo methodInfo) + public static MethodInfoAssertions Should([NotNull] this MethodInfo methodInfo) { - return new MethodInfoAssertions(methodInfo); + return new MethodInfoAssertions(methodInfo, AssertionChain.GetOrCreate()); } /// @@ -826,7 +784,7 @@ public static MethodInfoSelectorAssertions Should(this MethodInfoSelector method { Guard.ThrowIfArgumentIsNull(methodSelector); - return new MethodInfoSelectorAssertions(methodSelector.ToArray()); + return new MethodInfoSelectorAssertions(AssertionChain.GetOrCreate(), methodSelector.ToArray()); } /// @@ -835,9 +793,9 @@ public static MethodInfoSelectorAssertions Should(this MethodInfoSelector method /// /// [Pure] - public static PropertyInfoAssertions Should(this PropertyInfo propertyInfo) + public static PropertyInfoAssertions Should([NotNull] this PropertyInfo propertyInfo) { - return new PropertyInfoAssertions(propertyInfo); + return new PropertyInfoAssertions(propertyInfo, AssertionChain.GetOrCreate()); } /// @@ -851,7 +809,7 @@ public static PropertyInfoSelectorAssertions Should(this PropertyInfoSelector pr { Guard.ThrowIfArgumentIsNull(propertyInfoSelector); - return new PropertyInfoSelectorAssertions(propertyInfoSelector.ToArray()); + return new PropertyInfoSelectorAssertions(AssertionChain.GetOrCreate(), propertyInfoSelector.ToArray()); } /// @@ -859,9 +817,9 @@ public static PropertyInfoSelectorAssertions Should(this PropertyInfoSelector pr /// current . /// [Pure] - public static ActionAssertions Should(this Action action) + public static ActionAssertions Should([NotNull] this Action action) { - return new ActionAssertions(action, Extractor); + return new ActionAssertions(action, Extractor, AssertionChain.GetOrCreate()); } /// @@ -869,9 +827,9 @@ public static ActionAssertions Should(this Action action) /// current . /// [Pure] - public static NonGenericAsyncFunctionAssertions Should(this Func action) + public static NonGenericAsyncFunctionAssertions Should([NotNull] this Func action) { - return new NonGenericAsyncFunctionAssertions(action, Extractor); + return new NonGenericAsyncFunctionAssertions(action, Extractor, AssertionChain.GetOrCreate()); } /// @@ -879,9 +837,9 @@ public static NonGenericAsyncFunctionAssertions Should(this Func action) /// current System.Func{Task{T}}. /// [Pure] - public static GenericAsyncFunctionAssertions Should(this Func> action) + public static GenericAsyncFunctionAssertions Should([NotNull] this Func> action) { - return new GenericAsyncFunctionAssertions(action, Extractor); + return new GenericAsyncFunctionAssertions(action, Extractor, AssertionChain.GetOrCreate()); } /// @@ -889,9 +847,9 @@ public static GenericAsyncFunctionAssertions Should(this Func> act /// current . /// [Pure] - public static FunctionAssertions Should(this Func func) + public static FunctionAssertions Should([NotNull] this Func func) { - return new FunctionAssertions(func, Extractor); + return new FunctionAssertions(func, Extractor, AssertionChain.GetOrCreate()); } /// @@ -901,7 +859,7 @@ public static FunctionAssertions Should(this Func func) [Pure] public static TaskCompletionSourceAssertions Should(this TaskCompletionSource tcs) { - return new TaskCompletionSourceAssertions(tcs); + return new TaskCompletionSourceAssertions(tcs, AssertionChain.GetOrCreate()); } #if !NETSTANDARD2_0 @@ -910,14 +868,27 @@ public static TaskCompletionSourceAssertions Should(this TaskCompletionSou /// Starts monitoring for its events. /// /// The object for which to monitor the events. - /// - /// An optional delegate that returns the current date and time in UTC format. - /// Will revert to if no delegate was provided. + /// is . + public static IMonitor Monitor(this T eventSource) + { + return new EventMonitor(eventSource, new EventMonitorOptions()); + } + + /// + /// Starts monitoring for its events using the given . + /// + /// The object for which to monitor the events. + /// + /// Options to configure the EventMonitor. /// /// is . - public static IMonitor Monitor(this T eventSource, Func utcNow = null) + public static IMonitor Monitor(this T eventSource, Action configureOptions) { - return new EventMonitor(eventSource, utcNow ?? (() => DateTime.UtcNow)); + Guard.ThrowIfArgumentIsNull(configureOptions, nameof(configureOptions)); + + var options = new EventMonitorOptions(); + configureOptions(options); + return new EventMonitor(eventSource, options); } #endif @@ -930,7 +901,7 @@ public static IMonitor Monitor(this T eventSource, Func utcNow = [Pure] public static TaskCompletionSourceAssertions Should(this TaskCompletionSource tcs) { - return new TaskCompletionSourceAssertions(tcs); + return new TaskCompletionSourceAssertions(tcs, AssertionChain.GetOrCreate()); } #endif @@ -1027,7 +998,7 @@ public static void Should(this MethodInfoSelectorAssertions _) /// [Obsolete("You are asserting the 'AndConstraint' itself. Remove the 'Should()' method directly following 'And'", error: true)] - public static void Should(this NumericAssertions _) + public static void Should(this NumericAssertionsBase _) where TSubject : struct, IComparable where TAssertions : NumericAssertions { diff --git a/Src/FluentAssertions/AssertionOptions.cs b/Src/FluentAssertions/AssertionOptions.cs deleted file mode 100644 index c40eff298e..0000000000 --- a/Src/FluentAssertions/AssertionOptions.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System; -using FluentAssertions.Common; -using FluentAssertions.Equivalency; -using FluentAssertions.Formatting; - -namespace FluentAssertions; - -/// -/// Holds any global options that control the behavior of FluentAssertions. -/// -public static class AssertionOptions -{ - private static EquivalencyAssertionOptions defaults = new(); - - static AssertionOptions() - { - EquivalencyPlan = new EquivalencyPlan(); - } - - public static EquivalencyAssertionOptions CloneDefaults() - { - return new EquivalencyAssertionOptions(defaults); - } - - internal static TOptions CloneDefaults(Func predicate) - where TOptions : EquivalencyAssertionOptions - { - Guard.ThrowIfArgumentIsNull(predicate); - - return predicate(defaults); - } - - /// - /// Allows configuring the defaults used during a structural equivalency assertion. - /// - /// - /// This method is not thread-safe and should not be invoked from within a unit test. - /// See the docs on how to safely use it. - /// - /// - /// An action that is used to configure the defaults. - /// - /// is . - public static void AssertEquivalencyUsing( - Func defaultsConfigurer) - { - Guard.ThrowIfArgumentIsNull(defaultsConfigurer); - - defaults = defaultsConfigurer(defaults); - } - - /// - /// Represents a mutable plan consisting of steps that are executed while asserting a (collection of) object(s) - /// is structurally equivalent to another (collection of) object(s). - /// - /// - /// Members on this property are not thread-safe and should not be invoked from within a unit test. - /// See the docs on how to safely use it. - /// - public static EquivalencyPlan EquivalencyPlan { get; } - - /// - /// Gets the default formatting options used by the formatters in Fluent Assertions. - /// - /// - /// Members on this property should not be invoked from within a unit test. - /// See the docs on how to safely use it. - /// - public static FormattingOptions FormattingOptions { get; } = new(); -} diff --git a/Src/FluentAssertions/AsyncAssertionsExtensions.cs b/Src/FluentAssertions/AsyncAssertionsExtensions.cs index 53c6c349b9..4981a66dd0 100644 --- a/Src/FluentAssertions/AsyncAssertionsExtensions.cs +++ b/Src/FluentAssertions/AsyncAssertionsExtensions.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using FluentAssertions.Specialized; @@ -26,7 +27,7 @@ public static class AsyncAssertionsExtensions /// public static async Task, T>> WithResult( this Task, T>> task, - T expected, string because = "", params object[] becauseArgs) + T expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { var andWhichConstraint = await task; var subject = andWhichConstraint.Subject; @@ -49,7 +50,7 @@ public static async Task, T /// public static async Task, T>> WithResult( this Task, T>> task, - T expected, string because = "", params object[] becauseArgs) + T expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { var andWhichConstraint = await task; var subject = andWhichConstraint.Subject; diff --git a/Src/FluentAssertions/CallerIdentification/CallerStatementBuilder.cs b/Src/FluentAssertions/CallerIdentification/CallerStatementBuilder.cs deleted file mode 100644 index 75f4ff0f18..0000000000 --- a/Src/FluentAssertions/CallerIdentification/CallerStatementBuilder.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace FluentAssertions.CallerIdentification; - -internal class CallerStatementBuilder -{ - private readonly StringBuilder statement; - private readonly List priorityOrderedParsingStrategies; - private ParsingState parsingState = ParsingState.InProgress; - - internal CallerStatementBuilder() - { - statement = new StringBuilder(); - - priorityOrderedParsingStrategies = new List - { - new QuotesParsingStrategy(), - new MultiLineCommentParsingStrategy(), - new SingleLineCommentParsingStrategy(), - new SemicolonParsingStrategy(), - new ShouldCallParsingStrategy(), - new AwaitParsingStrategy(), - new AddNonEmptySymbolParsingStrategy() - }; - } - - internal void Append(string symbols) - { - using var symbolEnumerator = symbols.GetEnumerator(); - - while (symbolEnumerator.MoveNext() && parsingState != ParsingState.Done) - { - var hasParsingStrategyWaitingForEndContext = priorityOrderedParsingStrategies - .Exists(s => s.IsWaitingForContextEnd()); - - parsingState = ParsingState.InProgress; - - foreach (var parsingStrategy in - priorityOrderedParsingStrategies - .SkipWhile(parsingStrategy => - hasParsingStrategyWaitingForEndContext - && !parsingStrategy.IsWaitingForContextEnd())) - { - parsingState = parsingStrategy.Parse(symbolEnumerator.Current, statement); - - if (parsingState != ParsingState.InProgress) - { - break; - } - } - } - - if (IsDone()) - { - return; - } - - priorityOrderedParsingStrategies - .ForEach(strategy => strategy.NotifyEndOfLineReached()); - } - - internal bool IsDone() => parsingState == ParsingState.Done; - - public override string ToString() => statement.ToString(); -} diff --git a/Src/FluentAssertions/CallerIdentification/ParsingState.cs b/Src/FluentAssertions/CallerIdentification/ParsingState.cs index 6548ae2627..18ac092068 100644 --- a/Src/FluentAssertions/CallerIdentification/ParsingState.cs +++ b/Src/FluentAssertions/CallerIdentification/ParsingState.cs @@ -2,7 +2,24 @@ internal enum ParsingState { + /// + /// Is returned by a parser when the next one can take a look at the current symbol + /// InProgress, + + /// + /// Is returned by a parser when it decides a symbol has been processed enough and no + /// other parsers need to look at the current symbol anymore. + /// GoToNextSymbol, - Done + + /// + /// Is returned by a parser if it has found a candidate identifier. + /// + CandidateFound, + + /// + /// Is returned by a parser to indicate that the parsing has been fully completed. + /// + Completed } diff --git a/Src/FluentAssertions/CallerIdentification/SemicolonParsingStrategy.cs b/Src/FluentAssertions/CallerIdentification/SemicolonParsingStrategy.cs index 17a6601cc0..c5480ef6b5 100644 --- a/Src/FluentAssertions/CallerIdentification/SemicolonParsingStrategy.cs +++ b/Src/FluentAssertions/CallerIdentification/SemicolonParsingStrategy.cs @@ -9,7 +9,7 @@ public ParsingState Parse(char symbol, StringBuilder statement) if (symbol is ';') { statement.Clear(); - return ParsingState.Done; + return ParsingState.Completed; } return ParsingState.InProgress; diff --git a/Src/FluentAssertions/CallerIdentification/ShouldCallParsingStrategy.cs b/Src/FluentAssertions/CallerIdentification/ShouldCallParsingStrategy.cs index 4ff4555312..e7a82a85dd 100644 --- a/Src/FluentAssertions/CallerIdentification/ShouldCallParsingStrategy.cs +++ b/Src/FluentAssertions/CallerIdentification/ShouldCallParsingStrategy.cs @@ -2,37 +2,49 @@ namespace FluentAssertions.CallerIdentification; +/// +/// Tries to determine if the statement ends with ".Should(" or ".Should.", and assumes +/// the preceding identifier is the actual identifier. +/// internal class ShouldCallParsingStrategy : IParsingStrategy { - private const string ShouldCall = ".Should"; + private const string ExpectedPhrase = ".Should"; public ParsingState Parse(char symbol, StringBuilder statement) { - if (statement.Length >= ShouldCall.Length + 1) + if (IsLongEnough(statement) && EndsWithExpectedPhrase(statement) && EndsWithInvocation(statement)) { - var leftIndex = statement.Length - 2; - var rightIndex = ShouldCall.Length - 1; + // Remove the ".Should." or ".Should(" part from the statement, so we keep the actual identifier + statement.Remove(statement.Length - (ExpectedPhrase.Length + 1), ExpectedPhrase.Length + 1); + return ParsingState.CandidateFound; + } - for (var i = 0; i < ShouldCall.Length; i++) - { - if (statement[leftIndex - i] != ShouldCall[rightIndex - i]) - { - return ParsingState.InProgress; - } - } + return ParsingState.InProgress; + } + + private static bool IsLongEnough(StringBuilder statement) => statement.Length >= ExpectedPhrase.Length + 1; - if (statement[^1] is not ('(' or '.')) + private static bool EndsWithExpectedPhrase(StringBuilder statement) + { + // Start from the index on the character just before the last ( or . + var rightIndexInStatement = statement.Length - 2; + + var rightIndexInExpectedPhrase = ExpectedPhrase.Length - 1; + + // Do a reverse comparison to see if the statement ends with ".Should" + for (var i = 0; i < ExpectedPhrase.Length; i++) + { + if (statement[rightIndexInStatement - i] != ExpectedPhrase[rightIndexInExpectedPhrase - i]) { - return ParsingState.InProgress; + return false; } - - statement.Remove(statement.Length - (ShouldCall.Length + 1), ShouldCall.Length + 1); - return ParsingState.Done; } - return ParsingState.InProgress; + return true; } + private static bool EndsWithInvocation(StringBuilder statement) => statement[^1] is '(' or '.'; + public bool IsWaitingForContextEnd() { return false; diff --git a/Src/FluentAssertions/CallerIdentification/StatementParser.cs b/Src/FluentAssertions/CallerIdentification/StatementParser.cs new file mode 100644 index 0000000000..2b9497653d --- /dev/null +++ b/Src/FluentAssertions/CallerIdentification/StatementParser.cs @@ -0,0 +1,77 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace FluentAssertions.CallerIdentification; + +internal class StatementParser +{ + private readonly StringBuilder statement; + private readonly List parsingStrategies; + private readonly List candidates = new(); + private ParsingState state = ParsingState.InProgress; + + internal StatementParser() + { + statement = new StringBuilder(); + + parsingStrategies = + [ + new QuotesParsingStrategy(), + new MultiLineCommentParsingStrategy(), + new SingleLineCommentParsingStrategy(), + new SemicolonParsingStrategy(), + new ShouldCallParsingStrategy(), + new WhichParsingStrategy(), + new AwaitParsingStrategy(), + new AddNonEmptySymbolParsingStrategy() + ]; + } + + /// + /// Gets the identifiers preceding a Should or Which clause as extracted from lines of code passed to + /// + public string[] Identifiers => candidates.ToArray(); + + public void Append(string symbols) + { + using var symbolEnumerator = symbols.GetEnumerator(); + + while (symbolEnumerator.MoveNext() && state != ParsingState.Completed) + { + // The logic ensures that parsing does not continue with irrelevant strategies when a strategy is currently in the middle + // of a multi-symbol context (e.g., waiting for "_/" to match the beginning "/_"). In such cases, it skips over strategies + // that aren't waiting for the "end of context" while allowing the active (waiting) strategy to resume processing. + IEnumerable activeParsers = parsingStrategies; + if (parsingStrategies.Exists(s => s.IsWaitingForContextEnd())) + { + activeParsers = parsingStrategies.SkipWhile(parsingStrategy => !parsingStrategy.IsWaitingForContextEnd()); + } + + state = ParsingState.InProgress; + + foreach (IParsingStrategy parser in activeParsers) + { + state = parser.Parse(symbolEnumerator.Current, statement); + if (state == ParsingState.CandidateFound) + { + candidates.Add(statement.ToString()); + } + + if (state != ParsingState.InProgress) + { + break; + } + } + } + + if (IsDone()) + { + return; + } + + parsingStrategies.ForEach(strategy => strategy.NotifyEndOfLineReached()); + } + + public bool IsDone() => state == ParsingState.Completed; +} diff --git a/Src/FluentAssertions/CallerIdentification/WhichParsingStrategy.cs b/Src/FluentAssertions/CallerIdentification/WhichParsingStrategy.cs new file mode 100644 index 0000000000..872deb78e5 --- /dev/null +++ b/Src/FluentAssertions/CallerIdentification/WhichParsingStrategy.cs @@ -0,0 +1,54 @@ +using System.Text; + +namespace FluentAssertions.CallerIdentification; + +/// +/// Tries to find the .Which. construct and assumes everything preceding it has become irrelevant +/// for the chained assertion. +/// +internal class WhichParsingStrategy : IParsingStrategy +{ + private const string ExpectedPhrase = ".Which."; + + public ParsingState Parse(char symbol, StringBuilder statement) + { + if (IsLongEnough(statement) && EndsWithExpectedPhrase(statement)) + { + // Remove everything we collected up to know and assume everything following the + // .Which. property is a new assertion + statement.Clear(); + } + + return ParsingState.InProgress; + } + + private static bool IsLongEnough(StringBuilder statement) => statement.Length >= ExpectedPhrase.Length; + + private static bool EndsWithExpectedPhrase(StringBuilder statement) + { + // Start from the index of the last character + var rightIndexInStatement = statement.Length - 1; + + var rightIndexInExpectedPhrase = ExpectedPhrase.Length - 1; + + // Do a reverse comparison to see if the statement ends with ".Which." + for (var i = 0; i < ExpectedPhrase.Length; i++) + { + if (statement[rightIndexInStatement - i] != ExpectedPhrase[rightIndexInExpectedPhrase - i]) + { + return false; + } + } + + return true; + } + + public bool IsWaitingForContextEnd() + { + return false; + } + + public void NotifyEndOfLineReached() + { + } +} diff --git a/Src/FluentAssertions/CallerIdentifier.cs b/Src/FluentAssertions/CallerIdentifier.cs index b94f66d1f3..4948e4ea0f 100644 --- a/Src/FluentAssertions/CallerIdentifier.cs +++ b/Src/FluentAssertions/CallerIdentifier.cs @@ -1,8 +1,10 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; +using System.Reflection; using System.Text.RegularExpressions; using System.Threading; using FluentAssertions.CallerIdentification; @@ -18,9 +20,28 @@ public static class CallerIdentifier { public static Action Logger { get; set; } = _ => { }; + /// + /// Gets the identifier that precedes the first Should call in the chain. + /// public static string DetermineCallerIdentity() { - string caller = null; + return DetermineCallerIdentities().FirstOrDefault(); + } + + /// + /// Gets all identifiers of all assertions in order of appearance. + /// + /// + /// For example, given the following code + /// collection.Should().ContainSingle() + /// .Which.Parameters.Should().ContainSingle() + /// .Which.Should().Be(3) + /// + /// will return collection, Parameters and an empty string. + /// + public static string[] DetermineCallerIdentities() + { + string[] callers = []; try { @@ -56,7 +77,7 @@ public static string DetermineCallerIdentity() && !IsCustomAssertion(frame) && !IsCurrentAssembly(frame)) { - caller = ExtractVariableNameFrom(frame); + callers = ExtractCallersFrom(frame).ToArray(); break; } } @@ -67,7 +88,7 @@ public static string DetermineCallerIdentity() Logger(e.ToString()); } - return caller; + return callers; } private sealed class StackFrameReference : IDisposable @@ -132,7 +153,16 @@ internal static bool OnlyOneFluentAssertionScopeOnCallStack() private static bool IsCustomAssertion(StackFrame frame) { - return frame.GetMethod()?.IsDecoratedWithOrInherit() == true; + MethodBase getMethod = frame.GetMethod(); + + if (getMethod is not null) + { + return + getMethod.IsDecoratedWithOrInherit() || + getMethod.ReflectedType?.Assembly.IsDefined(typeof(CustomAssertionsAssemblyAttribute)) == true; + } + + return false; } private static bool IsDynamic(StackFrame frame) @@ -159,33 +189,28 @@ private static bool IsCompilerServices(StackFrame frame) return frame.GetMethod()?.DeclaringType?.Namespace is "System.Runtime.CompilerServices"; } - private static string ExtractVariableNameFrom(StackFrame frame) + private static IEnumerable ExtractCallersFrom(StackFrame frame) { - string caller = null; - string statement = GetSourceCodeStatementFrom(frame); - - if (!string.IsNullOrEmpty(statement)) + foreach (string identifier in GetCallerIdentifiersFrom(frame)) { - Logger(statement); + Logger(identifier); - if (!IsBooleanLiteral(statement) && !IsNumeric(statement) && !IsStringLiteral(statement) && - !StartsWithNewKeyword(statement)) + if (!IsBooleanLiteral(identifier) && !IsNumeric(identifier) && !IsStringLiteral(identifier) && + !StartsWithNewKeyword(identifier)) { - caller = statement; + yield return identifier; } } - - return caller; } - private static string GetSourceCodeStatementFrom(StackFrame frame) + private static string[] GetCallerIdentifiersFrom(StackFrame frame) { string fileName = frame.GetFileName(); int expectedLineNumber = frame.GetFileLineNumber(); if (string.IsNullOrEmpty(fileName) || expectedLineNumber == 0) { - return null; + return []; } try @@ -199,19 +224,18 @@ private static string GetSourceCodeStatementFrom(StackFrame frame) currentLine++; } - return currentLine == expectedLineNumber - && line != null - ? GetSourceCodeStatementFrom(frame, reader, line) + return currentLine == expectedLineNumber && line != null + ? GetCallerIdentifiersFrom(frame, reader, line) : null; } catch { // We don't care. Just assume the symbol file is not available or unreadable - return null; + return []; } } - private static string GetSourceCodeStatementFrom(StackFrame frame, StreamReader reader, string line) + private static string[] GetCallerIdentifiersFrom(StackFrame frame, StreamReader reader, string line) { int column = frame.GetFileColumnNumber(); @@ -220,15 +244,15 @@ private static string GetSourceCodeStatementFrom(StackFrame frame, StreamReader line = line.Substring(Math.Min(column - 1, line.Length - 1)); } - var sb = new CallerStatementBuilder(); + var parser = new StatementParser(); do { - sb.Append(line); + parser.Append(line); } - while (!sb.IsDone() && (line = reader.ReadLine()) != null); + while (!parser.IsDone() && (line = reader.ReadLine()) != null); - return sb.ToString(); + return parser.Identifiers; } private static bool StartsWithNewKeyword(string candidate) @@ -258,7 +282,7 @@ private static StackFrame[] GetFrames(StackTrace stack) #if !NET6_0_OR_GREATER if (frames == null) { - return Array.Empty(); + return []; } #endif return frames diff --git a/Src/FluentAssertions/Collections/GenericCollectionAssertions.cs b/Src/FluentAssertions/Collections/GenericCollectionAssertions.cs index 5c393ae628..afff6d0f95 100644 --- a/Src/FluentAssertions/Collections/GenericCollectionAssertions.cs +++ b/Src/FluentAssertions/Collections/GenericCollectionAssertions.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; @@ -18,8 +17,8 @@ namespace FluentAssertions.Collections; [DebuggerNonUserCode] public class GenericCollectionAssertions : GenericCollectionAssertions, T, GenericCollectionAssertions> { - public GenericCollectionAssertions(IEnumerable actualValue) - : base(actualValue) + public GenericCollectionAssertions(IEnumerable actualValue, AssertionChain assertionChain) + : base(actualValue, assertionChain) { } } @@ -29,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,31 +66,30 @@ public GenericCollectionAssertions(TCollection actualValue) /// /// Zero or more objects to format using the placeholders in . /// - public AndWhichConstraint> AllBeAssignableTo(string because = "", + public AndWhichConstraint> AllBeAssignableTo( + [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 .", typeof(TExpectation).FullName); - IEnumerable matches = Enumerable.Empty(); + 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); @@ -105,24 +107,23 @@ public AndWhichConstraint> AllBeAssignabl /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint AllBeAssignableTo(Type expectedType, string because = "", params object[] becauseArgs) + public AndConstraint AllBeAssignableTo(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.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); } @@ -136,7 +137,7 @@ public AndConstraint AllBeAssignableTo(Type expectedType, string be /// and the result is equal. /// The type of a collection property is ignored as long as the collection implements and all /// items in the collection are structurally equal. - /// Notice that actual behavior is determined by the global defaults managed by . + /// Notice that actual behavior is determined by the global defaults managed by . /// /// The expected element. /// @@ -147,7 +148,7 @@ public AndConstraint AllBeAssignableTo(Type expectedType, string be /// Zero or more objects to format using the placeholders in . /// public AndConstraint AllBeEquivalentTo(TExpectation expectation, - string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return AllBeEquivalentTo(expectation, options => options, because, becauseArgs); } @@ -161,14 +162,14 @@ public AndConstraint AllBeEquivalentTo(TExpectation e /// and the result is equal. /// The type of a collection property is ignored as long as the collection implements and all /// items in the collection are structurally equal. - /// Notice that actual behavior is determined by the global defaults managed by . + /// Notice that actual behavior is determined by the global defaults managed by . /// /// The expected element. /// - /// A reference to the configuration object that can be used + /// A reference to the configuration object that can be used /// to influence the way the object graphs are compared. You can also provide an alternative instance of the - /// class. The global defaults are determined by the - /// class. + /// class. The global defaults are determined by the + /// class. /// /// /// A formatted phrase as is supported by explaining why the assertion @@ -179,8 +180,8 @@ public AndConstraint AllBeEquivalentTo(TExpectation e /// /// is . public AndConstraint AllBeEquivalentTo(TExpectation expectation, - Func, EquivalencyAssertionOptions> config, - string because = "", + Func, EquivalencyOptions> config, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(config); @@ -193,7 +194,7 @@ public AndConstraint AllBeEquivalentTo(TExpectation e // in case user needs to use them. Strict ordering improves algorithmic complexity // from O(n^2) to O(n). For bigger tables it is necessary in order to achieve acceptable // execution times. - Func, EquivalencyAssertionOptions> forceStrictOrderingConfig = + Func, EquivalencyOptions> forceStrictOrderingConfig = x => config(x).WithStrictOrderingFor(s => string.IsNullOrEmpty(s.Path)); return BeEquivalentTo(repeatedExpectation, forceStrictOrderingConfig, because, becauseArgs); @@ -210,31 +211,30 @@ public AndConstraint AllBeEquivalentTo(TExpectation e /// /// Zero or more objects to format using the placeholders in . /// - public AndWhichConstraint> AllBeOfType(string because = "", + public AndWhichConstraint> AllBeOfType( + [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 .", typeof(TExpectation).FullName); - IEnumerable matches = Enumerable.Empty(); + 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) == 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); @@ -252,24 +252,23 @@ public AndWhichConstraint> AllBeOfType. /// /// is . - public AndConstraint AllBeOfType(Type expectedType, 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); } @@ -284,20 +283,19 @@ public AndConstraint AllBeOfType(Type expectedType, string because /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeEmpty(string because = "", params object[] becauseArgs) + 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); } @@ -311,7 +309,7 @@ public AndConstraint BeEmpty(string because = "", params object[] b /// and the result is equal. /// The type of a collection property is ignored as long as the collection implements and all /// items in the collection are structurally equal. - /// Notice that actual behavior is determined by the global defaults managed by . + /// Notice that actual behavior is determined by the global defaults managed by . /// /// An with the expected elements. /// @@ -322,7 +320,7 @@ public AndConstraint BeEmpty(string because = "", params object[] b /// Zero or more objects to format using the placeholders in . /// public AndConstraint BeEquivalentTo(IEnumerable expectation, - string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return BeEquivalentTo(expectation, config => config, because, becauseArgs); } @@ -336,14 +334,14 @@ public AndConstraint BeEquivalentTo(IEnumerable and all /// items in the collection are structurally equal. - /// Notice that actual behavior is determined by the global defaults managed by . + /// Notice that actual behavior is determined by the global defaults managed by . /// /// An with the expected elements. /// - /// A reference to the configuration object that can be used + /// A reference to the configuration object that can be used /// to influence the way the object graphs are compared. You can also provide an alternative instance of the - /// class. The global defaults are determined by the - /// class. + /// class. The global defaults are determined by the + /// class. /// /// /// A formatted phrase as is supported by explaining why the assertion @@ -354,16 +352,18 @@ public AndConstraint BeEquivalentTo(IEnumerable /// is . public AndConstraint BeEquivalentTo(IEnumerable expectation, - Func, EquivalencyAssertionOptions> config, string because = "", + Func, EquivalencyOptions> config, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(config); - EquivalencyAssertionOptions> options = - config(AssertionOptions.CloneDefaults()).AsCollection(); + EquivalencyOptions> options = + config(AssertionConfiguration.Current.Equivalency.CloneDefaults()).AsCollection(); var context = - new EquivalencyValidationContext(Node.From>(() => AssertionScope.Current.CallerIdentity), + new EquivalencyValidationContext( + Node.From>(() => CallerIdentifier.DetermineCallerIdentity()), options) { Reason = new Reason(because, becauseArgs), @@ -400,7 +400,8 @@ public AndConstraint BeEquivalentTo(IEnumerable public AndConstraint> BeInAscendingOrder( - Expression> propertyExpression, string because = "", params object[] becauseArgs) + Expression> propertyExpression, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return BeInAscendingOrder(propertyExpression, GetComparer(), because, becauseArgs); } @@ -424,7 +425,7 @@ public AndConstraint> BeInAscendingOrder /// is . public AndConstraint> BeInAscendingOrder( - IComparer comparer, 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."); @@ -454,7 +455,8 @@ public AndConstraint> BeInAscendingOrder( /// /// is . public AndConstraint> BeInAscendingOrder( - Expression> propertyExpression, IComparer comparer, string because = "", + Expression> propertyExpression, IComparer comparer, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(comparer, nameof(comparer), @@ -477,7 +479,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(string because = "", params object[] becauseArgs) + public AndConstraint> BeInAscendingOrder( + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return BeInAscendingOrder(GetComparer(), because, becauseArgs); } @@ -499,7 +502,8 @@ public AndConstraint> BeInAscendingOrder(string /// /// 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, string because = "", + public AndConstraint> BeInAscendingOrder(Func comparison, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return BeInOrder(Comparer.Create((x, y) => comparison(x, y)), SortOrder.Ascending, because, becauseArgs); @@ -523,7 +527,8 @@ public AndConstraint> BeInAscendingOrder(Func public AndConstraint> BeInDescendingOrder( - Expression> propertyExpression, string because = "", params object[] becauseArgs) + Expression> propertyExpression, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { return BeInDescendingOrder(propertyExpression, GetComparer(), because, becauseArgs); } @@ -547,7 +552,7 @@ public AndConstraint> BeInDescendingOrder /// is . public AndConstraint> BeInDescendingOrder( - IComparer comparer, 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."); @@ -577,8 +582,8 @@ public AndConstraint> BeInDescendingOrder( /// /// is . public AndConstraint> BeInDescendingOrder( - Expression> propertyExpression, IComparer comparer, string because = "", - params object[] becauseArgs) + Expression> propertyExpression, IComparer comparer, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(comparer, nameof(comparer), "Cannot assert collection ordering without specifying a comparer."); @@ -600,7 +605,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(string because = "", params object[] becauseArgs) + public AndConstraint> BeInDescendingOrder( + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return BeInDescendingOrder(GetComparer(), because, becauseArgs); } @@ -622,8 +628,8 @@ public AndConstraint> BeInDescendingOrder(string /// /// 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, string because = "", - params object[] becauseArgs) + public AndConstraint> BeInDescendingOrder(Func comparison, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return BeInOrder(Comparer.Create((x, y) => comparison(x, y)), SortOrder.Descending, because, becauseArgs); } @@ -638,12 +644,13 @@ public AndConstraint> BeInDescendingOrder(Func /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeNullOrEmpty(string because = "", params object[] becauseArgs) + public AndConstraint BeNullOrEmpty( + [StringSyntax("CompositeFormat")] 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}.", @@ -664,24 +671,23 @@ public AndConstraint BeNullOrEmpty(string because = "", params obje /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint BeSubsetOf(IEnumerable expectedSuperset, string because = "", + public AndConstraint BeSubsetOf(IEnumerable expectedSuperset, + [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); } @@ -697,20 +703,21 @@ public AndConstraint BeSubsetOf(IEnumerable expectedSuperset, st /// /// Zero or more objects to format using the placeholders in . /// - public AndWhichConstraint Contain(T expected, 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 = Enumerable.Empty(); + 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); @@ -718,7 +725,7 @@ public AndWhichConstraint Contain(T expected, string because = " matches = collection.Where(item => EqualityComparer.Default.Equals(item, expected)); } - return new AndWhichConstraint((TAssertions)this, matches); + return new AndWhichConstraint((TAssertions)this, matches, assertionChain); } /// @@ -733,31 +740,42 @@ public AndWhichConstraint Contain(T expected, string because = " /// Zero or more objects to format using the placeholders in . /// /// is . - public AndWhichConstraint Contain(Expression> predicate, string because = "", + public AndWhichConstraint Contain(Expression> predicate, + [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 contain {0}{reason}, but found .", predicate.Body); - IEnumerable matches = Enumerable.Empty(); + 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); } - return new AndWhichConstraint((TAssertions)this, matches); + return new AndWhichConstraint((TAssertions)this, matches, assertionChain, $"[{firstMatchingIndex}]"); } /// @@ -774,34 +792,35 @@ public AndWhichConstraint Contain(Expression> pred /// /// is . /// is empty. - public AndConstraint Contain(IEnumerable expected, 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); + IEnumerable missingItems = expectedObjects.Except(Subject!); if (missingItems.Any()) { 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()); @@ -823,7 +842,7 @@ public AndConstraint Contain(IEnumerable expected, string becaus /// /// By default, objects within the collection are seen as equivalent to the expected object when both object graphs have equally named properties with the same /// value, irrespective of the type of those objects. - /// Notice that actual behavior is determined by the global defaults managed by . + /// Notice that actual behavior is determined by the global defaults managed by . /// /// /// The expected element. @@ -834,7 +853,8 @@ public AndConstraint Contain(IEnumerable expected, string becaus /// /// Zero or more objects to format using the placeholders in . /// - public AndWhichConstraint ContainEquivalentOf(TExpectation expectation, string because = "", + public AndWhichConstraint ContainEquivalentOf(TExpectation expectation, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return ContainEquivalentOf(expectation, config => config, because, becauseArgs); @@ -851,15 +871,15 @@ public AndWhichConstraint ContainEquivalentOf(TExp /// /// By default, objects within the collection are seen as equivalent to the expected object when both object graphs have equally named properties with the same /// value, irrespective of the type of those objects. - /// Notice that actual behavior is determined by the global defaults managed by . + /// Notice that actual behavior is determined by the global defaults managed by . /// /// /// The expected element. /// - /// A reference to the configuration object that can be used + /// A reference to the configuration object that can be used /// to influence the way the object graphs are compared. You can also provide an alternative instance of the - /// class. The global defaults are determined by the - /// class. + /// class. The global defaults are determined by the + /// class. /// /// /// A formatted phrase as is supported by explaining why the assertion @@ -870,27 +890,29 @@ public AndWhichConstraint ContainEquivalentOf(TExp /// /// is . public AndWhichConstraint ContainEquivalentOf(TExpectation expectation, - Func, - EquivalencyAssertionOptions> config, 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) { - EquivalencyAssertionOptions options = config(AssertionOptions.CloneDefaults()); + EquivalencyOptions options = + config(AssertionConfiguration.Current.Equivalency.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), @@ -910,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); } @@ -947,17 +969,18 @@ public AndConstraint ContainInOrder(params T[] expected) /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint ContainInOrder(IEnumerable expected, string because = "", + public AndConstraint ContainInOrder(IEnumerable expected, + [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(); @@ -971,7 +994,7 @@ public AndConstraint ContainInOrder(IEnumerable expected, string if (subjectIndex == -1) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith( "Expected {context:collection} {0} to contain items {1} in order{reason}" + @@ -1009,17 +1032,18 @@ public AndConstraint ContainInConsecutiveOrder(params T[] expected) /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint ContainInConsecutiveOrder(IEnumerable expected, string because = "", + public AndConstraint ContainInConsecutiveOrder(IEnumerable expected, + [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(); @@ -1051,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}" + @@ -1072,20 +1096,19 @@ public AndConstraint ContainInConsecutiveOrder(IEnumerable expec /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint ContainItemsAssignableTo(string because = "", params object[] becauseArgs) + public AndConstraint ContainItemsAssignableTo( + [StringSyntax("CompositeFormat")] 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); } @@ -1100,7 +1123,8 @@ public AndConstraint ContainItemsAssignableTo(string /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotContainItemsAssignableTo(string because = "", params object[] becauseArgs) => + public AndConstraint NotContainItemsAssignableTo( + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) => NotContainItemsAssignableTo(typeof(TExpectation), because, becauseArgs); /// @@ -1116,22 +1140,21 @@ public AndConstraint NotContainItemsAssignableTo(stri /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotContainItemsAssignableTo(Type type, string because = "", params object[] becauseArgs) + public AndConstraint NotContainItemsAssignableTo(Type type, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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); } @@ -1146,40 +1169,49 @@ public AndConstraint NotContainItemsAssignableTo(Type type, string /// /// Zero or more objects to format using the placeholders in . /// - public AndWhichConstraint ContainSingle(string because = "", params object[] becauseArgs) + public AndWhichConstraint ContainSingle( + [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 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."); break; + } + case 1: // Success Condition + { 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); break; + } } } - return new AndWhichConstraint((TAssertions)this, match); + return new AndWhichConstraint((TAssertions)this, match, assertionChain, "[0]"); } /// @@ -1195,25 +1227,25 @@ public AndWhichConstraint ContainSingle(string because = "", par /// /// is . public AndWhichConstraint ContainSingle(Expression> predicate, - string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] 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 = Array.Empty(); + 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); @@ -1223,13 +1255,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.", @@ -1241,7 +1273,7 @@ public AndWhichConstraint ContainSingle(Expression } } - return new AndWhichConstraint((TAssertions)this, matches); + return new AndWhichConstraint((TAssertions)this, matches, assertionChain, "[0]"); } /// @@ -1258,7 +1290,8 @@ public AndWhichConstraint ContainSingle(Expression /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint EndWith(IEnumerable expectation, 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); } @@ -1282,7 +1315,8 @@ public AndConstraint EndWith(IEnumerable expectation, string bec /// /// is . public AndConstraint EndWith( - IEnumerable expectation, Func equalityComparison, string because = "", + IEnumerable expectation, Func equalityComparison, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(expectation, nameof(expectation), "Cannot compare collection with ."); @@ -1305,9 +1339,10 @@ public AndConstraint EndWith( /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint EndWith(T element, string because = "", params object[] becauseArgs) + public AndConstraint EndWith(T element, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - return EndWith(new[] { element }, ObjectExtensions.GetComparer(), because, becauseArgs); + return EndWith([element], ObjectExtensions.GetComparer(), because, becauseArgs); } /// @@ -1340,7 +1375,8 @@ public AndConstraint Equal(params T[] elements) /// Zero or more objects to format using the placeholders in . /// public AndConstraint Equal( - IEnumerable expectation, Func equalityComparison, string because = "", + IEnumerable expectation, Func equalityComparison, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { AssertSubjectEquality(expectation, equalityComparison, because, becauseArgs); @@ -1360,7 +1396,8 @@ public AndConstraint Equal( /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint Equal(IEnumerable expected, string because = "", params object[] becauseArgs) + public AndConstraint Equal(IEnumerable expected, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { AssertSubjectEquality(expected, ObjectExtensions.GetComparer(), because, becauseArgs); @@ -1378,18 +1415,19 @@ public AndConstraint Equal(IEnumerable expected, string because /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveCount(int expected, 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(); + int actualCount = Subject!.Count(); - Execute.Assertion + assertionChain .ForCondition(actualCount == expected) .BecauseOf(because, becauseArgs) .FailWith( @@ -1412,26 +1450,27 @@ public AndConstraint HaveCount(int expected, string because = "", p /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint HaveCount(Expression> countPredicate, string because = "", + public AndConstraint HaveCount(Expression> countPredicate, + [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(); - int actualCount = Subject.Count(); + int actualCount = Subject!.Count(); 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); @@ -1452,29 +1491,24 @@ public AndConstraint HaveCount(Expression> countPre /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveCountGreaterThanOrEqualTo(int expected, string because = "", + public AndConstraint HaveCountGreaterThanOrEqualTo(int expected, + [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); } - [EditorBrowsable(EditorBrowsableState.Never)] - public AndConstraint HaveCountGreaterOrEqualTo(int expected, string because = "", params object[] becauseArgs) => - HaveCountGreaterThanOrEqualTo(expected, because, becauseArgs); - /// /// Asserts that the number of items in the collection is greater than the supplied amount. /// @@ -1486,20 +1520,19 @@ public AndConstraint HaveCountGreaterOrEqualTo(int expected, string /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveCountGreaterThan(int expected, 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); } @@ -1515,28 +1548,23 @@ public AndConstraint HaveCountGreaterThan(int expected, string beca /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveCountLessThanOrEqualTo(int expected, string because = "", params object[] becauseArgs) + 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); } - [EditorBrowsable(EditorBrowsableState.Never)] - public AndConstraint HaveCountLessOrEqualTo(int expected, string because = "", params object[] becauseArgs) => - HaveCountLessThanOrEqualTo(expected, because, becauseArgs); - /// /// Asserts that the number of items in the collection is less than the supplied amount. /// @@ -1548,20 +1576,19 @@ public AndConstraint HaveCountLessOrEqualTo(int expected, string be /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveCountLessThan(int expected, 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); } @@ -1579,36 +1606,37 @@ public AndConstraint HaveCountLessThan(int expected, string because /// /// Zero or more objects to format using the placeholders in . /// - public AndWhichConstraint HaveElementAt(int index, T element, string because = "", + public AndWhichConstraint HaveElementAt(int index, T element, + [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()) + 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}]"); } /// @@ -1623,27 +1651,27 @@ public AndWhichConstraint HaveElementAt(int index, T element, st /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveElementPreceding(T successor, T expectation, string because = "", + public AndConstraint HaveElementPreceding(T successor, T expectation, + [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); } @@ -1660,27 +1688,27 @@ public AndConstraint HaveElementPreceding(T successor, T expectatio /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveElementSucceeding(T predecessor, T expectation, string because = "", + public AndConstraint HaveElementSucceeding(T predecessor, T expectation, + [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); } @@ -1697,23 +1725,22 @@ public AndConstraint HaveElementSucceeding(T predecessor, T expecta /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint HaveSameCount(IEnumerable otherCollection, string because = "", + public AndConstraint HaveSameCount(IEnumerable otherCollection, + [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); } @@ -1730,22 +1757,23 @@ public AndConstraint HaveSameCount(IEnumerable. /// /// is . - public AndConstraint IntersectWith(IEnumerable otherCollection, string because = "", + public AndConstraint IntersectWith(IEnumerable otherCollection, + [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); + IEnumerable sharedItems = Subject!.Intersect(otherCollection); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(sharedItems.Any()) .FailWith( @@ -1766,19 +1794,18 @@ public AndConstraint IntersectWith(IEnumerable otherCollection, /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeEmpty(string because = "", params object[] becauseArgs) + public AndConstraint NotBeEmpty( + [StringSyntax("CompositeFormat")] 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); } @@ -1796,21 +1823,22 @@ public AndConstraint NotBeEmpty(string because = "", params object[ /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint NotBeEquivalentTo(IEnumerable unexpected, string because = "", + public AndConstraint NotBeEquivalentTo(IEnumerable unexpected, + [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.", @@ -1827,10 +1855,10 @@ public AndConstraint NotBeEquivalentTo(IEnumerable /// An with the unexpected elements. /// /// - /// A reference to the configuration object that can be used + /// A reference to the configuration object that can be used /// to influence the way the object graphs are compared. You can also provide an alternative instance of the - /// class. The global defaults are determined by the - /// class. + /// class. The global defaults are determined by the + /// class. /// /// /// A formatted phrase as is supported by explaining why the assertion @@ -1840,14 +1868,14 @@ public AndConstraint NotBeEquivalentTo(IEnumerable. /// public AndConstraint NotBeEquivalentTo(IEnumerable unexpected, - Func, EquivalencyAssertionOptions> config, - string because = "", params object[] becauseArgs) + Func, EquivalencyOptions> config, + [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 ."); } @@ -1861,7 +1889,7 @@ public AndConstraint NotBeEquivalentTo(IEnumerable 0) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:collection} {0} not to be equivalent to collection {1}{reason}.", Subject, @@ -1888,7 +1916,8 @@ public AndConstraint NotBeEquivalentTo(IEnumerable public AndConstraint NotBeInAscendingOrder( - Expression> propertyExpression, string because = "", params object[] becauseArgs) + Expression> propertyExpression, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { return NotBeInAscendingOrder(propertyExpression, GetComparer(), because, becauseArgs); } @@ -1912,7 +1941,7 @@ public AndConstraint NotBeInAscendingOrder( /// /// is . public AndConstraint NotBeInAscendingOrder( - IComparer comparer, 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."); @@ -1942,7 +1971,8 @@ public AndConstraint NotBeInAscendingOrder( /// /// is . public AndConstraint NotBeInAscendingOrder( - Expression> propertyExpression, IComparer comparer, string because = "", + Expression> propertyExpression, IComparer comparer, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(comparer, nameof(comparer), @@ -1965,7 +1995,8 @@ 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(string because = "", params object[] becauseArgs) + public AndConstraint NotBeInAscendingOrder( + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return NotBeInAscendingOrder(GetComparer(), because, becauseArgs); } @@ -1986,7 +2017,8 @@ public AndConstraint NotBeInAscendingOrder(string because = "", par /// /// 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, string because = "", + public AndConstraint NotBeInAscendingOrder(Func comparison, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return NotBeInOrder(Comparer.Create((x, y) => comparison(x, y)), SortOrder.Ascending, because, becauseArgs); @@ -2010,7 +2042,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, string because = "", params object[] becauseArgs) + Expression> propertyExpression, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { return NotBeInDescendingOrder(propertyExpression, GetComparer(), because, becauseArgs); } @@ -2034,7 +2067,7 @@ public AndConstraint NotBeInDescendingOrder( /// /// is . public AndConstraint NotBeInDescendingOrder( - IComparer comparer, 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."); @@ -2064,7 +2097,8 @@ public AndConstraint NotBeInDescendingOrder( /// /// is . public AndConstraint NotBeInDescendingOrder( - Expression> propertyExpression, IComparer comparer, string because = "", + Expression> propertyExpression, IComparer comparer, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(comparer, nameof(comparer), @@ -2087,7 +2121,8 @@ 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(string because = "", params object[] becauseArgs) + public AndConstraint NotBeInDescendingOrder( + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return NotBeInDescendingOrder(GetComparer(), because, becauseArgs); } @@ -2108,7 +2143,8 @@ public AndConstraint NotBeInDescendingOrder(string because = "", pa /// /// 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, string because = "", + public AndConstraint NotBeInDescendingOrder(Func comparison, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return NotBeInOrder(Comparer.Create((x, y) => comparison(x, y)), SortOrder.Descending, because, becauseArgs); @@ -2124,7 +2160,8 @@ public AndConstraint NotBeInDescendingOrder(Func compari /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeNullOrEmpty(string because = "", params object[] becauseArgs) + public AndConstraint NotBeNullOrEmpty( + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return NotBeNull(because, becauseArgs) .And.NotBeEmpty(because, becauseArgs); @@ -2141,19 +2178,20 @@ public AndConstraint NotBeNullOrEmpty(string because = "", params o /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeSubsetOf(IEnumerable unexpectedSuperset, string because = "", + public AndConstraint NotBeSubsetOf(IEnumerable unexpectedSuperset, + [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.", @@ -2165,7 +2203,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); @@ -2186,30 +2224,27 @@ public AndConstraint NotBeSubsetOf(IEnumerable unexpectedSuperse /// /// Zero or more objects to format using the placeholders in . /// - public AndWhichConstraint NotContain(T unexpected, 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 = Enumerable.Empty(); - - 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); } /// @@ -2224,22 +2259,23 @@ public AndWhichConstraint NotContain(T unexpected, string becaus /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint NotContain(Expression> predicate, string because = "", + public AndConstraint NotContain(Expression> predicate, + [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)); + 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}.", @@ -2263,7 +2299,8 @@ public AndConstraint NotContain(Expression> predicate /// /// is . /// is empty. - public AndConstraint NotContain(IEnumerable unexpected, 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"); @@ -2272,27 +2309,27 @@ public AndConstraint NotContain(IEnumerable unexpected, string b 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); + IEnumerable foundItems = unexpectedObjects.Intersect(Subject!); if (foundItems.Any()) { 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()); @@ -2314,7 +2351,7 @@ public AndConstraint NotContain(IEnumerable unexpected, string b /// /// By default, objects within the collection are seen as not equivalent to the expected object when both object graphs have unequally named properties with the same /// value, irrespective of the type of those objects. - /// Notice that actual behavior is determined by the global defaults managed by . + /// Notice that actual behavior is determined by the global defaults managed by . /// /// /// The unexpected element. @@ -2325,7 +2362,8 @@ public AndConstraint NotContain(IEnumerable unexpected, string b /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotContainEquivalentOf(TExpectation unexpected, string because = "", + public AndConstraint NotContainEquivalentOf(TExpectation unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return NotContainEquivalentOf(unexpected, config => config, because, becauseArgs); @@ -2342,15 +2380,15 @@ public AndConstraint NotContainEquivalentOf(TExpectat /// /// By default, objects within the collection are seen as not equivalent to the expected object when both object graphs have unequally named properties with the same /// value, irrespective of the type of those objects. - /// Notice that actual behavior is determined by the global defaults managed by . + /// Notice that actual behavior is determined by the global defaults managed by . /// /// /// The unexpected element. /// - /// A reference to the configuration object that can be used + /// A reference to the configuration object that can be used /// to influence the way the object graphs are compared. You can also provide an alternative instance of the - /// class. The global defaults are determined by the - /// class. + /// class. The global defaults are determined by the + /// class. /// /// /// A formatted phrase as is supported by explaining why the assertion @@ -2362,20 +2400,22 @@ public AndConstraint NotContainEquivalentOf(TExpectat /// is . [SuppressMessage("Design", "MA0051:Method is too long", Justification = "Needs refactoring")] public AndConstraint NotContainEquivalentOf(TExpectation unexpected, - Func, - EquivalencyAssertionOptions> config, 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) { - EquivalencyAssertionOptions options = config(AssertionOptions.CloneDefaults()); + EquivalencyOptions options = + config(AssertionConfiguration.Current.Equivalency.CloneDefaults()); var foundIndices = new List(); @@ -2383,10 +2423,10 @@ public AndConstraint NotContainEquivalentOf(TExpectat { int index = 0; - foreach (T actualItem in Subject) + 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), @@ -2417,22 +2457,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); + } + }); } } } @@ -2468,7 +2507,8 @@ public AndConstraint NotContainInOrder(params T[] unexpected) /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint NotContainInOrder(IEnumerable unexpected, string because = "", + public AndConstraint NotContainInOrder(IEnumerable unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(unexpected, nameof(unexpected), @@ -2476,7 +2516,7 @@ public AndConstraint NotContainInOrder(IEnumerable unexpected, s if (Subject is null) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Cannot verify absence of ordered containment in a collection."); @@ -2500,7 +2540,7 @@ public AndConstraint NotContainInOrder(IEnumerable unexpected, s } } - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith( "Expected {context:collection} {0} to not contain items {1} in order{reason}, " + @@ -2539,7 +2579,8 @@ public AndConstraint NotContainInConsecutiveOrder(params T[] unexpe /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint NotContainInConsecutiveOrder(IEnumerable unexpected, string because = "", + public AndConstraint NotContainInConsecutiveOrder(IEnumerable unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(unexpected, nameof(unexpected), @@ -2547,7 +2588,7 @@ public AndConstraint NotContainInConsecutiveOrder(IEnumerable un if (Subject is null) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Cannot verify absence of ordered containment in a collection."); @@ -2577,12 +2618,12 @@ 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}, " + "but items appeared in order ending at index {2}.", - Subject, unexpectedItems, (subjectIndex + consecutiveItems) - 2); + Subject, unexpectedItems, subjectIndex + consecutiveItems - 2); } subjectIndex++; @@ -2605,26 +2646,27 @@ public AndConstraint NotContainInConsecutiveOrder(IEnumerable un /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint NotContainNulls(Expression> predicate, string because = "", + public AndConstraint NotContainNulls(Expression> predicate, + [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(); - T[] values = Subject + T[] values = Subject! .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}.", @@ -2644,16 +2686,17 @@ public AndConstraint NotContainNulls(Expression /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotContainNulls(string because = "", params object[] becauseArgs) + public AndConstraint NotContainNulls( + [StringSyntax("CompositeFormat")] 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 + int[] indices = Subject! .Select((item, index) => (Item: item, Index: index)) .Where(e => e.Item is null) .Select(e => e.Index) @@ -2663,7 +2706,7 @@ public AndConstraint NotContainNulls(string because = "", params ob { 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}.", @@ -2671,7 +2714,7 @@ public AndConstraint NotContainNulls(string because = "", params ob } else { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .FailWith("Expected {context:collection} not to contain s{reason}, but found one at index {0}.", indices[0]); @@ -2695,25 +2738,25 @@ public AndConstraint NotContainNulls(string because = "", params ob /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint NotEqual(IEnumerable unexpected, 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); } @@ -2729,20 +2772,19 @@ public AndConstraint NotEqual(IEnumerable unexpected, string bec /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveCount(int unexpected, 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); } @@ -2760,12 +2802,12 @@ public AndConstraint NotHaveCount(int unexpected, string because = /// /// is . public AndConstraint NotHaveSameCount(IEnumerable otherCollection, - string because = "", + [StringSyntax("CompositeFormat")] 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) @@ -2799,13 +2841,14 @@ public AndConstraint NotHaveSameCount(IEnumerable. /// /// is . - public AndConstraint NotIntersectWith(IEnumerable otherCollection, string because = "", + public AndConstraint NotIntersectWith(IEnumerable otherCollection, + [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) @@ -2840,28 +2883,23 @@ public AndConstraint NotIntersectWith(IEnumerable otherCollectio /// /// is . public AndConstraint OnlyContain( - Expression> predicate, 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()) - .ForCondition(collection => collection.Count > 0) - .FailWith("but the collection is empty.") - .Then - .Given(collection => collection.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); } @@ -2878,21 +2916,22 @@ public AndConstraint OnlyContain( /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint OnlyHaveUniqueItems(Expression> predicate, string because = "", + public AndConstraint OnlyHaveUniqueItems(Expression> predicate, + [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(); - IGrouping[] groupWithMultipleItems = Subject + IGrouping[] groupWithMultipleItems = Subject! .GroupBy(compiledPredicate) .Where(g => g.Count() > 1) .ToArray(); @@ -2901,7 +2940,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.", @@ -2910,7 +2949,7 @@ public AndConstraint OnlyHaveUniqueItems(Expression OnlyHaveUniqueItems(Expression /// Zero or more objects to format using the placeholders in . /// - public AndConstraint OnlyHaveUniqueItems(string because = "", params object[] becauseArgs) + public AndConstraint OnlyHaveUniqueItems( + [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 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.", @@ -2959,10 +3000,10 @@ public AndConstraint OnlyHaveUniqueItems(string because = "", param } 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]); } } } @@ -2985,23 +3026,20 @@ public AndConstraint OnlyHaveUniqueItems(string because = "", param /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint AllSatisfy(Action expected, 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 - .ForCondition(subject => subject.Any()) - .FailWith("but collection is empty.") - .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; @@ -3014,17 +3052,14 @@ public AndConstraint AllSatisfy(Action expected, string because if (failuresFromInspectors.Length > 0) { string failureMessage = Environment.NewLine - + string.Join(Environment.NewLine, failuresFromInspectors.Select(x => x.IndentLines())); + + 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); } return new AndConstraint((TAssertions)this); @@ -3062,7 +3097,8 @@ public AndConstraint SatisfyRespectively(params Action[] element /// /// is . /// is empty. - public AndConstraint SatisfyRespectively(IEnumerable> expected, string because = "", + public AndConstraint SatisfyRespectively(IEnumerable> expected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(expected, nameof(expected), "Cannot verify against a collection of inspectors"); @@ -3072,25 +3108,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; @@ -3102,15 +3136,14 @@ public AndConstraint SatisfyRespectively(IEnumerable> exp if (failuresFromInspectors.Length > 0) { string failureMessage = Environment.NewLine - + string.Join(Environment.NewLine, failuresFromInspectors.Select(x => x.IndentLines())); + + 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)); } } @@ -3152,7 +3185,8 @@ public AndConstraint Satisfy(params Expression>[] pre /// /// is . /// is empty. - public AndConstraint Satisfy(IEnumerable>> predicates, string because = "", + public AndConstraint Satisfy(IEnumerable>> predicates, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(predicates, nameof(predicates), "Cannot verify against a collection of predicates"); @@ -3162,7 +3196,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) @@ -3171,7 +3205,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(); @@ -3187,8 +3221,8 @@ public AndConstraint Satisfy(IEnumerable>> message += doubleNewLine + "The following predicates did not have matching elements:"; message += doubleNewLine + - string.Join(Environment.NewLine, - unmatchedPredicates.Select(predicate => Formatter.ToString(predicate.Expression))); + string.Join(Environment.NewLine, + unmatchedPredicates.Select(predicate => Formatter.ToString(predicate.Expression))); } List> unmatchedElements = maximumMatchingSolution.GetUnmatchedElements(); @@ -3203,10 +3237,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)); } } @@ -3228,7 +3262,8 @@ public AndConstraint Satisfy(IEnumerable>> /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint StartWith(IEnumerable expectation, 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); } @@ -3252,7 +3287,8 @@ public AndConstraint StartWith(IEnumerable expectation, string b /// /// is . public AndConstraint StartWith( - IEnumerable expectation, Func equalityComparison, string because = "", + IEnumerable expectation, Func equalityComparison, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(expectation, nameof(expectation), "Cannot compare collection with ."); @@ -3275,16 +3311,17 @@ public AndConstraint StartWith( /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint StartWith(T element, string because = "", params object[] becauseArgs) + public AndConstraint StartWith(T element, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { - return StartWith(new[] { element }, ObjectExtensions.GetComparer(), because, becauseArgs); + return StartWith([element], ObjectExtensions.GetComparer(), because, becauseArgs); } internal AndConstraint> BeOrderedBy( Expression> propertyExpression, IComparer comparer, SortOrder direction, - string because, + [StringSyntax("CompositeFormat")] string because, object[] becauseArgs) { if (IsValidProperty(propertyExpression, because, becauseArgs)) @@ -3297,18 +3334,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( @@ -3330,57 +3367,57 @@ protected static IEnumerable RepeatAsManyAs(TExpecta { if (enumerable is null) { - return Enumerable.Empty(); + return []; } return RepeatAsManyAsIterator(value, enumerable); } protected void AssertCollectionEndsWith(IEnumerable actual, - ICollection expected, Func equalityComparison, string because = "", + ICollection expected, Func equalityComparison, + [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, string because = "", + ICollection expected, Func equalityComparison, + [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, string because = "", params object[] becauseArgs) + Func equalityComparison, [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(equalityComparison); @@ -3396,21 +3433,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) @@ -3456,7 +3488,7 @@ private static T SuccessorOf(T predecessor, TCollection subject) { IList collection = subject.ConvertOrCastToList(); int index = collection.IndexOf(predecessor); - return index < (collection.Count - 1) ? collection[index + 1] : default; + return index < collection.Count - 1 ? collection[index + 1] : default; } private string[] CollectFailuresFromInspectors(IEnumerable> elementInspectors) @@ -3496,7 +3528,8 @@ private string[] CollectFailuresFromInspectors(IEnumerable> elementIns return collectionFailures; } - private bool IsValidProperty(Expression> propertyExpression, string because, + private bool IsValidProperty(Expression> propertyExpression, + [StringSyntax("CompositeFormat")] string because, object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(propertyExpression, nameof(propertyExpression), @@ -3504,18 +3537,20 @@ private bool IsValidProperty(Expression> propertyE 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, - string because, + [StringSyntax("CompositeFormat")] string because, object[] becauseArgs) { if (IsValidProperty(propertyExpression, because, becauseArgs)) @@ -3528,7 +3563,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}.", @@ -3543,18 +3578,19 @@ private AndConstraint NotBeOrderedBy( /// Elements are compared using their implementation. /// private AndConstraint> BeInOrder( - IComparer comparer, SortOrder expectedOrder, 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 = new List(0).OrderBy(x => x); + IOrderedEnumerable ordering = Array.Empty().OrderBy(x => x); - if (success) + if (assertionChain.Succeeded) { IList actualItems = Subject.ConvertOrCastToList(); @@ -3569,36 +3605,38 @@ 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.", + " 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)); } /// /// Asserts the current collection does not have all elements in ascending order. Elements are compared /// using their implementation. /// - private AndConstraint NotBeInOrder(IComparer comparer, SortOrder order, string because = "", + private AndConstraint NotBeInOrder(IComparer comparer, SortOrder order, + [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(); @@ -3612,7 +3650,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 bff6dd4974..68f3c66a4a 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 @@ -54,26 +56,26 @@ public GenericDictionaryAssertions(TCollection keyValuePairs) /// /// is . public AndConstraint Equal(T expected, - string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where T : IEnumerable> { 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); + ICollection subjectKeys = GetKeys(Subject).ConvertOrCastToCollection(); + ICollection expectedKeys = GetKeys(expected).ConvertOrCastToCollection(); IEnumerable missingKeys = expectedKeys.Except(subjectKeys); IEnumerable additionalKeys = subjectKeys.Except(expectedKeys); 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}.", @@ -117,27 +120,27 @@ public AndConstraint Equal(T expected, /// /// is . public AndConstraint NotEqual(T unexpected, - string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where T : IEnumerable> { 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."); } - IEnumerable subjectKeys = GetKeys(Subject); - IEnumerable unexpectedKeys = GetKeys(unexpected); + IEnumerable subjectKeys = GetKeys(Subject).ConvertOrCastToCollection(); + IEnumerable unexpectedKeys = GetKeys(unexpected).ConvertOrCastToCollection(); IEnumerable missingKeys = unexpectedKeys.Except(subjectKeys); IEnumerable additionalKeys = subjectKeys.Except(unexpectedKeys); @@ -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); } @@ -169,7 +172,7 @@ public AndConstraint NotEqual(T unexpected, /// and the result is equal. /// The type of the values in the dictionaries are ignored as long as both dictionaries contain the same keys and /// the values for each key are structurally equivalent. Notice that actual behavior is determined by the global - /// defaults managed by the class. + /// defaults managed by the class. /// /// The expected element. /// @@ -180,7 +183,7 @@ public AndConstraint NotEqual(T unexpected, /// Zero or more objects to format using the placeholders in . /// public AndConstraint BeEquivalentTo(TExpectation expectation, - string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return BeEquivalentTo(expectation, options => options, because, becauseArgs); } @@ -194,14 +197,14 @@ public AndConstraint BeEquivalentTo(TExpectation expe /// and the result is equal. /// The type of the values in the dictionaries are ignored as long as both dictionaries contain the same keys and /// the values for each key are structurally equivalent. Notice that actual behavior is determined by the global - /// defaults managed by the class. + /// defaults managed by the class. /// /// The expected element. /// - /// A reference to the configuration object that can be used + /// A reference to the configuration object that can be used /// to influence the way the object graphs are compared. You can also provide an alternative instance of the - /// class. The global defaults are determined by the - /// class. + /// class. The global defaults are determined by the + /// class. /// /// /// A formatted phrase as is supported by explaining why the assertion @@ -212,15 +215,15 @@ public AndConstraint BeEquivalentTo(TExpectation expe /// /// is . public AndConstraint BeEquivalentTo(TExpectation expectation, - Func, EquivalencyAssertionOptions> config, string because = "", - params object[] becauseArgs) + Func, EquivalencyOptions> config, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(config); - EquivalencyAssertionOptions options = config(AssertionOptions.CloneDefaults()); + EquivalencyOptions options = config(AssertionConfiguration.Current.Equivalency.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 @@ -253,9 +256,9 @@ public AndConstraint BeEquivalentTo(TExpectation expe /// Zero or more objects to format using the placeholders in . /// public WhoseValueConstraint ContainKey(TKey expected, - string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - AndConstraint andConstraint = ContainKeys(new[] { expected }, because, becauseArgs); + AndConstraint andConstraint = ContainKeys([expected], because, becauseArgs); _ = TryGetValue(Subject, expected, out TValue value); @@ -287,7 +290,7 @@ public AndConstraint ContainKeys(params TKey[] expected) /// is . /// is empty. public AndConstraint ContainKeys(IEnumerable expected, - string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(expected, nameof(expected), "Cannot verify key containment against a collection of keys"); @@ -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()); @@ -343,16 +347,16 @@ public AndConstraint ContainKeys(IEnumerable expected, /// Zero or more objects to format using the placeholders in . /// public AndConstraint NotContainKey(TKey unexpected, - string because = "", params object[] becauseArgs) + [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); @@ -386,7 +390,7 @@ public AndConstraint NotContainKeys(params TKey[] unexpected) /// is . /// is empty. public AndConstraint NotContainKeys(IEnumerable unexpected, - string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(unexpected, nameof(unexpected), "Cannot verify key containment against a collection of keys"); @@ -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()); @@ -444,14 +448,12 @@ public AndConstraint NotContainKeys(IEnumerable unexpected, /// Zero or more objects to format using the placeholders in . /// public AndWhichConstraint ContainValue(TValue expected, - string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - AndWhichConstraint> innerConstraint = - ContainValuesAndWhich(new[] { 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,69 +480,57 @@ public AndConstraint ContainValues(params TValue[] expected) /// /// is . /// is empty. - public AndConstraint ContainValues(IEnumerable expected, - string because = "", params object[] becauseArgs) + 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, - 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); + .FailWith("Expected {context:dictionary} to contain values {0}{reason}, but found .", expected); - IEnumerable matchedConstraint = null; - - 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 @@ -560,16 +550,16 @@ private static IEnumerable RepetitionPreservingIntersect( /// Zero or more objects to format using the placeholders in . /// public AndConstraint NotContainValue(TValue unexpected, - string because = "", params object[] becauseArgs) + [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); @@ -603,7 +593,7 @@ public AndConstraint NotContainValues(params TValue[] unexpected) /// is . /// is empty. public AndConstraint NotContainValues(IEnumerable unexpected, - string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(unexpected, nameof(unexpected), "Cannot verify value containment against a collection of values"); @@ -613,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)); @@ -626,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()); @@ -674,9 +665,9 @@ public AndConstraint Contain(params KeyValuePair[] ex /// /// is . /// is empty. - [SuppressMessage("Design", "MA0051:Method is too long", Justification = "Needs refactoring")] + [SuppressMessage("Class Design", "AV1010:Member hides inherited member")] public new AndConstraint Contain(IEnumerable> expected, - string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(expected, nameof(expected), "Cannot compare dictionary with ."); @@ -685,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)); @@ -700,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, @@ -708,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]); @@ -724,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}.", @@ -735,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); @@ -759,8 +750,9 @@ public AndConstraint Contain(params KeyValuePair[] ex /// /// Zero or more objects to format using the placeholders in . /// + [SuppressMessage("Class Design", "AV1010:Member hides inherited member")] public new AndConstraint Contain(KeyValuePair expected, - string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return Contain(expected.Key, expected.Value, because, becauseArgs); } @@ -781,21 +773,21 @@ public AndConstraint Contain(params KeyValuePair[] ex /// Zero or more objects to format using the placeholders in . /// public AndConstraint Contain(TKey key, TValue value, - string because = "", params object[] becauseArgs) + [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, @@ -803,7 +795,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, @@ -844,8 +836,9 @@ public AndConstraint NotContain(params KeyValuePair[] /// /// is . /// is empty. + [SuppressMessage("Class Design", "AV1010:Member hides inherited member")] public new AndConstraint NotContain(IEnumerable> items, - string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(items, nameof(items), "Cannot compare dictionary with ."); @@ -854,13 +847,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(); @@ -876,7 +869,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.", @@ -886,7 +879,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.", @@ -912,8 +905,9 @@ public AndConstraint NotContain(params KeyValuePair[] /// /// Zero or more objects to format using the placeholders in . /// + [SuppressMessage("Class Design", "AV1010:Member hides inherited member")] public new AndConstraint NotContain(KeyValuePair item, - string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return NotContain(item.Key, item.Value, because, becauseArgs); } @@ -934,17 +928,17 @@ public AndConstraint NotContain(params KeyValuePair[] /// Zero or more objects to format using the placeholders in . /// public AndConstraint NotContain(TKey key, TValue value, - string because = "", params object[] becauseArgs) + [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/MaximumMatching/MaximumMatchingProblem.cs b/Src/FluentAssertions/Collections/MaximumMatching/MaximumMatchingProblem.cs index 23110182a8..b5955cbe83 100644 --- a/Src/FluentAssertions/Collections/MaximumMatching/MaximumMatchingProblem.cs +++ b/Src/FluentAssertions/Collections/MaximumMatching/MaximumMatchingProblem.cs @@ -23,9 +23,9 @@ public MaximumMatchingProblem( Elements.AddRange(elements.Select((element, index) => new Element(element, index))); } - public List> Predicates { get; } = new(); + public List> Predicates { get; } = []; - public List> Elements { get; } = new(); + public List> Elements { get; } = []; public MaximumMatchingSolution Solve() => new MaximumMatchingSolver(this).Solve(); } diff --git a/Src/FluentAssertions/Collections/MaximumMatching/MaximumMatchingSolver.cs b/Src/FluentAssertions/Collections/MaximumMatching/MaximumMatchingSolver.cs index b096f61c99..8bf25ee394 100644 --- a/Src/FluentAssertions/Collections/MaximumMatching/MaximumMatchingSolver.cs +++ b/Src/FluentAssertions/Collections/MaximumMatching/MaximumMatchingSolver.cs @@ -14,7 +14,7 @@ namespace FluentAssertions.Collections.MaximumMatching; internal class MaximumMatchingSolver { private readonly MaximumMatchingProblem problem; - private readonly Dictionary, List>> matchingElementsByPredicate = new(); + private readonly Dictionary, List>> matchingElementsByPredicate = []; public MaximumMatchingSolver(MaximumMatchingProblem problem) { @@ -76,7 +76,7 @@ private IEnumerable FindMatchForPredicate(Predicate predicate, Ma } } - return Enumerable.Empty(); + return []; } private List> GetMatchingElements(Predicate predicate) @@ -98,7 +98,7 @@ private struct Match private sealed class MatchCollection : IEnumerable { - private readonly Dictionary, Match> matchesByElement = new(); + private readonly Dictionary, Match> matchesByElement = []; public void UpdateFrom(IEnumerable matches) { @@ -123,7 +123,7 @@ public Predicate GetMatchedPredicate(Element element) private sealed class BreadthFirstSearchTracker { private readonly Queue> unmatchedPredicatesQueue = new(); - private readonly Dictionary, Match> previousMatchByPredicate = new(); + private readonly Dictionary, Match> previousMatchByPredicate = []; private readonly MatchCollection originalMatches; diff --git a/Src/FluentAssertions/Collections/StringCollectionAssertions.cs b/Src/FluentAssertions/Collections/StringCollectionAssertions.cs index 070d50399f..20fcd05306 100644 --- a/Src/FluentAssertions/Collections/StringCollectionAssertions.cs +++ b/Src/FluentAssertions/Collections/StringCollectionAssertions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using FluentAssertions.Common; using FluentAssertions.Equivalency; @@ -12,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) { } } @@ -25,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) { } } @@ -35,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; } /// @@ -49,6 +53,7 @@ public StringCollectionAssertions(TCollection actualValue) /// the element order, use instead. /// /// An with the expected elements. + [SuppressMessage("Class Design", "AV1010:Member hides inherited member")] public new AndConstraint Equal(params string[] expected) { return base.Equal(expected.AsEnumerable()); @@ -92,8 +97,8 @@ public AndConstraint BeEquivalentTo(params string[] expectation) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeEquivalentTo(IEnumerable expectation, string because = "", - params object[] becauseArgs) + public AndConstraint BeEquivalentTo(IEnumerable expectation, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return BeEquivalentTo(expectation, config => config, because, becauseArgs); } @@ -107,10 +112,10 @@ public AndConstraint BeEquivalentTo(IEnumerable expectation /// /// An with the expected elements. /// - /// A reference to the configuration object that can be used + /// A reference to the configuration object that can be used /// to influence the way the object graphs are compared. You can also provide an alternative instance of the - /// class. The global defaults are determined by the - /// class. + /// class. The global defaults can be modified through + /// . /// /// /// A formatted phrase as is supported by explaining why the assertion @@ -121,16 +126,16 @@ public AndConstraint BeEquivalentTo(IEnumerable expectation /// /// is . public AndConstraint BeEquivalentTo(IEnumerable expectation, - Func, EquivalencyAssertionOptions> config, string because = "", - params object[] becauseArgs) + Func, EquivalencyOptions> config, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(config); - EquivalencyAssertionOptions> - options = config(AssertionOptions.CloneDefaults()).AsCollection(); + EquivalencyOptions> + options = config(AssertionConfiguration.Current.Equivalency.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 @@ -160,7 +165,7 @@ public AndConstraint BeEquivalentTo(IEnumerable expectation /// Zero or more objects to format using the placeholders in . /// public AndConstraint AllBe(string expectation, - string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return AllBe(expectation, options => options, because, becauseArgs); } @@ -170,10 +175,10 @@ public AndConstraint AllBe(string expectation, /// /// An expected . /// - /// A reference to the configuration object that can be used + /// A reference to the configuration object that can be used /// to influence the way the object graphs are compared. You can also provide an alternative instance of the - /// class. The global defaults are determined by the - /// class. + /// class. The global defaults are determined by the + /// class. /// /// /// A formatted phrase as is supported by explaining why the assertion @@ -184,9 +189,8 @@ public AndConstraint AllBe(string expectation, /// /// is . public AndConstraint AllBe(string expectation, - Func, EquivalencyAssertionOptions> config, - string because = "", - params object[] becauseArgs) + Func, EquivalencyOptions> config, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(config); @@ -198,7 +202,7 @@ public AndConstraint AllBe(string expectation, // in case user needs to use them. Strict ordering improves algorithmic complexity // from O(n^2) to O(n). For bigger tables it is necessary in order to achieve acceptable // execution times. - Func, EquivalencyAssertionOptions> forceStringOrderingConfig = + Func, EquivalencyOptions> forceStringOrderingConfig = x => config(x).WithStrictOrderingFor(s => string.IsNullOrEmpty(s.Path)); return BeEquivalentTo(repeatedExpectation, forceStringOrderingConfig, because, becauseArgs); @@ -239,8 +243,8 @@ public AndConstraint AllBe(string expectation, /// /// is . /// is empty. - public AndWhichConstraint ContainMatch(string wildcardPattern, string because = "", - params object[] becauseArgs) + public AndWhichConstraint ContainMatch(string wildcardPattern, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(wildcardPattern, nameof(wildcardPattern), "Cannot match strings in collection against . Provide a wildcard pattern or use the Contain method."); @@ -248,45 +252,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 = new List(0); + 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(); + int? firstMatchingIndex = null; - return Subject.Any(item => - { - item.Should().Match(wildcardPattern); - return scope.Discard().Length == 0; - }); - } - - 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); } /// @@ -324,8 +331,8 @@ private IEnumerable AllThatMatch(string wildcardPattern) /// /// is . /// is empty. - public AndConstraint NotContainMatch(string wildcardPattern, string because = "", - params object[] becauseArgs) + public AndConstraint NotContainMatch(string wildcardPattern, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(wildcardPattern, nameof(wildcardPattern), "Cannot match strings in collection against . Provide a wildcard pattern or use the NotContain method."); @@ -333,15 +340,15 @@ public AndConstraint NotContainMatch(string wildcardPattern, string 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..4d5b9b51e7 100644 --- a/Src/FluentAssertions/Collections/SubsequentOrderingAssertions.cs +++ b/Src/FluentAssertions/Collections/SubsequentOrderingAssertions.cs @@ -1,15 +1,165 @@ +using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Linq.Expressions; +using FluentAssertions.Common; +using FluentAssertions.Execution; namespace FluentAssertions.Collections; [DebuggerNonUserCode] public class SubsequentOrderingAssertions - : SubsequentOrderingGenericCollectionAssertions, T, SubsequentOrderingAssertions> + : GenericCollectionAssertions, T> { - public SubsequentOrderingAssertions(IEnumerable actualValue, IOrderedEnumerable previousOrderedEnumerable) - : base(actualValue, previousOrderedEnumerable) + private readonly IOrderedEnumerable previousOrderedEnumerable; + private bool subsequentOrdering; + + public SubsequentOrderingAssertions(IEnumerable actualValue, IOrderedEnumerable previousOrderedEnumerable, AssertionChain assertionChain) + : base(actualValue, assertionChain) + { + this.previousOrderedEnumerable = previousOrderedEnumerable; + } + + /// + /// Asserts that a subsequence is ordered in ascending order according to the value of the specified + /// . + /// + /// + /// A lambda expression that references the property that should be used to determine the expected ordering. + /// + /// + /// 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 . + /// + /// + /// Empty and single element collections are considered to be ordered both in ascending and descending order at the same time. + /// + public AndConstraint> ThenBeInAscendingOrder( + Expression> propertyExpression, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + return ThenBeInAscendingOrder(propertyExpression, GetComparer(), because, becauseArgs); + } + + /// + /// Asserts that a subsequence is ordered in ascending order according to the value of the specified + /// and implementation. + /// + /// + /// A lambda expression that references the property that should be used to determine the expected ordering. + /// + /// + /// The object that should be used to determine the expected ordering. + /// + /// + /// 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 . + /// + /// + /// Empty and single element collections are considered to be ordered both in ascending and descending order at the same time. + /// + /// is . + public AndConstraint> ThenBeInAscendingOrder( + Expression> propertyExpression, IComparer comparer, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNull(comparer, nameof(comparer), + "Cannot assert collection ordering without specifying a comparer."); + + return ThenBeOrderedBy(propertyExpression, comparer, SortOrder.Ascending, because, becauseArgs); + } + + /// + /// Asserts that a subsequence is ordered in descending order according to the value of the specified + /// . + /// + /// + /// A lambda expression that references the property that should be used to determine the expected ordering. + /// + /// + /// 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 . + /// + /// + /// Empty and single element collections are considered to be ordered both in ascending and descending order at the same time. + /// + public AndConstraint> ThenBeInDescendingOrder( + Expression> propertyExpression, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + return ThenBeInDescendingOrder(propertyExpression, GetComparer(), because, becauseArgs); + } + + /// + /// Asserts that a subsequence is ordered in descending order according to the value of the specified + /// and implementation. + /// + /// + /// A lambda expression that references the property that should be used to determine the expected ordering. + /// + /// + /// The object that should be used to determine the expected ordering. + /// + /// + /// 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 . + /// + /// + /// Empty and single element collections are considered to be ordered both in ascending and descending order at the same time. + /// + /// is . + public AndConstraint> ThenBeInDescendingOrder( + Expression> propertyExpression, IComparer comparer, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNull(comparer, nameof(comparer), + "Cannot assert collection ordering without specifying a comparer."); + + return ThenBeOrderedBy(propertyExpression, comparer, SortOrder.Descending, because, becauseArgs); + } + + private AndConstraint> ThenBeOrderedBy( + Expression> propertyExpression, + IComparer comparer, + SortOrder direction, + [StringSyntax("CompositeFormat")] string because, + object[] becauseArgs) { + subsequentOrdering = true; + return BeOrderedBy(propertyExpression, comparer, direction, because, becauseArgs); + } + + internal sealed override IOrderedEnumerable GetOrderedEnumerable( + Expression> propertyExpression, + IComparer comparer, + SortOrder direction, + ICollection unordered) + { + if (subsequentOrdering) + { + Func keySelector = propertyExpression.Compile(); + + IOrderedEnumerable expectation = direction == SortOrder.Ascending + ? previousOrderedEnumerable.ThenBy(keySelector, comparer) + : previousOrderedEnumerable.ThenByDescending(keySelector, comparer); + + return expectation; + } + + return base.GetOrderedEnumerable(propertyExpression, comparer, direction, unordered); } } diff --git a/Src/FluentAssertions/Collections/SubsequentOrderingGenericCollectionAssertions.cs b/Src/FluentAssertions/Collections/SubsequentOrderingGenericCollectionAssertions.cs deleted file mode 100644 index 9d8291c53f..0000000000 --- a/Src/FluentAssertions/Collections/SubsequentOrderingGenericCollectionAssertions.cs +++ /dev/null @@ -1,174 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Linq.Expressions; -using FluentAssertions.Common; - -namespace FluentAssertions.Collections; - -[DebuggerNonUserCode] -public class SubsequentOrderingGenericCollectionAssertions - : GenericCollectionAssertions - where TCollection : IEnumerable - where TAssertions : SubsequentOrderingGenericCollectionAssertions -{ - private readonly IOrderedEnumerable previousOrderedEnumerable; - private bool subsequentOrdering; - - public SubsequentOrderingGenericCollectionAssertions(TCollection actualValue, IOrderedEnumerable previousOrderedEnumerable) - : base(actualValue) - { - this.previousOrderedEnumerable = previousOrderedEnumerable; - } - - /// - /// Asserts that a subsequence is ordered in ascending order according to the value of the specified - /// . - /// - /// - /// A lambda expression that references the property that should be used to determine the expected ordering. - /// - /// - /// 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 . - /// - /// - /// Empty and single element collections are considered to be ordered both in ascending and descending order at the same time. - /// - public AndConstraint> ThenBeInAscendingOrder( - Expression> propertyExpression, string because = "", params object[] becauseArgs) - { - return ThenBeInAscendingOrder(propertyExpression, GetComparer(), because, becauseArgs); - } - - /// - /// Asserts that a subsequence is ordered in ascending order according to the value of the specified - /// and implementation. - /// - /// - /// A lambda expression that references the property that should be used to determine the expected ordering. - /// - /// - /// The object that should be used to determine the expected ordering. - /// - /// - /// 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 . - /// - /// - /// Empty and single element collections are considered to be ordered both in ascending and descending order at the same time. - /// - /// is . - public AndConstraint> ThenBeInAscendingOrder( - Expression> propertyExpression, IComparer comparer, string because = "", - params object[] becauseArgs) - { - Guard.ThrowIfArgumentIsNull(comparer, nameof(comparer), - "Cannot assert collection ordering without specifying a comparer."); - - return ThenBeOrderedBy(propertyExpression, comparer, SortOrder.Ascending, because, becauseArgs); - } - - /// - /// Asserts that a subsequence is ordered in descending order according to the value of the specified - /// . - /// - /// - /// A lambda expression that references the property that should be used to determine the expected ordering. - /// - /// - /// 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 . - /// - /// - /// Empty and single element collections are considered to be ordered both in ascending and descending order at the same time. - /// - public AndConstraint> ThenBeInDescendingOrder( - Expression> propertyExpression, string because = "", params object[] becauseArgs) - { - return ThenBeInDescendingOrder(propertyExpression, GetComparer(), because, becauseArgs); - } - - /// - /// Asserts that a subsequence is ordered in descending order according to the value of the specified - /// and implementation. - /// - /// - /// A lambda expression that references the property that should be used to determine the expected ordering. - /// - /// - /// The object that should be used to determine the expected ordering. - /// - /// - /// 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 . - /// - /// - /// Empty and single element collections are considered to be ordered both in ascending and descending order at the same time. - /// - /// is . - public AndConstraint> ThenBeInDescendingOrder( - Expression> propertyExpression, IComparer comparer, string because = "", - params object[] becauseArgs) - { - Guard.ThrowIfArgumentIsNull(comparer, nameof(comparer), - "Cannot assert collection ordering without specifying a comparer."); - - return ThenBeOrderedBy(propertyExpression, comparer, SortOrder.Descending, because, becauseArgs); - } - - private AndConstraint> ThenBeOrderedBy( - Expression> propertyExpression, - IComparer comparer, - SortOrder direction, - string because, - object[] becauseArgs) - { - subsequentOrdering = true; - return BeOrderedBy(propertyExpression, comparer, direction, because, becauseArgs); - } - - internal sealed override IOrderedEnumerable GetOrderedEnumerable( - Expression> propertyExpression, - IComparer comparer, - SortOrder direction, - ICollection unordered) - { - if (subsequentOrdering) - { - Func keySelector = propertyExpression.Compile(); - - IOrderedEnumerable expectation = direction == SortOrder.Ascending - ? previousOrderedEnumerable.ThenBy(keySelector, comparer) - : previousOrderedEnumerable.ThenByDescending(keySelector, comparer); - - return expectation; - } - - return base.GetOrderedEnumerable(propertyExpression, comparer, direction, unordered); - } -} - -[DebuggerNonUserCode] -public class SubsequentOrderingGenericCollectionAssertions - : SubsequentOrderingGenericCollectionAssertions> - where TCollection : IEnumerable -{ - public SubsequentOrderingGenericCollectionAssertions(TCollection actualValue, IOrderedEnumerable previousOrderedEnumerable) - : base(actualValue, previousOrderedEnumerable) - { - } -} diff --git a/Src/FluentAssertions/Common/AppSettingsConfigurationStore.cs b/Src/FluentAssertions/Common/AppSettingsConfigurationStore.cs deleted file mode 100644 index ee12469337..0000000000 --- a/Src/FluentAssertions/Common/AppSettingsConfigurationStore.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Even though .NET Standard 2.0 seems to support the ConfigurationManager class according -// to the NuGet package at https://www.nuget.org/packages/System.Configuration.ConfigurationManager, -// it will often throw a PlatformNotSupport exception. See -// https://docs.microsoft.com/en-us/dotnet/api/system.configuration.configurationmanager?view=netframework-4.8 - -using System.Configuration; - -namespace FluentAssertions.Common; - -internal class AppSettingsConfigurationStore : IConfigurationStore -{ - public string GetSetting(string name) - { - string value = ConfigurationManager.AppSettings[name]; - return !string.IsNullOrEmpty(value) ? value : null; - } -} diff --git a/Src/FluentAssertions/Common/Configuration.cs b/Src/FluentAssertions/Common/Configuration.cs deleted file mode 100644 index a74cee2744..0000000000 --- a/Src/FluentAssertions/Common/Configuration.cs +++ /dev/null @@ -1,130 +0,0 @@ -using System; -using FluentAssertions.Formatting; - -namespace FluentAssertions.Common; - -public class Configuration -{ - /// - /// Defines the key for the configuration of the test framework to be assumed in FluentAssertions. - /// - private const string TestFrameworkConfigurationKey = "FluentAssertions.TestFramework"; - - #region Private Definitions - - private readonly object propertiesAccessLock = new(); - private readonly IConfigurationStore store; - private string valueFormatterAssembly; - private ValueFormatterDetectionMode? valueFormatterDetectionMode; - private string testFrameworkName; - - #endregion - - /// - /// Gets the active configuration, - /// - public static Configuration Current => Services.Configuration; - - public Configuration(IConfigurationStore store) - { - this.store = store; - } - - /// - /// Gets or sets the mode on how Fluent Assertions will find custom implementations of - /// . - /// - public ValueFormatterDetectionMode ValueFormatterDetectionMode - { - get - { - lock (propertiesAccessLock) - { - return valueFormatterDetectionMode ??= DetermineFormatterDetectionMode(); - } - } - - set - { - valueFormatterDetectionMode = value; - } - } - - private ValueFormatterDetectionMode DetermineFormatterDetectionMode() - { - if (ValueFormatterAssembly is not null) - { - return ValueFormatterDetectionMode.Specific; - } - - string setting = store.GetSetting("valueFormatters"); - - if (!string.IsNullOrEmpty(setting)) - { - try - { - return (ValueFormatterDetectionMode)Enum.Parse(typeof(ValueFormatterDetectionMode), setting, ignoreCase: true); - } - catch (ArgumentException) - { - throw new InvalidOperationException( - $"'{setting}' is not a valid option for detecting value formatters. Valid options include Disabled, Specific and Scan."); - } - } - - return ValueFormatterDetectionMode.Disabled; - } - - /// - /// Gets or sets the assembly name to scan for custom value formatters in case - /// is set to . - /// - public string ValueFormatterAssembly - { - get - { - if (valueFormatterAssembly is null) - { - string assemblyName = store.GetSetting("valueFormattersAssembly"); - - if (!string.IsNullOrEmpty(assemblyName)) - { - valueFormatterAssembly = assemblyName; - } - } - - return valueFormatterAssembly; - } - - set - { - lock (propertiesAccessLock) - { - valueFormatterAssembly = value; - valueFormatterDetectionMode = null; - } - } - } - - /// - /// Gets or sets the name of the test framework to use. - /// - /// - /// If no name is provided, Fluent Assertions - /// will try to detect it by scanning the currently loaded assemblies. If it can't find a suitable provider, - /// and the run-time platform supports it, it'll try to get it from the . - /// - public string TestFrameworkName - { - get - { - if (string.IsNullOrEmpty(testFrameworkName)) - { - testFrameworkName = store.GetSetting(TestFrameworkConfigurationKey); - } - - return testFrameworkName; - } - set => testFrameworkName = value; - } -} diff --git a/Src/FluentAssertions/Common/ConfigurationStoreExceptionInterceptor.cs b/Src/FluentAssertions/Common/ConfigurationStoreExceptionInterceptor.cs deleted file mode 100644 index e4760a6c6b..0000000000 --- a/Src/FluentAssertions/Common/ConfigurationStoreExceptionInterceptor.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace FluentAssertions.Common; - -internal class ConfigurationStoreExceptionInterceptor : IConfigurationStore -{ - private readonly IConfigurationStore configurationStore; - - private bool underlyingStoreUnavailable; - - public ConfigurationStoreExceptionInterceptor(IConfigurationStore configurationStore) - { - this.configurationStore = configurationStore; - } - - public string GetSetting(string name) - { - if (underlyingStoreUnavailable) - { - return null; - } - - try - { - return configurationStore.GetSetting(name); - } - catch - { - underlyingStoreUnavailable = true; - return null; - } - } -} diff --git a/Src/FluentAssertions/Common/ExpressionExtensions.cs b/Src/FluentAssertions/Common/ExpressionExtensions.cs index 3200353ddb..bca2089a11 100644 --- a/Src/FluentAssertions/Common/ExpressionExtensions.cs +++ b/Src/FluentAssertions/Common/ExpressionExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -20,7 +21,7 @@ public static PropertyInfo GetPropertyInfo(this Expression when a property expression is expected.", + throw new ArgumentException($"Cannot use <{expression.Body}> when a property expression is expected.", nameof(expression)); } @@ -31,21 +32,21 @@ private static MemberInfo AttemptToGetMemberInfoFromExpression(Expres (((expression.Body as UnaryExpression)?.Operand ?? expression.Body) as MemberExpression)?.Member; /// - /// Gets a dotted path of property names representing the property expression, including the declaring type. + /// Gets one or more dotted paths of property names representing the property expression, including the declaring type. /// /// - /// E.g. Parent.Child.Sibling.Name. + /// E.g. ["Parent.Child.Sibling.Name"] or ["A.Dotted.Path1", "A.Dotted.Path2"]. /// /// is . -#pragma warning disable MA0051 - public static MemberPath GetMemberPath( + [SuppressMessage("Maintainability", "AV1500:Member or local function contains too many statements")] + public static IEnumerable GetMemberPaths( this Expression> expression) -#pragma warning restore MA0051 { Guard.ThrowIfArgumentIsNull(expression, nameof(expression), "Expected an expression, but found ."); - var segments = new List(); - var declaringTypes = new List(); + string singlePath = null; + List selectors = []; + List declaringTypes = []; Expression node = expression; while (node is not null) @@ -55,59 +56,113 @@ public static MemberPath GetMemberPath( #pragma warning restore IDE0010 { case ExpressionType.Lambda: + { node = ((LambdaExpression)node).Body; break; + } case ExpressionType.Convert: case ExpressionType.ConvertChecked: + { var unaryExpression = (UnaryExpression)node; node = unaryExpression.Operand; break; + } case ExpressionType.MemberAccess: + { var memberExpression = (MemberExpression)node; node = memberExpression.Expression; - segments.Add(memberExpression.Member.Name); + singlePath = $"{memberExpression.Member.Name}.{singlePath}"; declaringTypes.Add(memberExpression.Member.DeclaringType); break; + } case ExpressionType.ArrayIndex: + { var binaryExpression = (BinaryExpression)node; var indexExpression = (ConstantExpression)binaryExpression.Right; node = binaryExpression.Left; - segments.Add("[" + indexExpression.Value + "]"); + singlePath = $"[{indexExpression.Value}].{singlePath}"; break; + } case ExpressionType.Parameter: + { node = null; break; + } case ExpressionType.Call: + { var methodCallExpression = (MethodCallExpression)node; - if (methodCallExpression is not { Method.Name: "get_Item", Arguments: [ConstantExpression argumentExpression] }) + if (methodCallExpression is not + { Method.Name: "get_Item", Arguments: [ConstantExpression argumentExpression] }) { throw new ArgumentException(GetUnsupportedExpressionMessage(expression.Body), nameof(expression)); } node = methodCallExpression.Object; - segments.Add("[" + argumentExpression.Value + "]"); + singlePath = $"[{argumentExpression.Value}].{singlePath}"; + break; + } + + case ExpressionType.New: + { + var newExpression = (NewExpression)node; + + foreach (Expression member in newExpression.Arguments) + { + var expr = member.ToString(); + selectors.Add(expr[expr.IndexOf('.', StringComparison.Ordinal)..]); + declaringTypes.Add(((MemberExpression)member).Member.DeclaringType); + } + + node = null; break; + } default: + { throw new ArgumentException(GetUnsupportedExpressionMessage(expression.Body), nameof(expression)); + } } } - // If any members were accessed in the expression, the first one found is the last member. Type declaringType = declaringTypes.FirstOrDefault() ?? typeof(TDeclaringType); - IEnumerable reversedSegments = segments.AsEnumerable().Reverse(); - string segmentPath = string.Join(".", reversedSegments); + if (singlePath is null) + { +#if NET47 || NETSTANDARD2_0 + return selectors.Select(selector => + GetNewInstance(declaringType, selector)).ToList(); +#else + return selectors.ConvertAll(selector => + GetNewInstance(declaringType, selector)); +#endif + } - return new MemberPath(typeof(TDeclaringType), declaringType, segmentPath.Replace(".[", "[", StringComparison.Ordinal)); + return [GetNewInstance(declaringType, singlePath)]; + } + + private static MemberPath GetNewInstance(Type declaringType, string dottedPath) => + new(typeof(TReflectedType), declaringType, dottedPath.Trim('.').Replace(".[", "[", StringComparison.Ordinal)); + + /// + /// Gets the first dotted path of property names collected by + /// from a given property expression, including the declaring type. + /// + /// + /// E.g. Parent.Child.Sibling.Name. + /// + /// is . + public static MemberPath GetMemberPath( + this Expression> expression) + { + return expression.GetMemberPaths().FirstOrDefault() ?? new MemberPath(""); } /// @@ -128,32 +183,43 @@ public static void ValidateMemberPath( #pragma warning restore IDE0010 { case ExpressionType.Lambda: + { node = ((LambdaExpression)node).Body; break; + } case ExpressionType.Convert: case ExpressionType.ConvertChecked: + { var unaryExpression = (UnaryExpression)node; node = unaryExpression.Operand; break; + } case ExpressionType.MemberAccess: + { var memberExpression = (MemberExpression)node; node = memberExpression.Expression; break; + } case ExpressionType.ArrayIndex: + { var binaryExpression = (BinaryExpression)node; node = binaryExpression.Left; break; + } case ExpressionType.Parameter: + { node = null; break; + } case ExpressionType.Call: + { var methodCallExpression = (MethodCallExpression)node; if (methodCallExpression is not { Method.Name: "get_Item", Arguments: [ConstantExpression] }) @@ -163,9 +229,12 @@ public static void ValidateMemberPath( node = methodCallExpression.Object; break; + } default: + { throw new ArgumentException(GetUnsupportedExpressionMessage(expression.Body), nameof(expression)); + } } } } diff --git a/Src/FluentAssertions/Common/IClock.cs b/Src/FluentAssertions/Common/IClock.cs index 9e711b48bb..ad953a8ad3 100644 --- a/Src/FluentAssertions/Common/IClock.cs +++ b/Src/FluentAssertions/Common/IClock.cs @@ -19,7 +19,6 @@ public interface IClock /// Creates a task that will complete after a time delay. /// /// The time span to wait before completing the returned task - /// /// A task that represents the time delay. /// Task DelayAsync(TimeSpan delay, CancellationToken cancellationToken); diff --git a/Src/FluentAssertions/Common/ICollectionWrapper.cs b/Src/FluentAssertions/Common/ICollectionWrapper.cs deleted file mode 100644 index a4027e9746..0000000000 --- a/Src/FluentAssertions/Common/ICollectionWrapper.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Collections; - -namespace FluentAssertions.Common; - -/// -/// Used to provide access to the underlying for an object that wraps an underlying -/// collection. -/// -/// Collection type. -public interface ICollectionWrapper - where TCollection : ICollection -{ - TCollection UnderlyingCollection { get; } -} diff --git a/Src/FluentAssertions/Common/IConfigurationStore.cs b/Src/FluentAssertions/Common/IConfigurationStore.cs deleted file mode 100644 index c8ce460506..0000000000 --- a/Src/FluentAssertions/Common/IConfigurationStore.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace FluentAssertions.Common; - -public interface IConfigurationStore -{ - string GetSetting(string name); -} diff --git a/Src/FluentAssertions/Common/IReflector.cs b/Src/FluentAssertions/Common/IReflector.cs deleted file mode 100644 index 90b9c9ff84..0000000000 --- a/Src/FluentAssertions/Common/IReflector.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; - -namespace FluentAssertions.Common; - -public interface IReflector -{ - IEnumerable GetAllTypesFromAppDomain(Func predicate); -} diff --git a/Src/FluentAssertions/Common/DictionaryHelpers.cs b/Src/FluentAssertions/Common/KeyValuePairCollectionExtensions.cs similarity index 67% rename from Src/FluentAssertions/Common/DictionaryHelpers.cs rename to Src/FluentAssertions/Common/KeyValuePairCollectionExtensions.cs index 9570577726..1b2cbc65ce 100644 --- a/Src/FluentAssertions/Common/DictionaryHelpers.cs +++ b/Src/FluentAssertions/Common/KeyValuePairCollectionExtensions.cs @@ -4,7 +4,7 @@ namespace FluentAssertions.Common; -internal static class DictionaryHelpers +internal static class KeyValuePairCollectionExtensions { public static IEnumerable GetKeys(this TCollection collection) where TCollection : IEnumerable> @@ -31,18 +31,13 @@ public static IEnumerable GetValues(this TCol public static bool ContainsKey(this TCollection collection, TKey key) where TCollection : IEnumerable> { + Func areSameOrEqual = ObjectExtensions.GetComparer(); return collection switch { IDictionary dictionary => dictionary.ContainsKey(key), IReadOnlyDictionary readOnlyDictionary => readOnlyDictionary.ContainsKey(key), - _ => ContainsKey(collection, key), + _ => collection.Any(kvp => areSameOrEqual(kvp.Key, key)), }; - - static bool ContainsKey(TCollection collection, TKey key) - { - Func areSameOrEqual = ObjectExtensions.GetComparer(); - return collection.Any(kvp => areSameOrEqual(kvp.Key, key)); - } } public static bool TryGetValue(this TCollection collection, TKey key, out TValue value) @@ -52,41 +47,37 @@ public static bool TryGetValue(this TCollection colle { IDictionary dictionary => dictionary.TryGetValue(key, out value), IReadOnlyDictionary readOnlyDictionary => readOnlyDictionary.TryGetValue(key, out value), - _ => TryGetValue(collection, key, out value), + _ => TryGetValueLocal(collection, key, out value), }; + } - static bool TryGetValue(TCollection collection, TKey key, out TValue value) - { - Func areSameOrEqual = ObjectExtensions.GetComparer(); + private static bool TryGetValueLocal(TCollection collection, TKey key, out TValue value) + where TCollection : IEnumerable> + { + Func areSameOrEqual = ObjectExtensions.GetComparer(); - foreach (var kvp in collection) + foreach (var kvp in collection) + { + if (areSameOrEqual(kvp.Key, key)) { - if (areSameOrEqual(kvp.Key, key)) - { - value = kvp.Value; - return true; - } + value = kvp.Value; + return true; } - - value = default; - return false; } + + value = default; + return false; } public static TValue GetValue(this TCollection collection, TKey key) where TCollection : IEnumerable> { + Func areSameOrEqual = ObjectExtensions.GetComparer(); return collection switch { IDictionary dictionary => dictionary[key], IReadOnlyDictionary readOnlyDictionary => readOnlyDictionary[key], - _ => GetValue(collection, key), + _ => collection.First(kvp => areSameOrEqual(kvp.Key, key)).Value, }; - - static TValue GetValue(TCollection collection, TKey key) - { - Func areSameOrEqual = ObjectExtensions.GetComparer(); - return collection.First(kvp => areSameOrEqual(kvp.Key, key)).Value; - } } } diff --git a/Src/FluentAssertions/Common/MemberPath.cs b/Src/FluentAssertions/Common/MemberPath.cs index 77cf3a3c20..58334bcbca 100644 --- a/Src/FluentAssertions/Common/MemberPath.cs +++ b/Src/FluentAssertions/Common/MemberPath.cs @@ -17,10 +17,10 @@ internal class MemberPath private string[] segments; - private static readonly MemberPathSegmentEqualityComparer MemberPathSegmentEqualityComparer = new(); + private static readonly MemberPathSegmentEqualityComparer SegmentEqualityComparer = new(); public MemberPath(IMember member, string parentPath) - : this(member.ReflectedType, member.DeclaringType, parentPath.Combine(member.Name)) + : this(member.ReflectedType, member.DeclaringType, parentPath.Combine(member.Expectation.Name)) { } @@ -63,7 +63,7 @@ public bool IsSameAs(MemberPath candidate) { string[] candidateSegments = candidate.Segments; - return candidateSegments.SequenceEqual(Segments, MemberPathSegmentEqualityComparer); + return candidateSegments.SequenceEqual(Segments, SegmentEqualityComparer); } return false; @@ -74,7 +74,7 @@ private bool IsParentOf(MemberPath candidate) string[] candidateSegments = candidate.Segments; return candidateSegments.Length > Segments.Length && - candidateSegments.Take(Segments.Length).SequenceEqual(Segments, MemberPathSegmentEqualityComparer); + candidateSegments.Take(Segments.Length).SequenceEqual(Segments, SegmentEqualityComparer); } private bool IsChildOf(MemberPath candidate) @@ -83,7 +83,7 @@ private bool IsChildOf(MemberPath candidate) return candidateSegments.Length < Segments.Length && candidateSegments.SequenceEqual(Segments.Take(candidateSegments.Length), - MemberPathSegmentEqualityComparer); + SegmentEqualityComparer); } public MemberPath AsParentCollectionOf(MemberPath nextPath) @@ -103,7 +103,7 @@ public bool IsEquivalentTo(string path) public bool HasSameParentAs(MemberPath path) { return Segments.Length == path.Segments.Length - && GetParentSegments().SequenceEqual(path.GetParentSegments(), MemberPathSegmentEqualityComparer); + && GetParentSegments().SequenceEqual(path.GetParentSegments(), SegmentEqualityComparer); } private IEnumerable GetParentSegments() => Segments.Take(Segments.Length - 1); @@ -116,7 +116,7 @@ public bool HasSameParentAs(MemberPath path) private string[] Segments => segments ??= dottedPath .Replace("[]", "[*]", StringComparison.Ordinal) - .Split(new[] { '.', '[', ']' }, StringSplitOptions.RemoveEmptyEntries); + .Split(['.', '[', ']'], StringSplitOptions.RemoveEmptyEntries); /// /// Returns a copy of the current object as if it represented an un-indexed item in a collection. diff --git a/Src/FluentAssertions/Common/MemberPathSegmentEqualityComparer.cs b/Src/FluentAssertions/Common/MemberPathSegmentEqualityComparer.cs index fc0c53a8cf..07dbccad00 100644 --- a/Src/FluentAssertions/Common/MemberPathSegmentEqualityComparer.cs +++ b/Src/FluentAssertions/Common/MemberPathSegmentEqualityComparer.cs @@ -1,4 +1,6 @@ -using System; +#if NET6_0_OR_GREATER || NETSTANDARD2_1 +using System; +#endif using System.Collections.Generic; using System.Text.RegularExpressions; diff --git a/Src/FluentAssertions/Common/MethodInfoExtensions.cs b/Src/FluentAssertions/Common/MethodInfoExtensions.cs index 7fcef498a5..98637454ed 100644 --- a/Src/FluentAssertions/Common/MethodInfoExtensions.cs +++ b/Src/FluentAssertions/Common/MethodInfoExtensions.cs @@ -13,8 +13,12 @@ internal static class MethodInfoExtensions /// A sum of all possible . It's needed to calculate what options were used when decorating with . /// They are a subset of which can be checked on a type and therefore this mask has to be applied to check only for options. /// - private static readonly Lazy ImplementationOptionsMask = - new(() => Enum.GetValues(typeof(MethodImplOptions)).Cast().Sum(x => x)); + private static readonly int ImplementationOptionsMask = +#if NET6_0_OR_GREATER + Enum.GetValues().Cast().Sum(optionValue => optionValue); +#else + Enum.GetValues(typeof(MethodImplOptions)).Cast().Sum(optionValue => optionValue); +#endif internal static bool IsAsync(this MethodInfo methodInfo) { @@ -30,7 +34,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); @@ -46,12 +49,12 @@ internal static bool IsNonVirtual(this MethodInfo method) return !method.IsVirtual || method.IsFinal; } - private static (bool success, MethodImplAttribute attribute) RecreateMethodImplAttribute(MethodBase methodBase) + private static (bool Success, MethodImplAttribute Attribute) RecreateMethodImplAttribute(MethodBase methodBase) { MethodImplAttributes implementationFlags = methodBase.MethodImplementationFlags; int implementationFlagsMatchingImplementationOptions = - (int)implementationFlags & ImplementationOptionsMask.Value; + (int)implementationFlags & ImplementationOptionsMask; MethodImplOptions implementationOptions = (MethodImplOptions)implementationFlagsMatchingImplementationOptions; diff --git a/Src/FluentAssertions/Common/NullConfigurationStore.cs b/Src/FluentAssertions/Common/NullConfigurationStore.cs deleted file mode 100644 index b25caf75cf..0000000000 --- a/Src/FluentAssertions/Common/NullConfigurationStore.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace FluentAssertions.Common; - -internal class NullConfigurationStore : IConfigurationStore -{ - public string GetSetting(string name) - { - return string.Empty; - } -} 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/ReadOnlyNonGenericCollectionWrapper.cs b/Src/FluentAssertions/Common/ReadOnlyNonGenericCollectionWrapper.cs deleted file mode 100644 index 878eacda7f..0000000000 --- a/Src/FluentAssertions/Common/ReadOnlyNonGenericCollectionWrapper.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Data; -using System.Linq; - -namespace FluentAssertions.Common; - -internal static class ReadOnlyNonGenericCollectionWrapper -{ - public static ReadOnlyNonGenericCollectionWrapper Create(DataTableCollection collection) - { - return - collection != null - ? new ReadOnlyNonGenericCollectionWrapper(collection) - : null; - } - - public static ReadOnlyNonGenericCollectionWrapper Create(DataColumnCollection collection) - { - return - collection != null - ? new ReadOnlyNonGenericCollectionWrapper(collection) - : null; - } - - public static ReadOnlyNonGenericCollectionWrapper Create(DataRowCollection collection) - { - return - collection != null - ? new ReadOnlyNonGenericCollectionWrapper(collection) - : null; - } -} - -internal class ReadOnlyNonGenericCollectionWrapper : ICollection, ICollectionWrapper - where TCollection : ICollection -{ - public TCollection UnderlyingCollection { get; } - - /// - /// Initializes a new instance of the class. - /// - /// is . - public ReadOnlyNonGenericCollectionWrapper(TCollection collection) - { - Guard.ThrowIfArgumentIsNull(collection); - - UnderlyingCollection = collection; - } - - public int Count => UnderlyingCollection.Count; - - bool ICollection.IsReadOnly => true; - - public IEnumerator GetEnumerator() => UnderlyingCollection.Cast().GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => UnderlyingCollection.GetEnumerator(); - - public bool Contains(TItem item) => UnderlyingCollection.Cast().Contains(item); - - public void CopyTo(TItem[] array, int arrayIndex) => UnderlyingCollection.CopyTo(array, arrayIndex); - - void ICollection.Add(TItem item) => throw new NotSupportedException(); - - void ICollection.Clear() => throw new NotSupportedException(); - - bool ICollection.Remove(TItem item) => throw new NotSupportedException(); -} diff --git a/Src/FluentAssertions/Common/Services.cs b/Src/FluentAssertions/Common/Services.cs deleted file mode 100644 index dd283265ee..0000000000 --- a/Src/FluentAssertions/Common/Services.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using FluentAssertions.Execution; - -namespace FluentAssertions.Common; - -/// -/// Maintains the framework-specific services. -/// -public static class Services -{ - private static readonly object Lockable = new(); - private static Configuration configuration; - - static Services() - { - ResetToDefaults(); - } - - public static IConfigurationStore ConfigurationStore { get; set; } - - public static Configuration Configuration - { - get - { - lock (Lockable) - { - return configuration ??= new Configuration(ConfigurationStore); - } - } - } - - public static Action ThrowException { get; set; } - - public static IReflector Reflector { get; set; } - - public static void ResetToDefaults() - { - Reflector = new FullFrameworkReflector(); -#if NETFRAMEWORK || NET6_0_OR_GREATER - ConfigurationStore = new ConfigurationStoreExceptionInterceptor(new AppSettingsConfigurationStore()); -#else - ConfigurationStore = new NullConfigurationStore(); -#endif - ThrowException = new TestFrameworkProvider(Configuration).Throw; - } -} diff --git a/Src/FluentAssertions/Common/StopwatchTimer.cs b/Src/FluentAssertions/Common/StopwatchTimer.cs index bb81266cbb..889a316e5f 100644 --- a/Src/FluentAssertions/Common/StopwatchTimer.cs +++ b/Src/FluentAssertions/Common/StopwatchTimer.cs @@ -5,12 +5,7 @@ namespace FluentAssertions.Common; internal sealed class StopwatchTimer : ITimer { - private readonly Stopwatch stopwatch; - - public StopwatchTimer() - { - stopwatch = Stopwatch.StartNew(); - } + private readonly Stopwatch stopwatch = Stopwatch.StartNew(); public TimeSpan Elapsed => stopwatch.Elapsed; diff --git a/Src/FluentAssertions/Common/StringExtensions.cs b/Src/FluentAssertions/Common/StringExtensions.cs index 7f38809cee..859f375f19 100644 --- a/Src/FluentAssertions/Common/StringExtensions.cs +++ b/Src/FluentAssertions/Common/StringExtensions.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text.RegularExpressions; using FluentAssertions.Formatting; @@ -9,15 +11,13 @@ internal static class StringExtensions { /// /// Finds the first index at which the does not match the - /// string anymore, accounting for the specified . + /// string anymore, accounting for the specified . /// - public static int IndexOfFirstMismatch(this string value, string expected, StringComparison stringComparison) + public static int IndexOfFirstMismatch(this string value, string expected, IEqualityComparer comparer) { - Func comparer = GetCharComparer(stringComparison); - for (int index = 0; index < value.Length; index++) { - if (index >= expected.Length || !comparer(value[index], expected[index])) + if (index >= expected.Length || !comparer.Equals(value[index..(index + 1)], expected[index..(index + 1)])) { return index; } @@ -26,11 +26,6 @@ public static int IndexOfFirstMismatch(this string value, string expected, Strin return -1; } - private static Func GetCharComparer(StringComparison stringComparison) => - stringComparison == StringComparison.Ordinal - ? (x, y) => x == y - : (x, y) => char.ToUpperInvariant(x) == char.ToUpperInvariant(y); - /// /// Gets the quoted three characters at the specified index of a string, including the index itself. /// @@ -65,35 +60,25 @@ public static string EscapePlaceholders(this string value) => value.Replace("{", "{{", StringComparison.Ordinal).Replace("}", "}}", StringComparison.Ordinal); /// - /// Replaces all characters that might conflict with formatting placeholders with their escaped counterparts. - /// - internal static string UnescapePlaceholders(this string value) => - value.Replace("{{", "{", StringComparison.Ordinal).Replace("}}", "}", StringComparison.Ordinal); - - /// - /// Joins a string with one or more other strings using a specified separator. + /// Joins a string with another string using the specified separator. /// /// - /// Any string that is empty (including the original string) is ignored. + /// Any string that is empty or null (including the original string) is ignored. Also, if the second + /// string starts with an index operator, the separator is omitted. /// public static string Combine(this string @this, string other, string separator = ".") { - if (@this.Length == 0) + if (@this.IsNullOrEmpty()) { - return other.Length != 0 ? other : string.Empty; + return !other.IsNullOrEmpty() ? other : string.Empty; } - if (other.Length == 0) - { - return @this; - } - - if (other.StartsWith('[')) + if (other is null || other.StartsWith('[')) { separator = string.Empty; } - return @this + separator + other; + return @this + separator + (other ?? ""); } /// @@ -114,40 +99,62 @@ public static string Capitalize(this string @this) /// /// Appends tab character at the beginning of each line in a string. /// - /// public static string IndentLines(this string @this) { return string.Join(Environment.NewLine, - @this.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).Select(x => $"\t{x}")); + @this.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries).Select(x => $"\t{x}")); } public static string RemoveNewLines(this string @this) { return @this.Replace("\n", string.Empty, StringComparison.Ordinal) - .Replace("\r", string.Empty, StringComparison.Ordinal) - .Replace("\\r\\n", string.Empty, StringComparison.Ordinal); + .Replace("\r", string.Empty, StringComparison.Ordinal); + } + + public static string RemoveNewlineStyle(this string @this) + { + return @this.Replace("\r\n", "\n", StringComparison.Ordinal) + .Replace('\r', '\n'); + } + + public static string RemoveTrailingWhitespaceFromLines(this string input) + { + // This regex matches space (' ') and tab ('\t') characters followed by a line ending ('\r\n' or '\n') + return Regex.Replace(input, @"[ \t]+(?=\r?\n)", string.Empty); } /// - /// Counts the number of times a substring appears within a string by using the specified . + /// Counts the number of times the appears within a string by using the specified . /// - /// The string to search in. - /// The substring to search for. - /// The option to use for comparison. - public static int CountSubstring(this string str, string substring, StringComparison comparisonType) + public static int CountSubstring(this string str, string substring, IEqualityComparer comparer) { string actual = str ?? string.Empty; string search = substring ?? string.Empty; int count = 0; - int index = 0; - - while ((index = actual.IndexOf(search, index, comparisonType)) >= 0) + int maxIndex = actual.Length - search.Length; + for (int index = 0; index <= maxIndex; index++) { - index += search.Length; - count++; + if (comparer.Equals(actual[index..(index + search.Length)], search)) + { + count++; + } } return count; } + + /// + /// Determines if the is longer than 8 characters or contains an . + /// + 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([NotNullWhen(false)] this string value) + { + return string.IsNullOrEmpty(value); + } } diff --git a/Src/FluentAssertions/Common/TimeOnlyExtensions.cs b/Src/FluentAssertions/Common/TimeOnlyExtensions.cs index bbb77fc057..e327319193 100644 --- a/Src/FluentAssertions/Common/TimeOnlyExtensions.cs +++ b/Src/FluentAssertions/Common/TimeOnlyExtensions.cs @@ -21,8 +21,8 @@ public static bool IsCloseTo(this TimeOnly subject, TimeOnly other, TimeSpan pre long ticks = subject.Ticks; return startTicks <= endTicks - ? (startTicks <= ticks && endTicks >= ticks) - : (startTicks <= ticks || endTicks >= ticks); + ? startTicks <= ticks && endTicks >= ticks + : startTicks <= ticks || endTicks >= ticks; } } diff --git a/Src/FluentAssertions/Common/TypeExtensions.cs b/Src/FluentAssertions/Common/TypeExtensions.cs index b0e684e3e3..4cd89baa9c 100644 --- a/Src/FluentAssertions/Common/TypeExtensions.cs +++ b/Src/FluentAssertions/Common/TypeExtensions.cs @@ -99,21 +99,21 @@ public static IEnumerable GetMatchingOrInheritedAttributes GetCustomAttributes(this MemberInfo type, bool inherit = false) + public static IEnumerable GetCustomAttributes(this MemberInfo type) where TAttribute : Attribute { // Do not use MemberInfo.GetCustomAttributes. // There is an issue with PropertyInfo and EventInfo preventing the inherit option to work. // https://github.com/dotnet/runtime/issues/30219 - return CustomAttributeExtensions.GetCustomAttributes(type, inherit); + return CustomAttributeExtensions.GetCustomAttributes(type, inherit: false); } private static IEnumerable GetCustomAttributes(MemberInfo type, - Expression> isMatchingAttributePredicate, bool inherit = false) + Expression> isMatchingAttributePredicate) where TAttribute : Attribute { Func isMatchingAttribute = isMatchingAttributePredicate.Compile(); - return GetCustomAttributes(type, inherit).Where(isMatchingAttribute); + return GetCustomAttributes(type).Where(isMatchingAttribute); } private static TAttribute[] GetCustomAttributes(this Type type, bool inherit = false) @@ -138,7 +138,7 @@ public static bool IsEquivalentTo(this IMember property, IMember otherProperty) { return (property.DeclaringType.IsSameOrInherits(otherProperty.DeclaringType) || otherProperty.DeclaringType.IsSameOrInherits(property.DeclaringType)) && - property.Name == otherProperty.Name; + property.Expectation.Name == otherProperty.Expectation.Name; } /// @@ -149,7 +149,7 @@ public static Type[] GetClosedGenericInterfaces(this Type type, Type openGeneric { if (type.IsGenericType && type.GetGenericTypeDefinition() == openGenericType) { - return new[] { type }; + return [type]; } Type[] interfaces = type.GetInterfaces(); @@ -163,7 +163,7 @@ public static Type[] GetClosedGenericInterfaces(this Type type, Type openGeneric public static bool OverridesEquals(this Type type) { MethodInfo method = type - .GetMethod("Equals", new[] { typeof(object) }); + .GetMethod("Equals", [typeof(object)]); return method is not null && method.GetBaseDefinition().DeclaringType != method.DeclaringType; @@ -458,9 +458,9 @@ private static bool IsRecordStruct(this Type type) // and heuristic testing, apparently giving good results but not supported by official documentation. return type.BaseType == typeof(ValueType) && type.GetMethod("PrintMembers", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly, null, - new[] { typeof(StringBuilder) }, null) is { } && + [typeof(StringBuilder)], null) is { } && type.GetMethod("op_Equality", BindingFlags.Static | BindingFlags.Public | BindingFlags.DeclaredOnly, null, - new[] { type, type }, null)? + [type, type], null)? .IsDecoratedWith() == true; } diff --git a/Src/FluentAssertions/Common/FullFrameworkReflector.cs b/Src/FluentAssertions/Common/TypeReflector.cs similarity index 87% rename from Src/FluentAssertions/Common/FullFrameworkReflector.cs rename to Src/FluentAssertions/Common/TypeReflector.cs index 3c54cbe1d3..d630c2f0a3 100644 --- a/Src/FluentAssertions/Common/FullFrameworkReflector.cs +++ b/Src/FluentAssertions/Common/TypeReflector.cs @@ -6,9 +6,9 @@ namespace FluentAssertions.Common; -internal class FullFrameworkReflector : IReflector +internal static class TypeReflector { - public IEnumerable GetAllTypesFromAppDomain(Func predicate) + public static IEnumerable GetAllTypesFromAppDomain(Func predicate) { return AppDomain.CurrentDomain .GetAssemblies() @@ -21,6 +21,7 @@ private static bool IsRelevant(Assembly ass) string assemblyName = ass.GetName().Name; return + assemblyName is not null && !assemblyName.StartsWith("microsoft.", StringComparison.OrdinalIgnoreCase) && !assemblyName.StartsWith("xunit", StringComparison.OrdinalIgnoreCase) && !assemblyName.StartsWith("jetbrains.", StringComparison.OrdinalIgnoreCase) && @@ -47,11 +48,11 @@ private static IEnumerable GetExportedTypes(Assembly assembly) } catch (FileLoadException) { - return Enumerable.Empty(); + return []; } catch (Exception) { - return Array.Empty(); + return []; } } } diff --git a/Src/FluentAssertions/Common/ValueFormatterDetectionMode.cs b/Src/FluentAssertions/Common/ValueFormatterDetectionMode.cs index 8d8d170950..cc938cc6a2 100644 --- a/Src/FluentAssertions/Common/ValueFormatterDetectionMode.cs +++ b/Src/FluentAssertions/Common/ValueFormatterDetectionMode.cs @@ -1,8 +1,10 @@ -namespace FluentAssertions.Common; +using FluentAssertions.Configuration; + +namespace FluentAssertions.Common; /// /// Defines the modes in which custom implementations of -/// are detected as configured through . +/// are detected as configured through . /// public enum ValueFormatterDetectionMode { @@ -12,13 +14,13 @@ public enum ValueFormatterDetectionMode Disabled, /// - /// Only custom value formatters exposed through the assembly set in + /// Only custom value formatters exposed through the assembly set in /// are detected. /// Specific, /// - /// All custom value formatters in any assembly loaded in the current will be detected. + /// All custom value formatters in any assembly loaded in the current AppDomain will be detected. /// Scan, } diff --git a/Src/FluentAssertions/Configuration/GlobalConfiguration.cs b/Src/FluentAssertions/Configuration/GlobalConfiguration.cs new file mode 100644 index 0000000000..ebe6577e69 --- /dev/null +++ b/Src/FluentAssertions/Configuration/GlobalConfiguration.cs @@ -0,0 +1,32 @@ +namespace FluentAssertions.Configuration; + +public class GlobalConfiguration +{ + private TestFramework? testFramework; + + /// + /// Provides access to the formatting defaults for all assertions. + /// + public GlobalFormattingOptions Formatting { get; set; } = new(); + + /// + /// Provides access to the defaults used by the structural equivalency assertions. + /// + public GlobalEquivalencyOptions Equivalency { get; set; } = new(); + + /// + /// Sets a specific test framework to be used by FluentAssertions when throwing assertion exceptions. + /// + /// + /// If set to , the test framework will be automatically detected by scanning the appdomain. + /// + public TestFramework? TestFramework + { + get => testFramework; + set + { + testFramework = value; + AssertionEngine.TestFramework = null; + } + } +} diff --git a/Src/FluentAssertions/Configuration/GlobalEquivalencyOptions.cs b/Src/FluentAssertions/Configuration/GlobalEquivalencyOptions.cs new file mode 100644 index 0000000000..059efa534a --- /dev/null +++ b/Src/FluentAssertions/Configuration/GlobalEquivalencyOptions.cs @@ -0,0 +1,51 @@ +using System; +using FluentAssertions.Common; +using FluentAssertions.Equivalency; +using JetBrains.Annotations; + +namespace FluentAssertions.Configuration; + +public class GlobalEquivalencyOptions +{ + private EquivalencyOptions defaults = new(); + + /// + /// Represents a mutable plan consisting of steps that are executed while asserting a (collection of) object(s) + /// is structurally equivalent to another (collection of) object(s). + /// + /// + /// Members on this property are not thread-safe and should not be invoked from within a unit test. + /// See the docs on how to safely use it. + /// + public EquivalencyPlan Plan { get; } = new(); + + /// + /// Allows configuring the defaults used during a structural equivalency assertion. + /// + /// + /// This method is not thread-safe and should not be invoked from within a unit test. + /// See the docs on how to safely use it. + /// + /// + /// An action that is used to configure the defaults. + /// + /// is . + public void Modify(Func configureOptions) + { + Guard.ThrowIfArgumentIsNull(configureOptions); + + defaults = configureOptions(defaults); + } + + /// + /// Creates a clone of the default options and allows the caller to modify them. + /// + /// + /// Can be used by external packages like FluentAssertions.DataSets to create a copy of the default equivalency options. + /// + [PublicAPI] + public EquivalencyOptions CloneDefaults() + { + return new EquivalencyOptions(defaults); + } +} diff --git a/Src/FluentAssertions/Configuration/GlobalFormattingOptions.cs b/Src/FluentAssertions/Configuration/GlobalFormattingOptions.cs new file mode 100644 index 0000000000..4676e9c890 --- /dev/null +++ b/Src/FluentAssertions/Configuration/GlobalFormattingOptions.cs @@ -0,0 +1,36 @@ +using System.Diagnostics.CodeAnalysis; +using FluentAssertions.Common; +using FluentAssertions.Formatting; + +namespace FluentAssertions.Configuration; + +public class GlobalFormattingOptions : FormattingOptions +{ + private string valueFormatterAssembly; + + public string ValueFormatterAssembly + { + get => valueFormatterAssembly; + set + { + valueFormatterAssembly = value; + ValueFormatterDetectionMode = ValueFormatterDetectionMode.Specific; + } + } + + public ValueFormatterDetectionMode ValueFormatterDetectionMode { get; set; } + + [SuppressMessage("Class Design", "AV1010:Member hides inherited member")] + internal new GlobalFormattingOptions Clone() + { + return new GlobalFormattingOptions + { + UseLineBreaks = UseLineBreaks, + MaxDepth = MaxDepth, + MaxLines = MaxLines, + ScopedFormatters = [.. ScopedFormatters], + ValueFormatterAssembly = ValueFormatterAssembly, + ValueFormatterDetectionMode = ValueFormatterDetectionMode + }; + } +} diff --git a/Src/FluentAssertions/Configuration/TestFramework.cs b/Src/FluentAssertions/Configuration/TestFramework.cs new file mode 100644 index 0000000000..8ee15577d6 --- /dev/null +++ b/Src/FluentAssertions/Configuration/TestFramework.cs @@ -0,0 +1,14 @@ +namespace FluentAssertions.Configuration; + +/// +/// The test frameworks supported by Fluent Assertions. +/// +public enum TestFramework +{ + XUnit2, + XUnit3, + TUnit, + MsTest, + NUnit, + MSpec +} 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 new file mode 100644 index 0000000000..9e0cc90410 --- /dev/null +++ b/Src/FluentAssertions/CustomAssertionsAssemblyAttribute.cs @@ -0,0 +1,10 @@ +using System; + +namespace FluentAssertions; + +/// +/// Marks an assembly as containing extensions to Fluent Assertions that either uses the built-in assertions +/// internally, or directly uses AssertionChain. +/// +[AttributeUsage(AttributeTargets.Assembly)] +public sealed class CustomAssertionsAssemblyAttribute : Attribute; diff --git a/Src/FluentAssertions/Data/DataColumnAssertions.cs b/Src/FluentAssertions/Data/DataColumnAssertions.cs deleted file mode 100644 index 9adec7bea8..0000000000 --- a/Src/FluentAssertions/Data/DataColumnAssertions.cs +++ /dev/null @@ -1,146 +0,0 @@ -using System; -using System.Data; -using System.Diagnostics; -using FluentAssertions.Common; -using FluentAssertions.Equivalency; -using FluentAssertions.Execution; -using FluentAssertions.Primitives; - -namespace FluentAssertions.Data; - -/// -/// Provides convenient assertion methods on a that can be -/// used to assert equivalency. -/// -[DebuggerNonUserCode] -public class DataColumnAssertions : ReferenceTypeAssertions -{ - public DataColumnAssertions(DataColumn dataColumn) - : base(dataColumn) - { - } - - /// - /// Asserts that an instance of is equivalent to another. - /// - /// - /// Data columns are equivalent when the following members have the same values: - /// - /// - /// AllowDBNull - /// AutoIncrement - /// AutoIncrementSeed - /// AutoIncrementStep - /// Caption - /// ColumnName - /// DataType - /// DateTimeMode - /// DefaultValue - /// Expression - /// ExtendedProperties - /// MaxLength - /// Namespace - /// Prefix - /// ReadOnly - /// Unique - /// - /// - /// A with the expected configuration. - /// - /// 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 AndConstraint BeEquivalentTo(DataColumn expectation, string because = "", - params object[] becauseArgs) - { - return BeEquivalentTo( - expectation, - options => options, - because, - becauseArgs); - } - - /// - /// Asserts that an instance of is equivalent to another. - /// - /// - /// Data columns are equivalent when the following members have the same values: - /// - /// - /// AllowDBNull - /// AutoIncrement - /// AutoIncrementSeed - /// AutoIncrementStep - /// Caption - /// ColumnName - /// DataType - /// DateTimeMode - /// DefaultValue - /// Expression - /// ExtendedProperties - /// MaxLength - /// Namespace - /// Prefix - /// ReadOnly - /// Unique - /// - /// - /// - /// Testing of any property can be overridden using the callback. Exclude specific properties using - /// . - /// - /// - /// If or a related function is - /// used and the exclusion matches the subject , then the equivalency test will never - /// fail. - /// - /// - /// A with the expected configuration. - /// - /// A reference to the configuration object that can be used - /// to influence the way the object graphs are compared. You can also provide an alternative instance of the - /// class. The global defaults are determined by the - /// class. - /// - /// - /// 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 . - /// - /// is . - public AndConstraint BeEquivalentTo(DataColumn expectation, - Func, IDataEquivalencyAssertionOptions> config, - string because = "", params object[] becauseArgs) - { - Guard.ThrowIfArgumentIsNull(config); - - IDataEquivalencyAssertionOptions options = - config(AssertionOptions.CloneDefaults>(e => - new DataEquivalencyAssertionOptions(e))); - - var context = - new EquivalencyValidationContext(Node.From(() => AssertionScope.Current.CallerIdentity), options) - { - Reason = new Reason(because, becauseArgs), - TraceWriter = options.TraceWriter - }; - - var comparands = new Comparands - { - Subject = Subject, - Expectation = expectation, - CompileTimeType = typeof(DataColumn) - }; - - new EquivalencyValidator().AssertEquality(comparands, context); - - return new AndConstraint(this); - } - - protected override string Identifier => "DataColumn"; -} diff --git a/Src/FluentAssertions/Data/DataEquivalencyAssertionOptions.cs b/Src/FluentAssertions/Data/DataEquivalencyAssertionOptions.cs deleted file mode 100644 index b49c7d75ec..0000000000 --- a/Src/FluentAssertions/Data/DataEquivalencyAssertionOptions.cs +++ /dev/null @@ -1,267 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq.Expressions; -using System.Reflection; -using FluentAssertions.Equivalency; - -namespace FluentAssertions.Data; - -internal class DataEquivalencyAssertionOptions : EquivalencyAssertionOptions, IDataEquivalencyAssertionOptions -{ - private readonly HashSet excludeTableNames = new(); - private readonly HashSet excludeColumnNames = new(); - private readonly Dictionary> excludeColumnNamesByTableName = new(); - - public bool AllowMismatchedTypes { get; private set; } - - public bool IgnoreUnmatchedColumns { get; private set; } - - public bool ExcludeOriginalData { get; private set; } - - public RowMatchMode RowMatchMode { get; private set; } - - public ISet ExcludeTableNames => excludeTableNames; - - public ISet ExcludeColumnNames => excludeColumnNames; - - public DataEquivalencyAssertionOptions(EquivalencyAssertionOptions defaults) - : base(defaults) - { - } - - public IDataEquivalencyAssertionOptions AllowingMismatchedTypes() - { - AllowMismatchedTypes = true; - return this; - } - - public IDataEquivalencyAssertionOptions IgnoringUnmatchedColumns() - { - IgnoreUnmatchedColumns = true; - return this; - } - - public IDataEquivalencyAssertionOptions UsingRowMatchMode(RowMatchMode rowMatchMode) - { - RowMatchMode = rowMatchMode; - return this; - } - - public IDataEquivalencyAssertionOptions ExcludingOriginalData() - { - ExcludeOriginalData = true; - return this; - } - - public new IDataEquivalencyAssertionOptions Excluding(Expression> expression) - { - base.Excluding(expression); - return this; - } - - public IDataEquivalencyAssertionOptions ExcludingRelated(Expression> expression) - { - ExcludeMemberOfRelatedTypeByGeneratedPredicate(expression); - return this; - } - - public IDataEquivalencyAssertionOptions ExcludingRelated(Expression> expression) - { - ExcludeMemberOfRelatedTypeByGeneratedPredicate(expression); - return this; - } - - public IDataEquivalencyAssertionOptions ExcludingRelated(Expression> expression) - { - ExcludeMemberOfRelatedTypeByGeneratedPredicate(expression); - return this; - } - - public IDataEquivalencyAssertionOptions ExcludingRelated(Expression> expression) - { - ExcludeMemberOfRelatedTypeByGeneratedPredicate(expression); - return this; - } - - public IDataEquivalencyAssertionOptions ExcludingRelated(Expression> expression) - { - ExcludeMemberOfSubtypeOfRelatedTypeByGeneratedPredicate(expression); - ExcludeMemberOfSubtypeOfRelatedTypeByGeneratedPredicate(expression); - return this; - } - - public IDataEquivalencyAssertionOptions ExcludingRelated(Expression> expression) - { - ExcludeMemberOfRelatedTypeByGeneratedPredicate(expression); - return this; - } - - public IDataEquivalencyAssertionOptions ExcludingRelated(Expression> expression) - { - ExcludeMemberOfRelatedTypeByGeneratedPredicate(expression); - return this; - } - - private void ExcludeMemberOfRelatedTypeByGeneratedPredicate( - Expression> expression) - { - Expression> predicate = BuildMemberSelectionPredicate( - typeof(TDeclaringType), - GetMemberAccessTargetMember(expression.Body)); - - Excluding(predicate); - } - - private void ExcludeMemberOfSubtypeOfRelatedTypeByGeneratedPredicate( - Expression> expression) - where TInheritingType : TDeclaringType - { - Expression> predicate = BuildMemberSelectionPredicate( - typeof(TInheritingType), - GetMemberAccessTargetMember(expression.Body)); - - Excluding(predicate); - } - - private static MemberInfo GetMemberAccessTargetMember(Expression expression) - { - if (expression is UnaryExpression unaryExpression - && unaryExpression.NodeType == ExpressionType.Convert) - { - // If the expression is a value type, then accessing it will involve an - // implicit boxing conversion to type object that we need to ignore. - expression = unaryExpression.Operand; - } - - if (expression is MemberExpression memberExpression) - { - return memberExpression.Member; - } - - throw new ArgumentException("Expression must be a simple member access", nameof(expression)); - } - - private static Expression> BuildMemberSelectionPredicate(Type relatedSubjectType, - MemberInfo referencedMember) - { - ParameterExpression predicateMemberInfoArgument = Expression.Parameter(typeof(IMemberInfo)); - - BinaryExpression typeComparison = Expression.Equal( - Expression.MakeMemberAccess( - predicateMemberInfoArgument, - typeof(IMemberInfo).GetProperty(nameof(IMemberInfo.DeclaringType))), - Expression.Constant(relatedSubjectType)); - - BinaryExpression memberNameComparison = Expression.Equal( - Expression.MakeMemberAccess( - predicateMemberInfoArgument, - typeof(IMemberInfo).GetProperty(nameof(IMemberInfo.Name))), - Expression.Constant(referencedMember.Name)); - - BinaryExpression predicateBody = Expression.AndAlso( - typeComparison, - memberNameComparison); - - return Expression.Lambda>( - predicateBody, - predicateMemberInfoArgument); - } - - public new IDataEquivalencyAssertionOptions Excluding(Expression> predicate) - { - base.Excluding(predicate); - return this; - } - - public IDataEquivalencyAssertionOptions ExcludingTable(string tableName) - { - return ExcludingTables(tableName); - } - - public IDataEquivalencyAssertionOptions ExcludingTables(params string[] tableNames) - { - return ExcludingTables((IEnumerable)tableNames); - } - - public IDataEquivalencyAssertionOptions ExcludingTables(IEnumerable tableNames) - { - excludeTableNames.UnionWith(tableNames); - return this; - } - - public IDataEquivalencyAssertionOptions ExcludingColumnInAllTables(string columnName) - { - return ExcludingColumnsInAllTables(columnName); - } - - public IDataEquivalencyAssertionOptions ExcludingColumnsInAllTables(params string[] columnNames) - { - return ExcludingColumnsInAllTables((IEnumerable)columnNames); - } - - public IDataEquivalencyAssertionOptions ExcludingColumnsInAllTables(IEnumerable columnNames) - { - excludeColumnNames.UnionWith(columnNames); - return this; - } - - public IDataEquivalencyAssertionOptions ExcludingColumn(DataColumn column) - { - return ExcludingColumn(column.Table.TableName, column.ColumnName); - } - - public IDataEquivalencyAssertionOptions ExcludingColumns(params DataColumn[] columns) - { - return ExcludingColumns((IEnumerable)columns); - } - - public IDataEquivalencyAssertionOptions ExcludingColumns(IEnumerable columns) - { - foreach (DataColumn column in columns) - { - ExcludingColumn(column); - } - - return this; - } - - public IDataEquivalencyAssertionOptions ExcludingColumn(string tableName, string columnName) - { - return ExcludingColumns(tableName, columnName); - } - - public IDataEquivalencyAssertionOptions ExcludingColumns(string tableName, params string[] columnNames) - { - return ExcludingColumns(tableName, (IEnumerable)columnNames); - } - - public IDataEquivalencyAssertionOptions ExcludingColumns(string tableName, IEnumerable columnNames) - { - if (!excludeColumnNamesByTableName.TryGetValue(tableName, out HashSet excludeColumnNames)) - { - excludeColumnNames = new HashSet(); - excludeColumnNamesByTableName[tableName] = excludeColumnNames; - } - - excludeColumnNames.UnionWith(columnNames); - - return this; - } - - public bool ShouldExcludeColumn(DataColumn column) - { - if (excludeColumnNames.Contains(column.ColumnName)) - { - return true; - } - - if (excludeColumnNamesByTableName.TryGetValue(column.Table.TableName, out HashSet excludeColumnsForTable) - && excludeColumnsForTable.Contains(column.ColumnName)) - { - return true; - } - - return false; - } -} diff --git a/Src/FluentAssertions/Data/DataRowAssertions.cs b/Src/FluentAssertions/Data/DataRowAssertions.cs deleted file mode 100644 index 6f65842c99..0000000000 --- a/Src/FluentAssertions/Data/DataRowAssertions.cs +++ /dev/null @@ -1,217 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Diagnostics; -using System.Linq; -using FluentAssertions.Common; -using FluentAssertions.Equivalency; -using FluentAssertions.Execution; -using FluentAssertions.Primitives; - -namespace FluentAssertions.Data; - -/// -/// Provides convenient assertion methods on a that can be -/// used to assert equivalency and the presence of columns. -/// -[DebuggerNonUserCode] -public class DataRowAssertions : ReferenceTypeAssertions> - where TDataRow : DataRow -{ - public DataRowAssertions(TDataRow dataRow) - : base(dataRow) - { - } - - /// - /// Asserts that an instance of has a column with the expected column name. - /// - /// The value that is expected in . - /// - /// 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 AndWhichConstraint, DataColumn> HaveColumn(string expectedColumnName, string because = "", - params object[] becauseArgs) - { - var subjectColumn = default(DataColumn); - - if (Subject is null) - { - Execute.Assertion - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context:DataRow} to contain a column named {0}{reason}, but found .", - expectedColumnName); - } - else if (!Subject.Table.Columns.Contains(expectedColumnName)) - { - Execute.Assertion - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context:DataRow} to contain a column named {0}{reason}, but it does not.", - expectedColumnName); - } - else - { - subjectColumn = Subject.Table.Columns[expectedColumnName]; - } - - return new AndWhichConstraint, DataColumn>(this, subjectColumn); - } - - /// - /// Asserts that an instance of has columns with all of the supplied expected column names. - /// - /// An array of values expected in . - public AndConstraint> HaveColumns(params string[] expectedColumnNames) - { - return HaveColumns((IEnumerable)expectedColumnNames); - } - - /// - /// Asserts that an instance of has columns with all of the supplied expected column names. - /// - /// An of string values expected in . - /// - /// 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 AndConstraint> HaveColumns(IEnumerable expectedColumnNames, string because = "", - params object[] becauseArgs) - { - bool success = Execute.Assertion - .ForCondition(Subject is not null) - .BecauseOf(because, becauseArgs) - .FailWith( - "Expected {context:DataRow} to be in a table containing {0} column(s) with specific names{reason}, but found .", - () => expectedColumnNames.Count()); - - if (success) - { - foreach (var expectedColumnName in expectedColumnNames) - { - Execute.Assertion - .ForCondition(Subject.Table.Columns.Contains(expectedColumnName)) - .BecauseOf(because, becauseArgs) - .FailWith("Expected table containing {context:DataRow} to contain a column named {0}{reason}, but it does not.", - expectedColumnName); - } - } - - return new AndConstraint>(this); - } - - /// - /// Asserts that an instance of is equivalent to another. - /// - /// - /// Data rows are equivalent when they contain identical field data for the row they represent, and - /// the following members have the same values: - /// - /// - /// HasErrors - /// RowState - /// - /// - /// The objects must be of the same type; if two objects - /// are equivalent in all ways, except that one is part of a typed and is of a subclass - /// of , then by default, they will not be considered equivalent. This can be overridden - /// by using the overload that takes . - /// - /// A with the expected configuration. - /// - /// 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 AndConstraint> BeEquivalentTo(DataRow expectation, string because = "", - params object[] becauseArgs) - { - return BeEquivalentTo( - expectation, - options => options, - because, - becauseArgs); - } - - /// - /// Asserts that an instance of is equivalent to another. - /// - /// - /// Data rows are equivalent when they contain identical field data for the row they represent, and - /// the following members have the same values: - /// - /// - /// HasErrors - /// RowState - /// - /// - /// - /// The objects must be of the same type; if two objects - /// are equivalent in all ways, except that one is part of a typed and is of a subclass - /// of , then by default, they will not be considered equivalent. - /// - /// - /// This, as well as testing of any property can be overridden using the callback. - /// By calling , two - /// objects of differing types can be considered equivalent. Exclude specific properties using - /// . - /// Exclude columns of the data table (which also excludes the related field data in - /// objects) using or a related function. - /// - /// - /// - /// You can use - /// and related functions to exclude properties on other related System.Data types. - /// A with the expected configuration. - /// - /// A reference to the configuration object that can be used - /// to influence the way the object graphs are compared. You can also provide an alternative instance of the - /// class. The global defaults are determined by the - /// class. - /// - /// - /// 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 . - /// - /// is . - public AndConstraint> BeEquivalentTo(DataRow expectation, - Func, IDataEquivalencyAssertionOptions> config, string because = "", - params object[] becauseArgs) - { - Guard.ThrowIfArgumentIsNull(config); - - IDataEquivalencyAssertionOptions options = - config(AssertionOptions.CloneDefaults>(e => - new DataEquivalencyAssertionOptions(e))); - - var context = new EquivalencyValidationContext(Node.From(() => AssertionScope.Current.CallerIdentity), options) - { - Reason = new Reason(because, becauseArgs), - TraceWriter = options.TraceWriter - }; - - var comparands = new Comparands - { - Subject = Subject, - Expectation = expectation, - CompileTimeType = typeof(TDataRow), - }; - - new EquivalencyValidator().AssertEquality(comparands, context); - - return new AndConstraint>(this); - } - - protected override string Identifier => "DataRow"; -} diff --git a/Src/FluentAssertions/Data/DataSetAssertions.cs b/Src/FluentAssertions/Data/DataSetAssertions.cs deleted file mode 100644 index 2cf8559437..0000000000 --- a/Src/FluentAssertions/Data/DataSetAssertions.cs +++ /dev/null @@ -1,272 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Diagnostics; -using System.Linq; -using FluentAssertions.Common; -using FluentAssertions.Equivalency; -using FluentAssertions.Execution; -using FluentAssertions.Primitives; - -namespace FluentAssertions.Data; - -/// -/// Provides convenient assertion methods on a that can be -/// used to assert equivalency and the presence of tables. -/// -[DebuggerNonUserCode] -public class DataSetAssertions : ReferenceTypeAssertions> - where TDataSet : DataSet -{ - public DataSetAssertions(TDataSet dataSet) - : base(dataSet) - { - } - - /// - /// Asserts that an instance of contains exactly the expected number of tables in its collection. - /// - /// The expected number of rows. - /// - /// 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 AndConstraint> HaveTableCount(int expected, string because = "", - params object[] becauseArgs) - { - bool success = Execute.Assertion - .ForCondition(Subject is not null) - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context:DataSet} to contain exactly {0} table(s){reason}, but found .", expected); - - if (success) - { - int actualCount = Subject.Tables.Count; - - Execute.Assertion - .ForCondition(actualCount == expected) - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context:DataSet} to contain exactly {0} table(s){reason}, but found {1}.", expected, - actualCount); - } - - return new AndConstraint>(this); - } - - /// - /// Asserts that an instance of contains a table with the expected name. - /// - /// The value that is expected in . - /// - /// 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 AndWhichConstraint, DataTable> HaveTable(string expectedTableName, string because = "", - params object[] becauseArgs) - { - var subjectTable = default(DataTable); - - if (Subject is null) - { - Execute.Assertion - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context:DataSet} to contain a table named {0}{reason}, but found .", - expectedTableName); - } - else if (!Subject.Tables.Contains(expectedTableName)) - { - Execute.Assertion - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context:DataSet} to contain a table named {0}{reason}, but it does not.", expectedTableName); - } - else - { - subjectTable = Subject.Tables[expectedTableName]; - } - - return new AndWhichConstraint, DataTable>(this, subjectTable); - } - - /// - /// Asserts that an instance of has tables with all of the supplied expected column names. - /// - /// An array of values expected in . - public AndConstraint> HaveTables(params string[] expectedTableNames) - { - return HaveTables((IEnumerable)expectedTableNames); - } - - /// - /// Asserts that an instance of has tables with all of the supplied expected table names. - /// - /// An of string values expected in . - /// - /// 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 AndConstraint> HaveTables(IEnumerable expectedTableNames, string because = "", - params object[] becauseArgs) - { - bool success = Execute.Assertion - .ForCondition(Subject is not null) - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context:DataSet} to contain {0} table(s) with specific names{reason}, but found .", - () => expectedTableNames.Count()); - - if (success) - { - foreach (var expectedTableName in expectedTableNames) - { - Execute.Assertion - .ForCondition(Subject.Tables.Contains(expectedTableName)) - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context:DataSet} to contain a table named {0}{reason}, but it does not.", expectedTableName); - } - } - - return new AndConstraint>(this); - } - - /// - /// Asserts that an instance of is equivalent to another. - /// - /// - /// Data sets are equivalent when their and - /// collections are equivalent and the following members have the same values: - /// - /// - /// DataSetName - /// CaseSensitive - /// EnforceConstraints - /// HasErrors - /// Locale - /// Namespace - /// Prefix - /// RemotingFormat - /// SchemaSerializationMode - /// - /// - /// The objects must be of the same type; if two objects - /// are equivalent in all ways, except that one is a custom subclass of (e.g. to provide - /// typed accessors for values contained by the ), then by default, - /// they will not be considered equivalent. This can be overridden by using the overload that takes - /// . - /// - /// A with the expected configuration. - /// - /// 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 AndConstraint> BeEquivalentTo(DataSet expectation, string because = "", - params object[] becauseArgs) - { - return BeEquivalentTo( - expectation, - options => options, - because, - becauseArgs); - } - - /// - /// Asserts that an instance of is equivalent to another. - /// - /// - /// Data sets are equivalent when their and - /// collections are equivalent and the following members have the same values: - /// - /// - /// DataSetName - /// CaseSensitive - /// EnforceConstraints - /// HasErrors - /// Locale - /// Namespace - /// Prefix - /// RemotingFormat - /// SchemaSerializationMode - /// - /// - /// - /// The objects must be of the same type; if two objects - /// are equivalent in all ways, except that one is a custom subclass of (e.g. to provide - /// typed accessors for values contained by the ), then by default, - /// they will not be considered equivalent. - /// - /// - /// This, as well as testing of any property can be overridden using the callback. - /// By calling , two - /// objects of differing types can be considered equivalent. This setting applies to all types recursively tested - /// as part of the . - /// - /// - /// Exclude specific properties using . - /// Exclude specific tables within the data set using - /// or a related function. You can also indicate that columns should be excluded within the - /// objects recursively tested as part of the using - /// or a related function. The method - /// can be used to exclude columns across all objects in the that share - /// the same name. - /// - /// - /// You can use - /// and related functions to exclude properties on other related System.Data types. - /// - /// - /// A with the expected configuration. - /// - /// A reference to the configuration object that can be used - /// to influence the way the object graphs are compared. You can also provide an alternative instance of the - /// class. The global defaults are determined by the - /// class. - /// - /// - /// 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 . - /// - /// is . - public AndConstraint> BeEquivalentTo(DataSet expectation, - Func, IDataEquivalencyAssertionOptions> config, string because = "", - params object[] becauseArgs) - { - Guard.ThrowIfArgumentIsNull(config); - - IDataEquivalencyAssertionOptions options = - config(AssertionOptions.CloneDefaults>(e => - new DataEquivalencyAssertionOptions(e))); - - var comparands = new Comparands - { - Subject = Subject, - Expectation = expectation, - CompileTimeType = typeof(TDataSet) - }; - - var context = new EquivalencyValidationContext(Node.From(() => AssertionScope.Current.CallerIdentity), options) - { - Reason = new Reason(because, becauseArgs), - TraceWriter = options.TraceWriter, - }; - - var equivalencyValidator = new EquivalencyValidator(); - equivalencyValidator.AssertEquality(comparands, context); - - return new AndConstraint>(this); - } - - protected override string Identifier => "DataSet"; -} diff --git a/Src/FluentAssertions/Data/DataTableAssertions.cs b/Src/FluentAssertions/Data/DataTableAssertions.cs deleted file mode 100644 index 2fe7427d2e..0000000000 --- a/Src/FluentAssertions/Data/DataTableAssertions.cs +++ /dev/null @@ -1,284 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Diagnostics; -using System.Linq; -using FluentAssertions.Common; -using FluentAssertions.Equivalency; -using FluentAssertions.Execution; -using FluentAssertions.Primitives; - -namespace FluentAssertions.Data; - -/// -/// Provides convenient assertion methods on a that can be -/// used to assert equivalency and the presence of rows and columns. -/// -[DebuggerNonUserCode] -public class DataTableAssertions : ReferenceTypeAssertions> - where TDataTable : DataTable -{ - public DataTableAssertions(TDataTable dataTable) - : base(dataTable) - { - } - - /// - /// Asserts that an instance of contains exactly the expected number of rows in its collection. - /// - /// The expected number of rows. - /// - /// 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 AndConstraint> HaveRowCount(int expected, string because = "", - params object[] becauseArgs) - { - bool success = Execute.Assertion - .ForCondition(Subject is not null) - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context:DataTable} to contain exactly {0} row(s){reason}, but found .", expected); - - if (success) - { - int actualCount = Subject.Rows.Count; - - Execute.Assertion - .ForCondition(actualCount == expected) - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context:DataTable} to contain exactly {0} row(s){reason}, but found {1}.", expected, - actualCount); - } - - return new AndConstraint>(this); - } - - /// - /// Asserts that an instance of has a column with the expected column name. - /// - /// The value that is expected in . - /// - /// 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 AndWhichConstraint, DataColumn> HaveColumn(string expectedColumnName, - string because = "", params object[] becauseArgs) - { - var subjectColumn = default(DataColumn); - - if (Subject is null) - { - Execute.Assertion - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context:DataTable} to contain a column named {0}{reason}, but found .", - expectedColumnName); - } - else if (!Subject.Columns.Contains(expectedColumnName)) - { - Execute.Assertion - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context:DataTable} to contain a column named {0}{reason}, but it does not.", - expectedColumnName); - } - else - { - subjectColumn = Subject.Columns[expectedColumnName]; - } - - return new AndWhichConstraint, DataColumn>(this, subjectColumn); - } - - /// - /// Asserts that an instance of has columns with all of the supplied expected column names. - /// - /// An array of values expected in . - public AndConstraint> HaveColumns(params string[] expectedColumnNames) - { - return HaveColumns((IEnumerable)expectedColumnNames); - } - - /// - /// Asserts that an instance of has columns with all of the supplied expected column names. - /// - /// An of string values expected in . - /// - /// 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 AndConstraint> HaveColumns(IEnumerable expectedColumnNames, - string because = "", params object[] becauseArgs) - { - bool success = Execute.Assertion - .ForCondition(Subject is not null) - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context:DataTable} to contain {0} column(s) with specific names{reason}, but found .", - () => expectedColumnNames.Count()); - - if (success) - { - foreach (var expectedColumnName in expectedColumnNames) - { - Execute.Assertion - .ForCondition(Subject.Columns.Contains(expectedColumnName)) - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context:DataTable} to contain a column named {0}{reason}, but it does not.", - expectedColumnName); - } - } - - return new AndConstraint>(this); - } - - /// - /// Asserts that an instance of is equivalent to another. - /// - /// - /// Data tables are equivalent when the following members have the same values: - /// - /// - /// TableName - /// CaseSensitive - /// DisplayExpression - /// HasErrors - /// Locale - /// Namespace - /// Prefix - /// RemotingFormat - /// - /// - /// In addition, the following collections must contain equivalent data: - /// - /// - /// ChildRelations - /// Columns - /// Constraints - /// ExtendedProperties - /// ParentRelations - /// PrimaryKey - /// Rows - /// - /// - /// The objects must be of the same type; if two objects - /// are equivalent in all ways, except that one is a typed that is a subclass - /// of , then by default, they will not be considered equivalent. This can be overridden by - /// using the overload that takes . - /// - /// A with the expected configuration. - /// - /// 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 AndConstraint> BeEquivalentTo(DataTable expectation, string because = "", - params object[] becauseArgs) - { - return BeEquivalentTo( - expectation, - options => options, - because, - becauseArgs); - } - - /// - /// Asserts that an instance of is equivalent to another. - /// - /// - /// Data tables are equivalent when the following members have the same values: - /// - /// - /// TableName - /// CaseSensitive - /// DisplayExpression - /// HasErrors - /// Locale - /// Namespace - /// Prefix - /// RemotingFormat - /// - /// - /// In addition, the following collections must contain equivalent data: - /// - /// - /// ChildRelations - /// Columns - /// Constraints - /// ExtendedProperties - /// ParentRelations - /// PrimaryKey - /// Rows - /// - /// - /// The objects must be of the same type; if two objects - /// are equivalent in all ways, except that one is a typed that is a subclass - /// of , then by default, they will not be considered equivalent. - /// - /// - /// This, as well as testing of any property can be overridden using the callback. - /// By calling , two - /// objects of differing types can be considered equivalent. Exclude specific properties using - /// . - /// Exclude columns of the data table using - /// or a related function -- this excludes both the objects in - /// and associated field data in objects within the . - /// - /// - /// You can use - /// and related functions to exclude properties on other related System.Data types. - /// - /// - /// A with the expected configuration. - /// - /// A reference to the configuration object that can be used - /// to influence the way the object graphs are compared. You can also provide an alternative instance of the - /// class. The global defaults are determined by the - /// class. - /// - /// - /// 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 . - /// - /// is . - public AndConstraint> BeEquivalentTo(DataTable expectation, - Func, IDataEquivalencyAssertionOptions> config, - string because = "", params object[] becauseArgs) - { - Guard.ThrowIfArgumentIsNull(config); - - IDataEquivalencyAssertionOptions options = - config(AssertionOptions.CloneDefaults>(e => - new DataEquivalencyAssertionOptions(e))); - - var context = new EquivalencyValidationContext(Node.From(() => AssertionScope.Current.CallerIdentity), options) - { - Reason = new Reason(because, becauseArgs), - TraceWriter = options.TraceWriter - }; - - var comparands = new Comparands - { - Subject = Subject, - Expectation = expectation, - CompileTimeType = typeof(TDataTable), - }; - - new EquivalencyValidator().AssertEquality(comparands, context); - - return new AndConstraint>(this); - } - - protected override string Identifier => "DataTable"; -} diff --git a/Src/FluentAssertions/Data/IDataEquivalencyAssertionOptions.cs b/Src/FluentAssertions/Data/IDataEquivalencyAssertionOptions.cs deleted file mode 100644 index 41ebb4625d..0000000000 --- a/Src/FluentAssertions/Data/IDataEquivalencyAssertionOptions.cs +++ /dev/null @@ -1,193 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq.Expressions; -using FluentAssertions.Equivalency; - -namespace FluentAssertions.Data; - -/// -/// Provides access to configuration for equivalency assertions on System.Data types (, -/// , , , , -/// ). -/// -/// The System.Data type being tested for equivalency. -public interface IDataEquivalencyAssertionOptions : IEquivalencyAssertionOptions -{ - /// - /// Specifies that the subject and the expectation should not be considered non-equivalent if their exact data types do not match. - /// - IDataEquivalencyAssertionOptions AllowingMismatchedTypes(); - - /// - /// Specifies that when comparing , columns that are unmatched between the subject and the expectation should be ignored. - /// - IDataEquivalencyAssertionOptions IgnoringUnmatchedColumns(); - - /// - /// Specifies the that should be used when comparing . By default, rows are matched by their index in the collection. But, if the has a set, it is possible to use to indicate that rows should be matched by their primary key values, irrespective of their index within the collection. - /// - /// The to use when comparing between the subject and the expectation. - IDataEquivalencyAssertionOptions UsingRowMatchMode(RowMatchMode rowMatchMode); - - /// - /// Specifies that when comparing objects that are in the state, only the current field values should be compared. Original field values are excluded from comparison. This only affects comparisons where both the subject and the expectation are in the modified state. - /// - IDataEquivalencyAssertionOptions ExcludingOriginalData(); - - /// - /// Excludes members of the objects under test from comparison by means of a predicate that selects members based on objects describing them. - /// - /// A functor that returns true if the parameter refers to a member that should be excluded. - IDataEquivalencyAssertionOptions Excluding(Expression> predicate); - - /// - /// Excludes a member of the objects under test from comparison by means of an that refers to the member in question. - /// - /// An that accesses the member to be excluded. - IDataEquivalencyAssertionOptions Excluding(Expression> expression); - - /// - /// Excludes an entire table from comparison. When comparing objects, if a table is present by the supplied name, it is not considered for the purpose of determining equivalency. This configuration option has no effect when comparing other types of object, including . - /// - /// The value for for which tables within a should be ignored. - IDataEquivalencyAssertionOptions ExcludingTable(string tableName); - - /// - /// Excludes tables from comparison using names in an set. When comparing objects, if a table is present by one of the supplied names, it is not considered for the purpose of determining equivalency. This configuration option has no effect when comparing other types of object, including . - /// - /// An of values for for which tables within a should be ignored. - IDataEquivalencyAssertionOptions ExcludingTables(IEnumerable tableNames); - - /// - /// Excludes tables from comparison using an array of table names. When comparing objects, if a table is present by one of the supplied names, it is not considered for the purpose of determining equivalency. This configuration option has no effect when comparing other types of object, including . - /// - /// An array of values for for which tables within a should be ignored. - IDataEquivalencyAssertionOptions ExcludingTables(params string[] tableNames); - - /// - /// Excludes a column from comparison by . The column to be excluded is matched by the name of its associated and its own . - /// - /// A object that specifies which column should be ignored. - /// - /// When comparing objects (e.g. within ), excluded columns are ignored completely. When comparing objects, the data associated with excluded columns is ignored. - /// - IDataEquivalencyAssertionOptions ExcludingColumn(DataColumn column); - - /// - /// Excludes a column from comparison by the name of its associated and its own . - /// - /// The value for for which columns should be ignored. - /// The value for for which columns should be ignored. - /// - /// When comparing objects (e.g. within ), excluded columns are ignored completely. When comparing objects, the data associated with excluded columns is ignored. - /// - IDataEquivalencyAssertionOptions ExcludingColumn(string tableName, string columnName); - - /// - /// Exclude an enumerable set of columns from comparison by . For each item in the enumeration, the column to be excluded is matched by the name of its associated and its own . - /// - /// An object that specifies which column(s) should be ignored. - /// - /// When comparing objects (e.g. within ), excluded columns are ignored completely. When comparing objects, the data associated with excluded columns is ignored. - /// - IDataEquivalencyAssertionOptions ExcludingColumns(IEnumerable columns); - - /// - /// Excludes an array of columns from comparison by . For each element in the array, the column to be excluded is matched by the name of its associated and its own . - /// - /// An array of objects that specify which columns should be ignored. - /// - /// When comparing objects (e.g. within ), excluded columns are ignored completely. When comparing objects, the data associated with excluded columns is ignored. - /// - IDataEquivalencyAssertionOptions ExcludingColumns(params DataColumn[] columns); - - /// - /// Excludes an enumerable set of columns from comparison by name, within tables with a specified name./>. - /// - /// The value for for which columns should be ignored. - /// An of values that specify the values for which columns should be ignored. - /// - /// When comparing objects (e.g. within ), excluded columns are ignored completely. When comparing objects, the data associated with excluded columns is ignored. - /// - IDataEquivalencyAssertionOptions ExcludingColumns(string tableName, IEnumerable columnNames); - - /// - /// Excludes an array of columns from comparison by name, within tables with a specified name./>. - /// - /// The value for for which columns should be ignored. - /// An array of values that specify the values for which columns should be ignored. - /// - /// When comparing objects (e.g. within ), excluded columns are ignored completely. When comparing objects, the data associated with excluded columns is ignored. - /// - IDataEquivalencyAssertionOptions ExcludingColumns(string tableName, params string[] columnNames); - - /// - /// Excludes columns from comparison by comparing only the . If columns exist by the same name in multiple objects within a , they are all excluded from comparison. - /// - /// The value for for which columns should be ignored. - /// - /// When comparing objects (e.g. within ), excluded columns are ignored completely. When comparing objects, the data associated with excluded columns is ignored. - /// - IDataEquivalencyAssertionOptions ExcludingColumnInAllTables(string columnName); - - /// - /// Excludes columns from comparison by comparing only the . If columns exist by the same name in multiple objects within a , they are all excluded from comparison. - /// - /// An of values that specify the values for which columns should be ignored. - /// - /// When comparing objects (e.g. within ), excluded columns are ignored completely. When comparing objects, the data associated with excluded columns is ignored. - /// - IDataEquivalencyAssertionOptions ExcludingColumnsInAllTables(IEnumerable columnNames); - - /// - /// Excludes columns from comparison by comparing only the . If columns exist by the same name in multiple objects within a , they are all excluded from comparison. - /// - /// An array of values that specify the values for which columns should be ignored. - /// - /// When comparing objects (e.g. within ), excluded columns are ignored completely. When comparing objects, the data associated with excluded columns is ignored. - /// - IDataEquivalencyAssertionOptions ExcludingColumnsInAllTables(params string[] columnNames); - - /// - /// Excludes properties of from comparison by means of an that refers to the member in question. - /// - /// An that accesses the member to be excluded. - IDataEquivalencyAssertionOptions ExcludingRelated(Expression> expression); - - /// - /// Excludes properties of from comparison by means of an that refers to the member in question. - /// - /// An that accesses the member to be excluded. - IDataEquivalencyAssertionOptions ExcludingRelated(Expression> expression); - - /// - /// Excludes properties of from comparison by means of an that refers to the member in question. - /// - /// An that accesses the member to be excluded. - IDataEquivalencyAssertionOptions ExcludingRelated(Expression> expression); - - /// - /// Excludes properties of from comparison by means of an that refers to the member in question. - /// - /// An that accesses the member to be excluded. - IDataEquivalencyAssertionOptions ExcludingRelated(Expression> expression); - - /// - /// Excludes properties of from comparison by means of an that refers to the member in question. - /// - /// An that accesses the member to be excluded. - IDataEquivalencyAssertionOptions ExcludingRelated(Expression> expression); - - /// - /// Excludes properties of from comparison by means of an that refers to the member in question. - /// - /// An that accesses the member to be excluded. - IDataEquivalencyAssertionOptions ExcludingRelated(Expression> expression); - - /// - /// Excludes properties of from comparison by means of an that refers to the member in question. - /// - /// An that accesses the member to be excluded. - IDataEquivalencyAssertionOptions ExcludingRelated(Expression> expression); -} diff --git a/Src/FluentAssertions/Data/RowMatchMode.cs b/Src/FluentAssertions/Data/RowMatchMode.cs deleted file mode 100644 index c36d737b32..0000000000 --- a/Src/FluentAssertions/Data/RowMatchMode.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Data; - -namespace FluentAssertions.Data; - -/// -/// Indicates how objects from different objects should be matched -/// up for equivalency comparisons. -/// -public enum RowMatchMode -{ - /// - /// Indicates that objects should be matched up by their index within the - /// collection. This is the default. - /// - Index, - - /// - /// Indicates that objects should be matched up by the values they have for - /// the table's . For this to work, the rows must be from - /// objects with exactly equivalent - /// configuration. - /// - PrimaryKey, -} diff --git a/Src/FluentAssertions/DataColumnCollectionAssertionExtensions.cs b/Src/FluentAssertions/DataColumnCollectionAssertionExtensions.cs deleted file mode 100644 index d830033eb8..0000000000 --- a/Src/FluentAssertions/DataColumnCollectionAssertionExtensions.cs +++ /dev/null @@ -1,173 +0,0 @@ -using System; -using System.Data; -using System.Linq; -using FluentAssertions.Collections; -using FluentAssertions.Common; -using FluentAssertions.Execution; - -namespace FluentAssertions; - -public static class DataColumnCollectionAssertionExtensions -{ - /// - /// Asserts that an object reference refers to the exact same object as another object reference. - /// - /// The object that is being extended. - /// The expected object - /// - /// 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 . - /// - /// is . - public static AndConstraint> BeSameAs( - this GenericCollectionAssertions assertion, DataColumnCollection expected, string because = "", - params object[] becauseArgs) - { - Guard.ThrowIfArgumentIsNull( - expected, nameof(expected), "Cannot verify same reference against a collection (use BeNull instead?)."); - - if (assertion.Subject is ICollectionWrapper wrapper) - { - var actualSubject = wrapper.UnderlyingCollection; - - Execute.Assertion - .UsingLineBreaks - .ForCondition(ReferenceEquals(actualSubject, expected)) - .BecauseOf(because, becauseArgs) - .FailWith( - "Expected {context:column collection} to refer to {0}{reason}, but found {1} (different underlying object).", - expected, actualSubject); - } - else - { - Execute.Assertion - .BecauseOf(because, becauseArgs) - .FailWith( - "Invalid expectation: Expected {context:column collection} to refer to an instance of " + - "DataColumnCollection{reason}, but found {0}.", - assertion.Subject); - } - - return new AndConstraint>(assertion); - } - - /// - /// Asserts that an object reference refers to a different object than another object reference refers to. - /// - /// The object that is being extended. - /// The unexpected object - /// - /// 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 . - /// - /// is . - public static AndConstraint> NotBeSameAs( - this GenericCollectionAssertions assertion, DataColumnCollection unexpected, string because = "", - params object[] becauseArgs) - { - Guard.ThrowIfArgumentIsNull( - unexpected, nameof(unexpected), "Cannot verify same reference against a collection (use NotBeNull instead?)."); - - if (assertion.Subject is ICollectionWrapper wrapper) - { - var actualSubject = wrapper.UnderlyingCollection; - - Execute.Assertion - .UsingLineBreaks - .ForCondition(!ReferenceEquals(actualSubject, unexpected)) - .BecauseOf(because, becauseArgs) - .FailWith("Did not expect {context:column collection} to refer to {0}{reason}.", unexpected); - } - else - { - Execute.Assertion - .BecauseOf(because, becauseArgs) - .FailWith( - "Invalid expectation: Expected {context:column collection} to refer to a different instance of " + - "DataColumnCollection{reason}, but found {0}.", - assertion.Subject); - } - - return new AndConstraint>(assertion); - } - - /// - /// Assert that the current collection has the same number of elements as . - /// - /// The object that is being extended. - /// The other collection with the same expected number of elements - /// - /// 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 . - /// - /// is . - public static AndConstraint> HaveSameCount( - this GenericCollectionAssertions assertion, DataColumnCollection otherCollection, string because = "", - params object[] becauseArgs) - { - Guard.ThrowIfArgumentIsNull( - otherCollection, nameof(otherCollection), "Cannot verify count against a collection."); - - Execute.Assertion - .BecauseOf(because, becauseArgs) - .WithExpectation("Expected {context:collection} to have ") - .Given(() => assertion.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} column(s){reason}, but found {1}.", count => count.expected, count => count.actual) - .Then - .ClearExpectation(); - - return new AndConstraint>(assertion); - } - - /// - /// Assert that the current collection of s does not have the same number of columns as - /// . - /// - /// The object that is being extended. - /// The other with the unexpected number of - /// elements - /// - /// 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 . - /// - /// is . - public static AndConstraint> NotHaveSameCount( - this GenericCollectionAssertions assertion, DataColumnCollection otherCollection, string because = "", - params object[] becauseArgs) - { - Guard.ThrowIfArgumentIsNull( - otherCollection, nameof(otherCollection), "Cannot verify count against a collection."); - - Execute.Assertion - .BecauseOf(because, becauseArgs) - .WithExpectation("Expected {context:collection} to not have ") - .Given(() => assertion.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} column(s){reason}, but found {1}.", count => count.expected, count => count.actual) - .Then - .ClearExpectation(); - - return new AndConstraint>(assertion); - } -} diff --git a/Src/FluentAssertions/DataRowAssertionExtensions.cs b/Src/FluentAssertions/DataRowAssertionExtensions.cs deleted file mode 100644 index b48db92509..0000000000 --- a/Src/FluentAssertions/DataRowAssertionExtensions.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Data; -using System.Diagnostics; -using FluentAssertions.Data; -using JetBrains.Annotations; - -namespace FluentAssertions; - -/// -/// Contains an extension method for custom assertions in unit tests related to DataRow objects. -/// -[DebuggerNonUserCode] -public static class DataRowAssertionExtensions -{ - /// - /// Returns a object that can be used to assert the - /// current . - /// - [Pure] - public static DataRowAssertions Should(this TDataRow actualValue) - where TDataRow : DataRow - { - return new DataRowAssertions(actualValue); - } -} diff --git a/Src/FluentAssertions/DataRowCollectionAssertionExtensions.cs b/Src/FluentAssertions/DataRowCollectionAssertionExtensions.cs deleted file mode 100644 index 3e5f04df8e..0000000000 --- a/Src/FluentAssertions/DataRowCollectionAssertionExtensions.cs +++ /dev/null @@ -1,161 +0,0 @@ -using System; -using System.Data; -using System.Linq; -using FluentAssertions.Collections; -using FluentAssertions.Common; -using FluentAssertions.Execution; - -namespace FluentAssertions; - -public static class DataRowCollectionAssertionExtensions -{ - /// - /// Asserts that an object reference refers to the exact same object as another object reference. - /// - /// The expected object - /// - /// 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 static AndConstraint> BeSameAs( - this GenericCollectionAssertions assertion, DataRowCollection expected, string because = "", - params object[] becauseArgs) - { - if (assertion.Subject is ICollectionWrapper wrapper) - { - var actualSubject = wrapper.UnderlyingCollection; - - Execute.Assertion - .UsingLineBreaks - .ForCondition(ReferenceEquals(actualSubject, expected)) - .BecauseOf(because, becauseArgs) - .FailWith( - "Expected {context:row collection} to refer to {0}{reason}, but found {1} (different underlying object).", - expected, actualSubject); - } - else - { - Execute.Assertion - .BecauseOf(because, becauseArgs) - .FailWith( - "Invalid expectation: Expected {context:column collection} to refer to an instance of " + - "DataRowCollection{reason}, but found {0}.", - assertion.Subject); - } - - return new AndConstraint>(assertion); - } - - /// - /// Asserts that an object reference refers to a different object than another object reference refers to. - /// - /// The unexpected object - /// - /// 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 static AndConstraint> NotBeSameAs( - this GenericCollectionAssertions assertion, DataRowCollection unexpected, string because = "", - params object[] becauseArgs) - { - if (assertion.Subject is ICollectionWrapper wrapper) - { - var actualSubject = wrapper.UnderlyingCollection; - - Execute.Assertion - .UsingLineBreaks - .ForCondition(!ReferenceEquals(actualSubject, unexpected)) - .BecauseOf(because, becauseArgs) - .FailWith("Did not expect {context:row collection} to refer to {0}{reason}.", unexpected); - } - else - { - Execute.Assertion - .BecauseOf(because, becauseArgs) - .FailWith( - "Invalid expectation: Expected {context:column collection} to refer to a different instance of " + - "DataRowCollection{reason}, but found {0}.", - assertion.Subject); - } - - return new AndConstraint>(assertion); - } - - /// - /// Assert that the current collection of s has the same number of rows as - /// . - /// - /// The other collection with the same expected number of elements - /// - /// 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 . - /// - /// is . - public static AndConstraint> HaveSameCount( - this GenericCollectionAssertions assertion, DataRowCollection otherCollection, string because = "", - params object[] becauseArgs) - { - Guard.ThrowIfArgumentIsNull( - otherCollection, nameof(otherCollection), "Cannot verify count against a collection."); - - Execute.Assertion - .BecauseOf(because, becauseArgs) - .WithExpectation("Expected {context:collection} to have ") - .Given(() => assertion.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} row(s){reason}, but found {1}.", count => count.expected, count => count.actual) - .Then - .ClearExpectation(); - - return new AndConstraint>(assertion); - } - - /// - /// Assert that the current collection of s does not have the same number of rows as - /// . - /// - /// The other with the unexpected number of elements - /// - /// 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 . - /// - /// is . - public static AndConstraint> NotHaveSameCount( - this GenericCollectionAssertions assertion, DataRowCollection otherCollection, string because = "", - params object[] becauseArgs) - { - Guard.ThrowIfArgumentIsNull( - otherCollection, nameof(otherCollection), "Cannot verify count against a collection."); - - Execute.Assertion - .BecauseOf(because, becauseArgs) - .WithExpectation("Expected {context:collection} to not have ") - .Given(() => assertion.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} row(s){reason}, but found {1}.", count => count.expected, count => count.actual) - .Then - .ClearExpectation(); - - return new AndConstraint>(assertion); - } -} diff --git a/Src/FluentAssertions/DataSetAssertionExtensions.cs b/Src/FluentAssertions/DataSetAssertionExtensions.cs deleted file mode 100644 index e993ddf88b..0000000000 --- a/Src/FluentAssertions/DataSetAssertionExtensions.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Data; -using System.Diagnostics; -using FluentAssertions.Data; -using JetBrains.Annotations; - -namespace FluentAssertions; - -/// -/// Contains an extension method for custom assertions in unit tests related to DataSet objects. -/// -[DebuggerNonUserCode] -public static class DataSetAssertionExtensions -{ - /// - /// Returns a object that can be used to assert the - /// current . - /// - [Pure] - public static DataSetAssertions Should(this TDataSet actualValue) - where TDataSet : DataSet - { - return new DataSetAssertions(actualValue); - } -} diff --git a/Src/FluentAssertions/DataTableAssertionExtensions.cs b/Src/FluentAssertions/DataTableAssertionExtensions.cs deleted file mode 100644 index ee7ea0e191..0000000000 --- a/Src/FluentAssertions/DataTableAssertionExtensions.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Data; -using System.Diagnostics; -using FluentAssertions.Data; -using JetBrains.Annotations; - -namespace FluentAssertions; - -/// -/// Contains an extension method for custom assertions in unit tests related to DataTable objects. -/// -[DebuggerNonUserCode] -public static class DataTableAssertionExtensions -{ - /// - /// Returns a object that can be used to assert the - /// current . - /// - [Pure] - public static DataTableAssertions Should(this TDataTable actualValue) - where TDataTable : DataTable - { - return new DataTableAssertions(actualValue); - } -} diff --git a/Src/FluentAssertions/DataTableCollectionAssertionExtensions.cs b/Src/FluentAssertions/DataTableCollectionAssertionExtensions.cs deleted file mode 100644 index f9c0f7c1b2..0000000000 --- a/Src/FluentAssertions/DataTableCollectionAssertionExtensions.cs +++ /dev/null @@ -1,199 +0,0 @@ -using System; -using System.Data; -using System.Linq; -using FluentAssertions.Collections; -using FluentAssertions.Common; -using FluentAssertions.Execution; - -namespace FluentAssertions; - -public static class DataTableCollectionAssertionExtensions -{ - /// - /// Asserts that an object reference refers to the exact same object as another object reference. - /// - /// The expected object - /// - /// 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 static AndConstraint> BeSameAs( - this GenericCollectionAssertions assertion, DataTableCollection expected, string because = "", - params object[] becauseArgs) - { - if (assertion.Subject is ICollectionWrapper wrapper) - { - var actualSubject = wrapper.UnderlyingCollection; - - Execute.Assertion - .UsingLineBreaks - .ForCondition(ReferenceEquals(actualSubject, expected)) - .BecauseOf(because, becauseArgs) - .FailWith( - "Expected {context:table collection} to refer to {0}{reason}, but found {1} (different underlying object).", - expected, actualSubject); - } - else - { - Execute.Assertion - .BecauseOf(because, becauseArgs) - .FailWith( - "Invalid expectation: Expected {context:column collection} to refer to an instance of " + - "DataTableCollection{reason}, but found {0}.", - assertion.Subject); - } - - return new AndConstraint>(assertion); - } - - /// - /// Asserts that an object reference refers to a different object than another object reference refers to. - /// - /// The unexpected object - /// - /// 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 static AndConstraint> NotBeSameAs( - this GenericCollectionAssertions assertion, DataTableCollection unexpected, string because = "", - params object[] becauseArgs) - { - if (assertion.Subject is ICollectionWrapper wrapper) - { - var actualSubject = wrapper.UnderlyingCollection; - - Execute.Assertion - .UsingLineBreaks - .ForCondition(!ReferenceEquals(actualSubject, unexpected)) - .BecauseOf(because, becauseArgs) - .FailWith("Did not expect {context:table collection} to refer to {0}{reason}.", unexpected); - } - else - { - Execute.Assertion - .BecauseOf(because, becauseArgs) - .FailWith( - "Invalid expectation: Expected {context:column collection} to refer to a different instance of " + - "DataTableCollection{reason}, but found {0}.", - assertion.Subject); - } - - return new AndConstraint>(assertion); - } - - /// - /// Assert that the current collection of s has the same number of tables as - /// . - /// - /// The other with the same expected number of tables - /// - /// 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 static AndConstraint> HaveSameCount( - this GenericCollectionAssertions assertion, DataSet otherDataSet, string because = "", - params object[] becauseArgs) - { - return assertion.HaveSameCount(otherDataSet.Tables, because, becauseArgs); - } - - /// - /// Assert that the current collection of s does not have the same number of tables as - /// . - /// - /// The other with the unexpected number of tables - /// - /// 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 static AndConstraint> NotHaveSameCount( - this GenericCollectionAssertions assertion, DataSet otherDataSet, string because = "", - params object[] becauseArgs) - { - return assertion.NotHaveSameCount(otherDataSet.Tables, because, becauseArgs); - } - - /// - /// Assert that the current collection of s has the same number of tables as - /// . - /// - /// The other with the same expected number of tables - /// - /// 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 . - /// - /// is . - public static AndConstraint> HaveSameCount( - this GenericCollectionAssertions assertion, DataTableCollection otherCollection, string because = "", - params object[] becauseArgs) - { - Guard.ThrowIfArgumentIsNull( - otherCollection, nameof(otherCollection), "Cannot verify count against a collection."); - - Execute.Assertion - .BecauseOf(because, becauseArgs) - .WithExpectation("Expected {context:collection} to have ") - .Given(() => assertion.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} table(s){reason}, but found {1}.", count => count.expected, count => count.actual) - .Then - .ClearExpectation(); - - return new AndConstraint>(assertion); - } - - /// - /// Assert that the current collection of s does not have the same number of tables as - /// . - /// - /// The other with the unexpected number of tables - /// - /// 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 . - /// - /// is . - public static AndConstraint> NotHaveSameCount( - this GenericCollectionAssertions assertion, DataTableCollection otherCollection, string because = "", - params object[] becauseArgs) - { - Guard.ThrowIfArgumentIsNull( - otherCollection, nameof(otherCollection), "Cannot verify count against a collection."); - - Execute.Assertion - .BecauseOf(because, becauseArgs) - .WithExpectation("Expected {context:collection} to not have ") - .Given(() => assertion.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} table(s){reason}, but found {1}.", count => count.expected, count => count.actual) - .Then - .ClearExpectation(); - - return new AndConstraint>(assertion); - } -} diff --git a/Src/FluentAssertions/EnumAssertionsExtensions.cs b/Src/FluentAssertions/EnumAssertionsExtensions.cs index 31688fb9cc..828ca328cb 100644 --- a/Src/FluentAssertions/EnumAssertionsExtensions.cs +++ b/Src/FluentAssertions/EnumAssertionsExtensions.cs @@ -1,6 +1,8 @@ using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; +using FluentAssertions.Execution; using FluentAssertions.Primitives; namespace FluentAssertions; @@ -19,7 +21,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()); } /// @@ -27,9 +29,9 @@ public static EnumAssertions Should(this TEnum @enum) /// current . /// [Pure] - public static NullableEnumAssertions Should(this TEnum? @enum) + public static NullableEnumAssertions Should([NotNull] 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..d7cfd1e1e5 --- /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.Subject.Description); + + return chain + .WithReportable("configuration", () => context.Options.ToString()) + .BecauseOf(context.Reason); + } +} diff --git a/Src/FluentAssertions/Equivalency/Comparands.cs b/Src/FluentAssertions/Equivalency/Comparands.cs index 62c09762c3..4e763acf9b 100644 --- a/Src/FluentAssertions/Equivalency/Comparands.cs +++ b/Src/FluentAssertions/Equivalency/Comparands.cs @@ -29,6 +29,9 @@ public Comparands(object subject, object expectation, Type compileTimeType) /// public object Expectation { get; set; } + /// + /// Gets the compile-time type of the expectation object. + /// public Type CompileTimeType { get @@ -62,7 +65,7 @@ public Type RuntimeType /// /// If the expectation is a nullable type, it should return the type of the wrapped object. /// - public Type GetExpectedType(IEquivalencyAssertionOptions options) + public Type GetExpectedType(IEquivalencyOptions options) { Type type = options.UseRuntimeTyping ? RuntimeType : CompileTimeType; diff --git a/Src/FluentAssertions/Equivalency/ConversionSelector.cs b/Src/FluentAssertions/Equivalency/ConversionSelector.cs index d93c92fe9b..c2985fcae1 100644 --- a/Src/FluentAssertions/Equivalency/ConversionSelector.cs +++ b/Src/FluentAssertions/Equivalency/ConversionSelector.cs @@ -30,7 +30,7 @@ public ConversionSelectorRule(Func predicate, string descript private readonly List exclusions; public ConversionSelector() - : this(new List(), new List()) + : this([], []) { } diff --git a/Src/FluentAssertions/Equivalency/Digit.cs b/Src/FluentAssertions/Equivalency/Digit.cs index 29d8f9ddb0..123b552896 100644 --- a/Src/FluentAssertions/Equivalency/Digit.cs +++ b/Src/FluentAssertions/Equivalency/Digit.cs @@ -5,13 +5,13 @@ namespace FluentAssertions.Equivalency; internal class Digit { private readonly int length; - private readonly Digit nextDigit; + private readonly Digit next; private int index; - public Digit(int length, Digit nextDigit) + public Digit(int length, Digit next) { this.length = length; - this.nextDigit = nextDigit; + this.next = next; } public int[] GetIndices() @@ -23,7 +23,7 @@ public int[] GetIndices() while (digit is not null) { indices.Add(digit.index); - digit = digit.nextDigit; + digit = digit.next; } return indices.ToArray(); @@ -31,7 +31,7 @@ public int[] GetIndices() public bool Increment() { - bool success = nextDigit?.Increment() == true; + bool success = next?.Increment() == true; if (!success) { diff --git a/Src/FluentAssertions/Equivalency/EqualityStrategyProvider.cs b/Src/FluentAssertions/Equivalency/EqualityStrategyProvider.cs index 21903e2cd2..1d1c62864a 100644 --- a/Src/FluentAssertions/Equivalency/EqualityStrategyProvider.cs +++ b/Src/FluentAssertions/Equivalency/EqualityStrategyProvider.cs @@ -10,8 +10,8 @@ namespace FluentAssertions.Equivalency; internal sealed class EqualityStrategyProvider { - private readonly List referenceTypes = new(); - private readonly List valueTypes = new(); + private readonly List referenceTypes = []; + private readonly List valueTypes = []; private readonly ConcurrentDictionary typeCache = new(); [CanBeNull] @@ -48,24 +48,29 @@ public EqualityStrategy GetEqualityStrategy(Type type) { return EqualityStrategy.ForceMembers; } - else if (valueTypes.Count > 0 && valueTypes.Exists(t => typeKey.IsSameOrInherits(t))) + + if (valueTypes.Count > 0 && valueTypes.Exists(t => typeKey.IsSameOrInherits(t))) { return EqualityStrategy.ForceEquals; } - else if (!typeKey.IsPrimitive && referenceTypes.Count > 0 && - referenceTypes.Exists(t => typeKey.IsAssignableToOpenGeneric(t))) + + if (!typeKey.IsPrimitive && referenceTypes.Count > 0 && + referenceTypes.Exists(t => typeKey.IsAssignableToOpenGeneric(t))) { return EqualityStrategy.ForceMembers; } - else if (valueTypes.Count > 0 && valueTypes.Exists(t => typeKey.IsAssignableToOpenGeneric(t))) + + if (valueTypes.Count > 0 && valueTypes.Exists(t => typeKey.IsAssignableToOpenGeneric(t))) { return EqualityStrategy.ForceEquals; } - else if ((compareRecordsByValue.HasValue || defaultStrategy is null) && typeKey.IsRecord()) + + if ((compareRecordsByValue != null || defaultStrategy is null) && typeKey.IsRecord()) { return compareRecordsByValue is true ? EqualityStrategy.ForceEquals : EqualityStrategy.ForceMembers; } - else if (defaultStrategy is not null) + + if (defaultStrategy is not null) { return defaultStrategy(typeKey); } diff --git a/Src/FluentAssertions/Equivalency/EquivalencyAssertionOptions.cs b/Src/FluentAssertions/Equivalency/EquivalencyOptions.cs similarity index 80% rename from Src/FluentAssertions/Equivalency/EquivalencyAssertionOptions.cs rename to Src/FluentAssertions/Equivalency/EquivalencyOptions.cs index 8094aa0c1f..039a2195c1 100644 --- a/Src/FluentAssertions/Equivalency/EquivalencyAssertionOptions.cs +++ b/Src/FluentAssertions/Equivalency/EquivalencyOptions.cs @@ -1,5 +1,3 @@ -#region - using System; using System.Collections.Generic; using System.Linq.Expressions; @@ -9,23 +7,19 @@ using FluentAssertions.Equivalency.Ordering; using FluentAssertions.Equivalency.Selection; -#endregion - namespace FluentAssertions.Equivalency; -// REFACTOR rename to EquivalencyOptions - /// /// Represents the run-time type-specific behavior of a structural equivalency assertion. /// -public class EquivalencyAssertionOptions - : SelfReferenceEquivalencyAssertionOptions> +public class EquivalencyOptions + : SelfReferenceEquivalencyOptions> { - public EquivalencyAssertionOptions() + public EquivalencyOptions() { } - public EquivalencyAssertionOptions(IEquivalencyAssertionOptions defaults) + public EquivalencyOptions(IEquivalencyOptions defaults) : base(defaults) { } @@ -33,9 +27,13 @@ public EquivalencyAssertionOptions(IEquivalencyAssertionOptions defaults) /// /// Excludes the specified (nested) member from the structural equality check. /// - public EquivalencyAssertionOptions Excluding(Expression> expression) + public EquivalencyOptions Excluding(Expression> expression) { - AddSelectionRule(new ExcludeMemberByPathSelectionRule(expression.GetMemberPath())); + foreach (var memberPath in expression.GetMemberPaths()) + { + AddSelectionRule(new ExcludeMemberByPathSelectionRule(memberPath)); + } + return this; } @@ -46,9 +44,8 @@ public EquivalencyAssertionOptions Excluding(Expression For( Expression>> expression) { - var selectionRule = new ExcludeMemberByPathSelectionRule(expression.GetMemberPath()); - AddSelectionRule(selectionRule); - return new NestedExclusionOptionBuilder(this, selectionRule); + return new NestedExclusionOptionBuilder( + this, new ExcludeMemberByPathSelectionRule(expression.GetMemberPath())); } /// @@ -57,9 +54,13 @@ public NestedExclusionOptionBuilder For( /// /// This overrides the default behavior of including all declared members. /// - public EquivalencyAssertionOptions Including(Expression> expression) + public EquivalencyOptions Including(Expression> expression) { - AddSelectionRule(new IncludeMemberByPathSelectionRule(expression.GetMemberPath())); + foreach (var memberPath in expression.GetMemberPaths()) + { + AddSelectionRule(new IncludeMemberByPathSelectionRule(memberPath)); + } + return this; } @@ -67,7 +68,7 @@ public EquivalencyAssertionOptions Including(Expression to be compared in the order /// in which the items appear in the expectation. /// - public EquivalencyAssertionOptions WithStrictOrderingFor( + public EquivalencyOptions WithStrictOrderingFor( Expression> expression) { string expressionMemberPath = expression.GetMemberPath().ToString(); @@ -79,24 +80,26 @@ public EquivalencyAssertionOptions WithStrictOrderingFor( /// Causes the collection identified by to be compared ignoring the order /// in which the items appear in the expectation. /// - public EquivalencyAssertionOptions WithoutStrictOrderingFor( + public EquivalencyOptions WithoutStrictOrderingFor( Expression> expression) { string expressionMemberPath = expression.GetMemberPath().ToString(); + OrderingRules.Add(new PathBasedOrderingRule(expressionMemberPath) { Invert = true }); + return this; } /// /// Creates a new set of options based on the current instance which acts on a a collection of the . /// - public EquivalencyAssertionOptions> AsCollection() + public EquivalencyOptions> AsCollection() { - return new EquivalencyAssertionOptions>( - new CollectionMemberAssertionOptionsDecorator(this)); + return new EquivalencyOptions>( + new CollectionMemberOptionsDecorator(this)); } /// @@ -110,7 +113,7 @@ public EquivalencyAssertionOptions> AsCollection() /// If the types of the members are different, the usual logic applies depending or not if conversion options were specified. /// Fields can be mapped to properties and vice-versa. /// - public EquivalencyAssertionOptions WithMapping( + public EquivalencyOptions WithMapping( Expression> expectationMemberPath, Expression> subjectMemberPath) { @@ -134,7 +137,7 @@ public EquivalencyAssertionOptions WithMapping( /// if conversion options were specified. /// Fields can be mapped to properties and vice-versa. /// - public EquivalencyAssertionOptions WithMapping( + public EquivalencyOptions WithMapping( string expectationMemberPath, string subjectMemberPath) { @@ -155,7 +158,7 @@ public EquivalencyAssertionOptions WithMapping( /// If the types of the members are different, the usual logic applies depending or not if conversion options were specified. /// Fields can be mapped to properties and vice-versa. /// - public EquivalencyAssertionOptions WithMapping( + public EquivalencyOptions WithMapping( Expression> expectationMember, Expression> subjectMember) { @@ -177,7 +180,7 @@ public EquivalencyAssertionOptions WithMapping - public EquivalencyAssertionOptions WithMapping( + public EquivalencyOptions WithMapping( string expectationMemberName, string subjectMemberName) { @@ -187,20 +190,21 @@ public EquivalencyAssertionOptions WithMapping /// Represents the run-time type-agnostic behavior of a structural equivalency assertion. /// -public class EquivalencyAssertionOptions : SelfReferenceEquivalencyAssertionOptions +public class EquivalencyOptions : SelfReferenceEquivalencyOptions { - public EquivalencyAssertionOptions() + public EquivalencyOptions() { IncludingNestedObjects(); IncludingFields(); IncludingProperties(); - RespectingDeclaredTypes(); + PreferringDeclaredMemberTypes(); } } diff --git a/Src/FluentAssertions/EquivalencyPlan.cs b/Src/FluentAssertions/Equivalency/EquivalencyPlan.cs similarity index 75% rename from Src/FluentAssertions/EquivalencyPlan.cs rename to Src/FluentAssertions/Equivalency/EquivalencyPlan.cs index cf803c3ab7..f81b3d48c2 100644 --- a/Src/FluentAssertions/EquivalencyPlan.cs +++ b/Src/FluentAssertions/Equivalency/EquivalencyPlan.cs @@ -4,12 +4,12 @@ using System.Collections; using System.Collections.Generic; using System.Linq; -using FluentAssertions.Equivalency; +using FluentAssertions.Equivalency.Inlining; using FluentAssertions.Equivalency.Steps; #endregion -namespace FluentAssertions; +namespace FluentAssertions.Equivalency; /// /// Represents a mutable collection of equivalency steps that can be reordered and/or amended with additional @@ -17,12 +17,7 @@ namespace FluentAssertions; /// public class EquivalencyPlan : IEnumerable { - private List steps; - - public EquivalencyPlan() - { - steps = GetDefaultSteps(); - } + private List steps = GetDefaultSteps(); public IEnumerator GetEnumerator() { @@ -39,7 +34,7 @@ IEnumerator IEnumerable.GetEnumerator() /// . /// /// - /// This method is not thread-safe and should not be invoked on from within a unit test. + /// This method is not thread-safe and should not be modified through from within a unit test. /// See the docs on how to safely use it. /// public void Add() @@ -52,7 +47,7 @@ public void Add() /// Adds a new right after the specified . /// /// - /// This method is not thread-safe and should not be invoked on from within a unit test. + /// This method is not thread-safe and should not be modified through from within a unit test. /// See the docs on how to safely use it. /// public void AddAfter() @@ -74,7 +69,7 @@ public void AddAfter() /// Inserts a new before any of the built-in steps. /// /// - /// This method is not thread-safe and should not be invoked on from within a unit test. + /// This method is not thread-safe and should not be modified through from within a unit test. /// See the docs on how to safely use it. /// public void Insert() @@ -87,7 +82,7 @@ public void Insert() /// Inserts a new just before the . /// /// - /// This method is not thread-safe and should not be invoked on from within a unit test. + /// This method is not thread-safe and should not be modified through from within a unit test. /// See the docs on how to safely use it. /// public void InsertBefore() @@ -109,7 +104,7 @@ public void InsertBefore() /// Removes all instances of the specified from the current step. /// /// - /// This method is not thread-safe and should not be invoked on from within a unit test. + /// This method is not thread-safe and should not be modified through from within a unit test. /// See the docs on how to safely use it. /// public void Remove() @@ -122,7 +117,7 @@ public void Remove() /// Removes each and every built-in . /// /// - /// This method is not thread-safe and should not be invoked on from within a unit test. + /// This method is not thread-safe and should not be modified through from within a unit test. /// See the docs on how to safely use it. /// public void Clear() @@ -134,7 +129,7 @@ public void Clear() /// Removes all custom s. /// /// - /// This method should not be invoked on from within a unit test. + /// This method is not thread-safe and should not be modified through from within a unit test. /// See the docs on how to safely use it. /// public void Reset() @@ -144,32 +139,30 @@ public void Reset() private static List GetDefaultSteps() { - return new List(18) - { + return + [ new RunAllUserStepsEquivalencyStep(), new AutoConversionStep(), + new TypeEquivalencyStep(), new ReferenceEqualityEquivalencyStep(), +#if NET6_0_OR_GREATER + new JsonConversionStep(), +#endif + new InlineEquivalencyStep(), new GenericDictionaryEquivalencyStep(), - new DataSetEquivalencyStep(), - new DataTableEquivalencyStep(), - new DataColumnEquivalencyStep(), - new DataRelationEquivalencyStep(), - new DataRowCollectionEquivalencyStep(), - new DataRowEquivalencyStep(), new XDocumentEquivalencyStep(), new XElementEquivalencyStep(), new XAttributeEquivalencyStep(), - new ConstraintCollectionEquivalencyStep(), - new ConstraintEquivalencyStep(), new DictionaryEquivalencyStep(), new MultiDimensionalArrayEquivalencyStep(), new GenericEnumerableEquivalencyStep(), new EnumerableEquivalencyStep(), new StringEqualityEquivalencyStep(), + new DateAndTimeEquivalencyStep(), new EnumEqualityStep(), new ValueTypeEquivalencyStep(), new StructuralEqualityEquivalencyStep(), new SimpleEqualityEquivalencyStep(), - }; + ]; } } diff --git a/Src/FluentAssertions/Equivalency/EquivalencyResult.cs b/Src/FluentAssertions/Equivalency/EquivalencyResult.cs index 5fb0786888..b8f537e7e1 100644 --- a/Src/FluentAssertions/Equivalency/EquivalencyResult.cs +++ b/Src/FluentAssertions/Equivalency/EquivalencyResult.cs @@ -3,5 +3,5 @@ namespace FluentAssertions.Equivalency; public enum EquivalencyResult { ContinueWithNext, - AssertionCompleted + EquivalencyProven } diff --git a/Src/FluentAssertions/Equivalency/EquivalencyStep.cs b/Src/FluentAssertions/Equivalency/EquivalencyStep.cs index ddbbc2f68e..58f689f771 100644 --- a/Src/FluentAssertions/Equivalency/EquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/EquivalencyStep.cs @@ -6,19 +6,19 @@ public abstract class EquivalencyStep : IEquivalencyStep { public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, - IEquivalencyValidator nestedValidator) + IValidateChildNodeEquivalency valueChildNodes) { if (!typeof(T).IsAssignableFrom(comparands.GetExpectedType(context.Options))) { return EquivalencyResult.ContinueWithNext; } - return OnHandle(comparands, context, nestedValidator); + return OnHandle(comparands, context, valueChildNodes); } /// /// Implements , but only gets called when the expected type matches . /// protected abstract EquivalencyResult OnHandle(Comparands comparands, IEquivalencyValidationContext context, - IEquivalencyValidator nestedValidator); + IValidateChildNodeEquivalency nestedValidator); } diff --git a/Src/FluentAssertions/Equivalency/EquivalencyValidationContext.cs b/Src/FluentAssertions/Equivalency/EquivalencyValidationContext.cs index 5ebbe576b1..d6cee4a0cd 100644 --- a/Src/FluentAssertions/Equivalency/EquivalencyValidationContext.cs +++ b/Src/FluentAssertions/Equivalency/EquivalencyValidationContext.cs @@ -12,7 +12,7 @@ public class EquivalencyValidationContext : IEquivalencyValidationContext { private Tracer tracer; - public EquivalencyValidationContext(INode root, IEquivalencyAssertionOptions options) + public EquivalencyValidationContext(INode root, IEquivalencyOptions options) { Options = options; CurrentNode = root; @@ -25,7 +25,7 @@ public EquivalencyValidationContext(INode root, IEquivalencyAssertionOptions opt public Tracer Tracer => tracer ??= new Tracer(CurrentNode, TraceWriter); - public IEquivalencyAssertionOptions Options { get; } + public IEquivalencyOptions Options { get; } private CyclicReferenceDetector CyclicReferenceDetector { get; set; } @@ -71,17 +71,33 @@ public IEquivalencyValidationContext Clone() public bool IsCyclicReference(object expectation) { - bool compareByMembers = expectation is not null && Options.GetEqualityStrategy(expectation.GetType()) - is EqualityStrategy.Members or EqualityStrategy.ForceMembers; + EqualityStrategy strategy = expectation is not null + ? Options.GetEqualityStrategy(expectation.GetType()) + : EqualityStrategy.Equals; - var reference = new ObjectReference(expectation, CurrentNode.PathAndName, compareByMembers); - return CyclicReferenceDetector.IsCyclicReference(reference, Options.CyclicReferenceHandling, Reason); + var reference = new ObjectReference(expectation, CurrentNode.Subject.PathAndName, strategy); + + return CyclicReferenceDetector.IsCyclicReference(reference); } public ITraceWriter TraceWriter { get; set; } + /// + /// This method ensures that tracing starts with a fresh state when invoked. + /// + internal void ResetTracing() + { + // SMELL: We need to ensure that if tracing is enabled using the built-in internal writer, + // we start with a fresh instance of InternalTraceWriter. We can't add extend ITraceWriter + // as that would be a breaking change. + if (TraceWriter is InternalTraceWriter) + { + TraceWriter = new InternalTraceWriter(); + } + } + public override string ToString() { - return Invariant($"{{Path=\"{CurrentNode.Description}\"}}"); + return Invariant($"{{Path=\"{CurrentNode.Subject.PathAndName}\"}}"); } } diff --git a/Src/FluentAssertions/Equivalency/EquivalencyValidator.cs b/Src/FluentAssertions/Equivalency/EquivalencyValidator.cs index 230952a4aa..6a796efdec 100644 --- a/Src/FluentAssertions/Equivalency/EquivalencyValidator.cs +++ b/Src/FluentAssertions/Equivalency/EquivalencyValidator.cs @@ -7,19 +7,17 @@ namespace FluentAssertions.Equivalency; /// /// Is responsible for validating the equivalency of a subject with another object. /// -public class EquivalencyValidator : IEquivalencyValidator +internal class EquivalencyValidator : IValidateChildNodeEquivalency { private const int MaxDepth = 10; public void AssertEquality(Comparands comparands, EquivalencyValidationContext context) { - using var scope = new AssertionScope(); + context.ResetTracing(); - scope.AssumeSingleCaller(); - scope.AddReportable("configuration", () => context.Options.ToString()); - scope.BecauseOf(context.Reason); + using var scope = new AssertionScope(); - RecursivelyAssertEquality(comparands, context); + RecursivelyAssertEquivalencyOf(comparands, context); if (context.TraceWriter is not null) { @@ -27,66 +25,80 @@ public void AssertEquality(Comparands comparands, EquivalencyValidationContext c } } - public void RecursivelyAssertEquality(Comparands comparands, IEquivalencyValidationContext context) + private void RecursivelyAssertEquivalencyOf(Comparands comparands, IEquivalencyValidationContext context) { - var scope = AssertionScope.Current; + AssertEquivalencyOf(comparands, context); + } - if (ShouldContinueThisDeep(context.CurrentNode, context.Options, scope)) - { - TrackWhatIsNeededToProvideContextToFailures(scope, comparands, context.CurrentNode); + public void AssertEquivalencyOf(Comparands comparands, IEquivalencyValidationContext context) + { + var assertionChain = AssertionChain.GetOrCreate() + .For(context) + .BecauseOf(context.Reason); - if (context.IsCyclicReference(comparands.Expectation)) + if (ShouldContinueThisDeep(context.CurrentNode, context.Options, assertionChain)) + { + if (!context.IsCyclicReference(comparands.Expectation)) + { + TryToProveNodesAreEquivalent(comparands, context); + } + else if (context.Options.CyclicReferenceHandling == CyclicReferenceHandling.ThrowException) { - AssertComparandsPointToActualObjects(comparands); + assertionChain.FailWith("Expected {context:subject} to be {0}{reason}, but it contains a cyclic reference.", comparands.Expectation); } else { - TryToProveNodesAreEquivalent(comparands, context); + AssertEquivalencyForCyclicReference(comparands, assertionChain); } } } - private static bool ShouldContinueThisDeep(INode currentNode, IEquivalencyAssertionOptions options, - AssertionScope assertionScope) + private static bool ShouldContinueThisDeep(INode currentNode, IEquivalencyOptions options, + 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) + private static void AssertEquivalencyForCyclicReference(Comparands comparands, AssertionChain assertionChain) { - scope.Context = new Lazy(() => currentNode.Description); - - scope.TrackComparands(comparands.Subject, comparands.Expectation); - } + // We know that at this point the expectation is a non-null cyclic reference, so we don't want to continue the recursion. + // We still want to compare the subject with the expectation though. - private static void AssertComparandsPointToActualObjects(Comparands comparands) - { + // If they point at the same object, then equality is proven, and it doesn't matter that there's a cyclic reference. if (ReferenceEquals(comparands.Subject, comparands.Expectation)) { return; } + // If the expectation is non-null and the subject isn't, they would never be equivalent, regardless of how we deal with cyclic references, + // so we can just throw an exception here. if (comparands.Subject is null) { + assertionChain.ReuseOnce(); comparands.Subject.Should().BeSameAs(comparands.Expectation); } + + // If they point at different objects, and the expectation is a cyclic reference, we would never be + // able to prove that they are equal. And since we're supposed to ignore cyclic references, we can just return here. } private void TryToProveNodesAreEquivalent(Comparands comparands, IEquivalencyValidationContext context) { - using var _ = context.Tracer.WriteBlock(node => node.Description); + using var _ = context.Tracer.WriteBlock(node => node.Expectation.Description); - foreach (IEquivalencyStep step in AssertionOptions.EquivalencyPlan) + foreach (IEquivalencyStep step in AssertionConfiguration.Current.Equivalency.Plan) { var result = step.Handle(comparands, context, this); - if (result == EquivalencyResult.AssertionCompleted) + + if (result == EquivalencyResult.EquivalencyProven) { context.Tracer.WriteLine(GetMessage(step)); diff --git a/Src/FluentAssertions/Equivalency/Execution/CollectionMemberAssertionOptionsDecorator.cs b/Src/FluentAssertions/Equivalency/Execution/CollectionMemberOptionsDecorator.cs similarity index 74% rename from Src/FluentAssertions/Equivalency/Execution/CollectionMemberAssertionOptionsDecorator.cs rename to Src/FluentAssertions/Equivalency/Execution/CollectionMemberOptionsDecorator.cs index 93917bb3aa..ae70175cd1 100644 --- a/Src/FluentAssertions/Equivalency/Execution/CollectionMemberAssertionOptionsDecorator.cs +++ b/Src/FluentAssertions/Equivalency/Execution/CollectionMemberOptionsDecorator.cs @@ -4,17 +4,18 @@ using FluentAssertions.Equivalency.Ordering; using FluentAssertions.Equivalency.Selection; using FluentAssertions.Equivalency.Tracing; +using FluentAssertions.Equivalency.Typing; namespace FluentAssertions.Equivalency.Execution; /// /// Ensures that all the rules remove the collection index from the path before processing it further. /// -internal class CollectionMemberAssertionOptionsDecorator : IEquivalencyAssertionOptions +internal class CollectionMemberOptionsDecorator : IEquivalencyOptions, IContainTypingRules { - private readonly IEquivalencyAssertionOptions inner; + private readonly IEquivalencyOptions inner; - public CollectionMemberAssertionOptionsDecorator(IEquivalencyAssertionOptions inner) + public CollectionMemberOptionsDecorator(IEquivalencyOptions inner) { this.inner = inner; } @@ -32,6 +33,9 @@ public IEnumerable MatchingRules get { return inner.MatchingRules.ToArray(); } } + /// + public IEnumerable TypingRules => (inner as IContainTypingRules)?.TypingRules ?? []; + public OrderingRuleCollection OrderingRules { get @@ -73,5 +77,16 @@ public EqualityStrategy GetEqualityStrategy(Type type) return inner.GetEqualityStrategy(type); } + public bool IgnoreLeadingWhitespace => inner.IgnoreLeadingWhitespace; + + public bool IgnoreTrailingWhitespace => inner.IgnoreTrailingWhitespace; + + public bool IgnoreCase => inner.IgnoreCase; + + public bool IgnoreNewlineStyle => inner.IgnoreNewlineStyle; + + /// + public bool IgnoreJsonPropertyCasing => inner.IgnoreJsonPropertyCasing; + public ITraceWriter TraceWriter => inner.TraceWriter; } diff --git a/Src/FluentAssertions/Equivalency/Execution/CyclicReferenceDetector.cs b/Src/FluentAssertions/Equivalency/Execution/CyclicReferenceDetector.cs index 626e0367c5..4ca13ce49d 100644 --- a/Src/FluentAssertions/Equivalency/Execution/CyclicReferenceDetector.cs +++ b/Src/FluentAssertions/Equivalency/Execution/CyclicReferenceDetector.cs @@ -11,7 +11,7 @@ internal class CyclicReferenceDetector : ICloneable2 { #region Private Definitions - private HashSet observedReferences = new(); + private HashSet observedReferences = []; #endregion @@ -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/Execution/ObjectInfo.cs b/Src/FluentAssertions/Equivalency/Execution/ObjectInfo.cs index 0f45024f21..345e572a05 100644 --- a/Src/FluentAssertions/Equivalency/Execution/ObjectInfo.cs +++ b/Src/FluentAssertions/Equivalency/Execution/ObjectInfo.cs @@ -8,7 +8,7 @@ public ObjectInfo(Comparands comparands, INode currentNode) { Type = currentNode.Type; ParentType = currentNode.ParentType; - Path = currentNode.PathAndName; + Path = currentNode.Expectation.PathAndName; CompileTimeType = comparands.CompileTimeType; RuntimeType = comparands.RuntimeType; } diff --git a/Src/FluentAssertions/Equivalency/Execution/ObjectReference.cs b/Src/FluentAssertions/Equivalency/Execution/ObjectReference.cs index b2f92fc3b1..4d68cb292d 100644 --- a/Src/FluentAssertions/Equivalency/Execution/ObjectReference.cs +++ b/Src/FluentAssertions/Equivalency/Execution/ObjectReference.cs @@ -13,14 +13,22 @@ internal class ObjectReference { private readonly object @object; private readonly string path; - private readonly bool? compareByMembers; private string[] pathElements; - public ObjectReference(object @object, string path, bool? compareByMembers = null) + public ObjectReference(object @object, string path, EqualityStrategy equalityStrategy) { this.@object = @object; this.path = path; - this.compareByMembers = compareByMembers; + + CompareByMembers = equalityStrategy is EqualityStrategy.Members or EqualityStrategy.ForceMembers; + } + + public ObjectReference(object @object, string path) + { + this.@object = @object; + this.path = path; + + CompareByMembers = @object?.GetType().OverridesEquals() == false; } /// @@ -39,7 +47,7 @@ public override bool Equals(object obj) private string[] GetPathElements() => pathElements ??= path.ToUpperInvariant().Replace("][", "].[", StringComparison.Ordinal) - .Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries); + .Split('.', StringSplitOptions.RemoveEmptyEntries); private bool IsParentOrChildOf(ObjectReference other) { @@ -49,7 +57,9 @@ private bool IsParentOrChildOf(ObjectReference other) int commonElements = Math.Min(elements.Length, otherPath.Length); int longerPathAdditionalElements = Math.Max(elements.Length, otherPath.Length) - commonElements; - return longerPathAdditionalElements > 0 && otherPath.Take(commonElements).SequenceEqual(elements.Take(commonElements)); + return longerPathAdditionalElements > 0 && otherPath + .Take(commonElements) + .SequenceEqual(elements.Take(commonElements), StringComparer.Ordinal); } /// @@ -69,5 +79,14 @@ public override string ToString() return Invariant($"{{\"{path}\", {@object}}}"); } - public bool CompareByMembers => compareByMembers ?? (@object?.GetType().OverridesEquals() == false); + /// + /// Indicates whether the equality comparison for the object should be based on its members rather than its default + /// implementation of + /// + /// + /// This property returns if the equality strategy is set to or + /// , and the object does not override its method. + /// Otherwise, it returns + /// + public bool CompareByMembers { get; } } diff --git a/Src/FluentAssertions/Equivalency/Field.cs b/Src/FluentAssertions/Equivalency/Field.cs index e44a523d20..fc3d00a2fd 100644 --- a/Src/FluentAssertions/Equivalency/Field.cs +++ b/Src/FluentAssertions/Equivalency/Field.cs @@ -6,26 +6,21 @@ namespace FluentAssertions.Equivalency; /// -/// A specialized type of that represents a field of an object in a structural equivalency assertion. +/// A specialized type of that represents a field of an object in a structural equivalency assertion. /// -public class Field : Node, IMember +internal class Field : Node, IMember { private readonly FieldInfo fieldInfo; private bool? isBrowsable; public Field(FieldInfo fieldInfo, INode parent) - : this(fieldInfo.ReflectedType, fieldInfo, parent) - { - } - - public Field(Type reflectedType, FieldInfo fieldInfo, INode parent) { this.fieldInfo = fieldInfo; DeclaringType = fieldInfo.DeclaringType; - ReflectedType = reflectedType; - Path = parent.PathAndName; + ReflectedType = fieldInfo.ReflectedType; + Subject = new Pathway(parent.Subject.PathAndName, fieldInfo.Name, pathAndName => $"field {parent.GetSubjectId().Combine(pathAndName)}"); + Expectation = new Pathway(parent.Expectation.PathAndName, fieldInfo.Name, pathAndName => $"field {pathAndName}"); GetSubjectId = parent.GetSubjectId; - Name = fieldInfo.Name; Type = fieldInfo.FieldType; ParentType = fieldInfo.DeclaringType; RootIsCollection = parent.RootIsCollection; @@ -40,8 +35,6 @@ public object GetValue(object obj) public Type DeclaringType { get; set; } - public override string Description => $"field {GetSubjectId().Combine(PathAndName)}"; - public CSharpAccessModifier GetterAccessibility => fieldInfo.GetCSharpAccessModifier(); public CSharpAccessModifier SetterAccessibility => fieldInfo.GetCSharpAccessModifier(); diff --git a/Src/FluentAssertions/Equivalency/IEquivalencyAssertionOptions.cs b/Src/FluentAssertions/Equivalency/IEquivalencyOptions.cs similarity index 73% rename from Src/FluentAssertions/Equivalency/IEquivalencyAssertionOptions.cs rename to Src/FluentAssertions/Equivalency/IEquivalencyOptions.cs index b0ceeb4e2c..7e49d1b035 100644 --- a/Src/FluentAssertions/Equivalency/IEquivalencyAssertionOptions.cs +++ b/Src/FluentAssertions/Equivalency/IEquivalencyOptions.cs @@ -6,9 +6,9 @@ namespace FluentAssertions.Equivalency; /// -/// Provides the run-time details of the class. +/// Provides the run-time details of the class. /// -public interface IEquivalencyAssertionOptions +public interface IEquivalencyOptions { /// /// Gets an ordered collection of selection rules that define what members (e.g. properties or fields) are included. @@ -98,4 +98,32 @@ public interface IEquivalencyAssertionOptions /// Determines the right strategy for evaluating the equality of objects of this type. /// EqualityStrategy GetEqualityStrategy(Type type); + + /// + /// Gets a value indicating whether leading whitespace is ignored when comparing s. + /// + bool IgnoreLeadingWhitespace { get; } + + /// + /// Gets a value indicating whether trailing whitespace is ignored when comparing s. + /// + bool IgnoreTrailingWhitespace { get; } + + /// + /// Gets a value indicating whether a case-insensitive comparer is used when comparing s. + /// + bool IgnoreCase { get; } + + /// + /// Gets a value indicating whether the newline style is ignored when comparing s. + /// + /// + /// Enabling this option will replace all occurrences of \r\n and \r with \n in the strings before comparing them. + /// + bool IgnoreNewlineStyle { get; } + + /// + /// When set to true, the comparison will ignore the casing of JSON property names when comparing objects to JSON trees. + /// + bool IgnoreJsonPropertyCasing { get; } } diff --git a/Src/FluentAssertions/Equivalency/IEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/IEquivalencyStep.cs index bd55ae1774..d0279009ae 100644 --- a/Src/FluentAssertions/Equivalency/IEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/IEquivalencyStep.cs @@ -9,11 +9,12 @@ public interface IEquivalencyStep /// Executes an operation such as an equivalency assertion on the provided . /// /// - /// Should return if the subject matches the expectation or if no additional assertions + /// Should return if the subject matches the expectation or if no additional assertions /// have to be executed. Should return otherwise. /// /// /// May throw when preconditions are not met or if it detects mismatching data. /// - EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, IEquivalencyValidator nestedValidator); + EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, + IValidateChildNodeEquivalency valueChildNodes); } diff --git a/Src/FluentAssertions/Equivalency/IEquivalencyValidationContext.cs b/Src/FluentAssertions/Equivalency/IEquivalencyValidationContext.cs index 62b4522706..4ea00fa840 100644 --- a/Src/FluentAssertions/Equivalency/IEquivalencyValidationContext.cs +++ b/Src/FluentAssertions/Equivalency/IEquivalencyValidationContext.cs @@ -17,21 +17,21 @@ public interface IEquivalencyValidationContext /// /// A formatted phrase and the placeholder values explaining why the assertion is needed. /// - public Reason Reason { get; } + Reason Reason { get; } /// /// Gets an object that can be used by the equivalency algorithm to provide a trace when the - /// option is used. + /// option is used. /// Tracer Tracer { get; } - IEquivalencyAssertionOptions Options { get; } + IEquivalencyOptions Options { get; } /// /// Determines whether the specified object reference is a cyclic reference to the same object earlier in the /// equivalency validation. /// - public bool IsCyclicReference(object expectation); + bool IsCyclicReference(object expectation); /// /// Creates a context from the current object intended to assert the equivalency of a nested member. diff --git a/Src/FluentAssertions/Equivalency/IMemberMatchingRule.cs b/Src/FluentAssertions/Equivalency/IMemberMatchingRule.cs index 20fd81b316..ad0f0f508c 100644 --- a/Src/FluentAssertions/Equivalency/IMemberMatchingRule.cs +++ b/Src/FluentAssertions/Equivalency/IMemberMatchingRule.cs @@ -1,3 +1,5 @@ +using FluentAssertions.Execution; + namespace FluentAssertions.Equivalency; /// @@ -11,21 +13,19 @@ public interface IMemberMatchingRule /// during a structural equality. /// /// - /// Whether or not a match is required or optional is up to the specific rule. If no match is found and this is not an issue, + /// Whether a match is required or optional is up to the specific rule. If no match is found and this is not an issue, /// 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, IEquivalencyAssertionOptions options); + IMember Match(IMember expectedMember, object subject, INode parent, IEquivalencyOptions options, AssertionChain assertionChain); } diff --git a/Src/FluentAssertions/Equivalency/INode.cs b/Src/FluentAssertions/Equivalency/INode.cs index 29274315a0..102760f49f 100644 --- a/Src/FluentAssertions/Equivalency/INode.cs +++ b/Src/FluentAssertions/Equivalency/INode.cs @@ -1,5 +1,4 @@ using System; -using JetBrains.Annotations; namespace FluentAssertions.Equivalency; @@ -15,14 +14,6 @@ public interface INode /// GetSubjectId GetSubjectId { get; } - /// - /// Gets the name of this node. - /// - /// - /// "Property2" - /// - string Name { get; set; } - /// /// Gets the type of this node, e.g. the type of the field or property, or the type of the collection item. /// @@ -34,24 +25,17 @@ public interface INode /// /// Is for the root object. /// - [CanBeNull] Type ParentType { get; } /// - /// Gets the path from the root object UNTIL the current node, separated by dots or index/key brackets. + /// Gets the path from the root of the subject upto and including the current node. /// - /// - /// "Parent[0].Property2" - /// - string Path { get; } + Pathway Subject { get; internal set; } /// - /// Gets the full path from the root object up to and including the name of the node. + /// Gets the path from the root of the expectation upto and including the current node. /// - /// - /// "Parent[0]" - /// - string PathAndName { get; } + Pathway Expectation { get; } /// /// Gets a zero-based number representing the depth within the object graph @@ -62,14 +46,6 @@ public interface INode /// int Depth { get; } - /// - /// Gets the path including the description of the subject. - /// - /// - /// "property subject.Parent[0].Property2" - /// - string Description { get; } - /// /// Gets a value indicating whether the current node is the root. /// @@ -79,4 +55,17 @@ public interface INode /// Gets a value indicating if the root of this graph is a collection. /// bool RootIsCollection { get; } + + /// + /// Overrides the display text used when rendering the failure message to use the specified member + /// + /// + /// As the description of this object is used to render the "expectation" in a the failure message, and + /// we sometimes need to remap the expectation to a different member (e.g. when a member is + /// mapped to another member with a different name), we need to adjust the description. + /// + /// + /// The specific member in the subject that the current node should be remapped to. + /// + void AdjustForRemappedSubject(IMember subjectMember); } diff --git a/Src/FluentAssertions/Equivalency/IEquivalencyValidator.cs b/Src/FluentAssertions/Equivalency/IValidateChildNodeEquivalency.cs similarity index 56% rename from Src/FluentAssertions/Equivalency/IEquivalencyValidator.cs rename to Src/FluentAssertions/Equivalency/IValidateChildNodeEquivalency.cs index 1d7aacb28e..dfc38ad990 100644 --- a/Src/FluentAssertions/Equivalency/IEquivalencyValidator.cs +++ b/Src/FluentAssertions/Equivalency/IValidateChildNodeEquivalency.cs @@ -1,9 +1,9 @@ namespace FluentAssertions.Equivalency; -public interface IEquivalencyValidator +public interface IValidateChildNodeEquivalency { /// /// Runs a deep recursive equivalency assertion on the provided . /// - void RecursivelyAssertEquality(Comparands comparands, IEquivalencyValidationContext context); + void AssertEquivalencyOf(Comparands comparands, IEquivalencyValidationContext context); } diff --git a/Src/FluentAssertions/Equivalency/Inlining/ActionBasedInlineAssertion.cs b/Src/FluentAssertions/Equivalency/Inlining/ActionBasedInlineAssertion.cs new file mode 100644 index 0000000000..d314e64b53 --- /dev/null +++ b/Src/FluentAssertions/Equivalency/Inlining/ActionBasedInlineAssertion.cs @@ -0,0 +1,26 @@ +using System; +using FluentAssertions.Execution; + +namespace FluentAssertions.Equivalency.Inlining; + +/// +/// Represents an inline equivalency assertion, which is implemented using an action. This class +/// enables customized assertion logic to be injected during equivalency validation, typically using one +/// of the assertion APIs provided by Fluent Assertions. +/// +/// The expected type of the subject to which the assertion is applied. +internal class ActionBasedInlineAssertion(Action assertion) : IInlineEquivalencyAssertion +{ + /// + public void Execute(AssertionChain assertionChain, Comparands comparands) + { + assertionChain + .ForCondition(comparands.Subject is T) + .FailWith("Expected {context:subject} to be of type {0}, but found {1}.", typeof(T), comparands.Subject?.GetType()); + + if (assertionChain.Succeeded) + { + assertion((T)comparands.Subject); + } + } +} diff --git a/Src/FluentAssertions/Equivalency/Inlining/ConditionBasedInlineAssertion.cs b/Src/FluentAssertions/Equivalency/Inlining/ConditionBasedInlineAssertion.cs new file mode 100644 index 0000000000..eef612dbbd --- /dev/null +++ b/Src/FluentAssertions/Equivalency/Inlining/ConditionBasedInlineAssertion.cs @@ -0,0 +1,24 @@ +using System; +using System.Linq.Expressions; +using FluentAssertions.Execution; + +namespace FluentAssertions.Equivalency.Inlining; + +/// +/// Represents a condition-based inline equivalency assertion that evaluates a specified condition against a subject during object equivalency checks. +/// +/// The expected type of the subject being asserted. +internal class ConditionBasedInlineAssertion(Expression> condition) : IInlineEquivalencyAssertion +{ + /// + public void Execute(AssertionChain assertionChain, Comparands comparands) + { + assertionChain + .ForCondition(comparands.Subject is T) + .FailWith("Expected {context:subject} to be of type {0}, but found {1}.", typeof(T), comparands.Subject?.GetType()) + .Then + .Given(() => (T)comparands.Subject) + .ForCondition(subject => condition.Compile()(subject)) + .FailWith("Expected {context:subject} to meet condition {0}, but it did not.", condition); + } +} diff --git a/Src/FluentAssertions/Equivalency/Inlining/IInlineEquivalencyAssertion.cs b/Src/FluentAssertions/Equivalency/Inlining/IInlineEquivalencyAssertion.cs new file mode 100644 index 0000000000..03858b8ba3 --- /dev/null +++ b/Src/FluentAssertions/Equivalency/Inlining/IInlineEquivalencyAssertion.cs @@ -0,0 +1,22 @@ +using FluentAssertions.Execution; + +namespace FluentAssertions.Equivalency.Inlining; + +/// +/// Defines an interface for inline equivalency assertions, which are used to compare objects +/// during the equivalency validation process with specific conditions or custom logic. +/// +public interface IInlineEquivalencyAssertion +{ + /// + /// Executes the inline equivalency assertion process on the specified comparands + /// using a provided assertion chain. This method is utilized to perform a + /// customized comparison between objects during equivalency validation. + /// + /// An instance + /// used to track and assert conditions during the equivalency assertion process. + /// This enables the chaining of multiple assertions with contextual explanations. + /// A instance + /// that holds the pair of objects being compared, as well as their associated contextual metadata. + void Execute(AssertionChain assertionChain, Comparands comparands); +} diff --git a/Src/FluentAssertions/Equivalency/Inlining/InlineEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Inlining/InlineEquivalencyStep.cs new file mode 100644 index 0000000000..b4c5b62f97 --- /dev/null +++ b/Src/FluentAssertions/Equivalency/Inlining/InlineEquivalencyStep.cs @@ -0,0 +1,33 @@ +using FluentAssertions.Execution; + +namespace FluentAssertions.Equivalency.Inlining; + +/// +/// An implementation of that enables inline equivalency assertions. +/// +/// +/// This step checks if the `Expectation` in the provided +/// implements the interface. If so, it delegates +/// the equivalency comparison to the specified inline equivalency assertion logic. +/// Otherwise, it signals the equivalency process to continue with the next step in the chain. +/// This step allows users to define custom equivalency behaviors inline during assertion +/// execution. +/// +public class InlineEquivalencyStep : IEquivalencyStep +{ + /// + public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, + IValidateChildNodeEquivalency valueChildNodes) + { + if (comparands.Expectation is IInlineEquivalencyAssertion equivalencyAssertion) + { + var assertionChain = AssertionChain.GetOrCreate().For(context); + + equivalencyAssertion.Execute(assertionChain, comparands); + + return EquivalencyResult.EquivalencyProven; + } + + return EquivalencyResult.ContinueWithNext; + } +} diff --git a/Src/FluentAssertions/Equivalency/JsonProperty.cs b/Src/FluentAssertions/Equivalency/JsonProperty.cs new file mode 100644 index 0000000000..c50668f4b0 --- /dev/null +++ b/Src/FluentAssertions/Equivalency/JsonProperty.cs @@ -0,0 +1,147 @@ +#if NET6_0_OR_GREATER +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json.Nodes; +using FluentAssertions.Common; + +namespace FluentAssertions.Equivalency; + +/// +/// Represents a JSON property in the context of equivalency assertions. This class is used to encapsulate +/// information about a specific JSON property, its parent object, and its counterpart in an expectation object +/// when performing comparisons. +/// +internal class JsonProperty(JsonNode property, JsonObject parent, INode expectationParent) : IMember +{ + // SMELL: A lot of properties are required by the IMember interface, but they are not used. In the future + // we need to change the interface that IMemberMatchingRule returns so we don't need all this. + + /// + public GetSubjectId GetSubjectId + { + get => expectationParent.GetSubjectId; + } + + /// + public Type Type + { + get => property.GetType(); + } + + /// + public Type ParentType + { + get => parent.GetType(); + } + + /// + public Pathway Subject + { + get; + set; + } + + /// + // ReSharper disable once UnassignedGetOnlyAutoProperty + public Pathway Expectation { get; } + + /// + public int Depth + { + get => 0; + } + + /// + public bool IsRoot + { + get => false; + } + + /// + public bool RootIsCollection + { + get => false; + } + + /// + public void AdjustForRemappedSubject(IMember subjectMember) + { + } + + /// + public Type DeclaringType + { + get => parent.GetType(); + } + + /// + public Type ReflectedType + { + get => parent.GetType(); + } + + /// + public object GetValue(object obj) => property; + + /// + public CSharpAccessModifier GetterAccessibility + { + get => CSharpAccessModifier.Public; + } + + /// + public CSharpAccessModifier SetterAccessibility + { + get => CSharpAccessModifier.Public; + } + + /// + public bool IsBrowsable + { + get => false; + } + + /// + /// Tries to find a JSON property on the object which name matches that + /// of the .NET member identified by . + /// + /// + /// Whether the name matching is case-sensitive or not is determined by . + /// + /// + /// An instance of if a matching property was found, or null otherwise. + /// + public static JsonProperty Find(IMember expectedMember, INode parent, object subject, StringComparison nameComparisonMode) + { + if (subject is not JsonObject jsonNode) + { + return null; + } + + JsonProperty property = null; + + // Use the name of the expectation as the name of the JSON property to look for + string propertyName = expectedMember.Expectation.Name; + + // Find the JSON property with the same name as the .NET member + KeyValuePair match = jsonNode + .AsEnumerable() + .SingleOrDefault(x => x.Key.Equals(propertyName, nameComparisonMode)); + + if (match.Key is not null) + { + // Build an IMember that represents the JSON property + property = new JsonProperty(jsonNode[match.Key], jsonNode, parent) + { + Subject = new Pathway(jsonNode.GetPath(), propertyName, pathAndName => $"JSON property {pathAndName}"), + }; + } + + return property; + } + + public override string ToString() => Subject.Description; +} + +#endif diff --git a/Src/FluentAssertions/Equivalency/Matching/MappedMemberMatchingRule.cs b/Src/FluentAssertions/Equivalency/Matching/MappedMemberMatchingRule.cs index a324bf0bc3..6730620579 100644 --- a/Src/FluentAssertions/Equivalency/Matching/MappedMemberMatchingRule.cs +++ b/Src/FluentAssertions/Equivalency/Matching/MappedMemberMatchingRule.cs @@ -1,6 +1,6 @@ using System; -using System.Text.RegularExpressions; using FluentAssertions.Common; +using FluentAssertions.Execution; namespace FluentAssertions.Equivalency.Matching; @@ -15,12 +15,12 @@ internal class MappedMemberMatchingRule : IMemberMatchin public MappedMemberMatchingRule(string expectationMemberName, string subjectMemberName) { - if (Regex.IsMatch(expectationMemberName, @"[\.\[\]]")) + if (IsNestedPath(expectationMemberName)) { throw new ArgumentException("The expectation's member name cannot be a nested path", nameof(expectationMemberName)); } - if (Regex.IsMatch(subjectMemberName, @"[\.\[\]]")) + if (IsNestedPath(subjectMemberName)) { throw new ArgumentException("The subject's member name cannot be a nested path", nameof(subjectMemberName)); } @@ -29,10 +29,13 @@ public MappedMemberMatchingRule(string expectationMemberName, string subjectMemb this.subjectMemberName = subjectMemberName; } - public IMember Match(IMember expectedMember, object subject, INode parent, IEquivalencyAssertionOptions options) + private static bool IsNestedPath(string path) => + path.Contains('.', StringComparison.Ordinal) || path.Contains('[', StringComparison.Ordinal) || path.Contains(']', StringComparison.Ordinal); + + 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) + expectedMember.Subject.Name == expectationMemberName) { var member = MemberFactory.Find(subject, subjectMemberName, parent); diff --git a/Src/FluentAssertions/Equivalency/Matching/MappedPathMatchingRule.cs b/Src/FluentAssertions/Equivalency/Matching/MappedPathMatchingRule.cs index f612908757..3c529aec80 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, IEquivalencyAssertionOptions options) + public IMember Match(IMember expectedMember, object subject, INode parent, IEquivalencyOptions options, AssertionChain assertionChain) { MemberPath path = expectationPath; @@ -51,7 +52,7 @@ public IMember Match(IMember expectedMember, object subject, INode parent, IEqui path = path.WithCollectionAsRoot(); } - if (path.IsEquivalentTo(expectedMember.PathAndName)) + if (path.IsEquivalentTo(expectedMember.Expectation.PathAndName)) { var member = MemberFactory.Find(subject, subjectPath.MemberName, parent); diff --git a/Src/FluentAssertions/Equivalency/Matching/MustMatchByNameRule.cs b/Src/FluentAssertions/Equivalency/Matching/MustMatchByNameRule.cs index 52c2cacff2..abf638afaa 100644 --- a/Src/FluentAssertions/Equivalency/Matching/MustMatchByNameRule.cs +++ b/Src/FluentAssertions/Equivalency/Matching/MustMatchByNameRule.cs @@ -1,59 +1,30 @@ -using System.Reflection; -using FluentAssertions.Common; using FluentAssertions.Execution; namespace FluentAssertions.Equivalency.Matching; /// -/// Requires the subject to have a member with the exact same name as the expectation has. +/// First tries to find a JSON property with the same name, and if that fails, falls back to a field or property with the same name. /// internal class MustMatchByNameRule : IMemberMatchingRule { - public IMember Match(IMember expectedMember, object subject, INode parent, IEquivalencyAssertionOptions options) + /// + public IMember Match(IMember expectedMember, object subject, INode parent, IEquivalencyOptions options, + AssertionChain assertionChain) { - IMember subjectMember = null; - - if (options.IncludedProperties != MemberVisibility.None) - { - PropertyInfo propertyInfo = subject.GetType().FindProperty( - expectedMember.Name, - options.IncludedProperties | MemberVisibility.ExplicitlyImplemented | MemberVisibility.DefaultInterfaceProperties); - - subjectMember = propertyInfo is not null && !propertyInfo.IsIndexer() ? new Property(propertyInfo, parent) : null; - } - - if (subjectMember is null && options.IncludedFields != MemberVisibility.None) - { - FieldInfo fieldInfo = subject.GetType().FindField( - expectedMember.Name, - options.IncludedFields); - - subjectMember = fieldInfo is not null ? new Field(fieldInfo, parent) : null; - } - - if (subjectMember is null) - { - Execute.Assertion.FailWith( - $"Expectation has {expectedMember.Description} that the other object does not have."); - } - else if (options.IgnoreNonBrowsableOnSubject && !subjectMember.IsBrowsable) - { - Execute.Assertion.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"); - } - else +#if NET6_0_OR_GREATER + if (subject is System.Text.Json.Nodes.JsonNode) { - // Everything is fine + return new MustMatchJsonPropertyByNameRule().Match(expectedMember, subject, parent, options, assertionChain); } +#endif - return subjectMember; + return new MustMatchMemberByNameRule().Match(expectedMember, subject, parent, options, assertionChain); } /// /// 2 public override string ToString() { - return "Match member by name (or throw)"; + return "Match (JSON) member by name (or throw)"; } } diff --git a/Src/FluentAssertions/Equivalency/Matching/MustMatchJsonPropertyByNameRule.cs b/Src/FluentAssertions/Equivalency/Matching/MustMatchJsonPropertyByNameRule.cs new file mode 100644 index 0000000000..2568314af2 --- /dev/null +++ b/Src/FluentAssertions/Equivalency/Matching/MustMatchJsonPropertyByNameRule.cs @@ -0,0 +1,32 @@ +#if NET6_0_OR_GREATER +using System; +using FluentAssertions.Execution; + +namespace FluentAssertions.Equivalency.Matching; + +/// +/// Defines a rule to match a member from an expected object with a member from a subject object +/// based on the existence of a JSON property with a matching name. +/// +internal class MustMatchJsonPropertyByNameRule : IMemberMatchingRule +{ + /// + public IMember Match(IMember expectedMember, object subject, INode parent, IEquivalencyOptions options, + AssertionChain assertionChain) + { + StringComparison comparison = + options.IgnoreJsonPropertyCasing ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; + + JsonProperty property = JsonProperty.Find(expectedMember, parent, subject, comparison); + + if (property is null) + { + assertionChain.FailWith("Expectation has {0} that the other object does not have.", + expectedMember.Expectation.AsNonFormattable()); + } + + return property; + } +} + +#endif diff --git a/Src/FluentAssertions/Equivalency/Matching/MustMatchMemberByNameRule.cs b/Src/FluentAssertions/Equivalency/Matching/MustMatchMemberByNameRule.cs new file mode 100644 index 0000000000..72bb3d65b3 --- /dev/null +++ b/Src/FluentAssertions/Equivalency/Matching/MustMatchMemberByNameRule.cs @@ -0,0 +1,53 @@ +using System.Reflection; +using FluentAssertions.Common; +using FluentAssertions.Execution; + +namespace FluentAssertions.Equivalency.Matching; + +/// +/// Requires the subject to have a member with the exact same name as the expectation has. +/// +internal class MustMatchMemberByNameRule : IMemberMatchingRule +{ + public IMember Match(IMember expectedMember, object subject, INode parent, IEquivalencyOptions options, AssertionChain assertionChain) + { + IMember subjectMember = null; + + if (options.IncludedProperties != MemberVisibility.None) + { + PropertyInfo propertyInfo = subject.GetType().FindProperty( + expectedMember.Subject.Name, + options.IncludedProperties | MemberVisibility.ExplicitlyImplemented | MemberVisibility.DefaultInterfaceProperties); + + subjectMember = propertyInfo is not null && !propertyInfo.IsIndexer() ? new Property(propertyInfo, parent) : null; + } + + if (subjectMember is null && options.IncludedFields != MemberVisibility.None) + { + FieldInfo fieldInfo = subject.GetType().FindField( + expectedMember.Subject.Name, + options.IncludedFields); + + subjectMember = fieldInfo is not null ? new Field(fieldInfo, parent) : null; + } + + if (subjectMember is null) + { + assertionChain.FailWith( + "Expectation has {0} that the other object does not have.", expectedMember.Expectation.AsNonFormattable()); + } + else if (options.IgnoreNonBrowsableOnSubject && !subjectMember.IsBrowsable) + { + assertionChain.FailWith( + "Expectation has {0} that is non-browsable in the other object, and non-browsable " + + "members on the subject are ignored with the current configuration", expectedMember.Expectation.AsNonFormattable()); + } + else + { + // Everything is fine + } + + return subjectMember; + } +} + diff --git a/Src/FluentAssertions/Equivalency/Matching/TryMatchByNameRule.cs b/Src/FluentAssertions/Equivalency/Matching/TryMatchByNameRule.cs index 36f817acb8..d75fa195b6 100644 --- a/Src/FluentAssertions/Equivalency/Matching/TryMatchByNameRule.cs +++ b/Src/FluentAssertions/Equivalency/Matching/TryMatchByNameRule.cs @@ -1,36 +1,28 @@ -using System.Reflection; -using FluentAssertions.Common; +using FluentAssertions.Execution; namespace FluentAssertions.Equivalency.Matching; /// -/// Finds a member of the expectation with the exact same name, but doesn't require it. +/// First tries to find a JSON property with the same name, and if that fails, falls back to a field or property with the same name. /// internal class TryMatchByNameRule : IMemberMatchingRule { - public IMember Match(IMember expectedMember, object subject, INode parent, IEquivalencyAssertionOptions options) + public IMember Match(IMember expectedMember, object subject, INode parent, IEquivalencyOptions options, AssertionChain assertionChain) { - if (options.IncludedProperties != MemberVisibility.None) +#if NET6_0_OR_GREATER + if (subject is System.Text.Json.Nodes.JsonNode) { - PropertyInfo property = subject.GetType().FindProperty(expectedMember.Name, - options.IncludedProperties | MemberVisibility.ExplicitlyImplemented); - - if (property is not null && !property.IsIndexer()) - { - return new Property(property, parent); - } + return new TryMatchJsonPropertyByNameRule().Match(expectedMember, subject, parent, options, assertionChain); } +#endif - FieldInfo field = subject.GetType() - .FindField(expectedMember.Name, options.IncludedFields); - - return field is not null ? new Field(field, parent) : null; + return new TryMatchMemberByNameRule().Match(expectedMember, subject, parent, options, assertionChain); } /// /// 2 public override string ToString() { - return "Try to match member by name"; + return "Try to match (JSON) member by name"; } } diff --git a/Src/FluentAssertions/Equivalency/Matching/TryMatchJsonPropertyByNameRule.cs b/Src/FluentAssertions/Equivalency/Matching/TryMatchJsonPropertyByNameRule.cs new file mode 100644 index 0000000000..201c9016df --- /dev/null +++ b/Src/FluentAssertions/Equivalency/Matching/TryMatchJsonPropertyByNameRule.cs @@ -0,0 +1,24 @@ +#if NET6_0_OR_GREATER +using System; +using FluentAssertions.Execution; + +namespace FluentAssertions.Equivalency.Matching; + +/// +/// A member matching rule that attempts to match a JSON property in a subject object with a corresponding +/// member in the expected object, using the provided equivalency options to determine comparison behavior. +/// +internal class TryMatchJsonPropertyByNameRule : IMemberMatchingRule +{ + /// + public IMember Match(IMember expectedMember, object subject, INode parent, IEquivalencyOptions options, + AssertionChain assertionChain) + { + StringComparison comparison = + options.IgnoreJsonPropertyCasing ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; + + return JsonProperty.Find(expectedMember, parent, subject, comparison); + } +} + +#endif diff --git a/Src/FluentAssertions/Equivalency/Matching/TryMatchMemberByNameRule.cs b/Src/FluentAssertions/Equivalency/Matching/TryMatchMemberByNameRule.cs new file mode 100644 index 0000000000..cbda928c08 --- /dev/null +++ b/Src/FluentAssertions/Equivalency/Matching/TryMatchMemberByNameRule.cs @@ -0,0 +1,30 @@ +using System.Reflection; +using FluentAssertions.Common; +using FluentAssertions.Execution; + +namespace FluentAssertions.Equivalency.Matching; + +/// +/// Finds a member of the expectation with the exact same name, but doesn't require it. +/// +internal class TryMatchMemberByNameRule : IMemberMatchingRule +{ + public IMember Match(IMember expectedMember, object subject, INode parent, IEquivalencyOptions options, AssertionChain assertionChain) + { + if (options.IncludedProperties != MemberVisibility.None) + { + PropertyInfo property = subject.GetType().FindProperty(expectedMember.Expectation.Name, + options.IncludedProperties | MemberVisibility.ExplicitlyImplemented); + + if (property is not null && !property.IsIndexer()) + { + return new Property(property, parent); + } + } + + FieldInfo field = subject.GetType() + .FindField(expectedMember.Expectation.Name, options.IncludedFields); + + return field is not null ? new Field(field, parent) : null; + } +} diff --git a/Src/FluentAssertions/Equivalency/MemberSelectionContext.cs b/Src/FluentAssertions/Equivalency/MemberSelectionContext.cs index 4974e4b5aa..2e5417bf16 100644 --- a/Src/FluentAssertions/Equivalency/MemberSelectionContext.cs +++ b/Src/FluentAssertions/Equivalency/MemberSelectionContext.cs @@ -10,9 +10,9 @@ public class MemberSelectionContext { private readonly Type compileTimeType; private readonly Type runtimeType; - private readonly IEquivalencyAssertionOptions options; + private readonly IEquivalencyOptions options; - public MemberSelectionContext(Type compileTimeType, Type runtimeType, IEquivalencyAssertionOptions options) + public MemberSelectionContext(Type compileTimeType, Type runtimeType, IEquivalencyOptions options) { this.runtimeType = runtimeType; this.compileTimeType = compileTimeType; diff --git a/Src/FluentAssertions/Equivalency/MultiDimensionalArrayEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/MultiDimensionalArrayEquivalencyStep.cs index 27bb4af3fd..4962f36860 100644 --- a/Src/FluentAssertions/Equivalency/MultiDimensionalArrayEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/MultiDimensionalArrayEquivalencyStep.cs @@ -11,18 +11,18 @@ namespace FluentAssertions.Equivalency; internal class MultiDimensionalArrayEquivalencyStep : IEquivalencyStep { public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, - IEquivalencyValidator nestedValidator) + IValidateChildNodeEquivalency valueChildNodes) { if (comparands.Expectation is not Array expectationAsArray || expectationAsArray.Rank == 1) { return EquivalencyResult.ContinueWithNext; } - if (AreComparable(comparands, expectationAsArray)) + if (AreComparable(comparands, expectationAsArray, AssertionChain.GetOrCreate().For(context))) { if (expectationAsArray.Length == 0) { - return EquivalencyResult.AssertionCompleted; + return EquivalencyResult.EquivalencyProven; } Digit digit = BuildDigitsRepresentingAllIndices(expectationAsArray); @@ -36,12 +36,13 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon IEquivalencyValidationContext itemContext = context.AsCollectionItem(listOfIndices); - nestedValidator.RecursivelyAssertEquality(new Comparands(subject, expectation, typeof(object)), itemContext); + valueChildNodes.AssertEquivalencyOf(new Comparands(subject, expectation, typeof(object)), + itemContext); } while (digit.Increment()); } - return EquivalencyResult.AssertionCompleted; + return EquivalencyResult.EquivalencyProven; } private static Digit BuildDigitsRepresentingAllIndices(Array subjectAsArray) @@ -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/NestedExclusionOptionBuilder.cs b/Src/FluentAssertions/Equivalency/NestedExclusionOptionBuilder.cs index bebf18fba3..1708c3e7a7 100644 --- a/Src/FluentAssertions/Equivalency/NestedExclusionOptionBuilder.cs +++ b/Src/FluentAssertions/Equivalency/NestedExclusionOptionBuilder.cs @@ -9,27 +9,33 @@ namespace FluentAssertions.Equivalency; public class NestedExclusionOptionBuilder { /// - /// The selected path starting at the first . + /// The selected path starting at the first . /// private readonly ExcludeMemberByPathSelectionRule currentPathSelectionRule; - private readonly EquivalencyAssertionOptions capturedAssertionOptions; + private readonly EquivalencyOptions capturedOptions; - internal NestedExclusionOptionBuilder(EquivalencyAssertionOptions capturedAssertionOptions, + internal NestedExclusionOptionBuilder(EquivalencyOptions capturedOptions, ExcludeMemberByPathSelectionRule currentPathSelectionRule) { - this.capturedAssertionOptions = capturedAssertionOptions; + this.capturedOptions = capturedOptions; this.currentPathSelectionRule = currentPathSelectionRule; } /// /// Selects a nested property to exclude. This ends the chain. /// - public EquivalencyAssertionOptions Exclude(Expression> expression) + public EquivalencyOptions Exclude(Expression> expression) { - var nextPath = expression.GetMemberPath(); - currentPathSelectionRule.AppendPath(nextPath); - return capturedAssertionOptions; + var currentSelectionPath = currentPathSelectionRule.CurrentPath; + + foreach (var path in expression.GetMemberPaths()) + { + var newPath = currentSelectionPath.AsParentCollectionOf(path); + capturedOptions.AddSelectionRule(new ExcludeMemberByPathSelectionRule(newPath)); + } + + return capturedOptions; } /// @@ -40,6 +46,6 @@ public NestedExclusionOptionBuilder For( { var nextPath = expression.GetMemberPath(); currentPathSelectionRule.AppendPath(nextPath); - return new NestedExclusionOptionBuilder(capturedAssertionOptions, currentPathSelectionRule); + return new NestedExclusionOptionBuilder(capturedOptions, currentPathSelectionRule); } } diff --git a/Src/FluentAssertions/Equivalency/Node.cs b/Src/FluentAssertions/Equivalency/Node.cs index 145d8291f7..b8b08bc94a 100644 --- a/Src/FluentAssertions/Equivalency/Node.cs +++ b/Src/FluentAssertions/Equivalency/Node.cs @@ -6,43 +6,40 @@ namespace FluentAssertions.Equivalency; -public class Node : INode +internal class Node : INode { private static readonly Regex MatchFirstIndex = new(@"^\[[0-9]+\]$"); - private string path; - private string name; - private string pathAndName; + private GetSubjectId subjectIdProvider; - public GetSubjectId GetSubjectId { get; protected set; } = () => string.Empty; + private string cachedSubjectId; + private Pathway subject; - public Type Type { get; protected set; } - - public Type ParentType { get; protected set; } - - public string Path + public GetSubjectId GetSubjectId { - get => path; - protected set - { - path = value; - pathAndName = null; - } + get => () => cachedSubjectId ??= subjectIdProvider(); + protected init => subjectIdProvider = value; } - public string PathAndName => pathAndName ??= Path.Combine(Name); + public Type Type { get; protected set; } + + public Type ParentType { get; protected set; } - public string Name + public Pathway Subject { - get => name; + get => subject; set { - name = value; - pathAndName = null; + subject = value; + + if (Expectation is null) + { + Expectation = value; + } } } - public virtual string Description => $"{GetSubjectId().Combine(PathAndName)}"; + public Pathway Expectation { get; protected set; } public bool IsRoot { @@ -50,20 +47,25 @@ public bool IsRoot { // If the root is a collection, we need treat the objects in that collection as the root of the graph because all options // refer to the type of the collection items. - return PathAndName.Length == 0 || (RootIsCollection && IsFirstIndex); + return Subject.PathAndName.Length == 0 || (RootIsCollection && IsFirstIndex); } } - private bool IsFirstIndex => MatchFirstIndex.IsMatch(PathAndName); + private bool IsFirstIndex => MatchFirstIndex.IsMatch(Subject.PathAndName); public bool RootIsCollection { get; protected set; } + public void AdjustForRemappedSubject(IMember subjectMember) + { + Subject = subjectMember.Subject; + } + public int Depth { get { const char memberSeparator = '.'; - return PathAndName.Count(chr => chr == memberSeparator); + return Subject.PathAndName.Count(chr => chr == memberSeparator); } } @@ -76,9 +78,8 @@ public static INode From(GetSubjectId getSubjectId) { return new Node { - GetSubjectId = () => getSubjectId() ?? "root", - Name = string.Empty, - Path = string.Empty, + subjectIdProvider = () => getSubjectId() ?? "root", + Subject = new Pathway(string.Empty, string.Empty, _ => getSubjectId()), Type = typeof(T), ParentType = null, RootIsCollection = IsCollection(typeof(T)) @@ -87,12 +88,16 @@ public static INode From(GetSubjectId getSubjectId) public static INode FromCollectionItem(string index, INode parent) { + Pathway.GetDescription getDescription = pathAndName => parent.GetSubjectId().Combine(pathAndName); + + string itemName = "[" + index + "]"; + return new Node { Type = typeof(T), ParentType = parent.Type, - Name = "[" + index + "]", - Path = parent.PathAndName, + Subject = new Pathway(parent.Subject, itemName, getDescription), + Expectation = new Pathway(parent.Expectation, itemName, getDescription), GetSubjectId = parent.GetSubjectId, RootIsCollection = parent.RootIsCollection }; @@ -100,12 +105,16 @@ public static INode FromCollectionItem(string index, INode parent) public static INode FromDictionaryItem(object key, INode parent) { + Pathway.GetDescription getDescription = pathAndName => parent.GetSubjectId().Combine(pathAndName); + + string itemName = "[" + key + "]"; + return new Node { Type = typeof(T), ParentType = parent.Type, - Name = "[" + key + "]", - Path = parent.PathAndName, + Subject = new Pathway(parent.Subject, itemName, getDescription), + Expectation = new Pathway(parent.Expectation, itemName, getDescription), GetSubjectId = parent.GetSubjectId, RootIsCollection = parent.RootIsCollection }; @@ -131,7 +140,7 @@ public override bool Equals(object obj) return Equals((Node)obj); } - private bool Equals(Node other) => (Type, Name, Path) == (other.Type, other.Name, other.Path); + private bool Equals(Node other) => (Type, Subject.Name, Subject.Path) == (other.Type, other.Subject.Name, other.Subject.Path); public override int GetHashCode() { @@ -139,11 +148,11 @@ public override int GetHashCode() { #pragma warning disable CA1307 int hashCode = Type.GetHashCode(); - hashCode = (hashCode * 397) + Path.GetHashCode(); - hashCode = (hashCode * 397) + Name.GetHashCode(); + hashCode = (hashCode * 397) + Subject.Path.GetHashCode(); + hashCode = (hashCode * 397) + Subject.Name.GetHashCode(); return hashCode; } } - public override string ToString() => Description; + public override string ToString() => Subject.Description; } diff --git a/Src/FluentAssertions/Equivalency/OrderingRuleCollection.cs b/Src/FluentAssertions/Equivalency/OrderingRuleCollection.cs index 5b6395b6c3..645bba4ad2 100644 --- a/Src/FluentAssertions/Equivalency/OrderingRuleCollection.cs +++ b/Src/FluentAssertions/Equivalency/OrderingRuleCollection.cs @@ -9,7 +9,7 @@ namespace FluentAssertions.Equivalency; /// public class OrderingRuleCollection : IEnumerable { - private readonly List rules = new(); + private readonly List rules = []; /// /// Initializes a new collection of ordering rules. diff --git a/Src/FluentAssertions/Equivalency/Pathway.cs b/Src/FluentAssertions/Equivalency/Pathway.cs new file mode 100644 index 0000000000..8bd3f24689 --- /dev/null +++ b/Src/FluentAssertions/Equivalency/Pathway.cs @@ -0,0 +1,73 @@ +using FluentAssertions.Common; + +namespace FluentAssertions.Equivalency; + +/// +/// Represents the path of a field or property in an object graph. +/// +public record Pathway +{ + public delegate string GetDescription(string pathAndName); + + private string path = string.Empty; + private string name = string.Empty; + private string pathAndName; + + private readonly GetDescription getDescription; + + public Pathway(string path, string name, GetDescription getDescription) + { + Path = path; + Name = name; + this.getDescription = getDescription; + } + + /// + /// Creates an instance of with the specified parent and name and a factory + /// to provide a description for the path and name. + /// + public Pathway(Pathway parent, string name, GetDescription getDescription) + { + Path = parent.PathAndName; + Name = name; + this.getDescription = getDescription; + } + + /// + /// Gets the path of the field or property without the name. + /// + public string Path + { + get => path; + private init + { + path = value; + pathAndName = null; + } + } + + /// + /// Gets the name of the field or property without the path. + /// + public string Name + { + get => name; + private init + { + name = value; + pathAndName = null; + } + } + + /// + /// Gets the path and name of the field or property separated by dots. + /// + public string PathAndName => pathAndName ??= path.Combine(name); + + /// + /// Gets the display representation of this path. + /// + public string Description => getDescription(PathAndName); + + public override string ToString() => Description; +} diff --git a/Src/FluentAssertions/Equivalency/Property.cs b/Src/FluentAssertions/Equivalency/Property.cs index fe43d16e1a..33cce0a884 100644 --- a/Src/FluentAssertions/Equivalency/Property.cs +++ b/Src/FluentAssertions/Equivalency/Property.cs @@ -9,7 +9,7 @@ namespace FluentAssertions.Equivalency; /// A specialized type of that represents a property of an object in a structural equivalency assertion. /// #pragma warning disable CA1716 -public class Property : Node, IMember +internal class Property : Node, IMember { private readonly PropertyInfo propertyInfo; private bool? isBrowsable; @@ -24,10 +24,10 @@ public Property(Type reflectedType, PropertyInfo propertyInfo, INode parent) ReflectedType = reflectedType; this.propertyInfo = propertyInfo; DeclaringType = propertyInfo.DeclaringType; - Name = propertyInfo.Name; + Subject = new Pathway(parent.Subject.PathAndName, propertyInfo.Name, pathAndName => $"property {parent.GetSubjectId().Combine(pathAndName)}"); + Expectation = new Pathway(parent.Expectation.PathAndName, propertyInfo.Name, pathAndName => $"property {pathAndName}"); Type = propertyInfo.PropertyType; ParentType = propertyInfo.DeclaringType; - Path = parent.PathAndName; GetSubjectId = parent.GetSubjectId; RootIsCollection = parent.RootIsCollection; } @@ -41,8 +41,6 @@ public object GetValue(object obj) public Type ReflectedType { get; } - public override string Description => $"property {GetSubjectId().Combine(PathAndName)}"; - public CSharpAccessModifier GetterAccessibility => propertyInfo.GetGetMethod(nonPublic: true).GetCSharpAccessModifier(); public CSharpAccessModifier SetterAccessibility => propertyInfo.GetSetMethod(nonPublic: true).GetCSharpAccessModifier(); diff --git a/Src/FluentAssertions/Equivalency/Selection/AllPropertiesSelectionRule.cs b/Src/FluentAssertions/Equivalency/Selection/AllPropertiesSelectionRule.cs index cee5cfd696..b683a7ce28 100644 --- a/Src/FluentAssertions/Equivalency/Selection/AllPropertiesSelectionRule.cs +++ b/Src/FluentAssertions/Equivalency/Selection/AllPropertiesSelectionRule.cs @@ -14,8 +14,7 @@ internal class AllPropertiesSelectionRule : IMemberSelectionRule public IEnumerable SelectMembers(INode currentNode, IEnumerable selectedMembers, MemberSelectionContext context) { - MemberVisibility visibility = context.IncludedProperties | MemberVisibility.ExplicitlyImplemented | - MemberVisibility.DefaultInterfaceProperties; + MemberVisibility visibility = context.IncludedProperties; IEnumerable selectedProperties = context.Type .GetProperties(visibility.ToMemberKind()) diff --git a/Src/FluentAssertions/Equivalency/Selection/ExcludeMemberByPathSelectionRule.cs b/Src/FluentAssertions/Equivalency/Selection/ExcludeMemberByPathSelectionRule.cs index 4430f4eeec..6f7a19dfb4 100644 --- a/Src/FluentAssertions/Equivalency/Selection/ExcludeMemberByPathSelectionRule.cs +++ b/Src/FluentAssertions/Equivalency/Selection/ExcludeMemberByPathSelectionRule.cs @@ -27,6 +27,8 @@ public void AppendPath(MemberPath nextPath) memberToExclude = memberToExclude.AsParentCollectionOf(nextPath); } + public MemberPath CurrentPath => memberToExclude; + public override string ToString() { return "Exclude member " + memberToExclude; diff --git a/Src/FluentAssertions/Equivalency/Selection/ExcludeMembersByNameSelectionRule.cs b/Src/FluentAssertions/Equivalency/Selection/ExcludeMembersByNameSelectionRule.cs new file mode 100644 index 0000000000..271e5c15d6 --- /dev/null +++ b/Src/FluentAssertions/Equivalency/Selection/ExcludeMembersByNameSelectionRule.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace FluentAssertions.Equivalency.Selection; + +/// +/// Represents a member selection rule that excludes certain fields or properties from being considered during +/// structural equality checks based on their names. +/// +internal class ExcludeMembersByNameSelectionRule : IMemberSelectionRule +{ + private readonly string[] membersToExclude; + private readonly string description; + + /// + /// Initializes the rule with a list of filed or property names to exclude from the structural equality check. + /// + public ExcludeMembersByNameSelectionRule(string[] membersToExclude) + { + this.membersToExclude = membersToExclude; + description = string.Join(", ", membersToExclude); + } + + public bool IncludesMembers => false; + + public IEnumerable SelectMembers(INode currentNode, IEnumerable selectedMembers, + MemberSelectionContext context) + { + return selectedMembers.Where(m => !membersToExclude.Contains(m.Expectation.Name, StringComparer.Ordinal)).ToArray(); + } + + /// + /// 2 + public override string ToString() + { + return "Exclude members named: " + description; + } +} diff --git a/Src/FluentAssertions/Equivalency/Selection/MemberToMemberInfoAdapter.cs b/Src/FluentAssertions/Equivalency/Selection/MemberToMemberInfoAdapter.cs index b855a5b69d..da84255f61 100644 --- a/Src/FluentAssertions/Equivalency/Selection/MemberToMemberInfoAdapter.cs +++ b/Src/FluentAssertions/Equivalency/Selection/MemberToMemberInfoAdapter.cs @@ -14,9 +14,9 @@ public MemberToMemberInfoAdapter(IMember member) { this.member = member; DeclaringType = member.DeclaringType; - Name = member.Name; + Name = member.Expectation.Name; Type = member.Type; - Path = member.PathAndName; + Path = member.Expectation.PathAndName; } public string Name { get; } diff --git a/Src/FluentAssertions/Equivalency/Selection/SelectMemberByPathSelectionRule.cs b/Src/FluentAssertions/Equivalency/Selection/SelectMemberByPathSelectionRule.cs index 77b9507bbb..ac37b31834 100644 --- a/Src/FluentAssertions/Equivalency/Selection/SelectMemberByPathSelectionRule.cs +++ b/Src/FluentAssertions/Equivalency/Selection/SelectMemberByPathSelectionRule.cs @@ -11,7 +11,7 @@ internal abstract class SelectMemberByPathSelectionRule : IMemberSelectionRule public IEnumerable SelectMembers(INode currentNode, IEnumerable selectedMembers, MemberSelectionContext context) { - var currentPath = RemoveRootIndexQualifier(currentNode.PathAndName); + var currentPath = RemoveRootIndexQualifier(currentNode.Expectation.PathAndName); var members = selectedMembers.ToList(); AddOrRemoveMembersFrom(members, currentNode, currentPath, context); diff --git a/Src/FluentAssertions/Equivalency/SelfReferenceEquivalencyAssertionOptions.cs b/Src/FluentAssertions/Equivalency/SelfReferenceEquivalencyOptions.cs similarity index 73% rename from Src/FluentAssertions/Equivalency/SelfReferenceEquivalencyAssertionOptions.cs rename to Src/FluentAssertions/Equivalency/SelfReferenceEquivalencyOptions.cs index 3246847d7b..bc765110d4 100644 --- a/Src/FluentAssertions/Equivalency/SelfReferenceEquivalencyAssertionOptions.cs +++ b/Src/FluentAssertions/Equivalency/SelfReferenceEquivalencyOptions.cs @@ -12,35 +12,39 @@ using FluentAssertions.Equivalency.Selection; using FluentAssertions.Equivalency.Steps; using FluentAssertions.Equivalency.Tracing; +using FluentAssertions.Equivalency.Typing; namespace FluentAssertions.Equivalency; -#pragma warning disable CA1033 //An unsealed externally visible type provides an explicit method implementation of a public interface and does not provide an alternative externally visible method that has the same name. +#pragma warning disable CA1033 // An unsealed externally visible type provides an explicit method implementation of a public interface and does not provide an alternative externally visible method that has the same name. /// /// Represents the run-time behavior of a structural equivalency assertion. /// -public abstract class SelfReferenceEquivalencyAssertionOptions : IEquivalencyAssertionOptions - where TSelf : SelfReferenceEquivalencyAssertionOptions +public abstract class SelfReferenceEquivalencyOptions : IEquivalencyOptions, IContainTypingRules + where TSelf : SelfReferenceEquivalencyOptions { #region Private Definitions private readonly EqualityStrategyProvider equalityStrategyProvider; [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly List selectionRules = new(); + private readonly List selectionRules = []; [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly List matchingRules = new(); + private readonly List matchingRules = []; [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private readonly List userEquivalencySteps = new(); + private readonly List typingRules = []; + + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private readonly List userEquivalencySteps = []; [DebuggerBrowsable(DebuggerBrowsableState.Never)] private CyclicReferenceHandling cyclicReferenceHandling = CyclicReferenceHandling.ThrowException; [DebuggerBrowsable(DebuggerBrowsableState.Never)] - protected OrderingRuleCollection OrderingRules { get; } = new(); + protected OrderingRuleCollection OrderingRules { get; } = []; [DebuggerBrowsable(DebuggerBrowsableState.Never)] private bool isRecursive; @@ -56,9 +60,11 @@ public abstract class SelfReferenceEquivalencyAssertionOptions : IEquival private bool ignoreNonBrowsableOnSubject; private bool excludeNonBrowsableOnExpectation; + private IEqualityComparer stringComparer; + #endregion - private protected SelfReferenceEquivalencyAssertionOptions() + private protected SelfReferenceEquivalencyOptions() { equalityStrategyProvider = new EqualityStrategyProvider(); @@ -70,7 +76,7 @@ private protected SelfReferenceEquivalencyAssertionOptions() /// /// Creates an instance of the equivalency assertions options based on defaults previously configured by the caller. /// - protected SelfReferenceEquivalencyAssertionOptions(IEquivalencyAssertionOptions defaults) + protected SelfReferenceEquivalencyOptions(IEquivalencyOptions defaults) { equalityStrategyProvider = new EqualityStrategyProvider(defaults.GetEqualityStrategy) { @@ -86,6 +92,11 @@ protected SelfReferenceEquivalencyAssertionOptions(IEquivalencyAssertionOptions includedFields = defaults.IncludedFields; ignoreNonBrowsableOnSubject = defaults.IgnoreNonBrowsableOnSubject; excludeNonBrowsableOnExpectation = defaults.ExcludeNonBrowsableOnExpectation; + IgnoreLeadingWhitespace = defaults.IgnoreLeadingWhitespace; + IgnoreTrailingWhitespace = defaults.IgnoreTrailingWhitespace; + IgnoreCase = defaults.IgnoreCase; + IgnoreNewlineStyle = defaults.IgnoreNewlineStyle; + IgnoreJsonPropertyCasing = defaults.IgnoreJsonPropertyCasing; ConversionSelector = defaults.ConversionSelector.Clone(); @@ -94,6 +105,11 @@ protected SelfReferenceEquivalencyAssertionOptions(IEquivalencyAssertionOptions matchingRules.AddRange(defaults.MatchingRules); OrderingRules = new OrderingRuleCollection(defaults.OrderingRules); + if (defaults is IContainTypingRules typingRulesContainer) + { + typingRules.AddRange(typingRulesContainer.TypingRules); + } + TraceWriter = defaults.TraceWriter; RemoveSelectionRule(); @@ -103,7 +119,7 @@ protected SelfReferenceEquivalencyAssertionOptions(IEquivalencyAssertionOptions /// /// Gets an ordered collection of selection rules that define what members are included. /// - IEnumerable IEquivalencyAssertionOptions.SelectionRules + IEnumerable IEquivalencyOptions.SelectionRules { get { @@ -135,12 +151,14 @@ IEnumerable IEquivalencyAssertionOptions.SelectionRules /// Gets an ordered collection of matching rules that determine which subject members are matched with which /// expectation members. /// - IEnumerable IEquivalencyAssertionOptions.MatchingRules => matchingRules; + IEnumerable IEquivalencyOptions.MatchingRules => matchingRules; /// /// Gets an ordered collection of Equivalency steps how a subject is compared with the expectation. /// - IEnumerable IEquivalencyAssertionOptions.UserEquivalencySteps => userEquivalencySteps; + IEnumerable IEquivalencyOptions.UserEquivalencySteps => userEquivalencySteps; + + IEnumerable IContainTypingRules.TypingRules => typingRules; public ConversionSelector ConversionSelector { get; } = new(); @@ -149,37 +167,47 @@ IEnumerable IEquivalencyAssertionOptions.SelectionRules /// default, /// ordering is irrelevant. /// - OrderingRuleCollection IEquivalencyAssertionOptions.OrderingRules => OrderingRules; + OrderingRuleCollection IEquivalencyOptions.OrderingRules => OrderingRules; /// /// Gets value indicating whether the equality check will include nested collections and complex types. /// - bool IEquivalencyAssertionOptions.IsRecursive => isRecursive; + bool IEquivalencyOptions.IsRecursive => isRecursive; - bool IEquivalencyAssertionOptions.AllowInfiniteRecursion => allowInfiniteRecursion; + bool IEquivalencyOptions.AllowInfiniteRecursion => allowInfiniteRecursion; /// /// Gets value indicating how cyclic references should be handled. By default, it will throw an exception. /// - CyclicReferenceHandling IEquivalencyAssertionOptions.CyclicReferenceHandling => cyclicReferenceHandling; + CyclicReferenceHandling IEquivalencyOptions.CyclicReferenceHandling => cyclicReferenceHandling; - EnumEquivalencyHandling IEquivalencyAssertionOptions.EnumEquivalencyHandling => enumEquivalencyHandling; + EnumEquivalencyHandling IEquivalencyOptions.EnumEquivalencyHandling => enumEquivalencyHandling; - bool IEquivalencyAssertionOptions.UseRuntimeTyping => useRuntimeTyping; + bool IEquivalencyOptions.UseRuntimeTyping => useRuntimeTyping; - MemberVisibility IEquivalencyAssertionOptions.IncludedProperties => includedProperties; + MemberVisibility IEquivalencyOptions.IncludedProperties => includedProperties; - MemberVisibility IEquivalencyAssertionOptions.IncludedFields => includedFields; + MemberVisibility IEquivalencyOptions.IncludedFields => includedFields; - bool IEquivalencyAssertionOptions.IgnoreNonBrowsableOnSubject => ignoreNonBrowsableOnSubject; + bool IEquivalencyOptions.IgnoreNonBrowsableOnSubject => ignoreNonBrowsableOnSubject; - bool IEquivalencyAssertionOptions.ExcludeNonBrowsableOnExpectation => excludeNonBrowsableOnExpectation; + bool IEquivalencyOptions.ExcludeNonBrowsableOnExpectation => excludeNonBrowsableOnExpectation; public bool? CompareRecordsByValue => equalityStrategyProvider.CompareRecordsByValue; - EqualityStrategy IEquivalencyAssertionOptions.GetEqualityStrategy(Type type) + EqualityStrategy IEquivalencyOptions.GetEqualityStrategy(Type type) => equalityStrategyProvider.GetEqualityStrategy(type); + public bool IgnoreLeadingWhitespace { get; private set; } + + public bool IgnoreTrailingWhitespace { get; private set; } + + public bool IgnoreCase { get; private set; } + + public bool IgnoreNewlineStyle { get; private set; } + + public bool IgnoreJsonPropertyCasing { get; set; } + public ITraceWriter TraceWriter { get; private set; } /// @@ -190,7 +218,7 @@ EqualityStrategy IEquivalencyAssertionOptions.GetEqualityStrategy(Type type) /// public TSelf IncludingAllDeclaredProperties() { - RespectingDeclaredTypes(); + PreferringDeclaredMemberTypes(); ExcludingFields(); IncludingProperties(); @@ -208,7 +236,7 @@ public TSelf IncludingAllDeclaredProperties() /// public TSelf IncludingAllRuntimeProperties() { - RespectingRuntimeTypes(); + PreferringRuntimeMemberTypes(); ExcludingFields(); IncludingProperties(); @@ -259,7 +287,23 @@ public TSelf ExcludingFields() /// public TSelf IncludingProperties() { - includedProperties = MemberVisibility.Public; + includedProperties = MemberVisibility.Public | MemberVisibility.ExplicitlyImplemented | + MemberVisibility.DefaultInterfaceProperties; + + return (TSelf)this; + } + + /// + /// Disables the strict typing requirement for all members, allowing members in the expectation to be of different types + /// than members in the subject. + /// + /// + /// This is particularly useful when you have enabled strict typing globally or for specific tests but want to override it + /// for a particular assertion. + /// + public TSelf WithoutStrictTyping() + { + typingRules.Clear(); return (TSelf)this; } @@ -268,7 +312,9 @@ public TSelf IncludingProperties() /// public TSelf IncludingInternalProperties() { - includedProperties = MemberVisibility.Public | MemberVisibility.Internal; + includedProperties = MemberVisibility.Public | MemberVisibility.Internal | MemberVisibility.ExplicitlyImplemented | + MemberVisibility.DefaultInterfaceProperties; + return (TSelf)this; } @@ -284,6 +330,27 @@ public TSelf ExcludingProperties() return (TSelf)this; } + /// + /// Excludes the specified member(s) from the structural equality check anywhere in the object graph. + /// + public TSelf ExcludingMembersNamed(params string[] memberNames) + { + Guard.ThrowIfArgumentIsNull(memberNames, nameof(memberNames), "Member names cannot be null."); + Guard.ThrowIfArgumentIsEmpty(memberNames, nameof(memberNames), "At least one member name must be specified."); + + AddSelectionRule(new ExcludeMembersByNameSelectionRule(memberNames)); + return (TSelf)this; + } + + /// + /// Excludes properties that are explicitly implemented from the equivalency comparison. + /// + public TSelf ExcludingExplicitlyImplementedProperties() + { + includedProperties &= ~MemberVisibility.ExplicitlyImplemented; + return (TSelf)this; + } + /// /// Instructs the comparison to exclude non-browsable members in the expectation (members set to /// ). It is not required that they be marked non-browsable in the subject. Use @@ -306,18 +373,24 @@ public TSelf IgnoringNonBrowsableMembersOnSubject() } /// - /// Instructs the comparison to respect the expectation's runtime type. + /// Instructs the structural equality comparison to use the run-time types + /// of the members to drive the assertion. /// - public TSelf RespectingRuntimeTypes() + public TSelf PreferringRuntimeMemberTypes() { useRuntimeTyping = true; return (TSelf)this; } /// - /// Instructs the comparison to respect the expectation's declared type. + /// Instructs the structural equality comparison to prefer the declared types + /// of the members when executing assertions. /// - public TSelf RespectingDeclaredTypes() + /// + /// In reality, the declared types of the members are only known for the members of the root object, + /// or the objects in a root collection. Beyond that, FluentAssertions only knows the run-time types of the members. + /// + public TSelf PreferringDeclaredMemberTypes() { useRuntimeTyping = false; return (TSelf)this; @@ -382,7 +455,7 @@ public Restriction Using(Action /// - /// This is the default behavior. You can override this using . + /// This is the default behavior. You can override this using . /// public TSelf IncludingNestedObjects() { @@ -391,13 +464,13 @@ public TSelf IncludingNestedObjects() } /// - /// Stops the structural equality check from recursively comparing the members any nested objects. + /// Stops the structural equality check from recursively comparing the members of any nested objects. /// /// /// If a property or field points to a complex type or collection, a simple call will - /// be done instead of recursively looking at the properties or fields of the nested object. + /// be done instead of recursively looking at the members of the nested object. /// - public TSelf ExcludingNestedObjects() + public TSelf WithoutRecursing() { isRecursive = false; return (TSelf)this; @@ -427,17 +500,19 @@ public TSelf AllowingInfiniteRecursion() /// /// Clears all selection rules, including those that were added by default. /// - public void WithoutSelectionRules() + public TSelf WithoutSelectionRules() { selectionRules.Clear(); + return (TSelf)this; } /// /// Clears all matching rules, including those that were added by default. /// - public void WithoutMatchingRules() + public TSelf WithoutMatchingRules() { matchingRules.Clear(); + return (TSelf)this; } /// @@ -495,6 +570,18 @@ public TSelf Using(IEqualityComparer comparer) return (TSelf)this; } + /// + /// Ensures the equivalency comparison will use the specified implementation of + /// any time when a property is a . + /// + public TSelf Using(IEqualityComparer comparer) + { + userEquivalencySteps.Insert(0, new EqualityComparerEquivalencyStep(comparer)); + stringComparer = comparer; + + return (TSelf)this; + } + /// /// Causes all collections to be compared in the order in which the items appear in the expectation. /// @@ -539,6 +626,26 @@ public TSelf WithoutStrictOrderingFor(Expression> predic return (TSelf)this; } + /// + /// Causes all type comparisons to be strict, requiring exact type equality between + /// subject and expectation. + /// + public TSelf WithStrictTyping() + { + typingRules.Add(new AlwaysBeStrictTypingRule()); + return (TSelf)this; + } + + /// + /// Causes the types identified by the provided to be + /// compared using strict typing, requiring exact type equality. + /// + public TSelf WithStrictTypingFor(Expression> predicate) + { + typingRules.Add(new PredicateBasedTypingRule(predicate)); + return (TSelf)this; + } + /// /// Causes to compare Enum properties using the result of their ToString method. /// @@ -643,7 +750,7 @@ public TSelf ComparingByValue(Type type) /// public TSelf WithTracing(ITraceWriter writer = null) { - TraceWriter = writer ?? new StringBuilderTraceWriter(); + TraceWriter = writer ?? new InternalTraceWriter(); return (TSelf)this; } @@ -677,6 +784,67 @@ public TSelf WithoutAutoConversionFor(Expression> predic return (TSelf)this; } + /// + /// Instructs the comparison to ignore leading whitespace when comparing s. + /// + /// + /// Note: This affects the index of first mismatch, as the removed whitespace is also ignored for the index calculation! + /// + public TSelf IgnoringLeadingWhitespace() + { + IgnoreLeadingWhitespace = true; + return (TSelf)this; + } + + /// + /// Instructs the comparison to ignore trailing whitespace when comparing s. + /// + public TSelf IgnoringTrailingWhitespace() + { + IgnoreTrailingWhitespace = true; + return (TSelf)this; + } + + /// + /// Instructs the comparison to compare s case-insensitive. + /// + public TSelf IgnoringCase() + { + IgnoreCase = true; + return (TSelf)this; + } + + /// + /// Instructs the comparison to ignore the newline style when comparing s. + /// + /// + /// Enabling this option will replace all occurences of \r\n and \r with \n in the strings before comparing them. + /// + public TSelf IgnoringNewlineStyle() + { + IgnoreNewlineStyle = true; + return (TSelf)this; + } + +#if NET6_0_OR_GREATER + /// + /// Tells the comparison to ignore the casing when trying to match a property to a JSON property. + /// + public TSelf IgnoringJsonPropertyCasing() + { + IgnoreJsonPropertyCasing = true; + return (TSelf)this; + } +#endif + + /// + /// Returns the comparer for strings, which is either an explicitly specified comparer via or an ordinal comparer depending on . + /// + internal IEqualityComparer GetStringComparerOrDefault() + { + return stringComparer ?? (IgnoreCase ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal); + } + /// /// Returns a string that represents the current object. /// @@ -684,14 +852,13 @@ public TSelf WithoutAutoConversionFor(Expression> predic /// A string that represents the current object. /// /// 2 - [SuppressMessage("Design", "MA0051:Method is too long")] public override string ToString() { var builder = new StringBuilder(); - builder.Append("- Use ") + builder.Append("- Prefer the ") .Append(useRuntimeTyping ? "runtime" : "declared") - .AppendLine(" types and members"); + .AppendLine(" type of the members"); if (ignoreNonBrowsableOnSubject) { @@ -731,6 +898,11 @@ public override string ToString() builder.Append("- ").AppendLine(rule.ToString()); } + foreach (ITypingRule rule in typingRules) + { + builder.Append("- ").AppendLine(rule.ToString()); + } + foreach (IMemberMatchingRule rule in matchingRules) { builder.Append("- ").AppendLine(rule.ToString()); @@ -752,8 +924,9 @@ public override string ToString() } /// - /// Defines additional overrides when used with + /// Defines additional overrides when used with /// + [SuppressMessage("Design", "CA1034:Nested types should not be visible")] public class Restriction { private readonly Action> action; @@ -801,7 +974,7 @@ private void RemoveSelectionRule() selectionRules.RemoveAll(selectionRule => selectionRule is T); } - protected TSelf AddSelectionRule(IMemberSelectionRule selectionRule) + protected internal TSelf AddSelectionRule(IMemberSelectionRule selectionRule) { selectionRules.Add(selectionRule); return (TSelf)this; diff --git a/Src/FluentAssertions/Equivalency/Steps/AssertionContext.cs b/Src/FluentAssertions/Equivalency/Steps/AssertionContext.cs index 0e87fea241..f8a9c02f33 100644 --- a/Src/FluentAssertions/Equivalency/Steps/AssertionContext.cs +++ b/Src/FluentAssertions/Equivalency/Steps/AssertionContext.cs @@ -1,9 +1,11 @@ +using System.Diagnostics.CodeAnalysis; + namespace FluentAssertions.Equivalency.Steps; internal sealed class AssertionContext : IAssertionContext { - private AssertionContext(INode currentNode, TSubject subject, TSubject expectation, string because, - object[] becauseArgs) + private AssertionContext(INode currentNode, TSubject subject, TSubject expectation, + [StringSyntax("CompositeFormat")] string because, object[] becauseArgs) { SelectedNode = currentNode; Subject = subject; diff --git a/Src/FluentAssertions/Equivalency/Steps/AssertionResultSet.cs b/Src/FluentAssertions/Equivalency/Steps/AssertionResultSet.cs index f7bae2311d..8bde0c02aa 100644 --- a/Src/FluentAssertions/Equivalency/Steps/AssertionResultSet.cs +++ b/Src/FluentAssertions/Equivalency/Steps/AssertionResultSet.cs @@ -10,7 +10,7 @@ namespace FluentAssertions.Equivalency.Steps; /// internal class AssertionResultSet { - private readonly Dictionary set = new(); + private readonly Dictionary set = []; /// /// Adds the failures (if any) resulting from executing an assertion within a @@ -22,18 +22,18 @@ 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. /// /// /// The closest match is the set that contains the least amount of failures, or no failures at all, and preferably /// the set that is identified by the . /// - public string[] SelectClosestMatchFor(object key = null) + public string[] GetTheFailuresForTheSetWithTheFewestFailures(object key = null) { if (ContainsSuccessfulSet()) { - return Array.Empty(); + return []; } KeyValuePair[] bestResultSets = GetBestResultSets(); diff --git a/Src/FluentAssertions/Equivalency/Steps/AssertionRuleEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/AssertionRuleEquivalencyStep.cs index 4c3609bccd..cb6d2fcc03 100644 --- a/Src/FluentAssertions/Equivalency/Steps/AssertionRuleEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/AssertionRuleEquivalencyStep.cs @@ -10,20 +10,20 @@ 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(); } public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, - IEquivalencyValidator nestedValidator) + IValidateChildNodeEquivalency valueChildNodes) { bool success = false; @@ -41,7 +41,7 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon { // Convert into a child context context = context.Clone(); - converter.Handle(comparands, context, nestedValidator); + converter.Handle(comparands, context, valueChildNodes); converted = true; } @@ -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 @@ -59,7 +58,7 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon } } - return success ? EquivalencyResult.AssertionCompleted : EquivalencyResult.ContinueWithNext; + return success ? EquivalencyResult.EquivalencyProven : EquivalencyResult.ContinueWithNext; } private bool AppliesTo(Comparands comparands, INode currentNode) => predicate(new ObjectInfo(comparands, currentNode)); @@ -67,30 +66,35 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon private bool ExecuteAssertion(Comparands comparands, IEquivalencyValidationContext context) { bool subjectIsNull = comparands.Subject is null; - - bool subjectIsValidType = - AssertionScope.Current - .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 + var assertionChain = AssertionChain.GetOrCreate().For(context); + + assertionChain + .ForCondition(subjectIsNull || comparands.Subject.GetType().IsSameOrInherits(typeof(TSubject))) + .FailWith("Expected {0} from subject to be a {1}{reason}, but found a {2}.", + context.CurrentNode.Subject.AsNonFormattable(), + 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}.", + "Expected {0} from expectation to be a {1}{reason}, but found a {2}.", + context.CurrentNode.Subject.AsNonFormattable(), typeof(TSubject), comparands.Expectation?.GetType()); - if (subjectIsValidType && expectationIsValidType) + if (assertionChain.Succeeded) { if ((subjectIsNull || expectationIsNull) && !CanBeNull()) { return false; } - assertion(AssertionContext.CreateFrom(comparands, context)); + // Caller identification should not get confused about invoking a Should within the assertion action + string callerIdentifier = context.CurrentNode.Subject.ToString(); + assertionChain.OverrideCallerIdentifier(() => callerIdentifier); + assertionChain.ReuseOnce(); + + assertionAction(AssertionContext.CreateFrom(comparands, context)); return true; } diff --git a/Src/FluentAssertions/Equivalency/Steps/AutoConversionStep.cs b/Src/FluentAssertions/Equivalency/Steps/AutoConversionStep.cs index 55f9f4db5d..9d45787682 100644 --- a/Src/FluentAssertions/Equivalency/Steps/AutoConversionStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/AutoConversionStep.cs @@ -14,7 +14,7 @@ namespace FluentAssertions.Equivalency.Steps; public class AutoConversionStep : IEquivalencyStep { public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, - IEquivalencyValidator nestedValidator) + IValidateChildNodeEquivalency valueChildNodes) { if (!context.Options.ConversionSelector.RequiresConversion(comparands, context.CurrentNode)) { @@ -37,14 +37,14 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon if (TryChangeType(comparands.Subject, expectationType, out object convertedSubject)) { context.Tracer.WriteLine(member => - Invariant($"Converted subject {comparands.Subject} at {member.Description} to {expectationType}")); + Invariant($"Converted subject {comparands.Subject} at {member.Subject} to {expectationType}")); comparands.Subject = convertedSubject; } else { context.Tracer.WriteLine(member => - Invariant($"Subject {comparands.Subject} at {member.Description} could not be converted to {expectationType}")); + Invariant($"Subject {comparands.Subject} at {member.Subject} could not be converted to {expectationType}")); } return EquivalencyResult.ContinueWithNext; diff --git a/Src/FluentAssertions/Equivalency/Steps/ConstraintCollectionEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/ConstraintCollectionEquivalencyStep.cs deleted file mode 100644 index 991692215b..0000000000 --- a/Src/FluentAssertions/Equivalency/Steps/ConstraintCollectionEquivalencyStep.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System.Collections.Generic; -using System.Data; -using System.Linq; -using FluentAssertions.Execution; - -namespace FluentAssertions.Equivalency.Steps; - -public class ConstraintCollectionEquivalencyStep : EquivalencyStep -{ - protected override EquivalencyResult OnHandle(Comparands comparands, IEquivalencyValidationContext context, - IEquivalencyValidator nestedValidator) - { - if (comparands.Subject is not ConstraintCollection) - { - AssertionScope.Current - .FailWith("Expected a value of type ConstraintCollection at {context:Constraints}, but found {0}", - comparands.Subject.GetType()); - } - else - { - var subject = (ConstraintCollection)comparands.Subject; - var expectation = (ConstraintCollection)comparands.Expectation; - - var subjectConstraints = subject.Cast().ToDictionary(constraint => constraint.ConstraintName); - var expectationConstraints = expectation.Cast().ToDictionary(constraint => constraint.ConstraintName); - - IEnumerable constraintNames = subjectConstraints.Keys.Union(expectationConstraints.Keys); - - foreach (var constraintName in constraintNames) - { - AssertionScope.Current - .ForCondition(subjectConstraints.TryGetValue(constraintName, out Constraint subjectConstraint)) - .FailWith("Expected constraint named {0} in {context:Constraints collection}{reason}, but did not find one", - constraintName); - - AssertionScope.Current - .ForCondition(expectationConstraints.TryGetValue(constraintName, out Constraint expectationConstraint)) - .FailWith("Found unexpected constraint named {0} in {context:Constraints collection}", constraintName); - - if (subjectConstraint is not null && expectationConstraint is not null) - { - Comparands newComparands = new() - { - Subject = subjectConstraint, - Expectation = expectationConstraint, - CompileTimeType = typeof(Constraint) - }; - - IEquivalencyValidationContext nestedContext = context.AsCollectionItem(constraintName); - nestedValidator.RecursivelyAssertEquality(newComparands, nestedContext); - } - } - } - - return EquivalencyResult.AssertionCompleted; - } -} diff --git a/Src/FluentAssertions/Equivalency/Steps/ConstraintEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/ConstraintEquivalencyStep.cs deleted file mode 100644 index 4eb8c9ce1b..0000000000 --- a/Src/FluentAssertions/Equivalency/Steps/ConstraintEquivalencyStep.cs +++ /dev/null @@ -1,286 +0,0 @@ -using System.Collections.Generic; -using System.Data; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Text; -using FluentAssertions.Execution; -using FluentAssertions.Formatting; - -namespace FluentAssertions.Equivalency.Steps; - -public class ConstraintEquivalencyStep : EquivalencyStep -{ - protected override EquivalencyResult OnHandle(Comparands comparands, IEquivalencyValidationContext context, - IEquivalencyValidator nestedValidator) - { - if (comparands.Subject is not Constraint) - { - AssertionScope.Current - .FailWith("Expected {context:constraint} to be a value of type Constraint, but found {0}", - comparands.Subject.GetType()); - } - else - { - var subject = (Constraint)comparands.Subject; - var expectation = (Constraint)comparands.Expectation; - - var selectedMembers = GetMembersFromExpectation(comparands, context.CurrentNode, context.Options) - .ToDictionary(member => member.Name); - - CompareCommonProperties(context, nestedValidator, context.Options, subject, expectation, selectedMembers); - - bool matchingType = subject.GetType() == expectation.GetType(); - - AssertionScope.Current - .ForCondition(matchingType) - .FailWith("Expected {context:constraint} to be of type {0}, but found {1}", expectation.GetType(), - subject.GetType()); - - if (matchingType) - { - if (subject is UniqueConstraint subjectUniqueConstraint - && expectation is UniqueConstraint expectationUniqueConstraint) - { - CompareConstraints(nestedValidator, context, subjectUniqueConstraint, expectationUniqueConstraint, - selectedMembers); - } - else if (subject is ForeignKeyConstraint subjectForeignKeyConstraint - && expectation is ForeignKeyConstraint expectationForeignKeyConstraint) - { - CompareConstraints(nestedValidator, context, subjectForeignKeyConstraint, expectationForeignKeyConstraint, - selectedMembers); - } - else - { - AssertionScope.Current - .FailWith("Don't know how to handle {constraint:a Constraint} of type {0}", subject.GetType()); - } - } - } - - return EquivalencyResult.AssertionCompleted; - } - - private static void CompareCommonProperties(IEquivalencyValidationContext context, IEquivalencyValidator parent, - IEquivalencyAssertionOptions options, Constraint subject, Constraint expectation, - Dictionary selectedMembers) - { - if (selectedMembers.ContainsKey("ConstraintName")) - { - AssertionScope.Current - .ForCondition(subject.ConstraintName == expectation.ConstraintName) - .FailWith("Expected {context:constraint} to have a ConstraintName of {0}{reason}, but found {1}", - expectation.ConstraintName, subject.ConstraintName); - } - - if (selectedMembers.ContainsKey("Table")) - { - AssertionScope.Current - .ForCondition(subject.Table.TableName == expectation.Table.TableName) - .FailWith( - "Expected {context:constraint} to be associated with a Table with TableName of {0}{reason}, but found {1}", - expectation.Table.TableName, subject.Table.TableName); - } - - if (selectedMembers.TryGetValue("ExtendedProperties", out IMember expectationMember)) - { - IMember matchingMember = FindMatchFor(expectationMember, context.CurrentNode, subject, options); - - if (matchingMember is not null) - { - var nestedComparands = new Comparands - { - Subject = matchingMember.GetValue(subject), - Expectation = expectationMember.GetValue(expectation), - CompileTimeType = expectationMember.Type - }; - - parent.RecursivelyAssertEquality(nestedComparands, context.AsNestedMember(expectationMember)); - } - } - } - - private static void CompareConstraints(IEquivalencyValidator parent, IEquivalencyValidationContext context, - UniqueConstraint subject, UniqueConstraint expectation, Dictionary selectedMembers) - { - AssertionScope.Current - .ForCondition(subject.ConstraintName == expectation.ConstraintName) - .FailWith("Expected {context:constraint} to be named {0}{reason}, but found {1}", expectation.ConstraintName, - subject.ConstraintName); - - var nestedMember = new Property( - typeof(Constraint).GetProperty(nameof(subject.ExtendedProperties)), - context.CurrentNode); - - var nestedComparands = new Comparands - { - Subject = nestedMember.GetValue(subject), - Expectation = nestedMember.GetValue(expectation), - CompileTimeType = nestedMember.Type - }; - - parent.RecursivelyAssertEquality(nestedComparands, context.AsNestedMember(nestedMember)); - - if (selectedMembers.ContainsKey(nameof(expectation.IsPrimaryKey))) - { - AssertionScope.Current - .ForCondition(subject.IsPrimaryKey == expectation.IsPrimaryKey) - .FailWith("Expected {context:constraint} to be a {0} constraint{reason}, but found a {1} constraint", - expectation.IsPrimaryKey ? "Primary Key" : "Foreign Key", - subject.IsPrimaryKey ? "Primary Key" : "Foreign Key"); - } - - if (selectedMembers.ContainsKey(nameof(expectation.Columns))) - { - CompareConstraintColumns(subject.Columns, expectation.Columns); - } - } - - [SuppressMessage("Design", "MA0051:Method is too long", Justification = "Needs to be refactored")] - private static void CompareConstraints(IEquivalencyValidator parent, IEquivalencyValidationContext context, - ForeignKeyConstraint subject, ForeignKeyConstraint expectation, Dictionary selectedMembers) - { - AssertionScope.Current - .ForCondition(subject.ConstraintName == expectation.ConstraintName) - .FailWith("Expected {context:constraint} to be named {0}{reason}, but found {1}", expectation.ConstraintName, - subject.ConstraintName); - - var nestedMember = new Property( - typeof(Constraint).GetProperty(nameof(subject.ExtendedProperties)), - context.CurrentNode); - - var nestedComparands = new Comparands - { - Subject = nestedMember.GetValue(subject), - Expectation = nestedMember.GetValue(expectation), - CompileTimeType = nestedMember.Type - }; - - parent.RecursivelyAssertEquality(nestedComparands, context.AsNestedMember(nestedMember)); - - if (selectedMembers.ContainsKey(nameof(expectation.RelatedTable))) - { - AssertionScope.Current - .ForCondition(subject.RelatedTable.TableName == expectation.RelatedTable.TableName) - .FailWith("Expected {context:constraint} to have a related table named {0}{reason}, but found {1}", - expectation.RelatedTable.TableName, subject.RelatedTable.TableName); - } - - if (selectedMembers.ContainsKey(nameof(expectation.AcceptRejectRule))) - { - AssertionScope.Current - .ForCondition(subject.AcceptRejectRule == expectation.AcceptRejectRule) - .FailWith( - "Expected {context:constraint} to have AcceptRejectRule.{0}{reason}, but found AcceptRejectRule.{1}", - expectation.AcceptRejectRule, subject.AcceptRejectRule); - } - - if (selectedMembers.ContainsKey(nameof(expectation.DeleteRule))) - { - AssertionScope.Current - .ForCondition(subject.DeleteRule == expectation.DeleteRule) - .FailWith("Expected {context:constraint} to have DeleteRule Rule.{0}{reason}, but found Rule.{1}", - expectation.DeleteRule, subject.DeleteRule); - } - - if (selectedMembers.ContainsKey(nameof(expectation.UpdateRule))) - { - AssertionScope.Current - .ForCondition(subject.UpdateRule == expectation.UpdateRule) - .FailWith("Expected {context:constraint} to have UpdateRule Rule.{0}{reason}, but found Rule.{1}", - expectation.UpdateRule, subject.UpdateRule); - } - - if (selectedMembers.ContainsKey(nameof(expectation.Columns))) - { - CompareConstraintColumns(subject.Columns, expectation.Columns); - } - - if (selectedMembers.ContainsKey(nameof(expectation.RelatedColumns))) - { - CompareConstraintColumns(subject.RelatedColumns, expectation.RelatedColumns); - } - } - - private static void CompareConstraintColumns(DataColumn[] subjectColumns, DataColumn[] expectationColumns) - { - var subjectColumnNames = new HashSet(subjectColumns.Select(col => col.ColumnName)); - var expectationColumnNames = new HashSet(expectationColumns.Select(col => col.ColumnName)); - - var missingColumnNames = expectationColumnNames.Except(subjectColumnNames).ToList(); - var extraColumnNames = subjectColumnNames.Except(expectationColumnNames).ToList(); - - var failureMessage = new StringBuilder(); - - if (missingColumnNames.Count > 0) - { - failureMessage.Append("Expected {context:constraint} to include "); - - if (missingColumnNames.Count == 1) - { - failureMessage.Append("column ").Append(missingColumnNames.Single()); - } - else - { - failureMessage.Append("columns ").Append(missingColumnNames.JoinUsingWritingStyle()); - } - - failureMessage - .Append("{reason}, but constraint does not include ") - .Append(missingColumnNames.Count == 1 - ? "that column. " - : "these columns. "); - } - - if (extraColumnNames.Count > 0) - { - failureMessage.Append("Did not expect {context:constraint} to include "); - - if (extraColumnNames.Count == 1) - { - failureMessage.Append("column ").Append(extraColumnNames.Single()); - } - else - { - failureMessage.Append("columns ").Append(extraColumnNames.JoinUsingWritingStyle()); - } - - failureMessage.Append("{reason}, but it does."); - } - - bool successful = failureMessage.Length == 0; - - AssertionScope.Current - .ForCondition(successful) - .FailWith(failureMessage.ToString()); - } - - private static IMember FindMatchFor(IMember selectedMemberInfo, INode currentNode, object subject, - IEquivalencyAssertionOptions config) - { - IEnumerable query = - from rule in config.MatchingRules - let match = rule.Match(selectedMemberInfo, subject, currentNode, config) - where match is not null - select match; - - return query.FirstOrDefault(); - } - - private static IEnumerable GetMembersFromExpectation(Comparands comparands, INode currentNode, - IEquivalencyAssertionOptions options) - { - IEnumerable members = Enumerable.Empty(); - - foreach (IMemberSelectionRule rule in options.SelectionRules) - { - // Within a ConstraintCollection, different types of Constraint are kept polymorphically. - // As such, the concept of "compile-time type" isn't meaningful, and we override this - // with the discovered type of the constraint at runtime. - members = rule.SelectMembers(currentNode, members, - new MemberSelectionContext(comparands.RuntimeType, comparands.RuntimeType, options)); - } - - return members; - } -} diff --git a/Src/FluentAssertions/Equivalency/Steps/DataColumnEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/DataColumnEquivalencyStep.cs deleted file mode 100644 index 0d0e80d72e..0000000000 --- a/Src/FluentAssertions/Equivalency/Steps/DataColumnEquivalencyStep.cs +++ /dev/null @@ -1,142 +0,0 @@ -using System.Collections.Generic; -using System.Data; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using FluentAssertions.Data; -using FluentAssertions.Execution; - -namespace FluentAssertions.Equivalency.Steps; - -public class DataColumnEquivalencyStep : EquivalencyStep -{ - [SuppressMessage("Style", "IDE0019:Use pattern matching", Justification = "The code is easier to read without it.")] - protected override EquivalencyResult OnHandle(Comparands comparands, IEquivalencyValidationContext context, - IEquivalencyValidator nestedValidator) - { - var subject = comparands.Subject as DataColumn; - var expectation = comparands.Expectation as DataColumn; - - if (expectation is null) - { - if (subject is not null) - { - AssertionScope.Current.FailWith("Expected {context:DataColumn} value to be null, but found {0}", subject); - } - } - else if (subject is null) - { - if (comparands.Subject is null) - { - AssertionScope.Current.FailWith("Expected {context:DataColumn} to be non-null, but found null"); - } - else - { - AssertionScope.Current.FailWith("Expected {context:DataColumn} to be of type {0}, but found {1} instead", - expectation.GetType(), comparands.Subject.GetType()); - } - } - else - { - CompareSubjectAndExpectationOfTypeDataColumn(comparands, context, nestedValidator, subject); - } - - return EquivalencyResult.AssertionCompleted; - } - - private static void CompareSubjectAndExpectationOfTypeDataColumn(Comparands comparands, - IEquivalencyValidationContext context, IEquivalencyValidator parent, DataColumn subject) - { - bool compareColumn = true; - - var dataSetConfig = context.Options as DataEquivalencyAssertionOptions; - var dataTableConfig = context.Options as DataEquivalencyAssertionOptions; - var dataColumnConfig = context.Options as DataEquivalencyAssertionOptions; - - if (dataSetConfig?.ShouldExcludeColumn(subject) == true - || dataTableConfig?.ShouldExcludeColumn(subject) == true - || dataColumnConfig?.ShouldExcludeColumn(subject) == true) - { - compareColumn = false; - } - - if (compareColumn) - { - foreach (IMember expectationMember in GetMembersFromExpectation(context.CurrentNode, comparands, context.Options)) - { - if (expectationMember.Name != nameof(subject.Table)) - { - CompareMember(expectationMember, comparands, parent, context); - } - } - } - } - - private static void CompareMember(IMember expectationMember, Comparands comparands, IEquivalencyValidator parent, - IEquivalencyValidationContext context) - { - IMember matchingMember = FindMatchFor(expectationMember, comparands.Subject, context); - - if (matchingMember is not null) - { - var nestedComparands = new Comparands - { - Subject = matchingMember.GetValue(comparands.Subject), - Expectation = expectationMember.GetValue(comparands.Expectation), - CompileTimeType = expectationMember.Type - }; - - if (context.AsNestedMember(expectationMember) is not null) - { - parent.RecursivelyAssertEquality(nestedComparands, context.AsNestedMember(expectationMember)); - } - } - } - - private static IMember FindMatchFor(IMember selectedMemberInfo, object subject, IEquivalencyValidationContext context) - { - IEnumerable query = - from rule in context.Options.MatchingRules - let match = rule.Match(selectedMemberInfo, subject, context.CurrentNode, context.Options) - where match is not null - select match; - - return query.FirstOrDefault(); - } - - // NOTE: This list of candidate members is duplicated in the XML documentation for the - // DataColumn.BeEquivalentTo extension method in DataColumnAssertions.cs. If this ever - // needs to change, keep them in sync. - private static readonly HashSet CandidateMembers = new() - { - nameof(DataColumn.AllowDBNull), - nameof(DataColumn.AutoIncrement), - nameof(DataColumn.AutoIncrementSeed), - nameof(DataColumn.AutoIncrementStep), - nameof(DataColumn.Caption), - nameof(DataColumn.ColumnName), - nameof(DataColumn.DataType), - nameof(DataColumn.DateTimeMode), - nameof(DataColumn.DefaultValue), - nameof(DataColumn.Expression), - nameof(DataColumn.ExtendedProperties), - nameof(DataColumn.MaxLength), - nameof(DataColumn.Namespace), - nameof(DataColumn.Prefix), - nameof(DataColumn.ReadOnly), - nameof(DataColumn.Unique), - }; - - private static IEnumerable GetMembersFromExpectation(INode currentNode, Comparands comparands, - IEquivalencyAssertionOptions config) - { - IEnumerable members = Enumerable.Empty(); - - foreach (IMemberSelectionRule rule in config.SelectionRules) - { - members = rule.SelectMembers(currentNode, members, - new MemberSelectionContext(comparands.CompileTimeType, comparands.RuntimeType, config)); - } - - return members.Where(member => CandidateMembers.Contains(member.Name)); - } -} diff --git a/Src/FluentAssertions/Equivalency/Steps/DataRelationEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/DataRelationEquivalencyStep.cs deleted file mode 100644 index 0ec9c92051..0000000000 --- a/Src/FluentAssertions/Equivalency/Steps/DataRelationEquivalencyStep.cs +++ /dev/null @@ -1,244 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using FluentAssertions.Execution; - -namespace FluentAssertions.Equivalency.Steps; - -public class DataRelationEquivalencyStep : EquivalencyStep -{ - [SuppressMessage("Style", "IDE0019:Use pattern matching", Justification = "The code is easier to read without it.")] - protected override EquivalencyResult OnHandle(Comparands comparands, IEquivalencyValidationContext context, - IEquivalencyValidator nestedValidator) - { - var subject = comparands.Subject as DataRelation; - var expectation = comparands.Expectation as DataRelation; - - if (expectation is null) - { - if (subject is not null) - { - AssertionScope.Current.FailWith("Expected {context:DataRelation} to be null, but found {0}", subject); - } - } - else if (subject is null) - { - if (comparands.Subject is null) - { - AssertionScope.Current.FailWith("Expected {context:DataRelation} value to be non-null, but found null"); - } - else - { - AssertionScope.Current.FailWith("Expected {context:DataRelation} of type {0}, but found {1} instead", - expectation.GetType(), comparands.Subject.GetType()); - } - } - else - { - var selectedMembers = GetMembersFromExpectation(context.CurrentNode, comparands, context.Options) - .ToDictionary(member => member.Name); - - CompareScalarProperties(subject, expectation, selectedMembers); - - CompareCollections(context, comparands, nestedValidator, context.Options, selectedMembers); - - CompareRelationConstraints(context, nestedValidator, subject, expectation, selectedMembers); - } - - return EquivalencyResult.AssertionCompleted; - } - - private static void CompareScalarProperties(DataRelation subject, DataRelation expectation, - Dictionary selectedMembers) - { - if (selectedMembers.ContainsKey(nameof(expectation.RelationName))) - { - AssertionScope.Current - .ForCondition(subject.RelationName == expectation.RelationName) - .FailWith("Expected {context:DataRelation} to have RelationName of {0}{reason}, but found {1}", - expectation.RelationName, subject.RelationName); - } - - if (selectedMembers.ContainsKey(nameof(expectation.Nested))) - { - AssertionScope.Current - .ForCondition(subject.Nested == expectation.Nested) - .FailWith("Expected {context:DataRelation} to have Nested value of {0}{reason}, but found {1}", - expectation.Nested, subject.Nested); - } - - // Special case: Compare name only - if (selectedMembers.ContainsKey(nameof(expectation.DataSet))) - { - AssertionScope.Current - .ForCondition(subject.DataSet?.DataSetName == expectation.DataSet?.DataSetName) - .FailWith("Expected containing DataSet of {context:DataRelation} to be {0}{reason}, but found {1}", - expectation.DataSet?.DataSetName ?? "", - subject.DataSet?.DataSetName ?? ""); - } - } - - private static void CompareCollections(IEquivalencyValidationContext context, Comparands comparands, - IEquivalencyValidator parent, - IEquivalencyAssertionOptions config, Dictionary selectedMembers) - { - if (selectedMembers.TryGetValue(nameof(DataRelation.ExtendedProperties), out IMember expectationMember)) - { - IMember matchingMember = FindMatchFor(expectationMember, context.CurrentNode, comparands.Subject, config); - - if (matchingMember is not null) - { - var nestedComparands = new Comparands - { - Subject = matchingMember.GetValue(comparands.Subject), - Expectation = expectationMember.GetValue(comparands.Expectation), - CompileTimeType = expectationMember.Type - }; - - parent.RecursivelyAssertEquality(nestedComparands, context.AsNestedMember(expectationMember)); - } - } - } - - private static void CompareRelationConstraints(IEquivalencyValidationContext context, IEquivalencyValidator parent, - DataRelation subject, DataRelation expectation, - Dictionary selectedMembers) - { - CompareDataRelationConstraints( - parent, context, subject, expectation, selectedMembers, - "Child", - selectedMembers.ContainsKey(nameof(DataRelation.ChildTable)), - selectedMembers.ContainsKey(nameof(DataRelation.ChildColumns)), - selectedMembers.ContainsKey(nameof(DataRelation.ChildKeyConstraint)), - r => r.ChildColumns, - r => r.ChildTable); - - CompareDataRelationConstraints( - parent, context, subject, expectation, selectedMembers, - "Parent", - selectedMembers.ContainsKey(nameof(DataRelation.ParentTable)), - selectedMembers.ContainsKey(nameof(DataRelation.ParentColumns)), - selectedMembers.ContainsKey(nameof(DataRelation.ParentKeyConstraint)), - r => r.ParentColumns, - r => r.ParentTable); - } - - private static void CompareDataRelationConstraints( - IEquivalencyValidator parent, IEquivalencyValidationContext context, - DataRelation subject, DataRelation expectation, Dictionary selectedMembers, - string relationDirection, - bool compareTable, bool compareColumns, bool compareKeyConstraint, - Func getColumns, - Func getOtherTable) - { - if (compareColumns) - { - CompareDataRelationColumns(subject, expectation, getColumns); - } - - if (compareTable) - { - CompareDataRelationTable(subject, expectation, getOtherTable); - } - - if (compareKeyConstraint) - { - CompareDataRelationKeyConstraint(subject, expectation, parent, context, selectedMembers, relationDirection); - } - } - - private static void CompareDataRelationColumns(DataRelation subject, DataRelation expectation, - Func getColumns) - { - DataColumn[] subjectColumns = getColumns(subject); - DataColumn[] expectationColumns = getColumns(expectation); - - // These column references are in different tables in different data sets that _should_ be equivalent - // to one another. - bool success = AssertionScope.Current - .ForCondition(subjectColumns.Length == expectationColumns.Length) - .FailWith("Expected {context:DataRelation} to reference {0} column(s){reason}, but found {subjectColumns.Length}", - expectationColumns.Length, subjectColumns.Length); - - if (success) - { - for (int i = 0; i < expectationColumns.Length; i++) - { - DataColumn subjectColumn = subjectColumns[i]; - DataColumn expectationColumn = expectationColumns[i]; - - bool columnsAreEquivalent = - subjectColumn.Table.TableName == expectationColumn.Table.TableName && - subjectColumn.ColumnName == expectationColumn.ColumnName; - - AssertionScope.Current - .ForCondition(columnsAreEquivalent) - .FailWith( - "Expected {context:DataRelation} to reference column {0} in table {1}{reason}, but found a reference to {2} in table {3} instead", - expectationColumn.ColumnName, - expectationColumn.Table.TableName, - subjectColumn.ColumnName, - subjectColumn.Table.TableName); - } - } - } - - private static void CompareDataRelationTable(DataRelation subject, DataRelation expectation, - Func getOtherTable) - { - DataTable subjectTable = getOtherTable(subject); - DataTable expectationTable = getOtherTable(expectation); - - AssertionScope.Current - .ForCondition(subjectTable.TableName == expectationTable.TableName) - .FailWith("Expected {context:DataRelation} to reference a table named {0}{reason}, but found {1} instead", - expectationTable.TableName, subjectTable.TableName); - } - - private static void CompareDataRelationKeyConstraint(DataRelation subject, DataRelation expectation, - IEquivalencyValidator parent, IEquivalencyValidationContext context, Dictionary selectedMembers, - string relationDirection) - { - if (selectedMembers.TryGetValue(relationDirection + "KeyConstraint", out IMember expectationMember)) - { - IMember subjectMember = FindMatchFor(expectationMember, context.CurrentNode, subject, context.Options); - - var newComparands = new Comparands - { - Subject = subjectMember.GetValue(subject), - Expectation = expectationMember.GetValue(expectation), - CompileTimeType = expectationMember.Type - }; - - parent.RecursivelyAssertEquality(newComparands, context.AsNestedMember(expectationMember)); - } - } - - private static IMember FindMatchFor(IMember selectedMemberInfo, INode currentNode, object subject, - IEquivalencyAssertionOptions config) - { - IEnumerable query = - from rule in config.MatchingRules - let match = rule.Match(selectedMemberInfo, subject, currentNode, config) - where match is not null - select match; - - return query.FirstOrDefault(); - } - - private static IEnumerable GetMembersFromExpectation(INode currentNode, Comparands comparands, - IEquivalencyAssertionOptions config) - { - IEnumerable members = Enumerable.Empty(); - - foreach (IMemberSelectionRule rule in config.SelectionRules) - { - members = rule.SelectMembers(currentNode, members, - new MemberSelectionContext(comparands.CompileTimeType, comparands.RuntimeType, config)); - } - - return members; - } -} diff --git a/Src/FluentAssertions/Equivalency/Steps/DataRowCollectionEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/DataRowCollectionEquivalencyStep.cs deleted file mode 100644 index 62d434b11d..0000000000 --- a/Src/FluentAssertions/Equivalency/Steps/DataRowCollectionEquivalencyStep.cs +++ /dev/null @@ -1,244 +0,0 @@ -using System; -using System.Data; -using System.Linq; -using FluentAssertions.Data; -using FluentAssertions.Execution; - -namespace FluentAssertions.Equivalency.Steps; - -public class DataRowCollectionEquivalencyStep : EquivalencyStep -{ - protected override EquivalencyResult OnHandle(Comparands comparands, IEquivalencyValidationContext context, - IEquivalencyValidator nestedValidator) - { - if (comparands.Subject is not DataRowCollection) - { - AssertionScope.Current - .FailWith("Expected {context:value} to be of type DataRowCollection, but found {0}", - comparands.Subject.GetType()); - } - else - { - RowMatchMode rowMatchMode = context.Options switch - { - DataEquivalencyAssertionOptions dataSetConfig => dataSetConfig.RowMatchMode, - DataEquivalencyAssertionOptions dataTableConfig => dataTableConfig.RowMatchMode, - _ => RowMatchMode.Index - }; - - var subject = (DataRowCollection)comparands.Subject; - var expectation = (DataRowCollection)comparands.Expectation; - - bool success = AssertionScope.Current - .ForCondition(subject.Count == expectation.Count) - .FailWith("Expected {context:DataRowCollection} to contain {0} row(s){reason}, but found {1}", - expectation.Count, subject.Count); - - if (success) - { - switch (rowMatchMode) - { - case RowMatchMode.Index: - MatchRowsByIndexAndCompare(context, nestedValidator, subject, expectation); - break; - - case RowMatchMode.PrimaryKey: - MatchRowsByPrimaryKeyAndCompare(nestedValidator, context, subject, expectation); - break; - - default: - AssertionScope.Current.FailWith( - "Unknown RowMatchMode {0} when trying to compare {context:DataRowCollection}", rowMatchMode); - - break; - } - } - } - - return EquivalencyResult.AssertionCompleted; - } - - private static void MatchRowsByIndexAndCompare(IEquivalencyValidationContext context, IEquivalencyValidator parent, - DataRowCollection subject, DataRowCollection expectation) - { - for (int index = 0; index < expectation.Count; index++) - { - IEquivalencyValidationContext nestedContext = context.AsCollectionItem(index); - parent.RecursivelyAssertEquality(new Comparands(subject[index], expectation[index], typeof(DataRow)), nestedContext); - } - } - - private static void MatchRowsByPrimaryKeyAndCompare(IEquivalencyValidator parent, IEquivalencyValidationContext context, - DataRowCollection subject, DataRowCollection expectation) - { - Type[] subjectPrimaryKeyTypes = null; - Type[] expectationPrimaryKeyTypes = null; - - if (subject.Count > 0) - { - subjectPrimaryKeyTypes = GatherPrimaryKeyColumnTypes(subject[0].Table, "subject"); - } - - if (expectation.Count > 0) - { - expectationPrimaryKeyTypes = GatherPrimaryKeyColumnTypes(expectation[0].Table, "expectation"); - } - - bool matchingTypes = ComparePrimaryKeyTypes(subjectPrimaryKeyTypes, expectationPrimaryKeyTypes); - - if (matchingTypes) - { - GatherRowsByPrimaryKeyAndCompareData(parent, context, subject, expectation); - } - } - - private static Type[] GatherPrimaryKeyColumnTypes(DataTable table, string comparisonTerm) - { - Type[] primaryKeyTypes = null; - - if (table.PrimaryKey is null or { Length: 0 }) - { - AssertionScope.Current - .FailWith( - "Table {0} containing {1} {context:DataRowCollection} does not have a primary key. RowMatchMode.PrimaryKey cannot be applied.", - table.TableName, comparisonTerm); - } - else - { - primaryKeyTypes = new Type[table.PrimaryKey.Length]; - - for (int i = 0; i < table.PrimaryKey.Length; i++) - { - primaryKeyTypes[i] = table.PrimaryKey[i].DataType; - } - } - - return primaryKeyTypes; - } - - private static bool ComparePrimaryKeyTypes(Type[] subjectPrimaryKeyTypes, Type[] expectationPrimaryKeyTypes) - { - bool matchingTypes = false; - - if (subjectPrimaryKeyTypes is not null && expectationPrimaryKeyTypes is not null) - { - matchingTypes = subjectPrimaryKeyTypes.Length == expectationPrimaryKeyTypes.Length; - - for (int i = 0; matchingTypes && i < subjectPrimaryKeyTypes.Length; i++) - { - if (subjectPrimaryKeyTypes[i] != expectationPrimaryKeyTypes[i]) - { - matchingTypes = false; - } - } - - if (!matchingTypes) - { - AssertionScope.Current - .FailWith( - "Subject and expectation primary keys of table containing {context:DataRowCollection} do not have the same schema and cannot be compared. RowMatchMode.PrimaryKey cannot be applied."); - } - } - - return matchingTypes; - } - - private static void GatherRowsByPrimaryKeyAndCompareData(IEquivalencyValidator parent, IEquivalencyValidationContext context, - DataRowCollection subject, DataRowCollection expectation) - { - var expectationRowByKey = expectation.Cast() - .ToDictionary(row => ExtractPrimaryKey(row)); - - foreach (DataRow subjectRow in subject.Cast()) - { - CompoundKey key = ExtractPrimaryKey(subjectRow); - - if (!expectationRowByKey.TryGetValue(key, out DataRow expectationRow)) - { - AssertionScope.Current - .FailWith("Found unexpected row in {context:DataRowCollection} with key {0}", key); - } - else - { - expectationRowByKey.Remove(key); - - IEquivalencyValidationContext nestedContext = context.AsCollectionItem(key.ToString()); - parent.RecursivelyAssertEquality(new Comparands(subjectRow, expectationRow, typeof(DataRow)), nestedContext); - } - } - - if (expectationRowByKey.Count > 0) - { - if (expectationRowByKey.Count > 1) - { - AssertionScope.Current - .FailWith("{0} rows were expected in {context:DataRowCollection} and not found", expectationRowByKey.Count); - } - else - { - AssertionScope.Current - .FailWith( - "Expected to find a row with key {0} in {context:DataRowCollection}{reason}, but no such row was found", - expectationRowByKey.Keys.Single()); - } - } - } - - private sealed class CompoundKey : IEquatable - { - private readonly object[] values; - - public CompoundKey(params object[] values) - { - this.values = values; - } - - public bool Equals(CompoundKey other) - { - if (other is null) - { - return false; - } - - if (values.Length != other.values.Length) - { - return false; - } - - return values.SequenceEqual(other.values); - } - - public override bool Equals(object obj) => Equals(obj as CompoundKey); - - public override int GetHashCode() - { - int hash = 0; - - foreach (var value in values) - { - hash = hash * 389 ^ value.GetHashCode(); - } - - return hash; - } - - public override string ToString() - { - return "{ " + string.Join(", ", values) + " }"; - } - } - - private static CompoundKey ExtractPrimaryKey(DataRow row) - { - DataColumn[] primaryKey = row.Table.PrimaryKey; - - var values = new object[primaryKey.Length]; - - for (int i = 0; i < values.Length; i++) - { - values[i] = row[primaryKey[i]]; - } - - return new CompoundKey(values); - } -} diff --git a/Src/FluentAssertions/Equivalency/Steps/DataRowEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/DataRowEquivalencyStep.cs deleted file mode 100644 index 38240907a8..0000000000 --- a/Src/FluentAssertions/Equivalency/Steps/DataRowEquivalencyStep.cs +++ /dev/null @@ -1,220 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Data; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using FluentAssertions.Data; -using FluentAssertions.Execution; - -namespace FluentAssertions.Equivalency.Steps; - -public class DataRowEquivalencyStep : EquivalencyStep -{ - [SuppressMessage("Style", "IDE0019:Use pattern matching", Justification = "The code is easier to read without it.")] - protected override EquivalencyResult OnHandle(Comparands comparands, IEquivalencyValidationContext context, - IEquivalencyValidator nestedValidator) - { - var subject = comparands.Subject as DataRow; - var expectation = comparands.Expectation as DataRow; - - if (expectation is null) - { - if (subject is not null) - { - AssertionScope.Current.FailWith("Expected {context:DataRow} value to be null, but found {0}", subject); - } - } - else if (subject is null) - { - if (comparands.Subject is null) - { - AssertionScope.Current.FailWith("Expected {context:DataRow} to be non-null, but found null"); - } - else - { - AssertionScope.Current.FailWith("Expected {context:DataRow} to be of type {0}, but found {1} instead", - expectation.GetType(), comparands.Subject.GetType()); - } - } - else - { - var dataSetConfig = context.Options as DataEquivalencyAssertionOptions; - var dataTableConfig = context.Options as DataEquivalencyAssertionOptions; - var dataRowConfig = context.Options as DataEquivalencyAssertionOptions; - - if (dataSetConfig?.AllowMismatchedTypes != true - && dataTableConfig?.AllowMismatchedTypes != true - && dataRowConfig?.AllowMismatchedTypes != true) - { - AssertionScope.Current - .ForCondition(subject.GetType() == expectation.GetType()) - .FailWith("Expected {context:DataRow} to be of type {0}{reason}, but found {1}", - expectation.GetType(), subject.GetType()); - } - - SelectedDataRowMembers selectedMembers = - GetMembersFromExpectation(comparands, context.CurrentNode, context.Options); - - CompareScalarProperties(subject, expectation, selectedMembers); - - CompareFieldValues(context, nestedValidator, subject, expectation, dataSetConfig, dataTableConfig, - dataRowConfig); - } - - return EquivalencyResult.AssertionCompleted; - } - - private static void CompareScalarProperties(DataRow subject, DataRow expectation, SelectedDataRowMembers selectedMembers) - { - // Note: The members here are listed in the XML documentation for the DataRow.BeEquivalentTo extension - // method in DataRowAssertions.cs. If this ever needs to change, keep them in sync. - if (selectedMembers.HasErrors) - { - AssertionScope.Current - .ForCondition(subject.HasErrors == expectation.HasErrors) - .FailWith("Expected {context:DataRow} to have HasErrors value of {0}{reason}, but found {1} instead", - expectation.HasErrors, subject.HasErrors); - } - - if (selectedMembers.RowState) - { - AssertionScope.Current - .ForCondition(subject.RowState == expectation.RowState) - .FailWith("Expected {context:DataRow} to have RowState value of {0}{reason}, but found {1} instead", - expectation.RowState, subject.RowState); - } - } - - [SuppressMessage("Maintainability", "AV1561:Signature contains too many parameters", Justification = "Needs to be refactored")] - [SuppressMessage("Design", "MA0051:Method is too long", Justification = "Needs to be refactored")] - private static void CompareFieldValues(IEquivalencyValidationContext context, IEquivalencyValidator parent, - DataRow subject, DataRow expectation, DataEquivalencyAssertionOptions dataSetConfig, - DataEquivalencyAssertionOptions dataTableConfig, DataEquivalencyAssertionOptions dataRowConfig) - { - IEnumerable expectationColumnNames = expectation.Table.Columns.Cast() - .Select(col => col.ColumnName); - - IEnumerable subjectColumnNames = subject.Table.Columns.Cast() - .Select(col => col.ColumnName); - - bool ignoreUnmatchedColumns = - dataSetConfig?.IgnoreUnmatchedColumns == true || - dataTableConfig?.IgnoreUnmatchedColumns == true || - dataRowConfig?.IgnoreUnmatchedColumns == true; - - DataRowVersion subjectVersion = - subject.RowState == DataRowState.Deleted - ? DataRowVersion.Original - : DataRowVersion.Current; - - DataRowVersion expectationVersion = - expectation.RowState == DataRowState.Deleted - ? DataRowVersion.Original - : DataRowVersion.Current; - - bool compareOriginalVersions = - subject.RowState == DataRowState.Modified && expectation.RowState == DataRowState.Modified; - - if (dataSetConfig?.ExcludeOriginalData == true - || dataTableConfig?.ExcludeOriginalData == true - || dataRowConfig?.ExcludeOriginalData == true) - { - compareOriginalVersions = false; - } - - foreach (var columnName in expectationColumnNames.Union(subjectColumnNames)) - { - DataColumn expectationColumn = expectation.Table.Columns[columnName]; - DataColumn subjectColumn = subject.Table.Columns[columnName]; - - if (subjectColumn is not null - && (dataSetConfig?.ShouldExcludeColumn(subjectColumn) == true - || dataTableConfig?.ShouldExcludeColumn(subjectColumn) == true - || dataRowConfig?.ShouldExcludeColumn(subjectColumn) == true)) - { - continue; - } - - if (!ignoreUnmatchedColumns) - { - AssertionScope.Current - .ForCondition(subjectColumn is not null) - .FailWith("Expected {context:DataRow} to have column {0}{reason}, but found none", columnName); - - AssertionScope.Current - .ForCondition(expectationColumn is not null) - .FailWith("Found unexpected column {0} in {context:DataRow}", columnName); - } - - if (subjectColumn is not null && expectationColumn is not null) - { - CompareFieldValue(context, parent, subject, expectation, subjectColumn, subjectVersion, expectationColumn, - expectationVersion); - - if (compareOriginalVersions) - { - CompareFieldValue(context, parent, subject, expectation, subjectColumn, DataRowVersion.Original, - expectationColumn, DataRowVersion.Original); - } - } - } - } - - private static void CompareFieldValue(IEquivalencyValidationContext context, IEquivalencyValidator parent, DataRow subject, - DataRow expectation, DataColumn subjectColumn, DataRowVersion subjectVersion, DataColumn expectationColumn, - DataRowVersion expectationVersion) - { - IEquivalencyValidationContext nestedContext = context.AsCollectionItem( - subjectVersion == DataRowVersion.Current - ? subjectColumn.ColumnName - : $"{subjectColumn.ColumnName}, DataRowVersion.Original"); - - if (nestedContext is not null) - { - parent.RecursivelyAssertEquality( - new Comparands(subject[subjectColumn, subjectVersion], expectation[expectationColumn, expectationVersion], - typeof(object)), - nestedContext); - } - } - - private sealed class SelectedDataRowMembers - { - public bool HasErrors { get; init; } - - public bool RowState { get; init; } - } - - private static readonly ConcurrentDictionary<(Type CompileTimeType, Type RuntimeType, IEquivalencyAssertionOptions Config), - SelectedDataRowMembers> SelectedMembersCache = new(); - - private static SelectedDataRowMembers GetMembersFromExpectation(Comparands comparands, INode currentNode, - IEquivalencyAssertionOptions config) - { - var cacheKey = (comparands.CompileTimeType, comparands.RuntimeType, config); - - if (!SelectedMembersCache.TryGetValue(cacheKey, out SelectedDataRowMembers selectedDataRowMembers)) - { - IEnumerable members = Enumerable.Empty(); - - foreach (IMemberSelectionRule rule in config.SelectionRules) - { - members = rule.SelectMembers(currentNode, members, - new MemberSelectionContext(comparands.CompileTimeType, comparands.RuntimeType, config)); - } - - IMember[] selectedMembers = members.ToArray(); - - selectedDataRowMembers = new SelectedDataRowMembers - { - HasErrors = selectedMembers.Any(m => m.Name == nameof(DataRow.HasErrors)), - RowState = selectedMembers.Any(m => m.Name == nameof(DataRow.RowState)) - }; - - SelectedMembersCache.TryAdd(cacheKey, selectedDataRowMembers); - } - - return selectedDataRowMembers; - } -} diff --git a/Src/FluentAssertions/Equivalency/Steps/DataSetEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/DataSetEquivalencyStep.cs deleted file mode 100644 index ebbf3525b3..0000000000 --- a/Src/FluentAssertions/Equivalency/Steps/DataSetEquivalencyStep.cs +++ /dev/null @@ -1,270 +0,0 @@ -using System.Collections.Generic; -using System.Data; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using FluentAssertions.Data; -using FluentAssertions.Execution; - -namespace FluentAssertions.Equivalency.Steps; - -public class DataSetEquivalencyStep : EquivalencyStep -{ - protected override EquivalencyResult OnHandle(Comparands comparands, IEquivalencyValidationContext context, - IEquivalencyValidator nestedValidator) - { - var subject = comparands.Subject as DataSet; - - if (comparands.Expectation is not DataSet expectation) - { - if (subject is not null) - { - AssertionScope.Current.FailWith("Expected {context:DataSet} value to be null, but found {0}", subject); - } - } - else if (subject is null) - { - if (comparands.Subject is null) - { - AssertionScope.Current.FailWith("Expected {context:DataSet} to be non-null, but found null"); - } - else - { - AssertionScope.Current.FailWith("Expected {context:DataSet} to be of type {0}, but found {1} instead", - expectation.GetType(), comparands.Subject.GetType()); - } - } - else - { - var dataConfig = context.Options as DataEquivalencyAssertionOptions; - - if (dataConfig?.AllowMismatchedTypes != true) - { - AssertionScope.Current - .ForCondition(subject.GetType() == expectation.GetType()) - .FailWith("Expected {context:DataSet} to be of type {0}{reason}, but found {1}", expectation.GetType(), - subject.GetType()); - } - - var selectedMembers = GetMembersFromExpectation(comparands, context.CurrentNode, context.Options) - .ToDictionary(member => member.Name); - - CompareScalarProperties(subject, expectation, selectedMembers); - - CompareCollections(context, nestedValidator, context.Options, subject, expectation, dataConfig, selectedMembers); - } - - return EquivalencyResult.AssertionCompleted; - } - - [SuppressMessage("Design", "MA0051:Method is too long")] - private static void CompareScalarProperties(DataSet subject, DataSet expectation, Dictionary selectedMembers) - { - // Note: The members here are listed in the XML documentation for the DataSet.BeEquivalentTo extension - // method in DataSetAssertions.cs. If this ever needs to change, keep them in sync. - if (selectedMembers.ContainsKey(nameof(expectation.DataSetName))) - { - AssertionScope.Current - .ForCondition(subject.DataSetName == expectation.DataSetName) - .FailWith("Expected {context:DataSet} to have DataSetName {0}{reason}, but found {1} instead", - expectation.DataSetName, subject.DataSetName); - } - - if (selectedMembers.ContainsKey(nameof(expectation.CaseSensitive))) - { - AssertionScope.Current - .ForCondition(subject.CaseSensitive == expectation.CaseSensitive) - .FailWith("Expected {context:DataSet} to have CaseSensitive value of {0}{reason}, but found {1} instead", - expectation.CaseSensitive, subject.CaseSensitive); - } - - if (selectedMembers.ContainsKey(nameof(expectation.EnforceConstraints))) - { - AssertionScope.Current - .ForCondition(subject.EnforceConstraints == expectation.EnforceConstraints) - .FailWith("Expected {context:DataSet} to have EnforceConstraints value of {0}{reason}, but found {1} instead", - expectation.EnforceConstraints, subject.EnforceConstraints); - } - - if (selectedMembers.ContainsKey(nameof(expectation.HasErrors))) - { - AssertionScope.Current - .ForCondition(subject.HasErrors == expectation.HasErrors) - .FailWith("Expected {context:DataSet} to have HasErrors value of {0}{reason}, but found {1} instead", - expectation.HasErrors, subject.HasErrors); - } - - if (selectedMembers.ContainsKey(nameof(expectation.Locale))) - { - AssertionScope.Current - .ForCondition(Equals(subject.Locale, expectation.Locale)) - .FailWith("Expected {context:DataSet} to have Locale value of {0}{reason}, but found {1} instead", - expectation.Locale, subject.Locale); - } - - if (selectedMembers.ContainsKey(nameof(expectation.Namespace))) - { - AssertionScope.Current - .ForCondition(subject.Namespace == expectation.Namespace) - .FailWith("Expected {context:DataSet} to have Namespace value of {0}{reason}, but found {1} instead", - expectation.Namespace, subject.Namespace); - } - - if (selectedMembers.ContainsKey(nameof(expectation.Prefix))) - { - AssertionScope.Current - .ForCondition(subject.Prefix == expectation.Prefix) - .FailWith("Expected {context:DataSet} to have Prefix value of {0}{reason}, but found {1} instead", - expectation.Prefix, subject.Prefix); - } - - if (selectedMembers.ContainsKey(nameof(expectation.RemotingFormat))) - { - AssertionScope.Current - .ForCondition(subject.RemotingFormat == expectation.RemotingFormat) - .FailWith("Expected {context:DataSet} to have RemotingFormat value of {0}{reason}, but found {1} instead", - expectation.RemotingFormat, subject.RemotingFormat); - } - - if (selectedMembers.ContainsKey(nameof(expectation.SchemaSerializationMode))) - { - AssertionScope.Current - .ForCondition(subject.SchemaSerializationMode == expectation.SchemaSerializationMode) - .FailWith( - "Expected {context:DataSet} to have SchemaSerializationMode value of {0}{reason}, but found {1} instead", - expectation.SchemaSerializationMode, subject.SchemaSerializationMode); - } - } - - private static void CompareCollections(IEquivalencyValidationContext context, IEquivalencyValidator parent, - IEquivalencyAssertionOptions config, DataSet subject, DataSet expectation, - DataEquivalencyAssertionOptions dataConfig, Dictionary selectedMembers) - { - // Note: The collections here are listed in the XML documentation for the DataSet.BeEquivalentTo extension - // method in DataSetAssertions.cs. If this ever needs to change, keep them in sync. - CompareExtendedProperties(new Comparands(subject, expectation, typeof(DataSet)), context, parent, config, - selectedMembers); - - CompareTables(context, parent, subject, expectation, dataConfig, selectedMembers); - } - - private static void CompareExtendedProperties(Comparands comparands, IEquivalencyValidationContext context, - IEquivalencyValidator parent, IEquivalencyAssertionOptions config, Dictionary selectedMembers) - { - foreach (var collectionName in new[] { nameof(DataSet.ExtendedProperties), nameof(DataSet.Relations) }) - { - if (selectedMembers.TryGetValue(collectionName, out IMember expectationMember)) - { - IMember matchingMember = FindMatchFor(expectationMember, comparands.Subject, context.CurrentNode, config); - - if (matchingMember is not null) - { - var nestedComparands = new Comparands - { - Subject = matchingMember.GetValue(comparands.Subject), - Expectation = expectationMember.GetValue(comparands.Expectation), - CompileTimeType = expectationMember.Type - }; - - IEquivalencyValidationContext nestedContext = context.AsNestedMember(expectationMember); - parent.RecursivelyAssertEquality(nestedComparands, nestedContext); - } - } - } - } - - private static void CompareTables(IEquivalencyValidationContext context, IEquivalencyValidator parent, DataSet subject, - DataSet expectation, DataEquivalencyAssertionOptions dataConfig, Dictionary selectedMembers) - { - if (selectedMembers.ContainsKey(nameof(expectation.Tables))) - { - bool success = AssertionScope.Current - .ForCondition(subject.Tables.Count == expectation.Tables.Count) - .FailWith("Expected {context:DataSet} to contain {0}, but found {1} table(s)", expectation.Tables.Count, - subject.Tables.Count); - - if (!success) - { - return; - } - - if (dataConfig is not null) - { - bool excludeCaseSensitive = !selectedMembers.ContainsKey(nameof(DataSet.CaseSensitive)); - bool excludeLocale = !selectedMembers.ContainsKey(nameof(DataSet.Locale)); - - if (excludeCaseSensitive || excludeLocale) - { - dataConfig.Excluding(memberInfo => - memberInfo.DeclaringType == typeof(DataTable) && - ( - (excludeCaseSensitive && memberInfo.Name == nameof(DataTable.CaseSensitive)) - || - (excludeLocale && memberInfo.Name == nameof(DataTable.Locale)))); - } - } - - IEnumerable expectationTableNames = expectation.Tables.Cast() - .Select(table => table.TableName); - - IEnumerable subjectTableNames = subject.Tables.Cast() - .Select(table => table.TableName); - - foreach (string tableName in expectationTableNames.Union(subjectTableNames)) - { - if (dataConfig?.ExcludeTableNames.Contains(tableName) == true) - { - continue; - } - - CompareTable(context, parent, subject, expectation, tableName); - } - } - } - - private static void CompareTable(IEquivalencyValidationContext context, IEquivalencyValidator parent, DataSet subject, - DataSet expectation, string tableName) - { - DataTable expectationTable = expectation.Tables[tableName]; - DataTable subjectTable = subject.Tables[tableName]; - - bool success = AssertionScope.Current - .ForCondition(subjectTable is not null) - .FailWith("Expected {context:DataSet} to contain table {0}{reason}, but did not find it", tableName) - .Then - .ForCondition(expectationTable is not null) - .FailWith("Found unexpected table {0} in DataSet", tableName); - - if (success) - { - IEquivalencyValidationContext nestedContext = context.AsCollectionItem(tableName); - parent.RecursivelyAssertEquality(new Comparands(subjectTable, expectationTable, typeof(DataTable)), nestedContext); - } - } - - private static IMember FindMatchFor(IMember selectedMemberInfo, object subject, INode currentNode, - IEquivalencyAssertionOptions options) - { - IEnumerable query = - from rule in options.MatchingRules - let match = rule.Match(selectedMemberInfo, subject, currentNode, options) - where match is not null - select match; - - return query.FirstOrDefault(); - } - - private static IEnumerable GetMembersFromExpectation(Comparands comparands, - INode contextCurrentNode, - IEquivalencyAssertionOptions options) - { - IEnumerable members = Enumerable.Empty(); - - foreach (IMemberSelectionRule rule in options.SelectionRules) - { - members = rule.SelectMembers(contextCurrentNode, members, - new MemberSelectionContext(comparands.CompileTimeType, comparands.RuntimeType, options)); - } - - return members; - } -} diff --git a/Src/FluentAssertions/Equivalency/Steps/DataTableEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/DataTableEquivalencyStep.cs deleted file mode 100644 index 1fcf94909e..0000000000 --- a/Src/FluentAssertions/Equivalency/Steps/DataTableEquivalencyStep.cs +++ /dev/null @@ -1,198 +0,0 @@ -using System.Collections.Generic; -using System.Data; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using FluentAssertions.Data; -using FluentAssertions.Execution; - -namespace FluentAssertions.Equivalency.Steps; - -public class DataTableEquivalencyStep : EquivalencyStep -{ - [SuppressMessage("Style", "IDE0019:Use pattern matching", Justification = "The code is easier to read without it.")] - protected override EquivalencyResult OnHandle(Comparands comparands, IEquivalencyValidationContext context, - IEquivalencyValidator nestedValidator) - { - var subject = comparands.Subject as DataTable; - var expectation = comparands.Expectation as DataTable; - - if (expectation is null) - { - if (subject is not null) - { - AssertionScope.Current.FailWith("Expected {context:DataTable} value to be null, but found {0}", subject); - } - } - else if (subject is null) - { - if (comparands.Subject is null) - { - AssertionScope.Current.FailWith("Expected {context:DataTable} to be non-null, but found null"); - } - else - { - AssertionScope.Current.FailWith("Expected {context:DataTable} to be of type {0}, but found {1} instead", - expectation.GetType(), comparands.Subject.GetType()); - } - } - else - { - var dataSetConfig = context.Options as DataEquivalencyAssertionOptions; - var dataTableConfig = context.Options as DataEquivalencyAssertionOptions; - - if (dataSetConfig?.AllowMismatchedTypes != true - && dataTableConfig?.AllowMismatchedTypes != true) - { - AssertionScope.Current - .ForCondition(subject.GetType() == expectation.GetType()) - .FailWith("Expected {context:DataTable} to be of type {0}{reason}, but found {1}", expectation.GetType(), - subject.GetType()); - } - - var selectedMembers = GetMembersFromExpectation(context.CurrentNode, comparands, context.Options) - .ToDictionary(member => member.Name); - - CompareScalarProperties(subject, expectation, selectedMembers); - - CompareCollections(comparands, context, nestedValidator, context.Options, selectedMembers); - } - - return EquivalencyResult.AssertionCompleted; - } - - [SuppressMessage("Design", "MA0051:Method is too long")] - private static void CompareScalarProperties(DataTable subject, DataTable expectation, - Dictionary selectedMembers) - { - // Note: The members here are listed in the XML documentation for the DataTable.BeEquivalentTo extension - // method in DataTableAssertions.cs. If this ever needs to change, keep them in sync. - if (selectedMembers.ContainsKey(nameof(expectation.TableName))) - { - AssertionScope.Current - .ForCondition(subject.TableName == expectation.TableName) - .FailWith("Expected {context:DataTable} to have TableName {0}{reason}, but found {1} instead", - expectation.TableName, subject.TableName); - } - - if (selectedMembers.ContainsKey(nameof(expectation.CaseSensitive))) - { - AssertionScope.Current - .ForCondition(subject.CaseSensitive == expectation.CaseSensitive) - .FailWith("Expected {context:DataTable} to have CaseSensitive value of {0}{reason}, but found {1} instead", - expectation.CaseSensitive, subject.CaseSensitive); - } - - if (selectedMembers.ContainsKey(nameof(expectation.DisplayExpression))) - { - AssertionScope.Current - .ForCondition(subject.DisplayExpression == expectation.DisplayExpression) - .FailWith("Expected {context:DataTable} to have DisplayExpression value of {0}{reason}, but found {1} instead", - expectation.DisplayExpression, subject.DisplayExpression); - } - - if (selectedMembers.ContainsKey(nameof(expectation.HasErrors))) - { - AssertionScope.Current - .ForCondition(subject.HasErrors == expectation.HasErrors) - .FailWith("Expected {context:DataTable} to have HasErrors value of {0}{reason}, but found {1} instead", - expectation.HasErrors, subject.HasErrors); - } - - if (selectedMembers.ContainsKey(nameof(expectation.Locale))) - { - AssertionScope.Current - .ForCondition(Equals(subject.Locale, expectation.Locale)) - .FailWith("Expected {context:DataTable} to have Locale value of {0}{reason}, but found {1} instead", - expectation.Locale, subject.Locale); - } - - if (selectedMembers.ContainsKey(nameof(expectation.Namespace))) - { - AssertionScope.Current - .ForCondition(subject.Namespace == expectation.Namespace) - .FailWith("Expected {context:DataTable} to have Namespace value of {0}{reason}, but found {1} instead", - expectation.Namespace, subject.Namespace); - } - - if (selectedMembers.ContainsKey(nameof(expectation.Prefix))) - { - AssertionScope.Current - .ForCondition(subject.Prefix == expectation.Prefix) - .FailWith("Expected {context:DataTable} to have Prefix value of {0}{reason}, but found {1} instead", - expectation.Prefix, subject.Prefix); - } - - if (selectedMembers.ContainsKey(nameof(expectation.RemotingFormat))) - { - AssertionScope.Current - .ForCondition(subject.RemotingFormat == expectation.RemotingFormat) - .FailWith("Expected {context:DataTable} to have RemotingFormat value of {0}{reason}, but found {1} instead", - expectation.RemotingFormat, subject.RemotingFormat); - } - } - - private static void CompareCollections(Comparands comparands, IEquivalencyValidationContext context, - IEquivalencyValidator parent, IEquivalencyAssertionOptions config, Dictionary selectedMembers) - { - // Note: The collections here are listed in the XML documentation for the DataTable.BeEquivalentTo extension - // method in DataTableAssertions.cs. If this ever needs to change, keep them in sync. - var collectionNames = new[] - { - nameof(DataTable.ChildRelations), - nameof(DataTable.Columns), - nameof(DataTable.Constraints), - nameof(DataTable.ExtendedProperties), - nameof(DataTable.ParentRelations), - nameof(DataTable.PrimaryKey), - nameof(DataTable.Rows), - }; - - foreach (var collectionName in collectionNames) - { - if (selectedMembers.TryGetValue(collectionName, out IMember expectationMember)) - { - IMember matchingMember = FindMatchFor(expectationMember, comparands.Subject, context.CurrentNode, config); - - if (matchingMember is not null) - { - IEquivalencyValidationContext nestedContext = context.AsNestedMember(expectationMember); - - var nestedComparands = new Comparands - { - Subject = matchingMember.GetValue(comparands.Subject), - Expectation = expectationMember.GetValue(comparands.Expectation), - CompileTimeType = expectationMember.Type - }; - - parent.RecursivelyAssertEquality(nestedComparands, nestedContext); - } - } - } - } - - private static IMember FindMatchFor(IMember selectedMemberInfo, object subject, INode currentNode, - IEquivalencyAssertionOptions config) - { - IEnumerable query = - from rule in config.MatchingRules - let match = rule.Match(selectedMemberInfo, subject, currentNode, config) - where match is not null - select match; - - return query.FirstOrDefault(); - } - - private static IEnumerable GetMembersFromExpectation(INode currentNode, Comparands comparands, - IEquivalencyAssertionOptions config) - { - IEnumerable members = Enumerable.Empty(); - - foreach (IMemberSelectionRule rule in config.SelectionRules) - { - members = rule.SelectMembers(currentNode, members, - new MemberSelectionContext(comparands.CompileTimeType, comparands.RuntimeType, config)); - } - - return members; - } -} diff --git a/Src/FluentAssertions/Equivalency/Steps/DateAndTimeEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/DateAndTimeEquivalencyStep.cs new file mode 100644 index 0000000000..6b04dbb728 --- /dev/null +++ b/Src/FluentAssertions/Equivalency/Steps/DateAndTimeEquivalencyStep.cs @@ -0,0 +1,53 @@ +using System; +using FluentAssertions.Execution; + +namespace FluentAssertions.Equivalency.Steps; + +// AV1000: Type 'DateAndTimeEquivalencyStep' contains the word 'and', which suggests it has multiple purpose +#pragma warning disable AV1000 + +/// +/// Specific equivalency step for handling date and time types where the types are different and the failure message +/// should make that clear. +/// +public class DateAndTimeEquivalencyStep : IEquivalencyStep +{ + public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, + IValidateChildNodeEquivalency valueChildNodes) + { + object subject = comparands.Subject; + object expectation = comparands.Expectation; + + Type expectedType = comparands.GetExpectedType(context.Options); + Type subjectType = subject?.GetType(); + + if (IsOfDateOrTimeType(expectedType) && IsOfDateOrTimeType(subjectType) && subjectType != expectedType) + { + AssertionChain.GetOrCreate() + .For(context) + .ForCondition(subject!.Equals(expectation)) + .FailWith("Expected {context} to be {0} (of type {1}){reason}, but found {2} (of type {3}).", expectation, + expectedType, subject, subjectType); + + return EquivalencyResult.EquivalencyProven; + } + + return EquivalencyResult.ContinueWithNext; + } + +#if NET6_0_OR_GREATER + private static bool IsOfDateOrTimeType(Type type) => + type == typeof(DateTime) || + type == typeof(DateTimeOffset) || + type == typeof(TimeSpan) || + type == typeof(TimeOnly) || + type == typeof(DateOnly); +#else + private static bool IsOfDateOrTimeType(Type type) => + type == typeof(DateTime) || + type == typeof(DateTimeOffset) || + type == typeof(TimeSpan); +#endif +} + +#pragma warning restore AV1000 diff --git a/Src/FluentAssertions/Equivalency/Steps/DictionaryEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/DictionaryEquivalencyStep.cs index e0acf0f406..67db86940f 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,64 +9,73 @@ namespace FluentAssertions.Equivalency.Steps; public class DictionaryEquivalencyStep : EquivalencyStep { [SuppressMessage("ReSharper", "PossibleNullReferenceException")] - protected override EquivalencyResult OnHandle(Comparands comparands, IEquivalencyValidationContext context, - IEquivalencyValidator nestedValidator) + 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) { if (context.Options.IsRecursive) { context.Tracer.WriteLine(member => - Invariant($"Recursing into dictionary item {key} at {member.Description}")); + Invariant($"Recursing into dictionary item {key} at {member.Expectation}")); - nestedValidator.RecursivelyAssertEquality(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 { context.Tracer.WriteLine(member => Invariant( - $"Comparing dictionary item {key} at {member.Description} between subject and expectation")); + $"Comparing dictionary item {key} at {member.Expectation} between subject and expectation")); + assertionChain.WithCallerPostfix($"[{key.ToFormattedString()}]").ReuseOnce(); subject[key].Should().Be(expectation[key], context.Reason.FormattedMessage, context.Reason.Arguments); } } } - return EquivalencyResult.AssertionCompleted; + 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 bbe431fc26..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) @@ -94,7 +90,7 @@ private static DictionaryInterfaceInfo[] GetDictionaryInterfacesFrom(Type target { if (Type.GetTypeCode(key) != TypeCode.Object) { - return Array.Empty(); + return []; } return key @@ -125,7 +121,7 @@ public object ConvertFrom(object convertable) Type pairValueType = suitableKeyValuePairCollection.GenericTypeArguments[^1]; var methodInfo = ConvertToDictionaryMethod.MakeGenericMethod(Key, pairValueType); - return methodInfo.Invoke(null, new[] { convertable }); + return methodInfo.Invoke(null, [convertable]); } return null; diff --git a/Src/FluentAssertions/Equivalency/Steps/EnumEqualityStep.cs b/Src/FluentAssertions/Equivalency/Steps/EnumEqualityStep.cs index 274f6a7c3e..0cf246e82d 100644 --- a/Src/FluentAssertions/Equivalency/Steps/EnumEqualityStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/EnumEqualityStep.cs @@ -11,14 +11,16 @@ namespace FluentAssertions.Equivalency.Steps; public class EnumEqualityStep : IEquivalencyStep { public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, - IEquivalencyValidator nestedValidator) + IValidateChildNodeEquivalency valueChildNodes) { if (!comparands.GetExpectedType(context.Options).IsEnum) { return EquivalencyResult.ContinueWithNext; } - bool succeeded = Execute.Assertion + var assertionChain = AssertionChain.GetOrCreate().For(context); + + assertionChain .ForCondition(comparands.Subject?.GetType().IsEnum == true) .BecauseOf(context.Reason) .FailWith(() => @@ -27,36 +29,43 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon string expectationName = GetDisplayNameForEnumComparison(comparands.Expectation, expectationsUnderlyingValue); return new FailReason( - $"Expected {{context:enum}} to be equivalent to {expectationName}{{reason}}, but found {{0}}.", + "Expected {context:enum} to be equivalent to {0}{reason}, but found {1}.", + expectationName.AsNonFormattable(), 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: + { throw new InvalidOperationException($"Do not know how to handle {context.Options.EnumEquivalencyHandling}"); + } } } - return EquivalencyResult.AssertionCompleted; + 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(() => @@ -65,16 +74,17 @@ private static void HandleByValue(Comparands comparands, Reason reason) string expectationName = GetDisplayNameForEnumComparison(comparands.Expectation, expectationsUnderlyingValue); return new FailReason( - $"Expected {{context:enum}} to equal {expectationName} by value{{reason}}, but found {subjectsName}."); + "Expected {context:enum} to equal {0} by value{reason}, but found {1}.", + expectationName.AsNonFormattable(), subjectsName.AsNonFormattable()); }); } - 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(() => @@ -86,7 +96,8 @@ private static void HandleByName(Comparands comparands, Reason reason) string expectationName = GetDisplayNameForEnumComparison(comparands.Expectation, expectationsUnderlyingValue); return new FailReason( - $"Expected {{context:enum}} to equal {expectationName} by name{{reason}}, but found {subjectsName}."); + "Expected {context:enum} to equal {0} by name{reason}, but found {1}.", + expectationName.AsNonFormattable(), subjectsName.AsNonFormattable()); }); } @@ -100,7 +111,7 @@ private static string GetDisplayNameForEnumComparison(object o, decimal? v) string typePart = o.GetType().Name; string namePart = o.ToString().Replace(", ", "|", StringComparison.Ordinal); string valuePart = v.Value.ToString(CultureInfo.InvariantCulture); - return $"{typePart}.{namePart} {{{{value: {valuePart}}}}}"; + return $"{typePart}.{namePart} {{value: {valuePart}}}"; } private static decimal? ExtractDecimal(object o) diff --git a/Src/FluentAssertions/Equivalency/Steps/EnumerableEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/EnumerableEquivalencyStep.cs index 51e110041c..1b6906ba58 100644 --- a/Src/FluentAssertions/Equivalency/Steps/EnumerableEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/EnumerableEquivalencyStep.cs @@ -8,16 +8,18 @@ namespace FluentAssertions.Equivalency.Steps; public class EnumerableEquivalencyStep : IEquivalencyStep { public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, - IEquivalencyValidator nestedValidator) + IValidateChildNodeEquivalency valueChildNodes) { if (!IsCollection(comparands.GetExpectedType(context.Options))) { return EquivalencyResult.ContinueWithNext; } - if (AssertSubjectIsCollection(comparands.Subject)) + var assertionChain = AssertionChain.GetOrCreate().For(context); + + if (AssertSubjectIsCollection(assertionChain, comparands.Subject)) { - var validator = new EnumerableEquivalencyValidator(nestedValidator, context) + var validator = new EnumerableEquivalencyValidator(assertionChain, valueChildNodes, context) { Recursive = context.CurrentNode.IsRoot || context.Options.IsRecursive, OrderingRules = context.Options.OrderingRules @@ -26,23 +28,23 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon validator.Execute(ToArray(comparands.Subject), ToArray(comparands.Expectation)); } - return EquivalencyResult.AssertionCompleted; + 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) @@ -64,7 +66,7 @@ internal static object[] ToArray(object value) catch (InvalidOperationException) when (IsIgnorableArrayLikeType(value)) { // This is probably a default ImmutableArray or an empty ArraySegment. - return Array.Empty(); + return []; } } diff --git a/Src/FluentAssertions/Equivalency/Steps/EnumerableEquivalencyValidator.cs b/Src/FluentAssertions/Equivalency/Steps/EnumerableEquivalencyValidator.cs index 4231b55890..11e01ebb00 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 IEquivalencyValidator parent; + private readonly AssertionChain assertionChain; + private readonly IValidateChildNodeEquivalency parent; private readonly IEquivalencyValidationContext context; #endregion - public EnumerableEquivalencyValidator(IEquivalencyValidator parent, IEquivalencyValidationContext context) + public EnumerableEquivalencyValidator(AssertionChain assertionChain, IValidateChildNodeEquivalency parent, + IEquivalencyValidationContext context) { + this.assertionChain = assertionChain; this.parent = parent; this.context = context; Recursive = false; @@ -39,7 +42,7 @@ public void Execute(object[] subject, T[] expectation) if (Recursive) { using var _ = context.Tracer.WriteBlock(member => - Invariant($"Structurally comparing {subject} and expectation {expectation} at {member.Description}")); + Invariant($"Structurally comparing {subject} and expectation {expectation} at {member.Expectation}")); AssertElementGraphEquivalency(subject, expectation, context.CurrentNode); } @@ -47,37 +50,37 @@ public void Execute(object[] subject, T[] expectation) { using var _ = context.Tracer.WriteBlock(member => Invariant( - $"Comparing subject {subject} and expectation {expectation} at {member.Description} using simple value equality")); + $"Comparing subject {subject} and expectation {expectation} at {member.Expectation} using simple value equality")); subject.Should().BeEquivalentTo(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) { - unmatchedSubjectIndexes = new List(subjects.Length); - unmatchedSubjectIndexes.AddRange(Enumerable.Range(0, subjects.Length)); + unmatchedSubjectIndexes = Enumerable.Range(0, subjects.Length).ToList(); if (OrderingRules.IsOrderingStrictFor(new ObjectInfo(new Comparands(subjects, expectations, typeof(T[])), currentNode))) { @@ -99,18 +102,16 @@ private void AssertElementGraphEquivalencyWithStrictOrdering(object[] subject using var _ = context.Tracer.WriteBlock(member => Invariant( - $"Strictly comparing expectation {expectation} at {member.Description} to item with index {index} in {subjects}")); + $"Strictly comparing expectation {expectation} at {member.Expectation} to item with index {index} in {subjects}")); bool succeeded = StrictlyMatchAgainst(subjects, expectation, index); - if (!succeeded) { failedCount++; - if (failedCount >= FailedItemsFastFailThreshold) { context.Tracer.WriteLine(member => - $"Aborting strict order comparison of collections after {FailedItemsFastFailThreshold} items failed at {member.Description}"); + $"Aborting strict order comparison of collections after {FailedItemsFastFailThreshold} items failed at {member.Expectation}"); break; } @@ -128,7 +129,7 @@ private void AssertElementGraphEquivalencyWithLooseOrdering(object[] subjects using var _ = context.Tracer.WriteBlock(member => Invariant( - $"Finding the best match of {expectation} within all items in {subjects} at {member.Description}[{index}]")); + $"Finding the best match of {expectation} within all items in {subjects} at {member.Expectation}[{index}]")); bool succeeded = LooselyMatchAgainst(subjects, expectation, index); @@ -139,7 +140,7 @@ private void AssertElementGraphEquivalencyWithLooseOrdering(object[] subjects if (failedCount >= FailedItemsFastFailThreshold) { context.Tracer.WriteLine(member => - $"Fail failing loose order comparison of collection after {FailedItemsFastFailThreshold} items failed at {member.Description}"); + $"Fail failing loose order comparison of collection after {FailedItemsFastFailThreshold} items failed at {member.Expectation}"); break; } @@ -155,7 +156,7 @@ private bool LooselyMatchAgainst(IList subjects, T expectation, int e int index = 0; GetTraceMessage getMessage = member => - $"Comparing subject at {member.Description}[{index}] with the expectation at {member.Description}[{expectationIndex}]"; + $"Comparing subject at {member.Subject}[{index}] with the expectation at {member.Expectation}[{expectationIndex}]"; int indexToBeRemoved = -1; @@ -184,9 +185,9 @@ private bool LooselyMatchAgainst(IList subjects, T expectation, int e unmatchedSubjectIndexes.RemoveAt(indexToBeRemoved); } - foreach (string failure in results.SelectClosestMatchFor(expectationIndex)) + foreach (string failure in results.GetTheFailuresForTheSetWithTheFewestFailures(expectationIndex)) { - AssertionScope.Current.AddPreFormattedFailure(failure); + assertionChain.AddPreFormattedFailure(failure); } return indexToBeRemoved != -1; @@ -196,8 +197,7 @@ private string[] TryToMatch(object subject, T expectation, int expectationInd { using var scope = new AssertionScope(); - parent.RecursivelyAssertEquality(new Comparands(subject, expectation, typeof(T)), - context.AsCollectionItem(expectationIndex)); + parent.AssertEquivalencyOf(new Comparands(subject, expectation, typeof(T)), context.AsCollectionItem(expectationIndex)); return scope.Discard(); } @@ -208,7 +208,7 @@ private bool StrictlyMatchAgainst(object[] subjects, T expectation, int expec object subject = subjects[expectationIndex]; IEquivalencyValidationContext equivalencyValidationContext = context.AsCollectionItem(expectationIndex); - parent.RecursivelyAssertEquality(new Comparands(subject, expectation, typeof(T)), equivalencyValidationContext); + parent.AssertEquivalencyOf(new Comparands(subject, expectation, typeof(T)), equivalencyValidationContext); bool failed = scope.HasFailures(); return !failed; 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 42c9df3677..6bd954d16b 100644 --- a/Src/FluentAssertions/Equivalency/Steps/EqualityComparerEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/EqualityComparerEquivalencyStep.cs @@ -14,7 +14,7 @@ public EqualityComparerEquivalencyStep(IEqualityComparer comparer) } public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, - IEquivalencyValidator nestedValidator) + IValidateChildNodeEquivalency valueChildNodes) { var expectedType = context.Options.UseRuntimeTyping ? comparands.RuntimeType : comparands.CompileTimeType; @@ -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) @@ -39,7 +40,7 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon .FailWith("Expected {context:object} to be equal to {1} according to {0}{because}, but {2} was not.", comparer.ToString(), comparands.Expectation, comparands.Subject); - return EquivalencyResult.AssertionCompleted; + return EquivalencyResult.EquivalencyProven; } public override string ToString() diff --git a/Src/FluentAssertions/Equivalency/Steps/GenericDictionaryEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/GenericDictionaryEquivalencyStep.cs index bffb1ec68b..1d398fdcd2 100644 --- a/Src/FluentAssertions/Equivalency/Steps/GenericDictionaryEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/GenericDictionaryEquivalencyStep.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using FluentAssertions.Execution; @@ -11,13 +12,13 @@ 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 public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, - IEquivalencyValidator nestedValidator) + IValidateChildNodeEquivalency valueChildNodes) { if (comparands.Expectation is null) { @@ -37,13 +38,16 @@ 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, nestedValidator, actualDictionary, expectedDictionary); + AssertDictionaryEquivalence(comparands, assertionChain, context, valueChildNodes, actualDictionary, + expectedDictionary); } - return EquivalencyResult.AssertionCompleted; + return EquivalencyResult.EquivalencyProven; } private static bool IsNonGenericDictionary(object subject) @@ -53,19 +57,21 @@ private static bool IsNonGenericDictionary(object subject) return false; } - return !subject.GetType().GetInterfaces() - .Any(@interface => @interface.IsGenericType - && @interface.GetGenericTypeDefinition() == typeof(IDictionary<,>)); + return !subject.GetType().GetInterfaces().Any(@interface => + @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", @@ -79,7 +85,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}."); } @@ -88,7 +94,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 @@ -100,19 +108,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, - IEquivalencyValidator parent, DictionaryInterfaceInfo actualDictionary, DictionaryInterfaceInfo expectedDictionary) + [SuppressMessage("Maintainability", "AV1561:Signature contains too many parameters")] + 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, new[] { context, parent, context.Options, comparands.Subject, comparands.Expectation }); + .Invoke(null, [assertionChain, context, parent, context.Options, comparands.Subject, comparands.Expectation]); } + [SuppressMessage("Maintainability", "AV1561:Signature contains too many parameters")] private static void AssertDictionaryEquivalence( + AssertionChain assertionChain, EquivalencyValidationContext context, - IEquivalencyValidator parent, - IEquivalencyAssertionOptions options, + IValidateChildNodeEquivalency parent, + IEquivalencyOptions options, IDictionary subject, IDictionary expectation) where TExpectedKey : TSubjectKey { if (subject.Count != expectation.Count) { - FailWithLengthDifference(subject, expectation); + FailWithLengthDifference(subject, expectation, assertionChain); } else { @@ -181,18 +193,18 @@ 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 1830ec61a0..f7e4e7297f 100644 --- a/Src/FluentAssertions/Equivalency/Steps/GenericEnumerableEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/GenericEnumerableEquivalencyStep.cs @@ -16,7 +16,7 @@ public class GenericEnumerableEquivalencyStep : IEquivalencyStep #pragma warning restore SA1110 public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, - IEquivalencyValidator nestedValidator) + IValidateChildNodeEquivalency valueChildNodes) { Type expectedType = comparands.GetExpectedType(context.Options); @@ -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(nestedValidator, context) + var validator = new EnumerableEquivalencyValidator(assertionChain, valueChildNodes, context) { Recursive = context.CurrentNode.IsRoot || context.Options.IsRecursive, OrderingRules = context.Options.OrderingRules @@ -48,7 +50,7 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon try { HandleMethod.MakeGenericMethod(typeOfEnumeration) - .Invoke(null, new[] { validator, subjectAsArray, comparands.Expectation }); + .Invoke(null, [validator, subjectAsArray, comparands.Expectation]); } catch (TargetInvocationException e) { @@ -56,26 +58,26 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon } } - return EquivalencyResult.AssertionCompleted; + return EquivalencyResult.EquivalencyProven; } 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) @@ -95,7 +97,7 @@ private static Type[] GetIEnumerableInterfaces(Type type) // Avoid expensive calculation when the type in question can't possibly implement IEnumerable<>. if (Type.GetTypeCode(type) != TypeCode.Object) { - return Array.Empty(); + return []; } Type soughtType = typeof(IEnumerable<>); @@ -119,7 +121,7 @@ private static T[] ToArray(IEnumerable value) catch (InvalidOperationException) when (value.GetType().Name.Equals("ImmutableArray`1", StringComparison.Ordinal)) { // This is probably a default ImmutableArray - return Array.Empty(); + return []; } } } diff --git a/Src/FluentAssertions/Equivalency/Steps/JsonConversionStep.cs b/Src/FluentAssertions/Equivalency/Steps/JsonConversionStep.cs new file mode 100644 index 0000000000..8ae2289f0f --- /dev/null +++ b/Src/FluentAssertions/Equivalency/Steps/JsonConversionStep.cs @@ -0,0 +1,65 @@ +#if NET6_0_OR_GREATER + +using System; +using System.Globalization; +using System.Text.Json.Nodes; + +namespace FluentAssertions.Equivalency.Steps; + +public class JsonConversionStep : IEquivalencyStep +{ + /// + public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, + IValidateChildNodeEquivalency valueChildNodes) + { + if (comparands.Subject is JsonValue json) + { + if (json.TryGetValue(out long longValue)) + { + comparands.Subject = longValue; + } + else if (json.TryGetValue(out ulong ulongValue)) + { + comparands.Subject = ulongValue; + } + else if (json.TryGetValue(out double doubleValue)) + { + comparands.Subject = doubleValue; + } + else if (json.TryGetValue(out bool boolValue)) + { + comparands.Subject = boolValue; + } + else if (json.TryGetValue(out string stringValue)) + { + string[] iso8601Formats = + { + "yyyy-MM-ddTHH:mm:ssZ", + "yyyy-MM-ddTHH:mm:ss.fffZ", + "yyyy-MM-ddTHH:mm:ss", + "yyyy-MM-ddTHH:mm:ss.fff", + "yyyy-MM-dd" + }; + + var style = stringValue.EndsWith('Z') ? DateTimeStyles.AdjustToUniversal : DateTimeStyles.AssumeLocal; + if (DateTime.TryParseExact(stringValue, iso8601Formats, CultureInfo.InvariantCulture, style, + out DateTime exactResult)) + { + comparands.Subject = exactResult; + } + else + { + comparands.Subject = stringValue; + } + } + else + { + // We don't need to do anything + } + } + + return EquivalencyResult.ContinueWithNext; + } +} + +#endif diff --git a/Src/FluentAssertions/Equivalency/Steps/ReferenceEqualityEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/ReferenceEqualityEquivalencyStep.cs index dc1e6b6018..c41e899078 100644 --- a/Src/FluentAssertions/Equivalency/Steps/ReferenceEqualityEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/ReferenceEqualityEquivalencyStep.cs @@ -3,10 +3,10 @@ public class ReferenceEqualityEquivalencyStep : IEquivalencyStep { public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, - IEquivalencyValidator nestedValidator) + IValidateChildNodeEquivalency valueChildNodes) { return ReferenceEquals(comparands.Subject, comparands.Expectation) - ? EquivalencyResult.AssertionCompleted + ? EquivalencyResult.EquivalencyProven : EquivalencyResult.ContinueWithNext; } } diff --git a/Src/FluentAssertions/Equivalency/Steps/RunAllUserStepsEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/RunAllUserStepsEquivalencyStep.cs index 6bfc9d801c..114a0aa726 100644 --- a/Src/FluentAssertions/Equivalency/Steps/RunAllUserStepsEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/RunAllUserStepsEquivalencyStep.cs @@ -7,13 +7,13 @@ public class RunAllUserStepsEquivalencyStep : IEquivalencyStep { public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, - IEquivalencyValidator nestedValidator) + IValidateChildNodeEquivalency valueChildNodes) { foreach (IEquivalencyStep step in context.Options.UserEquivalencySteps) { - if (step.Handle(comparands, context, nestedValidator) == EquivalencyResult.AssertionCompleted) + if (step.Handle(comparands, context, valueChildNodes) == EquivalencyResult.EquivalencyProven) { - return EquivalencyResult.AssertionCompleted; + return EquivalencyResult.EquivalencyProven; } } diff --git a/Src/FluentAssertions/Equivalency/Steps/SimpleEqualityEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/SimpleEqualityEquivalencyStep.cs index 0d15f6c0ea..af3598ec1d 100644 --- a/Src/FluentAssertions/Equivalency/Steps/SimpleEqualityEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/SimpleEqualityEquivalencyStep.cs @@ -1,15 +1,21 @@ +using FluentAssertions.Execution; + namespace FluentAssertions.Equivalency.Steps; public class SimpleEqualityEquivalencyStep : IEquivalencyStep { public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, - IEquivalencyValidator nestedValidator) + IValidateChildNodeEquivalency valueChildNodes) { 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.AssertionCompleted; + return EquivalencyResult.EquivalencyProven; } return EquivalencyResult.ContinueWithNext; diff --git a/Src/FluentAssertions/Equivalency/Steps/StringEqualityEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/StringEqualityEquivalencyStep.cs index ce7f0f5ef9..dc385bc0c3 100644 --- a/Src/FluentAssertions/Equivalency/Steps/StringEqualityEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/StringEqualityEquivalencyStep.cs @@ -6,7 +6,7 @@ namespace FluentAssertions.Equivalency.Steps; public class StringEqualityEquivalencyStep : IEquivalencyStep { public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, - IEquivalencyValidator nestedValidator) + IValidateChildNodeEquivalency valueChildNodes) { Type expectationType = comparands.GetExpectedType(context.Options); @@ -15,26 +15,61 @@ 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.AssertionCompleted; + 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, context.Reason.FormattedMessage, context.Reason.Arguments); + .Be(expectation, CreateOptions(context.Options), context.Reason.FormattedMessage, context.Reason.Arguments); } - return EquivalencyResult.AssertionCompleted; + return EquivalencyResult.EquivalencyProven; } - private static bool ValidateAgainstNulls(Comparands comparands, INode currentNode) + private static Func, EquivalencyOptions> + CreateOptions(IEquivalencyOptions existingOptions) => + o => + { + if (existingOptions is EquivalencyOptions equivalencyOptions) + { + return equivalencyOptions; + } + + if (existingOptions.IgnoreLeadingWhitespace) + { + o.IgnoringLeadingWhitespace(); + } + + if (existingOptions.IgnoreTrailingWhitespace) + { + o.IgnoringTrailingWhitespace(); + } + + if (existingOptions.IgnoreCase) + { + o.IgnoringCase(); + } + + if (existingOptions.IgnoreNewlineStyle) + { + o.IgnoringNewlineStyle(); + } + + return o; + }; + + private static bool ValidateAgainstNulls(AssertionChain assertionChain, Comparands comparands, INode currentNode) { object expected = comparands.Expectation; object subject = comparands.Subject; @@ -43,8 +78,8 @@ private static bool ValidateAgainstNulls(Comparands comparands, INode currentNod if (onlyOneNull) { - AssertionScope.Current.FailWith( - $"Expected {currentNode.Description} to be {{0}}{{reason}}, but found {{1}}.", expected, subject); + assertionChain.FailWith( + "Expected {0} to be {1}{reason}, but found {2}.", currentNode.Subject.Description.AsNonFormattable(), expected, subject); return false; } @@ -52,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 {0} to be {1}, but found {2}.", currentNode.AsNonFormattable(), + 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 4b8091cf94..188b4f7d79 100644 --- a/Src/FluentAssertions/Equivalency/Steps/StructuralEqualityEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/StructuralEqualityEquivalencyStep.cs @@ -8,16 +8,18 @@ namespace FluentAssertions.Equivalency.Steps; public class StructuralEqualityEquivalencyStep : IEquivalencyStep { public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, - IEquivalencyValidator nestedValidator) + IValidateChildNodeEquivalency valueChildNodes) { if (!context.CurrentNode.IsRoot && !context.Options.IsRecursive) { 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}.", @@ -45,18 +47,19 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon foreach (IMember selectedMember in selectedMembers) { - AssertMemberEquality(comparands, context, nestedValidator, selectedMember, context.Options); + AssertMemberEquality(comparands, context, valueChildNodes, selectedMember, context.Options); } } - return EquivalencyResult.AssertionCompleted; + return EquivalencyResult.EquivalencyProven; } private static void AssertMemberEquality(Comparands comparands, IEquivalencyValidationContext context, - IEquivalencyValidator parent, IMember selectedMember, IEquivalencyAssertionOptions options) + 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) { var nestedComparands = new Comparands @@ -66,23 +69,20 @@ private static void AssertMemberEquality(Comparands comparands, IEquivalencyVali CompileTimeType = selectedMember.Type }; - if (selectedMember.Name != matchingMember.Name) - { - // In case the matching process selected a different member on the subject, - // adjust the current member so that assertion failures report the proper name. - selectedMember.Name = matchingMember.Name; - } + // In case the matching process selected a different member on the subject, + // adjust the current member so that assertion failures report the proper name. + selectedMember.AdjustForRemappedSubject(matchingMember); - parent.RecursivelyAssertEquality(nestedComparands, context.AsNestedMember(selectedMember)); + parent.AssertEquivalencyOf(nestedComparands, context.AsNestedMember(selectedMember)); } } private static IMember FindMatchFor(IMember selectedMember, INode currentNode, object subject, - IEquivalencyAssertionOptions 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; @@ -95,9 +95,9 @@ where match is not null } private static IEnumerable GetMembersFromExpectation(INode currentNode, Comparands comparands, - IEquivalencyAssertionOptions options) + IEquivalencyOptions options) { - IEnumerable members = Enumerable.Empty(); + IEnumerable members = []; foreach (IMemberSelectionRule rule in options.SelectionRules) { diff --git a/Src/FluentAssertions/Equivalency/Steps/TypeEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/TypeEquivalencyStep.cs new file mode 100644 index 0000000000..e63c850e43 --- /dev/null +++ b/Src/FluentAssertions/Equivalency/Steps/TypeEquivalencyStep.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections; +using System.Linq; +using FluentAssertions.Equivalency.Typing; +using FluentAssertions.Execution; + +namespace FluentAssertions.Equivalency.Steps; + +/// +/// An equivalency step that asserts two objects must be of the same type to be equivalent. +/// +/// +/// This differs from the default equivalency assertion which states that two objects are equivalent if they have the +/// same properties and values, regardless of their type. +/// +public class TypeEquivalencyStep : IEquivalencyStep +{ + public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, + IValidateChildNodeEquivalency valueChildNodes) + { + if (context.Options is not IContainTypingRules options) + { + return EquivalencyResult.ContinueWithNext; + } + + // When comparing using a collection we want to compare the children of the collection not the collection itself (the root object) + if (comparands.Subject is IEnumerable) + { + return EquivalencyResult.ContinueWithNext; + } + + // If both are null or reference the same instance, there's no need to have this step check anything + if (ReferenceEquals(comparands.Subject, comparands.Expectation)) + { + return EquivalencyResult.ContinueWithNext; + } + + // The above checks if both were null, but if only one is null then there's no need to have this step check anything + if (comparands.Subject is null || comparands.Expectation is null) + { + return EquivalencyResult.ContinueWithNext; + } + + // Check if strict typing should be applied based on typing rules + bool shouldUseStrictTyping = options.TypingRules.Any(rule => rule.UseStrictTyping(comparands, context.CurrentNode)); + if (!shouldUseStrictTyping) + { + return EquivalencyResult.ContinueWithNext; + } + + Type expectedType = comparands.GetExpectedType(context.Options); + + AssertionChain.GetOrCreate().For(context) + .ForCondition(comparands.Subject.GetType() == expectedType) + .FailWith("Expected {context:subject} to be of type {0}, but found {1}", expectedType, comparands.Subject.GetType()); + + return EquivalencyResult.ContinueWithNext; + } +} diff --git a/Src/FluentAssertions/Equivalency/Steps/ValueTypeEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/ValueTypeEquivalencyStep.cs index 71a131c275..2af82fa77d 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; @@ -8,7 +9,7 @@ namespace FluentAssertions.Equivalency.Steps; public class ValueTypeEquivalencyStep : IEquivalencyStep { public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, - IEquivalencyValidator nestedValidator) + IValidateChildNodeEquivalency valueChildNodes) { Type expectationType = comparands.GetExpectedType(context.Options); EqualityStrategy strategy = context.Options.GetEqualityStrategy(expectationType); @@ -23,12 +24,16 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon ? $"{expectationType} overrides Equals" : "we are forced to use Equals"; - return $"Treating {member.Description} as a value type because {strategyName}."; + return $"Treating {member.Expectation.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.AssertionCompleted; + return EquivalencyResult.EquivalencyProven; } return EquivalencyResult.ContinueWithNext; diff --git a/Src/FluentAssertions/Equivalency/Steps/XAttributeEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/XAttributeEquivalencyStep.cs index b9ed03a5b7..c403ed74d9 100644 --- a/Src/FluentAssertions/Equivalency/Steps/XAttributeEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/XAttributeEquivalencyStep.cs @@ -1,17 +1,21 @@ using System.Xml.Linq; +using FluentAssertions.Execution; namespace FluentAssertions.Equivalency.Steps; public class XAttributeEquivalencyStep : EquivalencyStep { - protected override EquivalencyResult OnHandle(Comparands comparands, IEquivalencyValidationContext context, - IEquivalencyValidator nestedValidator) + 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.AssertionCompleted; + return EquivalencyResult.EquivalencyProven; } } diff --git a/Src/FluentAssertions/Equivalency/Steps/XDocumentEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/XDocumentEquivalencyStep.cs index 76f569bee3..b88921f45b 100644 --- a/Src/FluentAssertions/Equivalency/Steps/XDocumentEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/XDocumentEquivalencyStep.cs @@ -1,17 +1,21 @@ using System.Xml.Linq; +using FluentAssertions.Execution; namespace FluentAssertions.Equivalency.Steps; public class XDocumentEquivalencyStep : EquivalencyStep { - protected override EquivalencyResult OnHandle(Comparands comparands, IEquivalencyValidationContext context, - IEquivalencyValidator nestedValidator) + 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.AssertionCompleted; + return EquivalencyResult.EquivalencyProven; } } diff --git a/Src/FluentAssertions/Equivalency/Steps/XElementEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/XElementEquivalencyStep.cs index 022038ca6c..9ed4e2d503 100644 --- a/Src/FluentAssertions/Equivalency/Steps/XElementEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/XElementEquivalencyStep.cs @@ -1,17 +1,21 @@ using System.Xml.Linq; +using FluentAssertions.Execution; namespace FluentAssertions.Equivalency.Steps; public class XElementEquivalencyStep : EquivalencyStep { - protected override EquivalencyResult OnHandle(Comparands comparands, IEquivalencyValidationContext context, - IEquivalencyValidator nestedValidator) + 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.AssertionCompleted; + return EquivalencyResult.EquivalencyProven; } } diff --git a/Src/FluentAssertions/Equivalency/Tracing/InternalTraceWriter.cs b/Src/FluentAssertions/Equivalency/Tracing/InternalTraceWriter.cs new file mode 100644 index 0000000000..1df987b6ae --- /dev/null +++ b/Src/FluentAssertions/Equivalency/Tracing/InternalTraceWriter.cs @@ -0,0 +1,5 @@ +namespace FluentAssertions.Equivalency.Tracing; + +internal sealed class InternalTraceWriter : StringBuilderTraceWriter +{ +} diff --git a/Src/FluentAssertions/Equivalency/Tracing/StringBuilderTraceWriter.cs b/Src/FluentAssertions/Equivalency/Tracing/StringBuilderTraceWriter.cs index cb1047defc..a0752de0e3 100644 --- a/Src/FluentAssertions/Equivalency/Tracing/StringBuilderTraceWriter.cs +++ b/Src/FluentAssertions/Equivalency/Tracing/StringBuilderTraceWriter.cs @@ -28,7 +28,7 @@ public IDisposable AddBlock(string trace) private void WriteLine(string trace) { - foreach (string traceLine in trace.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)) + foreach (string traceLine in trace.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries)) { builder.Append(new string(' ', depth * 2)).AppendLine(traceLine); } diff --git a/Src/FluentAssertions/Equivalency/Typing/AlwaysBeStrictTypingRule.cs b/Src/FluentAssertions/Equivalency/Typing/AlwaysBeStrictTypingRule.cs new file mode 100644 index 0000000000..8f81205c2c --- /dev/null +++ b/Src/FluentAssertions/Equivalency/Typing/AlwaysBeStrictTypingRule.cs @@ -0,0 +1,21 @@ +namespace FluentAssertions.Equivalency.Typing; + +/// +/// An implementation of that applies strict typing to all objects. +/// +internal class AlwaysBeStrictTypingRule : ITypingRule +{ + /// + public bool UseStrictTyping(Comparands comparands, INode node) + { + return true; + } + + /// + /// Returns a string representation of this object. + /// + public override string ToString() + { + return "The types of the fields and properties must be the same"; + } +} diff --git a/Src/FluentAssertions/Equivalency/Typing/IContainTypingRules.cs b/Src/FluentAssertions/Equivalency/Typing/IContainTypingRules.cs new file mode 100644 index 0000000000..cc8793bad0 --- /dev/null +++ b/Src/FluentAssertions/Equivalency/Typing/IContainTypingRules.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace FluentAssertions.Equivalency.Typing; + +/// +/// Marks a type as containing typing rules that influence how types are compared during equivalency assertions. +/// +internal interface IContainTypingRules +{ + /// + /// Gets a collection of typing rules that determine how types are handled during equivalency assertions. + /// + IEnumerable TypingRules { get; } +} diff --git a/Src/FluentAssertions/Equivalency/Typing/ITypingRule.cs b/Src/FluentAssertions/Equivalency/Typing/ITypingRule.cs new file mode 100644 index 0000000000..48cac976c8 --- /dev/null +++ b/Src/FluentAssertions/Equivalency/Typing/ITypingRule.cs @@ -0,0 +1,13 @@ +namespace FluentAssertions.Equivalency.Typing; + +/// +/// Represents a rule that determines whether the types of the fields and properties in an object graph should be the same between +/// subject and expectation. +/// +internal interface ITypingRule +{ + /// + /// Determines whether strict typing should be applied to the given node in the object graph. + /// + bool UseStrictTyping(Comparands comparands, INode node); +} diff --git a/Src/FluentAssertions/Equivalency/Typing/PredicateBasedTypingRule.cs b/Src/FluentAssertions/Equivalency/Typing/PredicateBasedTypingRule.cs new file mode 100644 index 0000000000..3aea5b3db2 --- /dev/null +++ b/Src/FluentAssertions/Equivalency/Typing/PredicateBasedTypingRule.cs @@ -0,0 +1,39 @@ +using System; +using System.Linq.Expressions; +using FluentAssertions.Equivalency.Execution; + +namespace FluentAssertions.Equivalency.Typing; + +/// +/// An implementation of that uses a predicate to determine +/// whether strict typing should be applied during equivalency comparison. +/// +internal class PredicateBasedTypingRule : ITypingRule +{ + private readonly Func predicate; + private readonly string description; + + /// + /// Initializes a new instance of the class with a predicate. + /// + /// A predicate that determines whether strict typing should be applied. + public PredicateBasedTypingRule(Expression> predicate) + { + description = predicate.Body.ToString(); + this.predicate = predicate.Compile(); + } + + /// + public bool UseStrictTyping(Comparands comparands, INode node) + { + return predicate(new ObjectInfo(comparands, node)); + } + + /// + /// Returns a string representation of this object. + /// + public override string ToString() + { + return $"Use strict typing when {description}"; + } +} diff --git a/Src/FluentAssertions/EventRaisingExtensions.cs b/Src/FluentAssertions/EventRaisingExtensions.cs index e88c28ad66..15798cba20 100644 --- a/Src/FluentAssertions/EventRaisingExtensions.cs +++ b/Src/FluentAssertions/EventRaisingExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Linq.Expressions; using FluentAssertions.Common; @@ -23,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]; @@ -46,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, @@ -83,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), @@ -136,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), @@ -145,4 +149,31 @@ public static IEventRecording WithArgs(this IEventRecording eventRecording, p return new FilteredEventRecording(eventRecording, eventsWithMatchingPredicate); } + + /// + /// Asserts that all occurrences of the events has arguments of type + /// and are for property . + /// + /// + /// The property name for which the property changed events should have been raised. + /// + /// + /// Returns only the property changed events affecting the particular property name. + /// + /// + /// If a or string.Empty is provided as property name, the events are return as-is. + /// + internal static IEventRecording WithPropertyChangeFor(this IEventRecording eventRecording, string propertyName) + { + if (string.IsNullOrEmpty(propertyName)) + { + return eventRecording; + } + + IEnumerable eventsForPropertyName = + eventRecording.Where(@event => @event.IsAffectingPropertyName(propertyName)) + .ToList(); + + return new FilteredEventRecording(eventRecording, eventsForPropertyName); + } } diff --git a/Src/FluentAssertions/Events/EventAssertions.cs b/Src/FluentAssertions/Events/EventAssertions.cs index 97fadab856..36210c41eb 100644 --- a/Src/FluentAssertions/Events/EventAssertions.cs +++ b/Src/FluentAssertions/Events/EventAssertions.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; using FluentAssertions.Common; @@ -14,11 +15,13 @@ namespace FluentAssertions.Events; /// public class EventAssertions : ReferenceTypeAssertions> { - private const string PropertyChangedEventName = "PropertyChanged"; + 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; } @@ -40,13 +43,13 @@ protected internal EventAssertions(IMonitor monitor) /// /// Zero or more objects to format using the placeholders in . /// - public IEventRecording Raise(string eventName, string because = "", params object[] becauseArgs) + public IEventRecording Raise(string eventName, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { IEventRecording recording = Monitor.GetRecordingFor(eventName); 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); } @@ -67,13 +70,13 @@ public IEventRecording Raise(string eventName, string because = "", params objec /// /// Zero or more objects to format using the placeholders in . /// - public void NotRaise(string eventName, string because = "", params object[] becauseArgs) + public void NotRaise(string eventName, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { IEventRecording events = Monitor.GetRecordingFor(eventName); 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); } @@ -93,21 +96,25 @@ public void NotRaise(string eventName, string because = "", params object[] beca /// /// Zero or more objects to format using the placeholders in . /// + /// + /// Returns only the events having arguments of type targeting the property. + /// + [SuppressMessage("Usage", "MA0002:IEqualityComparer or IComparer is missing")] public IEventRecording RaisePropertyChangeFor(Expression> propertyExpression, - string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { string propertyName = propertyExpression?.GetPropertyInfo().Name; 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()) @@ -115,14 +122,14 @@ 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}.", Monitor.Subject, PropertyChangedEventName, propertyName, actualPropertyNames); } - return recording; + return recording.WithPropertyChangeFor(propertyName); } /// @@ -139,25 +146,31 @@ public IEventRecording RaisePropertyChangeFor(Expression> proper /// Zero or more objects to format using the placeholders in . /// public void NotRaisePropertyChangeFor(Expression> propertyExpression, - string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { IEventRecording recording = Monitor.GetRecordingFor(PropertyChangedEventName); - string propertyName = propertyExpression.GetPropertyInfo().Name; + string propertyName = propertyExpression?.GetPropertyInfo().Name; - if (recording.Any(@event => GetAffectedPropertyName(@event) == propertyName)) + if (propertyName is null) + { + assertionChain + .BecauseOf(because, becauseArgs) + .ForCondition(!recording.Any()) + .FailWith( + "Did not expect object {0} to raise the {1} event{reason}, but it did.", + Monitor.Subject, PropertyChangedEventName); + } + else { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .FailWith("Did not expect object {0} to raise the {1} event for property {2}{reason}, but it did.", + .ForCondition(!recording.Any(@event => @event.IsAffectingPropertyName(propertyName))) + .FailWith( + "Did not expect object {0} to raise the {1} event for property {2}{reason}, but it did.", Monitor.Subject, PropertyChangedEventName, propertyName); } } - private static string GetAffectedPropertyName(OccurredEvent @event) - { - return @event.Parameters.OfType().Single().PropertyName; - } - protected override string Identifier => "subject"; } diff --git a/Src/FluentAssertions/Events/EventMonitor.cs b/Src/FluentAssertions/Events/EventMonitor.cs index a0bcf681ef..20ae36d1ea 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; @@ -16,31 +17,30 @@ internal sealed class EventMonitor : IMonitor { private readonly WeakReference subject; - private readonly ConcurrentDictionary recorderMap = new(); + private readonly ConcurrentDictionary recorderMap = new(StringComparer.Ordinal); - public EventMonitor(object eventSource, Func utcNow) + public EventMonitor(object eventSource, EventMonitorOptions options) { Guard.ThrowIfArgumentIsNull(eventSource, nameof(eventSource), "Cannot monitor the events of a object."); + Guard.ThrowIfArgumentIsNull(options, nameof(options), "Event monitor needs configuration."); + + this.options = options; subject = new WeakReference(eventSource); - Attach(typeof(T), utcNow); + Attach(typeof(T), this.options.TimestampProvider); } public T Subject => (T)subject.Target; private readonly ThreadSafeSequenceGenerator threadSafeSequenceGenerator = new(); + private readonly EventMonitorOptions options; - public EventMetadata[] MonitoredEvents - { - get - { - return recorderMap - .Values - .Select(recorder => new EventMetadata(recorder.EventName, recorder.EventHandlerType)) - .ToArray(); - } - } + public EventMetadata[] MonitoredEvents => + recorderMap + .Values + .Select(recorder => new EventMetadata(recorder.EventName, recorder.EventHandlerType)) + .ToArray(); public OccurredEvent[] OccurredEvents { @@ -67,7 +67,7 @@ public void Clear() public EventAssertions Should() { - return new EventAssertions(this); + return new EventAssertions(this, AssertionChain.GetOrCreate()); } public IEventRecording GetRecordingFor(string eventName) @@ -117,12 +117,24 @@ public void Dispose() { foreach (EventRecorder recorder in recorderMap.Values) { - recorder.Dispose(); + DisposeSafeIfRequested(recorder); } recorderMap.Clear(); } + private void DisposeSafeIfRequested(IDisposable recorder) + { + try + { + recorder.Dispose(); + } + catch when (options.ShouldIgnoreEventAccessorExceptions) + { + // ignore + } + } + private void AttachEventHandler(EventInfo eventInfo, Func utcNow) { if (!recorderMap.TryGetValue(eventInfo.Name, out _)) @@ -131,7 +143,22 @@ private void AttachEventHandler(EventInfo eventInfo, Func utcNow) if (recorderMap.TryAdd(eventInfo.Name, recorder)) { - recorder.Attach(subject, eventInfo); + AttachEventHandler(eventInfo, recorder); + } + } + } + + private void AttachEventHandler(EventInfo eventInfo, EventRecorder recorder) + { + try + { + recorder.Attach(subject, eventInfo); + } + catch when (options.ShouldIgnoreEventAccessorExceptions) + { + if (!options.ShouldRecordEventsWithBrokenAccessor) + { + recorderMap.TryRemove(eventInfo.Name, out _); } } } diff --git a/Src/FluentAssertions/Events/EventMonitorOptions.cs b/Src/FluentAssertions/Events/EventMonitorOptions.cs new file mode 100644 index 0000000000..c6cbf76d25 --- /dev/null +++ b/Src/FluentAssertions/Events/EventMonitorOptions.cs @@ -0,0 +1,56 @@ +using System; + +namespace FluentAssertions.Events; + +/// +/// Settings for the EventMonitor. +/// +public class EventMonitorOptions +{ + /// + /// Will ignore the events, if they throw an exception on any custom event accessor implementation. default: false. + /// + internal bool ShouldIgnoreEventAccessorExceptions { get; private set; } + + /// + /// This will record the event, even if the event accessor add event threw an exception. To ignore exceptions in the event add accessor, call property to set it to true. default: false. + /// + internal bool ShouldRecordEventsWithBrokenAccessor { get; private set; } + + /// + /// Func used to generate the timestamp. + /// + internal Func TimestampProvider { get; private set; } = () => DateTime.UtcNow; + + /// + /// When called it will ignore event accessor Exceptions. + /// + public EventMonitorOptions IgnoringEventAccessorExceptions() + { + ShouldIgnoreEventAccessorExceptions = true; + return this; + } + + /// + /// When called it will record the event even when the accessor threw an exception. + /// + public EventMonitorOptions RecordingEventsWithBrokenAccessor() + { + ShouldRecordEventsWithBrokenAccessor = true; + return this; + } + + /// + /// Sets the timestamp provider. By default it is . + /// + /// The timestamp provider. + internal EventMonitorOptions ConfigureTimestampProvider(Func timestampProvider) + { + if (timestampProvider != null) + { + TimestampProvider = timestampProvider; + } + + return this; + } +} diff --git a/Src/FluentAssertions/Events/EventRecorder.cs b/Src/FluentAssertions/Events/EventRecorder.cs index 11933f1fd2..768449083e 100644 --- a/Src/FluentAssertions/Events/EventRecorder.cs +++ b/Src/FluentAssertions/Events/EventRecorder.cs @@ -15,7 +15,7 @@ namespace FluentAssertions.Events; internal sealed class EventRecorder : IEventRecording, IDisposable { private readonly Func utcNow; - private readonly BlockingCollection raisedEvents = new(); + private readonly BlockingCollection raisedEvents = []; private readonly object lockable = new(); private Action cleanup; diff --git a/Src/FluentAssertions/Events/OccurredEvent.cs b/Src/FluentAssertions/Events/OccurredEvent.cs index a6b92e1a71..6f062b7050 100644 --- a/Src/FluentAssertions/Events/OccurredEvent.cs +++ b/Src/FluentAssertions/Events/OccurredEvent.cs @@ -1,4 +1,6 @@ using System; +using System.ComponentModel; +using System.Linq; namespace FluentAssertions.Events; @@ -26,4 +28,19 @@ public class OccurredEvent /// The order in which this event was raised on the monitored object. /// public int Sequence { get; set; } + + /// + /// Verifies if a property changed event is affecting a particular property. + /// + /// + /// The property name for which the property changed event should have been raised. + /// + /// + /// Returns if the event is affecting the property specified, otherwise. + /// + internal bool IsAffectingPropertyName(string propertyName) + { + return Parameters.OfType() + .Any(e => string.IsNullOrEmpty(e.PropertyName) || e.PropertyName == propertyName); + } } diff --git a/Src/FluentAssertions/ExceptionAssertionsExtensions.cs b/Src/FluentAssertions/ExceptionAssertionsExtensions.cs index 728bf7ca51..45de65a1a6 100644 --- a/Src/FluentAssertions/ExceptionAssertionsExtensions.cs +++ b/Src/FluentAssertions/ExceptionAssertionsExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using System.Threading.Tasks; using FluentAssertions.Execution; @@ -27,7 +28,7 @@ public static class ExceptionAssertionsExtensions public static async Task> WithMessage( this Task> task, string expectedWildcardPattern, - string because = "", + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TException : Exception { @@ -51,7 +52,7 @@ public static async Task> WithMessage> Where( this Task> task, Expression> exceptionExpression, - string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TException : Exception { return (await task).Where(exceptionExpression, because, becauseArgs); @@ -72,7 +73,7 @@ public static async Task> Where( /// public static async Task> WithInnerException( this Task> task, - string because = "", + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TException : Exception where TInnerException : Exception @@ -95,7 +96,7 @@ public static async Task> WithInnerExceptio public static async Task> WithInnerException( this Task> task, Type innerException, - string because = "", + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TException : Exception { @@ -117,7 +118,7 @@ public static async Task> WithInnerException public static async Task> WithInnerExceptionExactly( this Task> task, - string because = "", + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TException : Exception where TInnerException : Exception @@ -140,7 +141,7 @@ public static async Task> WithInnerExceptio public static async Task> WithInnerExceptionExactly( this Task> task, Type innerException, - string because = "", + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TException : Exception { @@ -162,11 +163,12 @@ public static async Task> WithInnerExceptionExact public static ExceptionAssertions WithParameterName( this ExceptionAssertions parent, string paramName, - string because = "", + [StringSyntax("CompositeFormat")] string because = "", 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); @@ -189,7 +191,7 @@ public static ExceptionAssertions WithParameterName( public static async Task> WithParameterName( this Task> task, string paramName, - string because = "", + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TException : ArgumentException { diff --git a/Src/FluentAssertions/Execution/AssertionChain.cs b/Src/FluentAssertions/Execution/AssertionChain.cs new file mode 100644 index 0000000000..7e9a3c77aa --- /dev/null +++ b/Src/FluentAssertions/Execution/AssertionChain.cs @@ -0,0 +1,351 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using System.Threading; +using FluentAssertions.Common; + +namespace FluentAssertions.Execution; + +/// +/// Provides a fluent API to build simple or composite assertions, and which can flow from one assertion to another. +/// +/// +/// This is the core engine of many of the assertion APIs in this library. When combined with , +/// you can run multiple assertions which failure messages will be collected until the scope is disposed. +/// +public sealed class AssertionChain +{ + private readonly Func getCurrentScope; + private readonly ContextDataDictionary contextData = new(); + private readonly SubjectIdentificationBuilder identifierBuilder; + private string fallbackIdentifier = "object"; + 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 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 => identifierBuilder.HasOverriddenIdentifier; + + /// + /// 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.DetermineCallerIdentities()); + } + + private AssertionChain(Func getCurrentScope, Func getCallerIdentifiers) + { + this.getCurrentScope = getCurrentScope; + + identifierBuilder = new SubjectIdentificationBuilder(getCallerIdentifiers, () => getCurrentScope().Name()); + } + + /// + /// The effective caller identifier including any prefixes and postfixes configured through + /// . + /// + /// + /// Can be overridden with . + /// + public string CallerIdentifier => identifierBuilder.Build(); + + /// + /// 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([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; + } + + [SuppressMessage("Maintainability", "AV1564:Parameter in public or internal member is of type bool or bool?")] + 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))); + + 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) + { + identifierBuilder.OverrideSubjectIdentifier(getCallerIdentifier); + } + + /// + /// 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) + { + identifierBuilder.UsePostfix(postfix); + + return this; + } + + /// + /// Marks the next assertion as being part of a chained call to Should where it needs to find the next + /// caller identifier. + /// + internal void AdvanceToNextIdentifier() + { + identifierBuilder.AdvanceToNextSubject(); + } + + /// + /// 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/AssertionFailedException.cs b/Src/FluentAssertions/Execution/AssertionFailedException.cs index 391b67227c..d21f2f6ab8 100644 --- a/Src/FluentAssertions/Execution/AssertionFailedException.cs +++ b/Src/FluentAssertions/Execution/AssertionFailedException.cs @@ -1,23 +1,13 @@ using System; -using System.Runtime.Serialization; namespace FluentAssertions.Execution; /// /// Represents the default exception in case no test framework is configured. /// -[Serializable] +/// The mandatory exception message #pragma warning disable CA1032, RCS1194 // AssertionFailedException should never be constructed with an empty message -public class AssertionFailedException : Exception +public class AssertionFailedException(string message) : Exception(message), IAssertionException #pragma warning restore CA1032, RCS1194 { - public AssertionFailedException(string message) - : base(message) - { - } - - protected AssertionFailedException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } } diff --git a/Src/FluentAssertions/Execution/AssertionScope.cs b/Src/FluentAssertions/Execution/AssertionScope.cs index 71a9a32f41..53c56c0c79 100644 --- a/Src/FluentAssertions/Execution/AssertionScope.cs +++ b/Src/FluentAssertions/Execution/AssertionScope.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Text; using System.Threading; @@ -13,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 life time and is not safe to be used in assertion 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 ContextDataItems 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); } /// @@ -75,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); } /// @@ -95,47 +67,51 @@ public AssertionScope(Lazy context) /// /// The assertion strategy for this scope. /// is . - private AssertionScope(IAssertionStrategy assertionStrategy, Lazy context) + private AssertionScope(Func name, IAssertionStrategy assertionStrategy) { - this.assertionStrategy = assertionStrategy - ?? throw new ArgumentNullException(nameof(assertionStrategy)); + parent = CurrentScope.Value; + CurrentScope.Value = this; - parent = GetCurrentAssertionScope(); + this.assertionStrategy = assertionStrategy + ?? throw new ArgumentNullException(nameof(assertionStrategy)); 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(); + } + + if (name().IsNullOrEmpty()) + { + return parentName; + } + + 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. @@ -145,213 +121,47 @@ 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; } /// /// Exposes the options the scope will use for formatting objects in case an assertion fails. /// - public FormattingOptions FormattingOptions { get; } = AssertionOptions.FormattingOptions.Clone(); - - internal bool Succeeded => succeeded == true; + public FormattingOptions FormattingOptions { get; } = AssertionConfiguration.Current.Formatting.Clone(); /// - /// Adds an explanation of why the assertion is supposed to succeed to the scope. + /// Adds a pre-formatted failure message to the current scope. /// - public AssertionScope BecauseOf(Reason reason) - { - return BecauseOf(reason.FormattedMessage, reason.Arguments); - } - - /// - public AssertionScope 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 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 ContextDataItems.DataItem("subject", subject, reportable: false, requiresFormatting: true)); - contextData.Add(new ContextDataItems.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) + public void AddPreFormattedFailure(string formattedFailureMessage) { - succeeded = condition; - - return this; + assertionStrategy.HandleFailure(formattedFailureMessage); } /// - /// 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. - /// + /// Adds some information to the assertion scope that will be included in the message + /// that is emitted if an assertion fails. /// - /// defining the number of expected occurrences. - /// The number of actual occurrences. - public AssertionScope ForConstraint(OccurrenceConstraint constraint, int actualOccurrences) + internal void AddReportable(string key, string value) { - constraint.RegisterReportables(this); - succeeded = constraint.Assert(actualOccurrences); - - return this; - } - - /// - public Continuation FailWith(Func failReasonFunc) - { - return FailWith(() => + reportableData.Add(new ContextDataDictionary.DataItem(key, value) { - 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; + Reportable = true, }); } - 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. + /// 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. /// - public void AddPreFormattedFailure(string formattedFailureMessage) + internal void AddReportable(string key, Func valueFunc) { - assertionStrategy.HandleFailure(formattedFailureMessage); + reportableData.Add(new ContextDataDictionary.DataItem(key, new DeferredReportable(valueFunc)) + { + Reportable = true + }); } /// @@ -362,33 +172,6 @@ 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. - /// - public void AddNonReportable(string key, object value) - { - contextData.Add(new ContextDataItems.DataItem(key, value, reportable: false, requiresFormatting: false)); - } - - /// - /// Adds some information to the assertion scope that will be included in the message - /// that is emitted if an assertion fails. - /// - public void AddReportable(string key, string value) - { - contextData.Add(new ContextDataItems.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. The value is only calculated on failure. - /// - public void AddReportable(string key, Func valueFunc) - { - contextData.Add(new ContextDataItems.DataItem(key, new DeferredReportable(valueFunc), reportable: true, - requiresFormatting: false)); - } - /// /// Returns all failures that happened up to this point and ensures they will not cause /// to fail the assertion. @@ -403,18 +186,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) { @@ -423,63 +198,29 @@ 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 + }); } - assertionStrategy.ThrowIfAny(reportable); + assertionStrategy.ThrowIfAny(reportableData.GetReportable()); } } - /// - public AssertionScope WithDefaultIdentifier(string identifier) + private sealed class DeferredReportable(Func valueFunc) { - fallbackIdentifier = identifier; - return this; - } + private readonly Lazy lazyValue = new(valueFunc); - /// - /// 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 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.BecauseOf(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/CollectingAssertionStrategy.cs b/Src/FluentAssertions/Execution/CollectingAssertionStrategy.cs index 9a9394c63b..4c017f1806 100644 --- a/Src/FluentAssertions/Execution/CollectingAssertionStrategy.cs +++ b/Src/FluentAssertions/Execution/CollectingAssertionStrategy.cs @@ -3,13 +3,12 @@ using System.Globalization; using System.Linq; using System.Text; -using FluentAssertions.Common; namespace FluentAssertions.Execution; internal class CollectingAssertionStrategy : IAssertionStrategy { - private readonly List failureMessages = new(); + private readonly List failureMessages = []; /// /// Returns the messages for the assertion failures that happened until now. @@ -44,7 +43,7 @@ public void ThrowIfAny(IDictionary context) } } - Services.ThrowException(builder.ToString()); + AssertionEngine.TestFramework.Throw(builder.ToString()); } } diff --git a/Src/FluentAssertions/Execution/ContextDataItems.cs b/Src/FluentAssertions/Execution/ContextDataDictionary.cs similarity index 52% rename from Src/FluentAssertions/Execution/ContextDataItems.cs rename to Src/FluentAssertions/Execution/ContextDataDictionary.cs index 519200f7e4..7bc42fdd97 100644 --- a/Src/FluentAssertions/Execution/ContextDataItems.cs +++ b/Src/FluentAssertions/Execution/ContextDataDictionary.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using FluentAssertions.Formatting; @@ -7,13 +8,15 @@ namespace FluentAssertions.Execution; /// /// Represents a collection of data items that are associated with an . /// -internal class ContextDataItems +internal class ContextDataDictionary { - private readonly List items = new(); + private readonly List items = []; public IDictionary GetReportable() { - return items.Where(item => item.Reportable).ToDictionary(item => item.Key, item => item.Value); + return items + .Where(item => item.Reportable) + .ToDictionary(item => item.Key, item => item.Value, StringComparer.Ordinal); } public string AsStringOrDefault(string key) @@ -33,9 +36,9 @@ public string AsStringOrDefault(string key) return null; } - public void Add(ContextDataItems contextDataItems) + public void Add(ContextDataDictionary contextDataDictionary) { - foreach (DataItem item in contextDataItems.items) + foreach (DataItem item in contextDataDictionary.items) { Add(item.Clone()); } @@ -44,7 +47,6 @@ public void Add(ContextDataItems contextDataItems) public void Add(DataItem item) { int existingItemIndex = items.FindIndex(i => i.Key == item.Key); - if (existingItemIndex >= 0) { items[existingItemIndex] = item; @@ -55,34 +57,24 @@ public void Add(DataItem item) } } - public T Get(string key) + public class DataItem(string key, object value) { - DataItem item = items.SingleOrDefault(i => i.Key == key); - return (T)(item?.Value ?? default(T)); - } + public string Key { get; } = key; - internal class DataItem - { - public DataItem(string key, object value, bool reportable, bool requiresFormatting) - { - Key = key; - Value = value; - Reportable = reportable; - RequiresFormatting = requiresFormatting; - } - - public string Key { get; } + public object Value { get; } = value; - public object Value { get; } + public bool Reportable { get; init; } - public bool Reportable { get; } - - public bool RequiresFormatting { get; } + public bool RequiresFormatting { get; init; } public DataItem Clone() { - object value = Value is ICloneable2 cloneable ? cloneable.Clone() : Value; - return new DataItem(Key, value, Reportable, RequiresFormatting); + object clone = Value is ICloneable2 cloneable ? cloneable.Clone() : Value; + return new DataItem(Key, clone) + { + Reportable = Reportable, + RequiresFormatting = RequiresFormatting + }; } } } 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 52cddb56f5..0000000000 --- a/Src/FluentAssertions/Execution/ContinuedAssertionScope.cs +++ /dev/null @@ -1,144 +0,0 @@ -using System; - -namespace FluentAssertions.Execution; - -/// -/// Allows chaining multiple assertion scopes together using . -/// -/// -/// If the parent scope has captured a failed assertion, -/// this class ensures that successive assertions are no longer evaluated. -/// -public 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 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(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/DefaultAssertionStrategy.cs b/Src/FluentAssertions/Execution/DefaultAssertionStrategy.cs index 3af8859666..2a0a5d8e03 100644 --- a/Src/FluentAssertions/Execution/DefaultAssertionStrategy.cs +++ b/Src/FluentAssertions/Execution/DefaultAssertionStrategy.cs @@ -1,37 +1,28 @@ -using System; using System.Collections.Generic; -using FluentAssertions.Common; +using System.Diagnostics.CodeAnalysis; namespace FluentAssertions.Execution; +[ExcludeFromCodeCoverage] internal class DefaultAssertionStrategy : IAssertionStrategy { /// /// Returns the messages for the assertion failures that happened until now. /// - public IEnumerable FailureMessages - { - get - { - return Array.Empty(); - } - } + public IEnumerable FailureMessages => []; /// /// Instructs the strategy to handle a assertion failure. /// public void HandleFailure(string message) { - Services.ThrowException(message); + AssertionEngine.TestFramework.Throw(message); } /// /// Discards and returns the failure messages that happened up to now. /// - public IEnumerable DiscardFailures() - { - return Array.Empty(); - } + public IEnumerable DiscardFailures() => []; /// /// Will throw a combined exception for any failures have been collected. diff --git a/Src/FluentAssertions/Execution/Execute.cs b/Src/FluentAssertions/Execution/Execute.cs deleted file mode 100644 index 208e98fb35..0000000000 --- a/Src/FluentAssertions/Execution/Execute.cs +++ /dev/null @@ -1,12 +0,0 @@ -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 => AssertionScope.Current; -} diff --git a/Src/FluentAssertions/Execution/FailReason.cs b/Src/FluentAssertions/Execution/FailReason.cs index a315164977..27bab552db 100644 --- a/Src/FluentAssertions/Execution/FailReason.cs +++ b/Src/FluentAssertions/Execution/FailReason.cs @@ -1,15 +1,15 @@ -namespace FluentAssertions.Execution; +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 65% rename from Src/FluentAssertions/Execution/MessageBuilder.cs rename to Src/FluentAssertions/Execution/FailureMessageFormatter.cs index 27f1b82e2a..9fc73c1682 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, ContextDataItems 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); @@ -46,7 +94,7 @@ public string Build(string message, object[] messageArgs, string reason, Context private static string SubstituteIdentifier(string message, string identifier, string fallbackIdentifier) { - const string pattern = @"(?:\s|^)\{context(?:\:(?[a-z|A-Z|\s]+))?\}"; + const string pattern = @"(?:\s|^)\{context(?:\:(?[a-zA-Z\s]+))?\}"; message = Regex.Replace(message, pattern, match => { @@ -75,9 +123,9 @@ private static string SubstituteIdentifier(string message, string identifier, st return message.TrimStart(); } - private static string SubstituteContextualTags(string message, ContextDataItems contextData) + private static string SubstituteContextualTags(string message, ContextDataDictionary contextData) { - const string pattern = @"(?[a-z|A-Z]+)(?:\:(?[a-z|A-Z|\s]+))?\}(?!\})"; + const string pattern = @"(?[a-zA-Z]+)(?:\:(?[a-zA-Z\s]+))?\}(?!\})"; return Regex.Replace(message, pattern, match => { @@ -91,7 +139,7 @@ private static string SubstituteContextualTags(string message, ContextDataItems 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/IAssertionException.cs b/Src/FluentAssertions/Execution/IAssertionException.cs new file mode 100644 index 0000000000..8c8c519d46 --- /dev/null +++ b/Src/FluentAssertions/Execution/IAssertionException.cs @@ -0,0 +1,7 @@ +namespace FluentAssertions.Execution; + +/// +/// This is a marker interface for xUnit.net v3 to set the test failure cause as an assertion failure. +/// See What’s New in xUnit.net v3 - Third party assertion library extension points. +/// +internal interface IAssertionException; diff --git a/Src/FluentAssertions/Execution/IAssertionScope.cs b/Src/FluentAssertions/Execution/IAssertionScope.cs deleted file mode 100644 index 0ed41c0292..0000000000 --- a/Src/FluentAssertions/Execution/IAssertionScope.cs +++ /dev/null @@ -1,173 +0,0 @@ -using System; - -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); - - /// - /// 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(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/Execution/ITestFramework.cs b/Src/FluentAssertions/Execution/ITestFramework.cs index dcbdabb840..9aa877ea26 100644 --- a/Src/FluentAssertions/Execution/ITestFramework.cs +++ b/Src/FluentAssertions/Execution/ITestFramework.cs @@ -5,7 +5,7 @@ namespace FluentAssertions.Execution; /// /// Represents an abstraction of a particular test framework such as MSTest, nUnit, etc. /// -internal interface ITestFramework +public interface ITestFramework { /// /// Gets a value indicating whether the corresponding test framework is currently available. diff --git a/Src/FluentAssertions/Execution/LateBoundTestFramework.cs b/Src/FluentAssertions/Execution/LateBoundTestFramework.cs index 087f4043e5..010150d46a 100644 --- a/Src/FluentAssertions/Execution/LateBoundTestFramework.cs +++ b/Src/FluentAssertions/Execution/LateBoundTestFramework.cs @@ -1,40 +1,63 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Reflection; namespace FluentAssertions.Execution; internal abstract class LateBoundTestFramework : ITestFramework { - private Assembly assembly; + private Func exceptionFactory = + _ => throw new InvalidOperationException($"{nameof(IsAvailable)} must be called first."); - [DoesNotReturn] - public void Throw(string message) - { - Type exceptionType = assembly.GetType(ExceptionFullName); + /// + /// When set to , the assembly specified by the property will + /// be dynamically loaded if it is not already loaded in the application domain. + /// When set to , the framework will not attempt to load the assembly dynamically. + /// + protected bool LoadAssembly { get; init; } - if (exceptionType is null) - { - throw new NotSupportedException( - $"Failed to create the assertion exception for the current test framework: \"{ExceptionFullName}, {assembly.FullName}\""); - } - - throw (Exception)Activator.CreateInstance(exceptionType, message); - } + [DoesNotReturn] + public void Throw(string message) => throw exceptionFactory(message); public bool IsAvailable { get { - string prefix = AssemblyName + ","; + var assembly = FindExceptionAssembly(); + var exceptionType = assembly?.GetType(ExceptionFullName); - assembly = Array.Find(AppDomain.CurrentDomain - .GetAssemblies(), a => a.FullName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)); + exceptionFactory = exceptionType != null + ? message => (Exception)Activator.CreateInstance(exceptionType, message) + : _ => throw new InvalidOperationException($"{GetType().Name} is not available"); - return assembly is not null; + return exceptionType is not null; } } + private Assembly FindExceptionAssembly() + { + var assembly = Array.Find(AppDomain.CurrentDomain.GetAssemblies(), a => a.GetName().Name == AssemblyName); + + if (assembly is null && LoadAssembly) + { + try + { + return Assembly.Load(new AssemblyName(AssemblyName)); + } + catch (FileNotFoundException) + { + return null; + } + catch (FileLoadException) + { + return null; + } + } + + return assembly; + } + protected internal abstract string AssemblyName { get; } protected abstract string ExceptionFullName { get; } diff --git a/Src/FluentAssertions/Execution/Reason.cs b/Src/FluentAssertions/Execution/Reason.cs index 79585d1be4..ca6996b66f 100644 --- a/Src/FluentAssertions/Execution/Reason.cs +++ b/Src/FluentAssertions/Execution/Reason.cs @@ -1,3 +1,5 @@ +using System.Diagnostics.CodeAnalysis; + namespace FluentAssertions.Execution; /// @@ -5,7 +7,7 @@ namespace FluentAssertions.Execution; /// public class Reason { - public Reason(string formattedMessage, object[] arguments) + public Reason([StringSyntax("CompositeFormat")] string formattedMessage, object[] arguments) { FormattedMessage = formattedMessage; Arguments = arguments; diff --git a/Src/FluentAssertions/Execution/StringExtensions.cs b/Src/FluentAssertions/Execution/StringExtensions.cs new file mode 100644 index 0000000000..ab1b13eb16 --- /dev/null +++ b/Src/FluentAssertions/Execution/StringExtensions.cs @@ -0,0 +1,20 @@ +namespace FluentAssertions.Execution; + +internal static class StringExtensions +{ + /// + /// Wraps the specified string in a to prevent any formatting applied during output. + /// + public static WithoutFormattingWrapper AsNonFormattable(this string value) + { + return new WithoutFormattingWrapper(value); + } + + /// + /// Wraps the specified value in a to prevent any formatting applied during output. + /// + public static WithoutFormattingWrapper AsNonFormattable(this object value) + { + return new WithoutFormattingWrapper(value?.ToString()); + } +} diff --git a/Src/FluentAssertions/Execution/SubjectIdentificationBuilder.cs b/Src/FluentAssertions/Execution/SubjectIdentificationBuilder.cs new file mode 100644 index 0000000000..8d8371000d --- /dev/null +++ b/Src/FluentAssertions/Execution/SubjectIdentificationBuilder.cs @@ -0,0 +1,102 @@ +using System; +using FluentAssertions.Common; + +namespace FluentAssertions.Execution; + +/// +/// Responsible for constructing the code phrase that identifies the subject-under-test and which is used +/// in assertion failures managed by a single . +/// +/// +/// Takes into account the caller identifiers extracted from the invoking code, any scopes created through +/// (nested) instances of and modifications made by the +/// . +/// +internal class SubjectIdentificationBuilder +{ + private readonly Func getScopeName; + private readonly Lazy identifiersExtractedFromTheCode; + private int identifierIndex; + private Func getSubject; + + public SubjectIdentificationBuilder(Func getCallerIdentifiers, Func getScopeName) + { + this.getScopeName = getScopeName; + + identifiersExtractedFromTheCode = new(() => getCallerIdentifiers()); + + getSubject = () => GetIdentifier(0); + } + + /// + /// Tells the builder to assume the assertion has moved to the next identifier in a chained assertion. + /// + public void AdvanceToNextSubject() + { + identifierIndex++; + var localIndex = identifierIndex; + + getSubject = () => GetIdentifier(localIndex); + } + + /// + /// Indicates whether the subject identifier has been overridden with a custom value during the assertion process. + /// + /// + /// When or is called, + /// this property is set to true, signaling that a manually defined identifier is now used instead of + /// automatically constructed identifiers. + /// + public bool HasOverriddenIdentifier { get; private set; } + + /// + /// Completely overrides the construction process with the specified lazy-evaluated identifier. + /// + public void OverrideSubjectIdentifier(Func getSubject) + { + HasOverriddenIdentifier = true; + this.getSubject = getSubject; + } + + /// + /// Appends the given postfix to the current subject and combines that with next subject. + /// + /// + /// If the postfix is [0], the current identifier is collection, and the next one + /// is Parameters, this method will assume the subject is collection[0].Parameters. + /// + public void UsePostfix(string postfix) + { + var localIndex = identifierIndex; + getSubject = () => (GetIdentifier(localIndex) + postfix).Combine(GetIdentifier(localIndex + 1)); + + HasOverriddenIdentifier = true; + } + + /// + /// Returns the constructed subject identifier. + /// + public string Build() + { + var scopeName = getScopeName(); + var callerIdentifier = getSubject(); + + if (scopeName is null) + { + return callerIdentifier ?? ""; + } + else if (callerIdentifier is null) + { + return scopeName; + } + else + { + return $"{scopeName}/{callerIdentifier}"; + } + } + + private string GetIdentifier(int index) + { + return identifiersExtractedFromTheCode.Value.Length > index ? identifiersExtractedFromTheCode.Value[index] : null; + } +} diff --git a/Src/FluentAssertions/Execution/TUnitFramework.cs b/Src/FluentAssertions/Execution/TUnitFramework.cs new file mode 100644 index 0000000000..94e8337ee6 --- /dev/null +++ b/Src/FluentAssertions/Execution/TUnitFramework.cs @@ -0,0 +1,13 @@ +namespace FluentAssertions.Execution; + +internal class TUnitFramework : LateBoundTestFramework +{ + public TUnitFramework() + { + LoadAssembly = true; + } + + protected override string ExceptionFullName => "TUnit.Assertions.Exceptions.AssertionException"; + + protected internal override string AssemblyName => "TUnit.Assertions"; +} diff --git a/Src/FluentAssertions/Execution/TestFrameworkFactory.cs b/Src/FluentAssertions/Execution/TestFrameworkFactory.cs new file mode 100644 index 0000000000..34ab089dde --- /dev/null +++ b/Src/FluentAssertions/Execution/TestFrameworkFactory.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using FluentAssertions.Configuration; + +namespace FluentAssertions.Execution; + +/// +/// Determines the test framework, either by scanning the current app domain for known test framework assemblies or by +/// passing the framework name directly. +/// +internal static class TestFrameworkFactory +{ + private static readonly Dictionary Frameworks = new() + { + [TestFramework.MSpec] = new MSpecFramework(), + [TestFramework.NUnit] = new NUnitTestFramework(), + [TestFramework.MsTest] = new MSTestFrameworkV2(), + + // Keep TUnitFramework and XUnitTestFramework last as they use a try/catch approach + [TestFramework.TUnit] = new TUnitFramework(), + [TestFramework.XUnit2] = new XUnitTestFramework("xunit.assert"), + [TestFramework.XUnit3] = new XUnitTestFramework("xunit.v3.assert"), + }; + + public static ITestFramework GetFramework(TestFramework? testFrameWork) + { + ITestFramework framework = null; + + if (testFrameWork is not null) + { + framework = AttemptToDetectUsingSetting((TestFramework)testFrameWork); + } + + framework ??= AttemptToDetectUsingDynamicScanning(); + + return framework ?? new FallbackTestFramework(); + } + + private static ITestFramework AttemptToDetectUsingSetting(TestFramework framework) + { + if (!Frameworks.TryGetValue(framework, out ITestFramework implementation)) + { + string frameworks = string.Join(", ", Frameworks.Keys); + var message = + $"FluentAssertions was configured to use the test framework '{framework}' but this is not supported. " + + $"Please use one of the supported frameworks: {frameworks}."; + + throw new InvalidOperationException(message); + } + + if (!implementation.IsAvailable) + { + string frameworks = string.Join(", ", Frameworks.Keys); + + var innerMessage = implementation is LateBoundTestFramework lateBoundTestFramework + ? $"the required assembly '{lateBoundTestFramework.AssemblyName}' could not be found" + : "it could not be found"; + + var message = + $"FluentAssertions was configured to use the test framework '{framework}' but {innerMessage}. " + + $"Please use one of the supported frameworks: {frameworks}."; + + throw new InvalidOperationException(message); + } + + return implementation; + } + + private static ITestFramework AttemptToDetectUsingDynamicScanning() + { + return Frameworks.Values.FirstOrDefault(framework => framework.IsAvailable); + } +} diff --git a/Src/FluentAssertions/Execution/TestFrameworkProvider.cs b/Src/FluentAssertions/Execution/TestFrameworkProvider.cs deleted file mode 100644 index f5e291d8f9..0000000000 --- a/Src/FluentAssertions/Execution/TestFrameworkProvider.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using FluentAssertions.Common; - -namespace FluentAssertions.Execution; - -/// -/// Implements a wrapper around all supported test frameworks to throw the correct assertion exception. -/// -internal class TestFrameworkProvider -{ - #region Private Definitions - - private static readonly Dictionary Frameworks = new(StringComparer.OrdinalIgnoreCase) - { - ["mspec"] = new MSpecFramework(), - ["nunit"] = new NUnitTestFramework(), - ["mstestv2"] = new MSTestFrameworkV2(), - ["xunit2"] = new XUnit2TestFramework() // Keep this the last one as it uses a try/catch approach - }; - - private readonly Configuration configuration; - - private ITestFramework testFramework; - - #endregion - - public TestFrameworkProvider(Configuration configuration) - { - this.configuration = configuration; - } - - [DoesNotReturn] - public void Throw(string message) - { - testFramework ??= DetectFramework(); - testFramework.Throw(message); - } - - private ITestFramework DetectFramework() - { - ITestFramework detectedFramework = AttemptToDetectUsingAppSetting() - ?? AttemptToDetectUsingDynamicScanning() - ?? new FallbackTestFramework(); - - return detectedFramework; - } - - private ITestFramework AttemptToDetectUsingAppSetting() - { - string frameworkName = configuration.TestFrameworkName; - - if (string.IsNullOrEmpty(frameworkName)) - { - return null; - } - - if (!Frameworks.TryGetValue(frameworkName, out ITestFramework framework)) - { - string frameworks = string.Join(", ", Frameworks.Keys); - - var message = - $"FluentAssertions was configured to use the test framework '{frameworkName}' but this is not supported. " + - $"Please use one of the supported frameworks: {frameworks}."; - - throw new InvalidOperationException(message); - } - - if (!framework.IsAvailable) - { - string frameworks = string.Join(", ", Frameworks.Keys); - - var innerMessage = framework is LateBoundTestFramework lateBoundTestFramework - ? $"the required assembly '{lateBoundTestFramework.AssemblyName}' could not be found" - : "it could not be found"; - - var message = - $"FluentAssertions was configured to use the test framework '{frameworkName}' but {innerMessage}. " + - $"Please use one of the supported frameworks: {frameworks}."; - - throw new InvalidOperationException(message); - } - - return framework; - } - - private static ITestFramework AttemptToDetectUsingDynamicScanning() - { - return Frameworks.Values.FirstOrDefault(framework => framework.IsAvailable); - } -} diff --git a/Src/FluentAssertions/Execution/WithoutFormattingWrapper.cs b/Src/FluentAssertions/Execution/WithoutFormattingWrapper.cs new file mode 100644 index 0000000000..0696cde026 --- /dev/null +++ b/Src/FluentAssertions/Execution/WithoutFormattingWrapper.cs @@ -0,0 +1,11 @@ +using FluentAssertions.Formatting; + +namespace FluentAssertions.Execution; + +/// +/// Wrapper to tell the not to apply any value formatters on this string. +/// +internal class WithoutFormattingWrapper(string value) +{ + public override string ToString() => value; +} diff --git a/Src/FluentAssertions/Execution/XUnit2TestFramework.cs b/Src/FluentAssertions/Execution/XUnit2TestFramework.cs deleted file mode 100644 index 5dc2b38012..0000000000 --- a/Src/FluentAssertions/Execution/XUnit2TestFramework.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using System.Reflection; - -namespace FluentAssertions.Execution; - -/// -/// Implements the XUnit (version 2) test framework adapter. -/// -internal class XUnit2TestFramework : ITestFramework -{ - private Assembly assembly; - - public bool IsAvailable - { - get - { - try - { - // For netfx the assembly is not in AppDomain by default, so we can't just scan AppDomain.CurrentDomain - assembly = Assembly.Load(new AssemblyName("xunit.assert")); - - return assembly is not null; - } - catch - { - return false; - } - } - } - - [DoesNotReturn] - public void Throw(string message) - { - Type exceptionType = assembly.GetType("Xunit.Sdk.XunitException") - ?? throw new NotSupportedException("Failed to create the XUnit assertion type"); - - throw (Exception)Activator.CreateInstance(exceptionType, message); - } -} diff --git a/Src/FluentAssertions/Execution/XUnitTestFramework.cs b/Src/FluentAssertions/Execution/XUnitTestFramework.cs new file mode 100644 index 0000000000..8ef0c1d7dc --- /dev/null +++ b/Src/FluentAssertions/Execution/XUnitTestFramework.cs @@ -0,0 +1,22 @@ +namespace FluentAssertions.Execution; + +/// +/// Implements the xUnit (version 2 and 3) test framework adapter. +/// +internal class XUnitTestFramework : LateBoundTestFramework +{ + private readonly string assemblyName; + + /// + /// Implements the xUnit (version 2 and 3) test framework adapter. + /// + public XUnitTestFramework(string assemblyName) + { + this.assemblyName = assemblyName; + LoadAssembly = true; + } + + protected internal override string AssemblyName => assemblyName; + + protected override string ExceptionFullName => "Xunit.Sdk.XunitException"; +} diff --git a/Src/FluentAssertions/Extensibility/AssertionEngineInitializerAttribute.cs b/Src/FluentAssertions/Extensibility/AssertionEngineInitializerAttribute.cs new file mode 100644 index 0000000000..c6f5d4f4e2 --- /dev/null +++ b/Src/FluentAssertions/Extensibility/AssertionEngineInitializerAttribute.cs @@ -0,0 +1,30 @@ +using System; +using System.Reflection; + +namespace FluentAssertions.Extensibility; + +/// +/// Can be added to an assembly so it gets a change to initialize Fluent Assertions before the first assertion happens. +/// +[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] +public sealed class AssertionEngineInitializerAttribute : Attribute +{ + private readonly string methodName; + private readonly Type type; + + /// + /// Defines the static void-returning and parameterless method that should be invoked before the first assertion happens. + /// +#pragma warning disable CA1019 + public AssertionEngineInitializerAttribute(Type type, string methodName) +#pragma warning restore CA1019 + { + this.type = type; + this.methodName = methodName; + } + + internal void Initialize() + { + type?.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static)?.Invoke(obj: null, parameters: null); + } +} diff --git a/Src/FluentAssertions/Extensions/FluentDateTimeExtensions.cs b/Src/FluentAssertions/Extensions/FluentDateTimeExtensions.cs index a9068bad60..fea03e6028 100644 --- a/Src/FluentAssertions/Extensions/FluentDateTimeExtensions.cs +++ b/Src/FluentAssertions/Extensions/FluentDateTimeExtensions.cs @@ -1,4 +1,6 @@ -using System; +#pragma warning disable AV1561 // Don’t declare signatures with more than 3 parameters + +using System; using System.Diagnostics; using FluentAssertions.Common; @@ -311,3 +313,5 @@ public static DateTimeOffset WithOffset(this DateTime self, TimeSpan offset) return self.ToDateTimeOffset(offset); } } + +#pragma warning restore AV1561 diff --git a/Src/FluentAssertions/FluentAssertions.csproj b/Src/FluentAssertions/FluentAssertions.csproj index 963db04116..d4dde24256 100644 --- a/Src/FluentAssertions/FluentAssertions.csproj +++ b/Src/FluentAssertions/FluentAssertions.csproj @@ -7,50 +7,47 @@ FluentAssertions.snk true 1591;1573 - NU1903 False $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb true true + 00240000048000009400000006020000002400005253413100040000010001002d25ff515c85b13ba08f61d466cff5d80a7f28ba197bbf8796085213e7a3406f970d2a4874932fed35db546e89af2da88c194bf1b7f7ac70de7988c78406f7629c547283061282a825616eb7eb48a9514a7570942936020a9bb37dca9ff60b778309900851575614491c6d25018fadb75828f4c7a17bf2d7dc86e7b6eafc5d8f - Dennis Doomen;Jonas Nyrup + Dennis Doomen;Jonas Nyrup;Xceed A very extensive set of extension methods that allow you to more naturally specify the expected outcome of a TDD or - BDD-style unit tests. Targets .NET Framework 4.7, .NET 6, as well as .NET Standard 2.0 and 2.1. - Supports the unit test frameworks MSTest2, NUnit3, XUnit2 and MSpec. + BDD-style unit tests. Works with .NET Standard 2.0 and higher, .NET Framework 4.7 and higher and .NET 6 and higher. + Supports the unit test frameworks MSTest2, NUnit3, NUnit4, XUnit2, XUnit3, MSpec and TUnit. - Supported by InfoSupport B.V. + IMPORTANT: This version is free for open-source projects and non-commercial use, but commercial use requires [a paid license](https://xceed.com/products/unit-testing/fluent-assertions/). +Check out the [license page](LICENSE) for more information. - https://www.fluentassertions.com + https://xceed.com/products/unit-testing/fluent-assertions/ https://github.com/fluentassertions/fluentassertions git - MSTest2;xUnit;NUnit;MSpec;TDD;BDD;Fluent;netstandard;uwp - Apache-2.0 + MSTest2;xUnit;NUnit;MSpec;TUnit;TDD;BDD;Fluent;netstandard;uwp + LICENSE + true FluentAssertions.png See https://fluentassertions.com/releases/ - Copyright Dennis Doomen 2010-$([System.DateTime]::Now.ToString(yyyy)) + Copyright 2024-$([System.DateTime]::Now.ToString(yyyy)) Xceed Software Inc., all rights reserved - - <_Parameter1>FluentAssertions.Specs, PublicKey=00240000048000009400000006020000002400005253413100040000010001002d25ff515c85b13ba08f61d466cff5d80a7f28ba197bbf8796085213e7a3406f970d2a4874932fed35db546e89af2da88c194bf1b7f7ac70de7988c78406f7629c547283061282a825616eb7eb48a9514a7570942936020a9bb37dca9ff60b778309900851575614491c6d25018fadb75828f4c7a17bf2d7dc86e7b6eafc5d8f - - - <_Parameter1>FluentAssertions.Equivalency.Specs, PublicKey=00240000048000009400000006020000002400005253413100040000010001002d25ff515c85b13ba08f61d466cff5d80a7f28ba197bbf8796085213e7a3406f970d2a4874932fed35db546e89af2da88c194bf1b7f7ac70de7988c78406f7629c547283061282a825616eb7eb48a9514a7570942936020a9bb37dca9ff60b778309900851575614491c6d25018fadb75828f4c7a17bf2d7dc86e7b6eafc5d8f - - - <_Parameter1>Benchmarks, PublicKey=00240000048000009400000006020000002400005253413100040000010001002d25ff515c85b13ba08f61d466cff5d80a7f28ba197bbf8796085213e7a3406f970d2a4874932fed35db546e89af2da88c194bf1b7f7ac70de7988c78406f7629c547283061282a825616eb7eb48a9514a7570942936020a9bb37dca9ff60b778309900851575614491c6d25018fadb75828f4c7a17bf2d7dc86e7b6eafc5d8f - + + + + - + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -59,33 +56,13 @@ - - - - - - - - - - - - - - - - - - - - @@ -94,17 +71,8 @@ - - - - - $(TargetFramework) - - - - diff --git a/Src/FluentAssertions/Formatting/Anchor.cs b/Src/FluentAssertions/Formatting/Anchor.cs new file mode 100644 index 0000000000..798b09e035 --- /dev/null +++ b/Src/FluentAssertions/Formatting/Anchor.cs @@ -0,0 +1,93 @@ +namespace FluentAssertions.Formatting; + +/// +/// Represents a point in the formatted object graph where a new fragment or line can be inserted. +/// +internal class Anchor +{ + private readonly FormattedObjectGraph parent; + private readonly int indentation; + private readonly int characterIndex; + private readonly Line line; + private readonly bool lineWasEmptyAtCreation; + + public Anchor(FormattedObjectGraph parent, Line line) + { + indentation = parent.Indentation; + this.parent = parent; + this.line = line; + lineWasEmptyAtCreation = line is null || line.Length == 0; + + // Track the point in the graph where this instance was created. + characterIndex = line?.LengthWithoutOffset ?? 0; + } + + public bool UseLineBreaks { get; set; } + + public void InsertFragment(string fragment) + { + // Insert the fragment to the line and character position the anchor points at. + if (line is null) + { + parent.InsertAtLineStartOrTop(fragment); + } + else + { + line.Insert(characterIndex, fragment); + } + + // If the current line already contained text beyond the anchor point, move that part to the next line. + if (line is not null && !RenderOnSingleLine) + { + parent.SplitLine(line, characterIndex + fragment.Length); + } + } + + public void InsertLineOrFragment(string fragment) + { + if (RenderOnSingleLine) + { + if (line is null) + { + parent.InsertAtLineStartOrTop(fragment); + } + else + { + line.Insert(characterIndex, fragment); + } + } + else + { + string fragmentWithWhitespace = FormattedObjectGraph.MakeWhitespace(indentation) + fragment; + + // If the line was empty when the anchor was created, we can insert the fragment right here. + // But if it wasn't empty, we need to continue the fragment on the next line. + if (lineWasEmptyAtCreation) + { + parent.InsertAtTop(fragmentWithWhitespace); + } + else + { + parent.AddLineAfter(line, fragmentWithWhitespace); + } + } + } + + internal void AddLineOrFragment(string fragment) + { + if (line is null) + { + parent.AddLineOrFragment(fragment); + } + else if (RenderOnSingleLine) + { + line.Append(fragment); + } + else + { + parent.AddLine(fragment); + } + } + + private bool RenderOnSingleLine => !UseLineBreaks && !parent.HasLinesBeyond(line); +} diff --git a/Src/FluentAssertions/Formatting/AttributeBasedFormatter.cs b/Src/FluentAssertions/Formatting/AttributeBasedFormatter.cs index e76edb2b34..913e32282a 100644 --- a/Src/FluentAssertions/Formatting/AttributeBasedFormatter.cs +++ b/Src/FluentAssertions/Formatting/AttributeBasedFormatter.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Reflection; using FluentAssertions.Common; +using FluentAssertions.Configuration; namespace FluentAssertions.Formatting; @@ -13,7 +14,7 @@ namespace FluentAssertions.Formatting; public class AttributeBasedFormatter : IValueFormatter { private Dictionary formatters; - private ValueFormatterDetectionMode detectionMode = ValueFormatterDetectionMode.Disabled; + private ValueFormatterDetectionMode detectionMode; /// /// Indicates whether the current can handle the specified . @@ -27,16 +28,13 @@ public bool CanHandle(object value) return IsScanningEnabled && value is not null && GetFormatter(value) is not null; } - private static bool IsScanningEnabled - { - get { return Configuration.Current.ValueFormatterDetectionMode != ValueFormatterDetectionMode.Disabled; } - } + private static bool IsScanningEnabled => AssertionConfiguration.Current.Formatting.ValueFormatterDetectionMode == ValueFormatterDetectionMode.Scan; public void Format(object value, FormattedObjectGraph formattedGraph, FormattingContext context, FormatChild formatChild) { MethodInfo method = GetFormatter(value); - object[] parameters = { value, formattedGraph }; + object[] parameters = [value, formattedGraph]; method.Invoke(null, parameters); } @@ -71,9 +69,12 @@ private Dictionary Formatters private void HandleValueFormatterDetectionModeChanges() { - if (detectionMode != Configuration.Current.ValueFormatterDetectionMode) + ValueFormatterDetectionMode configuredDetectionMode = + AssertionEngine.Configuration.Formatting.ValueFormatterDetectionMode; + + if (detectionMode != configuredDetectionMode) { - detectionMode = Configuration.Current.ValueFormatterDetectionMode; + detectionMode = configuredDetectionMode; formatters = null; } } @@ -81,7 +82,7 @@ private void HandleValueFormatterDetectionModeChanges() private static Dictionary FindCustomFormatters() { var query = - from type in Services.Reflector.GetAllTypesFromAppDomain(Applicable) + from type in TypeReflector.GetAllTypesFromAppDomain(Applicable) where type is not null from method in type.GetMethods(BindingFlags.Static | BindingFlags.Public) where method.IsStatic @@ -100,11 +101,11 @@ into formatterGroup private static bool Applicable(Assembly assembly) { - Configuration configuration = Configuration.Current; - ValueFormatterDetectionMode mode = configuration.ValueFormatterDetectionMode; + GlobalFormattingOptions options = AssertionEngine.Configuration.Formatting; + ValueFormatterDetectionMode mode = options.ValueFormatterDetectionMode; return mode == ValueFormatterDetectionMode.Scan || ( mode == ValueFormatterDetectionMode.Specific && - assembly.FullName.Split(',')[0].Equals(configuration.ValueFormatterAssembly, StringComparison.OrdinalIgnoreCase)); + assembly.FullName.Split(',')[0].Equals(options.ValueFormatterAssembly, StringComparison.OrdinalIgnoreCase)); } } diff --git a/Src/FluentAssertions/Formatting/BuildingLineState.cs b/Src/FluentAssertions/Formatting/BuildingLineState.cs new file mode 100644 index 0000000000..6cfe6c3b0c --- /dev/null +++ b/Src/FluentAssertions/Formatting/BuildingLineState.cs @@ -0,0 +1,44 @@ +using System.Text; + +namespace FluentAssertions.Formatting; + +/// +/// Represents the behavior of when it's still in the building phase and tries +/// to be as efficient as possible by using a . +/// +internal class BuildingLineState : ILineState +{ + private StringBuilder builder = new(); + + public ILineState Flush() + { + var newState = new FlushedLineState(builder.ToString()); + builder = null; + + return newState; + } + + public int Length => builder.Length; + + public void Append(string fragment) + { + builder.Append(fragment); + } + + public void InsertAtStart(string fragment) + { + builder.Insert(0, fragment); + } + + public void InsertAt(int startIndex, string fragment) + { + builder.Insert(startIndex, fragment); + } + + public Line Truncate(int characterIndex, int indentation, int whitespaceOffset) + { + return null; + } + + public string Render() => builder.ToString(); +} diff --git a/Src/FluentAssertions/Formatting/DateOnlyValueFormatter.cs b/Src/FluentAssertions/Formatting/DateOnlyValueFormatter.cs index c3b77fa28e..030acb2ab9 100644 --- a/Src/FluentAssertions/Formatting/DateOnlyValueFormatter.cs +++ b/Src/FluentAssertions/Formatting/DateOnlyValueFormatter.cs @@ -1,7 +1,8 @@ +#if NET6_0_OR_GREATER + using System; using System.Globalization; -#if NET6_0_OR_GREATER namespace FluentAssertions.Formatting; public class DateOnlyValueFormatter : IValueFormatter diff --git a/Src/FluentAssertions/Formatting/DateTimeOffsetValueFormatter.cs b/Src/FluentAssertions/Formatting/DateTimeOffsetValueFormatter.cs index 9e916f4a92..1c9085dab7 100644 --- a/Src/FluentAssertions/Formatting/DateTimeOffsetValueFormatter.cs +++ b/Src/FluentAssertions/Formatting/DateTimeOffsetValueFormatter.cs @@ -92,14 +92,7 @@ public void Format(object value, FormattedObjectGraph formattedGraph, Formatting if (!hasDate && !hasTime) { - if (HasMilliSeconds(dateTimeOffset)) - { - formattedGraph.AddFragment("0001-01-01 00:00:00." + dateTimeOffset.ToString("fff", CultureInfo.InvariantCulture)); - } - else - { - formattedGraph.AddFragment("0001-01-01 00:00:00.000"); - } + formattedGraph.AddFragment("0001-01-01 00:00:00.000"); } formattedGraph.AddFragment(">"); diff --git a/Src/FluentAssertions/Formatting/DefaultValueFormatter.cs b/Src/FluentAssertions/Formatting/DefaultValueFormatter.cs index 28da03d6dc..01ff94aab3 100644 --- a/Src/FluentAssertions/Formatting/DefaultValueFormatter.cs +++ b/Src/FluentAssertions/Formatting/DefaultValueFormatter.cs @@ -8,12 +8,6 @@ namespace FluentAssertions.Formatting; public class DefaultValueFormatter : IValueFormatter { - /// - /// The number of spaces to indent the members of this object by. - /// - /// The default value is 3. - protected virtual int SpacesPerIndentionLevel => 3; - /// /// Determines whether this instance can handle the specified value. /// @@ -82,8 +76,10 @@ private void WriteTypeAndMemberValues(object obj, FormattedObjectGraph formatted private void WriteTypeName(FormattedObjectGraph formattedGraph, Type type) { - var typeName = type.HasFriendlyName() ? TypeDisplayName(type) : string.Empty; - formattedGraph.AddFragment(typeName); + if (type.HasFriendlyName()) + { + formattedGraph.AddFragment(TypeDisplayName(type)); + } } private void WriteTypeValue(object obj, FormattedObjectGraph formattedGraph, FormatChild formatChild, Type type) @@ -95,7 +91,6 @@ private void WriteTypeValue(object obj, FormattedObjectGraph formattedGraph, For } else { - formattedGraph.EnsureInitialNewLine(); formattedGraph.AddLine("{"); WriteMemberValues(obj, members, formattedGraph, formatChild); formattedGraph.AddFragmentOnNewLine("}"); diff --git a/Src/FluentAssertions/Formatting/EnumerableValueFormatter.cs b/Src/FluentAssertions/Formatting/EnumerableValueFormatter.cs index 09d6a25a28..fe15e11746 100644 --- a/Src/FluentAssertions/Formatting/EnumerableValueFormatter.cs +++ b/Src/FluentAssertions/Formatting/EnumerableValueFormatter.cs @@ -33,8 +33,9 @@ public void Format(object value, FormattedObjectGraph formattedGraph, Formatting using var iterator = new Iterator(collection, MaxItems); - var iteratorGraph = formattedGraph.KeepOnSingleLineAsLongAsPossible(); - FormattedObjectGraph.PossibleMultilineFragment separatingCommaGraph = null; + var startingAnchor = formattedGraph.GetAnchor(); + startingAnchor.UseLineBreaks = context.UseLineBreaks; + Anchor commaSeparatorAnchor = null; while (iterator.MoveNext()) { @@ -46,26 +47,26 @@ public void Format(object value, FormattedObjectGraph formattedGraph, Formatting { using IDisposable _ = formattedGraph.WithIndentation(); string moreItemsMessage = value is ICollection c ? $"…{c.Count - MaxItems} more…" : "…more…"; - iteratorGraph.AddLineOrFragment(moreItemsMessage); + formattedGraph.AddLineOrFragment(moreItemsMessage); } - separatingCommaGraph?.InsertLineOrFragment(", "); - separatingCommaGraph = formattedGraph.KeepOnSingleLineAsLongAsPossible(); + commaSeparatorAnchor?.InsertFragment(", "); + commaSeparatorAnchor = formattedGraph.GetAnchor(); - // We cannot know whether or not the enumerable will take up more than one line of - // output until we have formatted the first item. So we format the first item, then + // We cannot know whether the enumerable will take up more than one line of + // output until we have formatted all items. So we format items, then // go back and insert the enumerable's opening brace in the correct place depending // on whether that first item was all on one line or not. if (iterator.IsLast) { - iteratorGraph.AddStartingLineOrFragment("{"); - iteratorGraph.AddLineOrFragment("}"); + startingAnchor.InsertLineOrFragment("{"); + startingAnchor.AddLineOrFragment("}"); } } if (iterator.IsEmpty) { - iteratorGraph.AddFragment("{empty}"); + formattedGraph.AddFragment("{empty}"); } } } diff --git a/Src/FluentAssertions/Formatting/FlushedLineState.cs b/Src/FluentAssertions/Formatting/FlushedLineState.cs new file mode 100644 index 0000000000..274a68de95 --- /dev/null +++ b/Src/FluentAssertions/Formatting/FlushedLineState.cs @@ -0,0 +1,50 @@ +using System.Text; + +namespace FluentAssertions.Formatting; + +/// +/// Represents the behavior of when most of the appending and inserting +/// has completed, and it no longer needs an internal . +/// +internal class FlushedLineState(string content) : ILineState +{ + private string content = content; + + public ILineState Flush() + { + return this; + } + + public int Length => content.Length; + + public void Append(string fragment) + { + content += fragment; + } + + public void InsertAtStart(string fragment) + { + content = fragment + content; + } + + public void InsertAt(int startIndex, string fragment) + { + content = content.Insert(startIndex, fragment); + } + + public Line Truncate(int characterIndex, int indentation, int whitespaceOffset) + { + string truncatedContent = content.Substring(characterIndex + whitespaceOffset); + + if (truncatedContent.Trim().Length > 0) + { + content = content.Substring(0, characterIndex + whitespaceOffset); + + return new Line(new string(' ', whitespaceOffset) + truncatedContent, indentation, whitespaceOffset); + } + + return null; + } + + public string Render() => content; +} diff --git a/Src/FluentAssertions/Formatting/FormattedObjectGraph.cs b/Src/FluentAssertions/Formatting/FormattedObjectGraph.cs index b668e290b8..641fadac5a 100644 --- a/Src/FluentAssertions/Formatting/FormattedObjectGraph.cs +++ b/Src/FluentAssertions/Formatting/FormattedObjectGraph.cs @@ -1,8 +1,5 @@ using System; -using System.Collections.Generic; using System.Linq; -using System.Text; -using FluentAssertions.Execution; namespace FluentAssertions.Formatting; @@ -17,17 +14,38 @@ namespace FluentAssertions.Formatting; /// public class FormattedObjectGraph { - private readonly int maxLines; - private readonly List lines = new(); - private readonly StringBuilder lineBuilder = new(); - private int indentation; - private string lineBuilderWhitespace = string.Empty; + private readonly LineCollection lines; + /// + /// The current line that is being written to, or if there is no active line. + /// + private Line currentLine; + + /// + /// This class is used by the class to collect all the output of the (nested calls of an) into + /// a the final representation. + /// + /// + /// The will ensure that the number of lines will be limited + /// to the maximum number of lines provided through its constructor. It will throw + /// a if the number of lines exceeds the maximum. + /// public FormattedObjectGraph(int maxLines) { - this.maxLines = maxLines; + lines = new LineCollection(maxLines); } + /// + /// Represents the current level of indentation applied to newly added lines or fragments. + /// + /// + /// The indentation level determines the amount of leading whitespace to be added to each line, + /// calculated based on . It is incremented or decremented with methods such as + /// , and affects all subsequent lines or fragments added to + /// the . + /// + internal int Indentation { get; private set; } + /// /// The number of spaces that should be used by every indentation level. /// @@ -36,7 +54,7 @@ public FormattedObjectGraph(int maxLines) /// /// Returns the number of lines of text currently in the graph. /// - public int LineCount => lines.Count + (lineBuilder.Length > 0 ? 1 : 0); + public int LineCount => lines.Count; /// /// Starts a new line with the provided text fragment. Additional text can be added to @@ -45,83 +63,72 @@ public FormattedObjectGraph(int maxLines) public void AddFragmentOnNewLine(string fragment) { FlushCurrentLine(); - - AddFragment(fragment); + GetCurrentLine().Append(fragment); } /// - /// Starts a new line with the provided line of text that does not allow - /// adding more fragments of text. + /// If there's only one line, adds a fragment to that line. If there are more lines, adds the fragment as + /// a new line that does not allow any further fragments. /// - public void AddLine(string line) + public void AddLineOrFragment(string fragment) { - FlushCurrentLine(); - - AppendSafely(Whitespace + line); - } - - /// - /// Adds a new fragment of text to the current line. - /// - public void AddFragment(string fragment) - { - if (lineBuilderWhitespace.Length > 0) + if (lines.Count == 1) { - lineBuilder.Append(lineBuilderWhitespace); - lineBuilderWhitespace = string.Empty; + AddFragment(fragment); + } + else + { + AddLine(fragment); } - - lineBuilder.Append(fragment); } /// - /// Adds a new line if there are no lines and no fragment that would cause a new line. + /// Starts a new line with the provided text that does not allow adding more + /// fragments of text. /// - internal void EnsureInitialNewLine() + public void AddLine(string content) { - if (LineCount == 0) - { - InsertInitialNewLine(); - } + FlushCurrentLine(); + + GetCurrentLine().Append(content); + FlushCurrentLine(); } /// - /// Inserts an empty line as the first line unless it is already. + /// Adds a new fragment of text to the current line. /// - private void InsertInitialNewLine() + public void AddFragment(string fragment) { - if (lines.Count == 0 || !string.IsNullOrEmpty(lines[0])) - { - lines.Insert(0, string.Empty); - lineBuilderWhitespace = Whitespace; - } + GetCurrentLine().Append(fragment); } private void FlushCurrentLine() { - if (lineBuilder.Length > 0) + // We only need to flush the line if there's something to flush. + if (currentLine is not null) { - AppendSafely(lineBuilderWhitespace + lineBuilder); - - lineBuilder.Clear(); - lineBuilderWhitespace = Whitespace; + currentLine.Flush(); + currentLine = null; } } - private void AppendSafely(string line) + private Line GetCurrentLine() { - if (lines.Count == maxLines) + // We prefer to lazily initialize the current line so we don't waste memory. + if (currentLine is null) { - lines.Add(string.Empty); - - lines.Add( - $"(Output has exceeded the maximum of {maxLines} lines. " + - $"Increase {nameof(FormattingOptions)}.{nameof(FormattingOptions.MaxLines)} on {nameof(AssertionScope)} or {nameof(AssertionOptions)} to include more lines.)"); + currentLine = new Line(Indentation); + lines.Add(currentLine); + } - throw new MaxLinesExceededException(); + // A single-line rendering doesn't need any indentation, so we postpone that decision + // until we know whether there will be more lines. + if (lines.Count > 1) + { + currentLine.EnsureWhitespace(); } - lines.Add(line); + return currentLine; } /// @@ -132,142 +139,60 @@ private void AppendSafely(string line) /// public IDisposable WithIndentation() { - indentation++; + Indentation++; return new Disposable(() => { - if (indentation > 0) + if (Indentation > 0) { - indentation--; + Indentation--; } }); } /// - /// Returns the final textual multi-line representation of the object graph. - /// - public override string ToString() - { - return string.Join(Environment.NewLine, lines.Concat(new[] { lineBuilder.ToString() })); - } - - internal PossibleMultilineFragment KeepOnSingleLineAsLongAsPossible() - { - return new PossibleMultilineFragment(this); - } - - private string Whitespace => MakeWhitespace(indentation); - - private static string MakeWhitespace(int indent) => new(' ', indent * SpacesPerIndentation); - - /// - /// Write fragments that may be on a single line or span multiple lines, - /// and this is not known until later parts of the fragment are written. + /// Get a reference to the current line (or the last line if there is no active line), so that we can + /// insert fragments and lines at that specific point. /// - internal record PossibleMultilineFragment + internal Anchor GetAnchor() { - private readonly FormattedObjectGraph parentGraph; - private readonly int startingLineBuilderIndex; - private readonly int startingLineCount; - - public PossibleMultilineFragment(FormattedObjectGraph parentGraph) + if (lines.Count == 0) { - this.parentGraph = parentGraph; - startingLineBuilderIndex = parentGraph.lineBuilder.Length; - startingLineCount = parentGraph.lines.Count; + return new Anchor(this, null); } - /// - /// Write the fragment at the position the graph was in when this instance was created. - /// - /// - /// If more lines have been added since this instance was created then write the - /// fragment on a new line, otherwise write it on the same line. - /// - /// - internal void AddStartingLineOrFragment(string fragment) - { - if (FormatOnSingleLine) - { - parentGraph.lineBuilder.Insert(startingLineBuilderIndex, fragment); - } - else - { - parentGraph.InsertInitialNewLine(); - parentGraph.lines.Insert(startingLineCount + 1, parentGraph.Whitespace + fragment); - InsertAtStartOfLine(startingLineCount + 2, MakeWhitespace(1)); - } - } - - private bool FormatOnSingleLine => parentGraph.lines.Count == startingLineCount; - - private void InsertAtStartOfLine(int lineIndex, string insertion) - { - if (!parentGraph.lines[lineIndex].StartsWith(insertion, StringComparison.Ordinal)) - { - parentGraph.lines[lineIndex] = parentGraph.lines[lineIndex].Insert(0, insertion); - } - } + return new Anchor(this, currentLine ?? lines.Last()); + } - public void InsertLineOrFragment(string fragment) - { - if (FormatOnSingleLine) - { - parentGraph.lineBuilder.Insert(startingLineBuilderIndex, fragment); - } - else - { - parentGraph.lines[startingLineCount] = parentGraph.lines[startingLineCount] - .Insert(startingLineBuilderIndex, InsertNewLineIntoFragment(fragment)); - } - } + internal static string MakeWhitespace(int indent) => new(' ', indent * SpacesPerIndentation); - private string InsertNewLineIntoFragment(string fragment) - { - if (StartingLineHasBeenAddedTo()) - { - return fragment + Environment.NewLine + MakeWhitespace(parentGraph.indentation + 1); - } + internal bool HasLinesBeyond(Line line) => lines.HasLinesBeyond(line); - return fragment; - } + internal void AddLineAfter(Line line, string content) + { + lines.AddLineAfter(line, new Line(content)); + } - private bool StartingLineHasBeenAddedTo() => parentGraph.lines[startingLineCount].Length > startingLineBuilderIndex; + internal void InsertAtTop(string content) + { + lines.InsertAtTop(new Line(content)); + } - /// - /// If more lines have been added since this instance was created then write the - /// fragment on a new line, otherwise write it on the same line. - /// - internal void AddLineOrFragment(string fragment) - { - if (FormatOnSingleLine) - { - parentGraph.AddFragment(fragment); - } - else - { - parentGraph.AddFragmentOnNewLine(fragment); - } - } + internal void InsertAtLineStartOrTop(string fragment) + { + lines.InsertAtLineStartOrTop(fragment); + } - /// - /// Write the fragment. If more lines have been added since this instance was - /// created then also flush the line and indent the next line. - /// - internal void AddEndingLineOrFragment(string fragment) - { - if (FormatOnSingleLine) - { - parentGraph.AddFragment(fragment); - } - else - { - parentGraph.AddFragment(fragment); - parentGraph.FlushCurrentLine(); - parentGraph.lineBuilderWhitespace += MakeWhitespace(1); - } - } + internal void SplitLine(Line line, int characterIndex) + { + lines.SplitLine(line, characterIndex); + } - internal void AddFragment(string fragment) => parentGraph.AddFragment(fragment); + /// + /// Returns the final textual multi-line representation of the object graph. + /// + public override string ToString() + { + return string.Join(Environment.NewLine, lines.Select(line => line.ToString())); } } diff --git a/Src/FluentAssertions/Formatting/Formatter.cs b/Src/FluentAssertions/Formatting/Formatter.cs index 206d2b16d3..a2c4dbc150 100644 --- a/Src/FluentAssertions/Formatting/Formatter.cs +++ b/Src/FluentAssertions/Formatting/Formatter.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using FluentAssertions.Common; -using FluentAssertions.Equivalency; using FluentAssertions.Equivalency.Execution; using FluentAssertions.Execution; using FluentAssertions.Xml; @@ -10,16 +10,17 @@ namespace FluentAssertions.Formatting; /// -/// Provides services for formatting an object being used in an assertion in a human readable format. +/// Provides services for formatting an object being used in an assertion in a human-readable format. /// public static class Formatter { #region Private Definitions - private static readonly List CustomFormatters = new(); + private static readonly List CustomFormatters = []; - private static readonly List DefaultFormatters = new() - { + private static readonly List DefaultFormatters = + [ + new PassthroughValueFormatter(), new XmlReaderValueFormatter(), new XmlNodeFormatter(), new AttributeBasedFormatter(), @@ -28,12 +29,14 @@ public static class Formatter new XElementValueFormatter(), new XAttributeValueFormatter(), new PropertyInfoFormatter(), + new MethodInfoFormatter(), new NullValueFormatter(), new GuidValueFormatter(), new DateTimeOffsetValueFormatter(), #if NET6_0_OR_GREATER new DateOnlyValueFormatter(), new TimeOnlyValueFormatter(), + new JsonNodeFormatter(), #endif new TimeSpanValueFormatter(), new Int32ValueFormatter(), @@ -57,7 +60,7 @@ public static class Formatter new EnumerableValueFormatter(), new EnumValueFormatter(), new DefaultValueFormatter() - }; + ]; /// /// Is used to detect recursive calls by implementations. @@ -70,7 +73,10 @@ public static class Formatter /// /// A list of objects responsible for formatting the objects represented by placeholders. /// - public static IEnumerable Formatters => CustomFormatters.Concat(DefaultFormatters); + public static IEnumerable Formatters => + AssertionScope.Current.FormattingOptions.ScopedFormatters + .Concat(CustomFormatters) + .Concat(DefaultFormatters); /// /// Returns a human-readable representation of a particular object. @@ -107,8 +113,9 @@ public static string ToString(object value, FormattingOptions options = null) try { - Format(value, output, context, (path, childValue, - output) => FormatChild(path, childValue, output, context, options, graph)); + Format(value, output, context, + (path, childValue, childOutput) + => FormatChild(path, childValue, childOutput, context, options, graph)); } catch (MaxLinesExceededException) { @@ -122,6 +129,7 @@ public static string ToString(object value, FormattingOptions options = null) } } + [SuppressMessage("Maintainability", "AV1561:Signature contains too many parameters")] private static void FormatChild(string path, object value, FormattedObjectGraph output, FormattingContext context, FormattingOptions options, ObjectGraph graph) { @@ -136,7 +144,7 @@ private static void FormatChild(string path, object value, FormattedObjectGraph else if (graph.Depth > options.MaxDepth) { output.AddLine($"Maximum recursion depth of {options.MaxDepth} was reached. " + - $" Increase {nameof(FormattingOptions.MaxDepth)} on {nameof(AssertionScope)} or {nameof(AssertionOptions)} to get more details."); + $" Increase {nameof(FormattingOptions.MaxDepth)} on {nameof(AssertionScope)} or {nameof(AssertionConfiguration)} to get more details."); } else { @@ -160,7 +168,7 @@ private static void Format(object value, FormattedObjectGraph output, Formatting } /// - /// Removes a custom formatter that was previously added though . + /// Removes a custom formatter that was previously added through . /// /// /// This method is not thread-safe and should not be invoked from within a unit test. @@ -211,7 +219,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/FormattingOptions.cs b/Src/FluentAssertions/Formatting/FormattingOptions.cs index 8b9f78db65..8cb16870f8 100644 --- a/Src/FluentAssertions/Formatting/FormattingOptions.cs +++ b/Src/FluentAssertions/Formatting/FormattingOptions.cs @@ -1,12 +1,17 @@ -namespace FluentAssertions.Formatting; +using System.Collections.Generic; +using FluentAssertions.Execution; + +namespace FluentAssertions.Formatting; public class FormattingOptions { + internal List ScopedFormatters { get; set; } = []; + /// /// Indicates whether the formatter should use line breaks when the supports it. /// /// - /// This value should not changed on from within a unit test. + /// This property is not thread-safe and should not be modified through from within a unit test. /// See the docs on how to safely use it. /// public bool UseLineBreaks { get; set; } @@ -15,7 +20,7 @@ public class FormattingOptions /// Determines the depth until which the library should try to render an object graph. /// /// - /// This value should not changed on from within a unit test. + /// This property is not thread-safe and should not be modified through from within a unit test. /// See the docs on how to safely use it. /// /// @@ -31,19 +36,42 @@ public class FormattingOptions /// Because of technical reasons, the actual output may be one or two lines longer. /// /// - /// This value should not changed on from within a unit test. + /// This property is not thread-safe and should not be modified through from within a unit test. /// See the docs on how to safely use it. /// /// public int MaxLines { get; set; } = 100; + /// + /// Removes a scoped formatter that was previously added through . + /// + /// A custom implementation of + public void RemoveFormatter(IValueFormatter formatter) + { + ScopedFormatters.Remove(formatter); + } + + /// + /// Ensures a scoped formatter is included in the chain, which is executed before the static custom formatters and the default formatters. + /// This also lasts only for the current until disposal. + /// + /// A custom implementation of + public void AddFormatter(IValueFormatter formatter) + { + if (!ScopedFormatters.Contains(formatter)) + { + ScopedFormatters.Insert(0, formatter); + } + } + internal FormattingOptions Clone() { return new FormattingOptions { UseLineBreaks = UseLineBreaks, MaxDepth = MaxDepth, - MaxLines = MaxLines + MaxLines = MaxLines, + ScopedFormatters = [.. ScopedFormatters], }; } } diff --git a/Src/FluentAssertions/Formatting/GuidValueFormatter.cs b/Src/FluentAssertions/Formatting/GuidValueFormatter.cs index 3d76933951..54063e89a5 100644 --- a/Src/FluentAssertions/Formatting/GuidValueFormatter.cs +++ b/Src/FluentAssertions/Formatting/GuidValueFormatter.cs @@ -18,6 +18,6 @@ public bool CanHandle(object value) public void Format(object value, FormattedObjectGraph formattedGraph, FormattingContext context, FormatChild formatChild) { - formattedGraph.AddFragment("{" + value + "}"); + formattedGraph.AddFragment($"{{{value}}}"); } } diff --git a/Src/FluentAssertions/Formatting/ILineState.cs b/Src/FluentAssertions/Formatting/ILineState.cs new file mode 100644 index 0000000000..1b0e0fe307 --- /dev/null +++ b/Src/FluentAssertions/Formatting/ILineState.cs @@ -0,0 +1,26 @@ +namespace FluentAssertions.Formatting; + +/// +/// Represents the state management of a line for structured content building or rendering. +/// +/// +/// This interface defines the operations that can be performed on a line, +/// including appending content, inserting content at specific positions, +/// truncating the line, and rendering its content. +/// +internal interface ILineState +{ + ILineState Flush(); + + int Length { get; } + + void Append(string fragment); + + void InsertAtStart(string fragment); + + void InsertAt(int startIndex, string fragment); + + Line Truncate(int characterIndex, int indentation, int whitespaceOffset); + + string Render(); +} diff --git a/Src/FluentAssertions/Formatting/JsonNodeFormatter.cs b/Src/FluentAssertions/Formatting/JsonNodeFormatter.cs new file mode 100644 index 0000000000..b57895fb09 --- /dev/null +++ b/Src/FluentAssertions/Formatting/JsonNodeFormatter.cs @@ -0,0 +1,28 @@ +#if NET6_0_OR_GREATER + +using System.Text.Json.Nodes; + +namespace FluentAssertions.Formatting; + +public class JsonNodeFormatter : IValueFormatter +{ + /// + public bool CanHandle(object value) => value is JsonNode; + + /// + public void Format(object value, FormattedObjectGraph formattedGraph, FormattingContext context, FormatChild formatChild) + { + var node = (JsonNode)value; + + if (context.UseLineBreaks) + { + formattedGraph.AddFragmentOnNewLine(node.ToString()); + } + else + { + formattedGraph.AddFragment(node.ToJsonString()); + } + } +} + +#endif diff --git a/Src/FluentAssertions/Formatting/Line.cs b/Src/FluentAssertions/Formatting/Line.cs new file mode 100644 index 0000000000..d5971f81f0 --- /dev/null +++ b/Src/FluentAssertions/Formatting/Line.cs @@ -0,0 +1,115 @@ +using System; + +namespace FluentAssertions.Formatting; + +/// +/// Represents a single line of output rendered through the . +/// +internal class Line +{ + /// + /// The level of indentation at the time this line was created. + /// + private int indentation; + + private ILineState state; + + /// + /// If any whitespace was inserted at the beginning of the line without an knowing about it, this + /// will be the length of that whitespace so that any calls to will be offset by this amount. + /// + private int whitespaceOffset; + + /// + /// Creates an empty line with the specified indentation that will be applied when + /// actual fragments are added. + /// + public Line(int indentation) + { + state = new BuildingLineState(); + this.indentation = indentation; + } + + public Line(string content) + { + state = new FlushedLineState(content); + } + + public Line(string truncatedContent, int indentation, int whitespaceOffset) + { + state = new FlushedLineState(truncatedContent); + this.indentation = indentation; + this.whitespaceOffset = whitespaceOffset; + } + + /// + /// Is used to close off the internal string builder. + /// + public void Flush() + { + state = state.Flush(); + } + + /// + /// Gets the length of the content, including any whitespace that was inserted at the beginning of the line. + /// + public int Length => state.Length; + + /// + /// Gets the length of the content without the offset of any whitespace that was inserted at the beginning of the line. + /// + public int LengthWithoutOffset => Length - whitespaceOffset; + + public void Append(string fragment) + { + state.Append(fragment); + } + + public void InsertAtStart(string fragment) + { + state.InsertAtStart(fragment); + } + + public void Insert(int characterIndex, string fragment) + { + int startIndex = Math.Min(characterIndex + whitespaceOffset, Length); + state.InsertAt(startIndex, fragment); + } + + /// + /// Ensures that the line is prefixed with the correct amount of whitespace. + /// + /// + /// Since we don't add the whitespace for the first line until we know that there is a second line, we need to be able + /// to fixup the whitespace for the second line at a later time. + /// + public void EnsureWhitespace() + { + if (indentation > 0) + { + string whitespace = FormattedObjectGraph.MakeWhitespace(indentation); + whitespaceOffset = whitespace.Length; + + state.InsertAt(0, whitespace); + + indentation = 0; + } + } + + /// + /// Truncates the current line at the specified character index and returns the remainder as a new line. + /// Returns if the remainder is empty. + /// + public Line Truncate(int characterIndex) + { + Flush(); + + return state.Truncate(characterIndex, indentation, whitespaceOffset); + } + + public override string ToString() + { + // Only trim spaces, but keep line breaks. + return state.Render().TrimEnd(' '); + } +} diff --git a/Src/FluentAssertions/Formatting/LineCollection.cs b/Src/FluentAssertions/Formatting/LineCollection.cs new file mode 100644 index 0000000000..7abde5c7cd --- /dev/null +++ b/Src/FluentAssertions/Formatting/LineCollection.cs @@ -0,0 +1,94 @@ +using System.Collections; +using System.Collections.Generic; +using FluentAssertions.Execution; + +namespace FluentAssertions.Formatting; + +/// +/// A collection of lines that will throw a when the number of lines +/// exceeds the maximum. +/// +internal class LineCollection(int maxLines) : IEnumerable +{ + private readonly List lines = []; + + public int Count => lines.Count; + + public bool HasLinesBeyond(Line line) + { + // Null means that we're referring to the top of the list + return (line is null && lines.Count > 1) || (line is not null && lines.IndexOf(line) < (lines.Count - 1)); + } + + public void Add(Line line) + { + lines.Add(line); + OnCollectionIsModified(); + } + + public void AddLineAfter(Line line, Line newLine) + { + int index = lines.IndexOf(line); + + Insert(index + 1, newLine); + } + + public void InsertAtTop(Line newLine) + { + Insert(0, newLine); + } + + public void InsertAtLineStartOrTop(string fragment) + { + // If there's a single line, insert at the beginning of that line + // If there are more than one line, insert as a new line at the top + if (lines.Count == 1) + { + lines[0].InsertAtStart(fragment); + } + else + { + Insert(0, new Line(fragment)); + } + } + + public void SplitLine(Line line, int characterIndex) + { + int lineIndex = lines.IndexOf(line); + + Line remainder = line.Truncate(characterIndex); + if (remainder is not null) + { + Insert(lineIndex + 1, remainder); + } + } + + private void Insert(int index, Line item) + { + lines.Insert(index, item); + OnCollectionIsModified(); + + if (index == 0 && lines.Count > 1) + { + lines[1].EnsureWhitespace(); + } + } + + private void OnCollectionIsModified() + { + if (lines.Count > maxLines) + { + lines.Add(new Line(0)); + + lines.Add(new Line( + $"(Output has exceeded the maximum of {maxLines} lines. " + + $"Increase {nameof(FormattingOptions)}.{nameof(FormattingOptions.MaxLines)} on {nameof(AssertionScope)} or {nameof(AssertionConfiguration)} to include more lines.)")); + + throw new MaxLinesExceededException(); + } + } + + public IEnumerator GetEnumerator() => lines.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} diff --git a/Src/FluentAssertions/Formatting/MaxLinesExceededException.cs b/Src/FluentAssertions/Formatting/MaxLinesExceededException.cs index 4920473fe4..f6d5b5b260 100644 --- a/Src/FluentAssertions/Formatting/MaxLinesExceededException.cs +++ b/Src/FluentAssertions/Formatting/MaxLinesExceededException.cs @@ -2,19 +2,6 @@ namespace FluentAssertions.Formatting; -public class MaxLinesExceededException : Exception -{ - public MaxLinesExceededException(string message, Exception innerException) - : base(message, innerException) - { - } - - public MaxLinesExceededException(string message) - : base(message) - { - } - - public MaxLinesExceededException() - { - } -} +#pragma warning disable RCS1194, CA1032 // Add constructors +public class MaxLinesExceededException : Exception; +#pragma warning restore CA1032, RCS1194 diff --git a/Src/FluentAssertions/Formatting/MethodInfoFormatter.cs b/Src/FluentAssertions/Formatting/MethodInfoFormatter.cs new file mode 100644 index 0000000000..6cbc6eafd1 --- /dev/null +++ b/Src/FluentAssertions/Formatting/MethodInfoFormatter.cs @@ -0,0 +1,36 @@ +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.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/MultidimensionalArrayFormatter.cs b/Src/FluentAssertions/Formatting/MultidimensionalArrayFormatter.cs index 340a490edd..02bdca8483 100644 --- a/Src/FluentAssertions/Formatting/MultidimensionalArrayFormatter.cs +++ b/Src/FluentAssertions/Formatting/MultidimensionalArrayFormatter.cs @@ -33,6 +33,9 @@ public void Format(object value, FormattedObjectGraph formattedGraph, Formatting int[] dimensionIndices = Enumerable.Range(0, arr.Rank).Select(dimension => arr.GetLowerBound(dimension)).ToArray(); int currentLoopIndex = 0; + + // ReSharper disable once NotDisposedResource (false positive) + // See https://youtrack.jetbrains.com/issue/QD-8114/It-is-not-a-problem-Local-variable-enumerator-is-never-disposed IEnumerator enumerator = arr.GetEnumerator(); // Emulate n-ary loop diff --git a/Src/FluentAssertions/Formatting/PassthroughValueFormatter.cs b/Src/FluentAssertions/Formatting/PassthroughValueFormatter.cs new file mode 100644 index 0000000000..190a0cc4dc --- /dev/null +++ b/Src/FluentAssertions/Formatting/PassthroughValueFormatter.cs @@ -0,0 +1,17 @@ +using FluentAssertions.Execution; + +namespace FluentAssertions.Formatting; + +/// +/// Ensures that any value wrapped in a +/// is passed through as-is. +/// +internal class PassthroughValueFormatter : IValueFormatter +{ + public bool CanHandle(object value) => value is WithoutFormattingWrapper; + + public void Format(object value, FormattedObjectGraph formattedGraph, FormattingContext context, FormatChild formatChild) + { + formattedGraph.AddFragment(((WithoutFormattingWrapper)value).ToString()); + } +} diff --git a/Src/FluentAssertions/Formatting/PredicateLambdaExpressionValueFormatter.cs b/Src/FluentAssertions/Formatting/PredicateLambdaExpressionValueFormatter.cs index 2528b91b33..c071948942 100644 --- a/Src/FluentAssertions/Formatting/PredicateLambdaExpressionValueFormatter.cs +++ b/Src/FluentAssertions/Formatting/PredicateLambdaExpressionValueFormatter.cs @@ -7,7 +7,7 @@ namespace FluentAssertions.Formatting; /// /// The is responsible for formatting -/// boolean lambda expressions. +/// Boolean lambda expressions. /// public class PredicateLambdaExpressionValueFormatter : IValueFormatter { @@ -130,11 +130,11 @@ private static bool ExpressionIsConstant(Expression expression) /// private sealed class AndOperatorChainExtractor : ExpressionVisitor { - public List AndChain { get; } = new(); + public List AndChain { get; } = []; public override Expression Visit(Expression node) { - if (node.NodeType == ExpressionType.AndAlso) + if (node!.NodeType == ExpressionType.AndAlso) { var binaryExpression = (BinaryExpression)node; Visit(binaryExpression.Left); 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/Formatting/StringValueFormatter.cs b/Src/FluentAssertions/Formatting/StringValueFormatter.cs index 6850d65db1..fdf30f7b52 100644 --- a/Src/FluentAssertions/Formatting/StringValueFormatter.cs +++ b/Src/FluentAssertions/Formatting/StringValueFormatter.cs @@ -16,8 +16,17 @@ public bool CanHandle(object value) public void Format(object value, FormattedObjectGraph formattedGraph, FormattingContext context, FormatChild formatChild) { - string result = "\"" + value + "\""; + string result = $""" + "{value}" + """; - formattedGraph.AddFragment(result); + if (context.UseLineBreaks) + { + formattedGraph.AddFragmentOnNewLine(result); + } + else + { + formattedGraph.AddFragment(result); + } } } diff --git a/Src/FluentAssertions/Formatting/TaskFormatter.cs b/Src/FluentAssertions/Formatting/TaskFormatter.cs index 34547d9d0a..993597ad2c 100644 --- a/Src/FluentAssertions/Formatting/TaskFormatter.cs +++ b/Src/FluentAssertions/Formatting/TaskFormatter.cs @@ -3,7 +3,7 @@ namespace FluentAssertions.Formatting; /// -/// Provides a human readable version of a generic or non-generic +/// Provides a human-readable version of a generic or non-generic /// including its state. /// public class TaskFormatter : IValueFormatter @@ -15,14 +15,8 @@ public bool CanHandle(object value) public void Format(object value, FormattedObjectGraph formattedGraph, FormattingContext context, FormatChild formatChild) { - if (value is Task task) - { - formatChild("type", task.GetType(), formattedGraph); - formattedGraph.AddFragment($" {{Status={task.Status}}}"); - } - else - { - formattedGraph.AddFragment(""); - } + var task = (Task)value; + formatChild("type", task.GetType(), formattedGraph); + formattedGraph.AddFragment($" {{Status={task.Status}}}"); } } diff --git a/Src/FluentAssertions/Formatting/TimeOnlyValueFormatter.cs b/Src/FluentAssertions/Formatting/TimeOnlyValueFormatter.cs index ef8b8fd3a8..a3cc0d7487 100644 --- a/Src/FluentAssertions/Formatting/TimeOnlyValueFormatter.cs +++ b/Src/FluentAssertions/Formatting/TimeOnlyValueFormatter.cs @@ -1,7 +1,8 @@ +#if NET6_0_OR_GREATER + using System; using System.Globalization; -#if NET6_0_OR_GREATER namespace FluentAssertions.Formatting; public class TimeOnlyValueFormatter : IValueFormatter diff --git a/Src/FluentAssertions/Formatting/XElementValueFormatter.cs b/Src/FluentAssertions/Formatting/XElementValueFormatter.cs index d50d3a596d..3d34495f38 100644 --- a/Src/FluentAssertions/Formatting/XElementValueFormatter.cs +++ b/Src/FluentAssertions/Formatting/XElementValueFormatter.cs @@ -48,6 +48,6 @@ private static string FormatElementWithChildren(XElement element) private static string[] SplitIntoSeparateLines(XElement element) { string formattedXml = element.ToString(); - return formattedXml.Split(new[] { Environment.NewLine }, StringSplitOptions.None); + return formattedXml.Split(Environment.NewLine); } } diff --git a/Src/FluentAssertions/JsonAssertionExtensions.cs b/Src/FluentAssertions/JsonAssertionExtensions.cs new file mode 100644 index 0000000000..6fc01d88c2 --- /dev/null +++ b/Src/FluentAssertions/JsonAssertionExtensions.cs @@ -0,0 +1,41 @@ +#if NET6_0_OR_GREATER + +using System.Diagnostics; +using System.Linq; +using System.Text.Json.Nodes; +using FluentAssertions.Collections; +using FluentAssertions.Execution; +using FluentAssertions.Specialized; +using JetBrains.Annotations; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +namespace FluentAssertions; + +/// +/// Contains an extension method for custom assertions in unit tests related to System.Text.Json objects. +/// +[DebuggerNonUserCode] +public static class JsonAssertionExtensions +{ + /// + /// Returns an object that provides various assertion APIs that act on a . + /// + [Pure] + public static JsonNodeAssertions Should([NotNull] this T jsonNode) + where T : JsonNode + { + return new JsonNodeAssertions(jsonNode, AssertionChain.GetOrCreate()); + } + + /// + /// Return an object that provides various assertion APIs that treat a + /// as a collection of s. + /// + [Pure] + public static GenericCollectionAssertions Should([NotNull] this JsonArray jsonArray) + { + return new GenericCollectionAssertions(jsonArray?.ToArray(), AssertionChain.GetOrCreate()); + } +} + +#endif diff --git a/Src/FluentAssertions/License.cs b/Src/FluentAssertions/License.cs new file mode 100644 index 0000000000..df3a6b8afb --- /dev/null +++ b/Src/FluentAssertions/License.cs @@ -0,0 +1,12 @@ +namespace FluentAssertions; + +/// +/// Provides access to the licensing state of Fluent Assertions +/// +public static class License +{ + /// + /// Can be used to accept the license and suppress the soft warning about the license requirements for commercial use + /// + public static bool Accepted { get; set; } +} 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 79558d964a..919f944d8e 100644 --- a/Src/FluentAssertions/Numeric/ComparableTypeAssertions.cs +++ b/Src/FluentAssertions/Numeric/ComparableTypeAssertions.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using FluentAssertions.Common; using FluentAssertions.Equivalency; @@ -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; } /// @@ -49,9 +51,9 @@ public ComparableTypeAssertions(IComparable value) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint Be(T expected, string because = "", params object[] becauseArgs) + 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); @@ -65,7 +67,7 @@ public AndConstraint Be(T expected, string because = "", params obj /// /// Objects are equivalent when both object graphs have equally named properties with the same value, /// irrespective of the type of those objects. Two properties are also equal if one type can be converted to another and the result is equal. - /// Notice that actual behavior is determined by the global defaults managed by . + /// Notice that actual behavior is determined by the global defaults managed by . /// /// The expected element. /// @@ -75,8 +77,8 @@ public AndConstraint Be(T expected, string because = "", params obj /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeEquivalentTo(TExpectation expectation, string because = "", - params object[] becauseArgs) + public AndConstraint BeEquivalentTo(TExpectation expectation, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return BeEquivalentTo(expectation, config => config, because, becauseArgs); } @@ -90,10 +92,10 @@ public AndConstraint BeEquivalentTo(TExpectation expe /// /// The expected element. /// - /// A reference to the configuration object that can be used + /// A reference to the configuration object that can be used /// to influence the way the object graphs are compared. You can also provide an alternative instance of the - /// class. The global defaults are determined by the - /// class. + /// class. The global defaults are determined by the + /// class. /// /// /// A formatted phrase as is supported by explaining why the assertion @@ -104,15 +106,15 @@ public AndConstraint BeEquivalentTo(TExpectation expe /// /// is . public AndConstraint BeEquivalentTo(TExpectation expectation, - Func, EquivalencyAssertionOptions> config, string because = "", - params object[] becauseArgs) + Func, EquivalencyOptions> config, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(config); - EquivalencyAssertionOptions options = config(AssertionOptions.CloneDefaults()); + EquivalencyOptions options = config(AssertionConfiguration.Current.Equivalency.CloneDefaults()); var context = new EquivalencyValidationContext( - Node.From(() => AssertionScope.Current.CallerIdentity), options) + Node.From(() => CurrentAssertionChain.CallerIdentifier), options) { Reason = new Reason(because, becauseArgs), TraceWriter = options.TraceWriter @@ -143,9 +145,9 @@ public AndConstraint BeEquivalentTo(TExpectation expe /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBe(T unexpected, string because = "", params object[] becauseArgs) + 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); @@ -167,9 +169,9 @@ public AndConstraint NotBe(T unexpected, string because = "", param /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeRankedEquallyTo(T expected, string because = "", params object[] becauseArgs) + 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); @@ -191,9 +193,9 @@ public AndConstraint BeRankedEquallyTo(T expected, string because = /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeRankedEquallyTo(T unexpected, string because = "", params object[] becauseArgs) + 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); @@ -214,9 +216,9 @@ public AndConstraint NotBeRankedEquallyTo(T unexpected, string beca /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeLessThan(T expected, string because = "", params object[] becauseArgs) + 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); @@ -237,9 +239,9 @@ public AndConstraint BeLessThan(T expected, string because = "", pa /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeLessThanOrEqualTo(T expected, string because = "", params object[] becauseArgs) + 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); @@ -247,10 +249,6 @@ public AndConstraint BeLessThanOrEqualTo(T expected, string because return new AndConstraint((TAssertions)this); } - [EditorBrowsable(EditorBrowsableState.Never)] - public AndConstraint BeLessOrEqualTo(T expected, string because = "", params object[] becauseArgs) => - BeLessThanOrEqualTo(expected, because, becauseArgs); - /// /// Asserts that the subject is greater than another object according to its implementation of . /// @@ -264,9 +262,9 @@ public AndConstraint BeLessOrEqualTo(T expected, string because = " /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeGreaterThan(T expected, string because = "", params object[] becauseArgs) + 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); @@ -287,9 +285,9 @@ public AndConstraint BeGreaterThan(T expected, string because = "", /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeGreaterThanOrEqualTo(T expected, string because = "", params object[] becauseArgs) + 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); @@ -297,10 +295,6 @@ public AndConstraint BeGreaterThanOrEqualTo(T expected, string beca return new AndConstraint((TAssertions)this); } - [EditorBrowsable(EditorBrowsableState.Never)] - public AndConstraint BeGreaterOrEqualTo(T expected, string because = "", params object[] becauseArgs) => - BeGreaterThanOrEqualTo(expected, because, becauseArgs); - /// /// Asserts that a value is within a range. /// @@ -320,10 +314,10 @@ public AndConstraint BeGreaterOrEqualTo(T expected, string because /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeInRange(T minimumValue, T maximumValue, string because = "", - params object[] becauseArgs) + 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}.", @@ -351,10 +345,10 @@ public AndConstraint BeInRange(T minimumValue, T maximumValue, stri /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeInRange(T minimumValue, T maximumValue, string because = "", - params object[] becauseArgs) + 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}.", @@ -387,10 +381,10 @@ public AndConstraint BeOneOf(params T[] validValues) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeOneOf(IEnumerable validValues, string because = "", - params object[] becauseArgs) + 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 c39c703496..6a1efeda3a 100644 --- a/Src/FluentAssertions/Numeric/NullableNumericAssertions.cs +++ b/Src/FluentAssertions/Numeric/NullableNumericAssertions.cs @@ -1,31 +1,44 @@ using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using FluentAssertions.Common; using FluentAssertions.Execution; namespace FluentAssertions.Numeric; +/// +/// Contains a number of methods to assert that a nullable is in the expected state. +/// [DebuggerNonUserCode] public class NullableNumericAssertions : NullableNumericAssertions> where T : struct, IComparable { - public NullableNumericAssertions(T? value) - : base(value) + public NullableNumericAssertions(T? value, AssertionChain assertionChain) + : base(value, assertionChain) { } } +/// +/// Contains a number of methods to assert that a nullable is in the expected state. +/// [DebuggerNonUserCode] -public class NullableNumericAssertions : NumericAssertions +public class NullableNumericAssertions : NumericAssertionsBase where T : struct, IComparable where TAssertions : NullableNumericAssertions { - public NullableNumericAssertions(T? value) - : base(value) + private readonly AssertionChain assertionChain; + + public NullableNumericAssertions(T? value, AssertionChain assertionChain) + : base(assertionChain) { + Subject = value; + this.assertionChain = assertionChain; } + public override T? Subject { get; } + /// /// Asserts that a nullable numeric value is not . /// @@ -36,9 +49,9 @@ public NullableNumericAssertions(T? value) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveValue(string because = "", params object[] becauseArgs) + public AndConstraint HaveValue([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject.HasValue) .BecauseOf(because, becauseArgs) .FailWith("Expected a value{reason}."); @@ -56,7 +69,7 @@ public AndConstraint HaveValue(string because = "", params object[] /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeNull(string because = "", params object[] becauseArgs) + public AndConstraint NotBeNull([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return HaveValue(because, becauseArgs); } @@ -71,9 +84,9 @@ public AndConstraint NotBeNull(string because = "", params object[] /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveValue(string because = "", params object[] becauseArgs) + 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); @@ -91,7 +104,7 @@ public AndConstraint NotHaveValue(string because = "", params objec /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeNull(string because = "", params object[] becauseArgs) + public AndConstraint BeNull([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return NotHaveValue(because, becauseArgs); } @@ -111,12 +124,11 @@ public AndConstraint BeNull(string because = "", params object[] be /// /// is . public AndConstraint Match(Expression> predicate, - string because = "", - params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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 c49a167111..756f58a818 100644 --- a/Src/FluentAssertions/Numeric/NumericAssertions.cs +++ b/Src/FluentAssertions/Numeric/NumericAssertions.cs @@ -1,10 +1,5 @@ using System; -using System.Collections.Generic; -using System.ComponentModel; using System.Diagnostics; -using System.Linq; -using System.Linq.Expressions; -using FluentAssertions.Common; using FluentAssertions.Execution; namespace FluentAssertions.Numeric; @@ -16,507 +11,25 @@ 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) { } } -#pragma warning disable CS0659, S1206 // Ignore not overriding Object.GetHashCode() -#pragma warning disable CA1065 // Ignore throwing NotSupportedException from Equals /// /// Contains a number of methods to assert that an is in the expected state. /// [DebuggerNonUserCode] -public class NumericAssertions +public class NumericAssertions : NumericAssertionsBase where T : struct, IComparable where TAssertions : NumericAssertions { - public NumericAssertions(T value) - : this((T?)value) - { - } - - private protected NumericAssertions(T? value) + public NumericAssertions(T value, AssertionChain assertionChain) + : base(assertionChain) { Subject = value; } - public T? Subject { get; } - - /// - /// Asserts that the integral number value is exactly the same as the value. - /// - /// The expected value. - /// - /// 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 AndConstraint Be(T expected, string because = "", params object[] becauseArgs) - { - Execute.Assertion - .ForCondition(Subject?.CompareTo(expected) == 0) - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context:value} to be {0}{reason}, but found {1}" + GenerateDifferenceMessage(expected), expected, - Subject); - - return new AndConstraint((TAssertions)this); - } - - /// - /// Asserts that the integral number value is exactly the same as the value. - /// - /// The expected value. - /// - /// 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 AndConstraint Be(T? expected, string because = "", params object[] becauseArgs) - { - Execute.Assertion - .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, - Subject); - - return new AndConstraint((TAssertions)this); - } - - /// - /// Asserts that the integral number value is not the same as the value. - /// - /// The unexpected value. - /// - /// 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 AndConstraint NotBe(T unexpected, string because = "", params object[] becauseArgs) - { - Execute.Assertion - .ForCondition(Subject?.CompareTo(unexpected) != 0) - .BecauseOf(because, becauseArgs) - .FailWith("Did not expect {context:value} to be {0}{reason}.", unexpected); - - return new AndConstraint((TAssertions)this); - } - - /// - /// Asserts that the integral number value is not the same as the value. - /// - /// The unexpected value. - /// - /// 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 AndConstraint NotBe(T? unexpected, string because = "", params object[] becauseArgs) - { - Execute.Assertion - .ForCondition(unexpected is { } value ? Subject?.CompareTo(value) != 0 : Subject.HasValue) - .BecauseOf(because, becauseArgs) - .FailWith("Did not expect {context:value} to be {0}{reason}.", unexpected); - - return new AndConstraint((TAssertions)this); - } - - /// - /// Asserts that the numeric value is greater than zero. - /// - /// - /// 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 AndConstraint BePositive(string because = "", params object[] becauseArgs) - { - Execute.Assertion - .ForCondition(Subject?.CompareTo(default) > 0) - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context:value} to be positive{reason}, but found {0}.", Subject); - - return new AndConstraint((TAssertions)this); - } - - /// - /// Asserts that the numeric value is less than zero. - /// - /// - /// 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 AndConstraint BeNegative(string because = "", params object[] becauseArgs) - { - Execute.Assertion - .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); - - return new AndConstraint((TAssertions)this); - } - - /// - /// Asserts that the numeric value is less than the specified value. - /// - /// The value to compare the current numeric value with. - /// - /// 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 AndConstraint BeLessThan(T expected, string because = "", params object[] becauseArgs) - { - if (IsNaN(expected)) - { - throw new ArgumentException("A value can never be less than NaN", nameof(expected)); - } - - Execute.Assertion - .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), - expected, Subject); - - return new AndConstraint((TAssertions)this); - } - - /// - /// Asserts that the numeric value is less than or equal to the specified value. - /// - /// The value to compare the current numeric value with. - /// - /// 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 AndConstraint BeLessThanOrEqualTo(T expected, string because = "", - params object[] becauseArgs) - { - if (IsNaN(expected)) - { - throw new ArgumentException("A value can never be less than or equal to NaN", nameof(expected)); - } - - Execute.Assertion - .ForCondition(Subject is { } value && !IsNaN(value) && value.CompareTo(expected) <= 0) - .BecauseOf(because, becauseArgs) - .FailWith( - "Expected {context:value} to be less than or equal to {0}{reason}, but found {1}" + - GenerateDifferenceMessage(expected), expected, Subject); - - return new AndConstraint((TAssertions)this); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - public AndConstraint BeLessOrEqualTo(T expected, string because = "", params object[] becauseArgs) => - BeLessThanOrEqualTo(expected, because, becauseArgs); - - /// - /// Asserts that the numeric value is greater than the specified value. - /// - /// The value to compare the current numeric value with. - /// - /// 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 AndConstraint BeGreaterThan(T expected, string because = "", - params object[] becauseArgs) - { - if (IsNaN(expected)) - { - throw new ArgumentException("A value can never be greater than NaN", nameof(expected)); - } - - Execute.Assertion - .ForCondition(Subject?.CompareTo(expected) > 0) - .BecauseOf(because, becauseArgs) - .FailWith( - "Expected {context:value} to be greater than {0}{reason}, but found {1}" + GenerateDifferenceMessage(expected), - expected, Subject); - - return new AndConstraint((TAssertions)this); - } - - /// - /// Asserts that the numeric value is greater than or equal to the specified value. - /// - /// The value to compare the current numeric value with. - /// - /// 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 AndConstraint BeGreaterThanOrEqualTo(T expected, string because = "", - params object[] becauseArgs) - { - if (IsNaN(expected)) - { - throw new ArgumentException("A value can never be greater than or equal to a NaN", nameof(expected)); - } - - Execute.Assertion - .ForCondition(Subject?.CompareTo(expected) >= 0) - .BecauseOf(because, becauseArgs) - .FailWith( - "Expected {context:value} to be greater than or equal to {0}{reason}, but found {1}" + - GenerateDifferenceMessage(expected), expected, Subject); - - return new AndConstraint((TAssertions)this); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - public AndConstraint BeGreaterOrEqualTo(T expected, string because = "", params object[] becauseArgs) => - BeGreaterThanOrEqualTo(expected, because, becauseArgs); - - /// - /// Asserts that a value is within a range. - /// - /// - /// Where the range is continuous or incremental depends on the actual type of the value. - /// - /// - /// The minimum valid value of the range (inclusive). - /// - /// - /// The maximum valid value of the range (inclusive). - /// - /// - /// 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 AndConstraint BeInRange(T minimumValue, T maximumValue, string because = "", - params object[] becauseArgs) - { - if (IsNaN(minimumValue) || IsNaN(maximumValue)) - { - throw new ArgumentException("A range cannot begin or end with NaN"); - } - - Execute.Assertion - .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}.", - minimumValue, maximumValue, Subject); - - return new AndConstraint((TAssertions)this); - } - - /// - /// Asserts that a value is not within a range. - /// - /// - /// Where the range is continuous or incremental depends on the actual type of the value. - /// - /// - /// The minimum valid value of the range. - /// - /// - /// The maximum valid value of the range. - /// - /// - /// 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 AndConstraint NotBeInRange(T minimumValue, T maximumValue, string because = "", - params object[] becauseArgs) - { - if (IsNaN(minimumValue) || IsNaN(maximumValue)) - { - throw new ArgumentException("A range cannot begin or end with NaN"); - } - - Execute.Assertion - .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}.", - minimumValue, maximumValue, Subject); - - return new AndConstraint((TAssertions)this); - } - - /// - /// Asserts that a value is one of the specified . - /// - /// - /// The values that are valid. - /// - public AndConstraint BeOneOf(params T[] validValues) - { - return BeOneOf(validValues, string.Empty); - } - - /// - /// Asserts that a value is one of the specified . - /// - /// - /// The values that are valid. - /// - /// - /// 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 AndConstraint BeOneOf(IEnumerable validValues, string because = "", - params object[] becauseArgs) - { - Execute.Assertion - .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); - - return new AndConstraint((TAssertions)this); - } - - /// - /// Asserts that the object is of the specified type . - /// - /// - /// The type that the subject is supposed to be of. - /// - /// - /// 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 . - /// - /// is . - public AndConstraint BeOfType(Type expectedType, string because = "", params object[] becauseArgs) - { - Guard.ThrowIfArgumentIsNull(expectedType); - - Type subjectType = Subject?.GetType(); - - if (expectedType.IsGenericTypeDefinition && subjectType?.IsGenericType == true) - { - subjectType.GetGenericTypeDefinition().Should().Be(expectedType, because, becauseArgs); - } - else - { - subjectType.Should().Be(expectedType, because, becauseArgs); - } - - return new AndConstraint((TAssertions)this); - } - - /// - /// Asserts that the object is not of the specified type . - /// - /// - /// The type that the subject is not supposed to be of. - /// - /// - /// 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 . - /// - /// is . - public AndConstraint NotBeOfType(Type unexpectedType, string because = "", params object[] becauseArgs) - { - Guard.ThrowIfArgumentIsNull(unexpectedType); - - bool success = Execute.Assertion - .ForCondition(Subject.HasValue) - .BecauseOf(because, becauseArgs) - .FailWith("Expected type not to be " + unexpectedType + "{reason}, but found ."); - - if (success) - { - Subject.GetType().Should().NotBe(unexpectedType, because, becauseArgs); - } - - return new AndConstraint((TAssertions)this); - } - - /// - /// Asserts that the is satisfied. - /// - /// - /// The predicate which must be satisfied - /// - /// - /// 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 . - /// - /// is . - public AndConstraint Match(Expression> predicate, - string because = "", - params object[] becauseArgs) - { - Guard.ThrowIfArgumentIsNull(predicate); - - Execute.Assertion - .ForCondition(predicate.Compile()((T)Subject)) - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context:value} to match {0}{reason}, but found {1}.", predicate.Body, Subject); - - return new AndConstraint((TAssertions)this); - } - - /// - public override bool Equals(object obj) => - throw new NotSupportedException("Equals is not part of Fluent Assertions. Did you mean Be() instead?"); - - private protected virtual bool IsNaN(T value) => false; - - /// - /// A method to generate additional information upon comparison failures. - /// - /// The current numeric value verified to be non-null. - /// The value to compare the current numeric value with. - /// - /// Returns the difference between a number value and the value. - /// Returns `null` if the compared numbers are small enough that a difference message is irrelevant. - /// - private protected virtual string CalculateDifferenceForFailureMessage(T subject, T expected) => null; - - private string GenerateDifferenceMessage(T? expected) - { - const string noDifferenceMessage = "."; - - if (Subject is not { } subject || expected is not T expectedValue) - { - return noDifferenceMessage; - } - - var difference = CalculateDifferenceForFailureMessage(subject, expectedValue); - return difference is null ? noDifferenceMessage : $" (difference of {difference})."; - } + public override T Subject { get; } } diff --git a/Src/FluentAssertions/Numeric/NumericAssertionsBase.cs b/Src/FluentAssertions/Numeric/NumericAssertionsBase.cs new file mode 100644 index 0000000000..224f4ad286 --- /dev/null +++ b/Src/FluentAssertions/Numeric/NumericAssertionsBase.cs @@ -0,0 +1,495 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Linq.Expressions; +using FluentAssertions.Common; +using FluentAssertions.Execution; + +namespace FluentAssertions.Numeric; + +#pragma warning disable CS0659, S1206 // Ignore not overriding Object.GetHashCode() +#pragma warning disable CA1065 // Ignore throwing NotSupportedException from Equals +public abstract class NumericAssertionsBase + where T : struct, IComparable + where TAssertions : NumericAssertionsBase +{ + public abstract TSubject Subject { get; } + + protected NumericAssertionsBase(AssertionChain assertionChain) + { + CurrentAssertionChain = assertionChain; + } + + /// + /// Asserts that the integral number value is exactly the same as the value. + /// + /// The expected value. + /// + /// 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 AndConstraint Be(T expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + CurrentAssertionChain + .ForCondition(Subject is T subject && subject.CompareTo(expected) == 0) + .BecauseOf(because, becauseArgs) + .FailWith("Expected {context:value} to be {0}{reason}, but found {1}" + GenerateDifferenceMessage(expected), expected, + Subject); + + return new AndConstraint((TAssertions)this); + } + + /// + /// Asserts that the integral number value is exactly the same as the value. + /// + /// The expected value. + /// + /// 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 AndConstraint Be(T? expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + CurrentAssertionChain + .ForCondition(expected is { } value ? Subject is T subject && subject.CompareTo(value) == 0 : Subject is not T) + .BecauseOf(because, becauseArgs) + .FailWith("Expected {context:value} to be {0}{reason}, but found {1}" + GenerateDifferenceMessage(expected), expected, + Subject); + + return new AndConstraint((TAssertions)this); + } + + /// + /// Asserts that the integral number value is not the same as the value. + /// + /// The unexpected value. + /// + /// 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 AndConstraint NotBe(T unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + CurrentAssertionChain + .ForCondition(Subject is not T subject || subject.CompareTo(unexpected) != 0) + .BecauseOf(because, becauseArgs) + .FailWith("Did not expect {context:value} to be {0}{reason}.", unexpected); + + return new AndConstraint((TAssertions)this); + } + + /// + /// Asserts that the integral number value is not the same as the value. + /// + /// The unexpected value. + /// + /// 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 AndConstraint NotBe(T? unexpected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + CurrentAssertionChain + .ForCondition(unexpected is { } value ? Subject is not T subject || subject.CompareTo(value) != 0 : Subject is T) + .BecauseOf(because, becauseArgs) + .FailWith("Did not expect {context:value} to be {0}{reason}.", unexpected); + + return new AndConstraint((TAssertions)this); + } + + /// + /// Asserts that the numeric value is greater than zero. + /// + /// + /// 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 AndConstraint BePositive([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + CurrentAssertionChain + .ForCondition(Subject is T subject && subject.CompareTo(default) > 0) + .BecauseOf(because, becauseArgs) + .FailWith("Expected {context:value} to be positive{reason}, but found {0}.", Subject); + + return new AndConstraint((TAssertions)this); + } + + /// + /// Asserts that the numeric value is less than zero. + /// + /// + /// 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 AndConstraint BeNegative([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + CurrentAssertionChain + .ForCondition(Subject is T value && !IsNaN(value) && value.CompareTo(default) < 0) + .BecauseOf(because, becauseArgs) + .FailWith("Expected {context:value} to be negative{reason}, but found {0}.", Subject); + + return new AndConstraint((TAssertions)this); + } + + /// + /// Asserts that the numeric value is less than the specified value. + /// + /// The value to compare the current numeric value with. + /// + /// 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 AndConstraint BeLessThan(T expected, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + if (IsNaN(expected)) + { + throw new ArgumentException("A value can never be less than NaN", nameof(expected)); + } + + CurrentAssertionChain + .ForCondition(Subject is T 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), + expected, Subject); + + return new AndConstraint((TAssertions)this); + } + + /// + /// Asserts that the numeric value is less than or equal to the specified value. + /// + /// The value to compare the current numeric value with. + /// + /// 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 AndConstraint BeLessThanOrEqualTo(T expected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + if (IsNaN(expected)) + { + throw new ArgumentException("A value can never be less than or equal to NaN", nameof(expected)); + } + + CurrentAssertionChain + .ForCondition(Subject is T value && !IsNaN(value) && value.CompareTo(expected) <= 0) + .BecauseOf(because, becauseArgs) + .FailWith( + "Expected {context:value} to be less than or equal to {0}{reason}, but found {1}" + + GenerateDifferenceMessage(expected), expected, Subject); + + return new AndConstraint((TAssertions)this); + } + + /// + /// Asserts that the numeric value is greater than the specified value. + /// + /// The value to compare the current numeric value with. + /// + /// 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 AndConstraint BeGreaterThan(T expected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + if (IsNaN(expected)) + { + throw new ArgumentException("A value can never be greater than NaN", nameof(expected)); + } + + CurrentAssertionChain + .ForCondition(Subject is T subject && subject.CompareTo(expected) > 0) + .BecauseOf(because, becauseArgs) + .FailWith( + "Expected {context:value} to be greater than {0}{reason}, but found {1}" + GenerateDifferenceMessage(expected), + expected, Subject); + + return new AndConstraint((TAssertions)this); + } + + /// + /// Asserts that the numeric value is greater than or equal to the specified value. + /// + /// The value to compare the current numeric value with. + /// + /// 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 AndConstraint BeGreaterThanOrEqualTo(T expected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + if (IsNaN(expected)) + { + throw new ArgumentException("A value can never be greater than or equal to a NaN", nameof(expected)); + } + + CurrentAssertionChain + .ForCondition(Subject is T subject && subject.CompareTo(expected) >= 0) + .BecauseOf(because, becauseArgs) + .FailWith( + "Expected {context:value} to be greater than or equal to {0}{reason}, but found {1}" + + GenerateDifferenceMessage(expected), expected, Subject); + + return new AndConstraint((TAssertions)this); + } + + /// + /// Asserts that a value is within a range. + /// + /// + /// Where the range is continuous or incremental depends on the actual type of the value. + /// + /// + /// The minimum valid value of the range (inclusive). + /// + /// + /// The maximum valid value of the range (inclusive). + /// + /// + /// 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 AndConstraint BeInRange(T minimumValue, T maximumValue, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + if (IsNaN(minimumValue) || IsNaN(maximumValue)) + { + throw new ArgumentException("A range cannot begin or end with NaN"); + } + + CurrentAssertionChain + .ForCondition(Subject is T 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}.", + minimumValue, maximumValue, Subject); + + return new AndConstraint((TAssertions)this); + } + + /// + /// Asserts that a value is not within a range. + /// + /// + /// Where the range is continuous or incremental depends on the actual type of the value. + /// + /// + /// The minimum valid value of the range. + /// + /// + /// The maximum valid value of the range. + /// + /// + /// 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 AndConstraint NotBeInRange(T minimumValue, T maximumValue, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + if (IsNaN(minimumValue) || IsNaN(maximumValue)) + { + throw new ArgumentException("A range cannot begin or end with NaN"); + } + + CurrentAssertionChain + .ForCondition(Subject is T 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}.", + minimumValue, maximumValue, Subject); + + return new AndConstraint((TAssertions)this); + } + + /// + /// Asserts that a value is one of the specified . + /// + /// + /// The values that are valid. + /// + public AndConstraint BeOneOf(params T[] validValues) + { + return BeOneOf(validValues, string.Empty); + } + + /// + /// Asserts that a value is one of the specified . + /// + /// + /// The values that are valid. + /// + /// + /// 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 AndConstraint BeOneOf(IEnumerable validValues, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + CurrentAssertionChain + .ForCondition(Subject is T value && validValues.Contains(value)) + .BecauseOf(because, becauseArgs) + .FailWith("Expected {context:value} to be one of {0}{reason}, but found {1}.", validValues, Subject); + + return new AndConstraint((TAssertions)this); + } + + /// + /// Asserts that the object is of the specified type . + /// + /// + /// The type that the subject is supposed to be of. + /// + /// + /// 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 . + /// + /// is . + public AndConstraint BeOfType(Type expectedType, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNull(expectedType); + + Type subjectType = Subject?.GetType(); + + if (expectedType.IsGenericTypeDefinition && subjectType?.IsGenericType == true) + { + subjectType.GetGenericTypeDefinition().Should().Be(expectedType, because, becauseArgs); + } + else + { + subjectType.Should().Be(expectedType, because, becauseArgs); + } + + return new AndConstraint((TAssertions)this); + } + + /// + /// Asserts that the object is not of the specified type . + /// + /// + /// The type that the subject is not supposed to be of. + /// + /// + /// 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 . + /// + /// is . + public AndConstraint NotBeOfType(Type unexpectedType, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNull(unexpectedType); + + CurrentAssertionChain + .ForCondition(Subject is T) + .BecauseOf(because, becauseArgs) + .FailWith("Expected type not to be " + unexpectedType + "{reason}, but found ."); + + if (CurrentAssertionChain.Succeeded) + { + Subject.GetType().Should().NotBe(unexpectedType, because, becauseArgs); + } + + return new AndConstraint((TAssertions)this); + } + + /// + /// Asserts that the is satisfied. + /// + /// + /// The predicate which must be satisfied + /// + /// + /// 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 . + /// + /// is . + public AndConstraint Match(Expression> predicate, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNull(predicate); + + CurrentAssertionChain + .ForCondition(Subject is T expression && predicate.Compile()(expression)) + .BecauseOf(because, becauseArgs) + .FailWith("Expected {context:value} to match {0}{reason}, but found {1}.", predicate.Body, Subject); + + return new AndConstraint((TAssertions)this); + } + + /// + public override bool Equals(object obj) => + throw new NotSupportedException("Equals is not part of Fluent Assertions. Did you mean Be() instead?"); + + private protected virtual bool IsNaN(T value) => false; + + /// + /// A method to generate additional information upon comparison failures. + /// + /// The current numeric value verified to be non-null. + /// The value to compare the current numeric value with. + /// + /// Returns the difference between a number value and the value. + /// Returns `null` if the compared numbers are small enough that a difference message is irrelevant. + /// + private protected virtual string CalculateDifferenceForFailureMessage(T subject, T expected) => null; + + private string GenerateDifferenceMessage(T? expected) + { + const string noDifferenceMessage = "."; + + if (Subject is not T subject || expected is not { } expectedValue) + { + return noDifferenceMessage; + } + + var difference = CalculateDifferenceForFailureMessage(subject, expectedValue); + return difference is null ? noDifferenceMessage : $" (difference of {difference})."; + } + + /// + /// Provides access to the that this assertion class was initialized with. + /// + 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 4bb65302f8..01e48fe96b 100644 --- a/Src/FluentAssertions/NumericAssertionsExtensions.cs +++ b/Src/FluentAssertions/NumericAssertionsExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using FluentAssertions.Common; using FluentAssertions.Execution; using FluentAssertions.Numeric; @@ -30,10 +31,10 @@ public static class NumericAssertionsExtensions /// Zero or more objects to format using the placeholders in . /// public static AndConstraint> BeCloseTo(this NumericAssertions parent, - sbyte nearbyValue, byte delta, string because = "", - params object[] becauseArgs) + sbyte nearbyValue, byte delta, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - sbyte actualValue = parent.Subject.Value; + sbyte actualValue = parent.Subject; sbyte minValue = (sbyte)(nearbyValue - delta); if (minValue > nearbyValue) @@ -48,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); } @@ -72,10 +74,10 @@ public static AndConstraint> BeCloseTo(this NumericAsse /// Zero or more objects to format using the placeholders in . /// public static AndConstraint> BeCloseTo(this NumericAssertions parent, - byte nearbyValue, byte delta, string because = "", - params object[] becauseArgs) + byte nearbyValue, byte delta, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - byte actualValue = parent.Subject.Value; + byte actualValue = parent.Subject; byte minValue = (byte)(nearbyValue - delta); if (minValue > nearbyValue) @@ -90,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); } @@ -114,10 +117,10 @@ public static AndConstraint> BeCloseTo(this NumericAsser /// Zero or more objects to format using the placeholders in . /// public static AndConstraint> BeCloseTo(this NumericAssertions parent, - short nearbyValue, ushort delta, string because = "", - params object[] becauseArgs) + short nearbyValue, ushort delta, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - short actualValue = parent.Subject.Value; + short actualValue = parent.Subject; short minValue = (short)(nearbyValue - delta); if (minValue > nearbyValue) @@ -132,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); } @@ -156,10 +161,10 @@ public static AndConstraint> BeCloseTo(this NumericAsse /// Zero or more objects to format using the placeholders in . /// public static AndConstraint> BeCloseTo(this NumericAssertions parent, - ushort nearbyValue, ushort delta, string because = "", - params object[] becauseArgs) + ushort nearbyValue, ushort delta, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - ushort actualValue = parent.Subject.Value; + ushort actualValue = parent.Subject; ushort minValue = (ushort)(nearbyValue - delta); if (minValue > nearbyValue) @@ -174,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); } @@ -198,10 +205,10 @@ public static AndConstraint> BeCloseTo(this NumericAss /// Zero or more objects to format using the placeholders in . /// public static AndConstraint> BeCloseTo(this NumericAssertions parent, - int nearbyValue, uint delta, string because = "", - params object[] becauseArgs) + int nearbyValue, uint delta, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - int actualValue = parent.Subject.Value; + int actualValue = parent.Subject; int minValue = (int)(nearbyValue - delta); if (minValue > nearbyValue) @@ -216,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); @@ -240,10 +250,10 @@ public static AndConstraint> BeCloseTo(this NumericAssert /// Zero or more objects to format using the placeholders in . /// public static AndConstraint> BeCloseTo(this NumericAssertions parent, - uint nearbyValue, uint delta, string because = "", - params object[] becauseArgs) + uint nearbyValue, uint delta, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - uint actualValue = parent.Subject.Value; + uint actualValue = parent.Subject; uint minValue = nearbyValue - delta; if (minValue > nearbyValue) @@ -258,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); } @@ -282,15 +295,17 @@ public static AndConstraint> BeCloseTo(this NumericAsser /// Zero or more objects to format using the placeholders in . /// public static AndConstraint> BeCloseTo(this NumericAssertions parent, - long nearbyValue, ulong delta, string because = "", - params object[] becauseArgs) + long nearbyValue, ulong delta, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - long actualValue = parent.Subject.Value; + long actualValue = parent.Subject; 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); } @@ -313,10 +328,10 @@ public static AndConstraint> BeCloseTo(this NumericAsser /// Zero or more objects to format using the placeholders in . /// public static AndConstraint> BeCloseTo(this NumericAssertions parent, - ulong nearbyValue, ulong delta, string because = "", - params object[] becauseArgs) + ulong nearbyValue, ulong delta, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - ulong actualValue = parent.Subject.Value; + ulong actualValue = parent.Subject; ulong minValue = nearbyValue - delta; if (minValue > nearbyValue) @@ -331,17 +346,20 @@ 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, + [SuppressMessage("Maintainability", "AV1561:Signature contains too many parameters")] + private static void FailIfValueOutsideBounds(AssertionChain assertionChain, bool valueWithinBounds, TValue nearbyValue, TDelta delta, TValue actualValue, - string because, object[] becauseArgs) + [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}.", @@ -370,10 +388,10 @@ private static void FailIfValueOutsideBounds(bool valueWithinBou /// Zero or more objects to format using the placeholders in . /// public static AndConstraint> NotBeCloseTo(this NumericAssertions parent, - sbyte distantValue, byte delta, string because = "", - params object[] becauseArgs) + sbyte distantValue, byte delta, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - sbyte actualValue = parent.Subject.Value; + sbyte actualValue = parent.Subject; sbyte minValue = (sbyte)(distantValue - delta); if (minValue > distantValue) @@ -388,8 +406,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); } @@ -412,10 +432,10 @@ public static AndConstraint> NotBeCloseTo(this NumericA /// Zero or more objects to format using the placeholders in . /// public static AndConstraint> NotBeCloseTo(this NumericAssertions parent, - byte distantValue, byte delta, string because = "", - params object[] becauseArgs) + byte distantValue, byte delta, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - byte actualValue = parent.Subject.Value; + byte actualValue = parent.Subject; byte minValue = (byte)(distantValue - delta); if (minValue > distantValue) @@ -430,8 +450,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); } @@ -454,10 +477,10 @@ public static AndConstraint> NotBeCloseTo(this NumericAs /// Zero or more objects to format using the placeholders in . /// public static AndConstraint> NotBeCloseTo(this NumericAssertions parent, - short distantValue, ushort delta, string because = "", - params object[] becauseArgs) + short distantValue, ushort delta, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - short actualValue = parent.Subject.Value; + short actualValue = parent.Subject; short minValue = (short)(distantValue - delta); if (minValue > distantValue) @@ -472,8 +495,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); } @@ -496,10 +522,10 @@ public static AndConstraint> NotBeCloseTo(this NumericA /// Zero or more objects to format using the placeholders in . /// public static AndConstraint> NotBeCloseTo(this NumericAssertions parent, - ushort distantValue, ushort delta, string because = "", - params object[] becauseArgs) + ushort distantValue, ushort delta, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - ushort actualValue = parent.Subject.Value; + ushort actualValue = parent.Subject; ushort minValue = (ushort)(distantValue - delta); if (minValue > distantValue) @@ -514,8 +540,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); } @@ -538,10 +566,10 @@ public static AndConstraint> NotBeCloseTo(this Numeric /// Zero or more objects to format using the placeholders in . /// public static AndConstraint> NotBeCloseTo(this NumericAssertions parent, - int distantValue, uint delta, string because = "", - params object[] becauseArgs) + int distantValue, uint delta, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - int actualValue = parent.Subject.Value; + int actualValue = parent.Subject; int minValue = (int)(distantValue - delta); if (minValue > distantValue) @@ -556,8 +584,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); } @@ -580,10 +611,10 @@ public static AndConstraint> NotBeCloseTo(this NumericAss /// Zero or more objects to format using the placeholders in . /// public static AndConstraint> NotBeCloseTo(this NumericAssertions parent, - uint distantValue, uint delta, string because = "", - params object[] becauseArgs) + uint distantValue, uint delta, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - uint actualValue = parent.Subject.Value; + uint actualValue = parent.Subject; uint minValue = distantValue - delta; if (minValue > distantValue) @@ -598,8 +629,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); } @@ -622,15 +655,17 @@ public static AndConstraint> NotBeCloseTo(this NumericAs /// Zero or more objects to format using the placeholders in . /// public static AndConstraint> NotBeCloseTo(this NumericAssertions parent, - long distantValue, ulong delta, string because = "", - params object[] becauseArgs) + long distantValue, ulong delta, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - long actualValue = parent.Subject.Value; + long actualValue = parent.Subject; 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); } @@ -653,10 +688,10 @@ public static AndConstraint> NotBeCloseTo(this NumericAs /// Zero or more objects to format using the placeholders in . /// public static AndConstraint> NotBeCloseTo(this NumericAssertions parent, - ulong distantValue, ulong delta, string because = "", - params object[] becauseArgs) + ulong distantValue, ulong delta, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - ulong actualValue = parent.Subject.Value; + ulong actualValue = parent.Subject; ulong minValue = distantValue - delta; if (minValue > distantValue) @@ -671,18 +706,24 @@ 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); } + [SuppressMessage("Maintainability", "AV1561:Signature contains too many parameters")] private static void FailIfValueInsideBounds( + AssertionChain assertionChain, bool valueOutsideBounds, TValue distantValue, TDelta delta, TValue actualValue, - string because, object[] becauseArgs) + [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}.", @@ -712,20 +753,22 @@ private static void FailIfValueInsideBounds( /// /// is negative. public static AndConstraint> BeApproximately(this NullableNumericAssertions parent, - float expectedValue, float precision, string because = "", - params object[] becauseArgs) + float expectedValue, float precision, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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); } @@ -752,8 +795,8 @@ public static AndConstraint> BeApproximately(th /// /// is negative. public static AndConstraint> BeApproximately(this NullableNumericAssertions parent, - float? expectedValue, float precision, string because = "", - params object[] becauseArgs) + float? expectedValue, float precision, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNegative(precision); @@ -762,13 +805,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); @@ -796,8 +841,8 @@ public static AndConstraint> BeApproximately(th /// /// is negative. public static AndConstraint> BeApproximately(this NumericAssertions parent, - float expectedValue, float precision, string because = "", - params object[] becauseArgs) + float expectedValue, float precision, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { if (float.IsNaN(expectedValue)) { @@ -808,17 +853,17 @@ public static AndConstraint> BeApproximately(this Numer if (float.IsPositiveInfinity(expectedValue)) { - FailIfDifferenceOutsidePrecision(float.IsPositiveInfinity(parent.Subject.Value), parent, expectedValue, precision, + FailIfDifferenceOutsidePrecision(float.IsPositiveInfinity(parent.Subject), parent, expectedValue, precision, float.NaN, because, becauseArgs); } else if (float.IsNegativeInfinity(expectedValue)) { - FailIfDifferenceOutsidePrecision(float.IsNegativeInfinity(parent.Subject.Value), parent, expectedValue, precision, + FailIfDifferenceOutsidePrecision(float.IsNegativeInfinity(parent.Subject), parent, expectedValue, precision, float.NaN, because, becauseArgs); } else { - float actualDifference = Math.Abs(expectedValue - parent.Subject.Value); + float actualDifference = Math.Abs(expectedValue - parent.Subject); FailIfDifferenceOutsidePrecision(actualDifference <= precision, parent, expectedValue, precision, actualDifference, because, becauseArgs); @@ -846,20 +891,22 @@ public static AndConstraint> BeApproximately(this Numer /// /// is negative. public static AndConstraint> BeApproximately(this NullableNumericAssertions parent, - double expectedValue, double precision, string because = "", - params object[] becauseArgs) + double expectedValue, double precision, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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); } @@ -886,8 +933,8 @@ public static AndConstraint> BeApproximately(t /// /// is negative. public static AndConstraint> BeApproximately(this NullableNumericAssertions parent, - double? expectedValue, double precision, string because = "", - params object[] becauseArgs) + double? expectedValue, double precision, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNegative(precision); @@ -896,13 +943,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); @@ -930,8 +979,8 @@ public static AndConstraint> BeApproximately(t /// /// is negative. public static AndConstraint> BeApproximately(this NumericAssertions parent, - double expectedValue, double precision, string because = "", - params object[] becauseArgs) + double expectedValue, double precision, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { if (double.IsNaN(expectedValue)) { @@ -942,17 +991,17 @@ public static AndConstraint> BeApproximately(this Nume if (double.IsPositiveInfinity(expectedValue)) { - FailIfDifferenceOutsidePrecision(double.IsPositiveInfinity(parent.Subject.Value), parent, expectedValue, precision, + FailIfDifferenceOutsidePrecision(double.IsPositiveInfinity(parent.Subject), parent, expectedValue, precision, double.NaN, because, becauseArgs); } else if (double.IsNegativeInfinity(expectedValue)) { - FailIfDifferenceOutsidePrecision(double.IsNegativeInfinity(parent.Subject.Value), parent, expectedValue, precision, + FailIfDifferenceOutsidePrecision(double.IsNegativeInfinity(parent.Subject), parent, expectedValue, precision, double.NaN, because, becauseArgs); } else { - double actualDifference = Math.Abs(expectedValue - parent.Subject.Value); + double actualDifference = Math.Abs(expectedValue - parent.Subject); FailIfDifferenceOutsidePrecision(actualDifference <= precision, parent, expectedValue, precision, actualDifference, because, becauseArgs); @@ -981,20 +1030,22 @@ public static AndConstraint> BeApproximately(this Nume /// is negative. public static AndConstraint> BeApproximately( this NullableNumericAssertions parent, - decimal expectedValue, decimal precision, string because = "", - params object[] becauseArgs) + decimal expectedValue, decimal precision, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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); } @@ -1022,8 +1073,8 @@ public static AndConstraint> BeApproximately( /// is negative. public static AndConstraint> BeApproximately( this NullableNumericAssertions parent, - decimal? expectedValue, decimal precision, string because = "", - params object[] becauseArgs) + decimal? expectedValue, decimal precision, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNegative(precision); @@ -1032,13 +1083,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); @@ -1066,12 +1119,12 @@ public static AndConstraint> BeApproximately( /// /// is negative. public static AndConstraint> BeApproximately(this NumericAssertions parent, - decimal expectedValue, decimal precision, string because = "", - params object[] becauseArgs) + decimal expectedValue, decimal precision, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNegative(precision); - decimal actualDifference = Math.Abs(expectedValue - parent.Subject.Value); + decimal actualDifference = Math.Abs(expectedValue - parent.Subject); FailIfDifferenceOutsidePrecision(actualDifference <= precision, parent, expectedValue, precision, actualDifference, because, becauseArgs); @@ -1079,13 +1132,16 @@ public static AndConstraint> BeApproximately(this Num return new AndConstraint>(parent); } + [SuppressMessage("Maintainability", "AV1561:Signature contains too many parameters")] private static void FailIfDifferenceOutsidePrecision( bool differenceWithinPrecision, NumericAssertions parent, T expectedValue, T precision, T actualDifference, - string because, object[] becauseArgs) + [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}.", @@ -1115,14 +1171,14 @@ private static void FailIfDifferenceOutsidePrecision( /// /// is negative. public static AndConstraint> NotBeApproximately(this NullableNumericAssertions parent, - float unexpectedValue, float precision, string because = "", - params object[] becauseArgs) + float unexpectedValue, float precision, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNegative(precision); 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); } @@ -1149,8 +1205,8 @@ public static AndConstraint> NotBeApproximately /// /// is negative. public static AndConstraint> NotBeApproximately(this NullableNumericAssertions parent, - float? unexpectedValue, float precision, string because = "", - params object[] becauseArgs) + float? unexpectedValue, float precision, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNegative(precision); @@ -1159,13 +1215,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); @@ -1193,8 +1251,8 @@ public static AndConstraint> NotBeApproximately /// /// is negative. public static AndConstraint> NotBeApproximately(this NumericAssertions parent, - float unexpectedValue, float precision, string because = "", - params object[] becauseArgs) + float unexpectedValue, float precision, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { if (float.IsNaN(unexpectedValue)) { @@ -1205,17 +1263,17 @@ public static AndConstraint> NotBeApproximately(this Nu if (float.IsPositiveInfinity(unexpectedValue)) { - FailIfDifferenceWithinPrecision(parent, !float.IsPositiveInfinity(parent.Subject.Value), unexpectedValue, precision, + FailIfDifferenceWithinPrecision(parent, !float.IsPositiveInfinity(parent.Subject), unexpectedValue, precision, float.NaN, because, becauseArgs); } else if (float.IsNegativeInfinity(unexpectedValue)) { - FailIfDifferenceWithinPrecision(parent, !float.IsNegativeInfinity(parent.Subject.Value), unexpectedValue, precision, + FailIfDifferenceWithinPrecision(parent, !float.IsNegativeInfinity(parent.Subject), unexpectedValue, precision, float.NaN, because, becauseArgs); } else { - float actualDifference = Math.Abs(unexpectedValue - parent.Subject.Value); + float actualDifference = Math.Abs(unexpectedValue - parent.Subject); FailIfDifferenceWithinPrecision(parent, actualDifference > precision, unexpectedValue, precision, actualDifference, because, becauseArgs); @@ -1244,14 +1302,14 @@ public static AndConstraint> NotBeApproximately(this Nu /// is negative. public static AndConstraint> NotBeApproximately( this NullableNumericAssertions parent, - double unexpectedValue, double precision, string because = "", - params object[] becauseArgs) + double unexpectedValue, double precision, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNegative(precision); 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); } @@ -1279,8 +1337,8 @@ public static AndConstraint> NotBeApproximatel /// is negative. public static AndConstraint> NotBeApproximately( this NullableNumericAssertions parent, - double? unexpectedValue, double precision, string because = "", - params object[] becauseArgs) + double? unexpectedValue, double precision, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNegative(precision); @@ -1289,13 +1347,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); @@ -1323,8 +1383,8 @@ public static AndConstraint> NotBeApproximatel /// /// is negative. public static AndConstraint> NotBeApproximately(this NumericAssertions parent, - double unexpectedValue, double precision, string because = "", - params object[] becauseArgs) + double unexpectedValue, double precision, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { if (double.IsNaN(unexpectedValue)) { @@ -1335,17 +1395,17 @@ public static AndConstraint> NotBeApproximately(this N if (double.IsPositiveInfinity(unexpectedValue)) { - FailIfDifferenceWithinPrecision(parent, !double.IsPositiveInfinity(parent.Subject.Value), unexpectedValue, precision, + FailIfDifferenceWithinPrecision(parent, !double.IsPositiveInfinity(parent.Subject), unexpectedValue, precision, double.NaN, because, becauseArgs); } else if (double.IsNegativeInfinity(unexpectedValue)) { - FailIfDifferenceWithinPrecision(parent, !double.IsNegativeInfinity(parent.Subject.Value), unexpectedValue, precision, + FailIfDifferenceWithinPrecision(parent, !double.IsNegativeInfinity(parent.Subject), unexpectedValue, precision, double.NaN, because, becauseArgs); } else { - double actualDifference = Math.Abs(unexpectedValue - parent.Subject.Value); + double actualDifference = Math.Abs(unexpectedValue - parent.Subject); FailIfDifferenceWithinPrecision(parent, actualDifference > precision, unexpectedValue, precision, actualDifference, because, becauseArgs); @@ -1374,14 +1434,14 @@ public static AndConstraint> NotBeApproximately(this N /// is negative. public static AndConstraint> NotBeApproximately( this NullableNumericAssertions parent, - decimal unexpectedValue, decimal precision, string because = "", - params object[] becauseArgs) + decimal unexpectedValue, decimal precision, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNegative(precision); 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); } @@ -1409,8 +1469,8 @@ public static AndConstraint> NotBeApproximate /// is negative. public static AndConstraint> NotBeApproximately( this NullableNumericAssertions parent, - decimal? unexpectedValue, decimal precision, string because = "", - params object[] becauseArgs) + decimal? unexpectedValue, decimal precision, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNegative(precision); @@ -1419,13 +1479,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); @@ -1453,12 +1515,12 @@ public static AndConstraint> NotBeApproximate /// /// is negative. public static AndConstraint> NotBeApproximately(this NumericAssertions parent, - decimal unexpectedValue, decimal precision, string because = "", - params object[] becauseArgs) + decimal unexpectedValue, decimal precision, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNegative(precision); - decimal actualDifference = Math.Abs(unexpectedValue - parent.Subject.Value); + decimal actualDifference = Math.Abs(unexpectedValue - parent.Subject); FailIfDifferenceWithinPrecision(parent, actualDifference > precision, unexpectedValue, precision, actualDifference, because, becauseArgs); @@ -1466,13 +1528,14 @@ public static AndConstraint> NotBeApproximately(this return new AndConstraint>(parent); } + [SuppressMessage("Maintainability", "AV1561:Signature contains too many parameters")] private static void FailIfDifferenceWithinPrecision( NumericAssertions parent, bool differenceOutsidePrecision, T unexpectedValue, T precision, T actualDifference, - string because, object[] becauseArgs) + [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}.", @@ -1481,9 +1544,212 @@ private static void FailIfDifferenceWithinPrecision( #endregion + #region BeNaN + + /// + /// Asserts that the number is seen as not a number (NaN). + /// + /// + /// 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 static AndConstraint> BeNaN(this NumericAssertions parent, + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) + { + float actualValue = parent.Subject; + + parent.CurrentAssertionChain + .ForCondition(float.IsNaN(actualValue)) + .BecauseOf(because, becauseArgs) + .FailWith("Expected {context:value} to be NaN{reason}, but found {0}.", actualValue); + + return new AndConstraint>(parent); + } + + /// + /// Asserts that the number is seen as not a number (NaN). + /// + /// + /// 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 static AndConstraint> BeNaN(this NumericAssertions parent, + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) + { + double actualValue = parent.Subject; + + parent.CurrentAssertionChain + .ForCondition(double.IsNaN(actualValue)) + .BecauseOf(because, becauseArgs) + .FailWith("Expected {context:value} to be NaN{reason}, but found {0}.", actualValue); + + return new AndConstraint>(parent); + } + + /// + /// Asserts that the number is seen as not a number (NaN). + /// + /// + /// 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 static AndConstraint> BeNaN(this NullableNumericAssertions parent, + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) + { + float? actualValue = parent.Subject; + + parent.CurrentAssertionChain + .ForCondition(actualValue is { } value && float.IsNaN(value)) + .BecauseOf(because, becauseArgs) + .FailWith("Expected {context:value} to be NaN{reason}, but found {0}.", actualValue); + + return new AndConstraint>(parent); + } + + /// + /// Asserts that the number is seen as not a number (NaN). + /// + /// + /// 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 static AndConstraint> BeNaN(this NullableNumericAssertions parent, + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) + { + double? actualValue = parent.Subject; + + parent.CurrentAssertionChain + .ForCondition(actualValue is { } value && double.IsNaN(value)) + .BecauseOf(because, becauseArgs) + .FailWith("Expected {context:value} to be NaN{reason}, but found {0}.", actualValue); + + return new AndConstraint>(parent); + } + + #endregion + + #region NotBeNaN + + /// + /// Asserts that the number is not seen as the special value not a number (NaN). + /// + /// + /// 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 static AndConstraint> NotBeNaN(this NumericAssertions parent, + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) + { + float actualValue = parent.Subject; + + parent.CurrentAssertionChain + .ForCondition(!float.IsNaN(actualValue)) + .BecauseOf(because, becauseArgs) + .FailWith("Did not expect {context:value} to be NaN{reason}."); + + return new AndConstraint>(parent); + } + + /// + /// Asserts that the number is not seen as the special value not a number (NaN). + /// + /// + /// 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 static AndConstraint> NotBeNaN(this NumericAssertions parent, + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) + { + double actualValue = parent.Subject; + + parent.CurrentAssertionChain + .ForCondition(!double.IsNaN(actualValue)) + .BecauseOf(because, becauseArgs) + .FailWith("Did not expect {context:value} to be NaN{reason}."); + + return new AndConstraint>(parent); + } + + /// + /// Asserts that the number is not seen as the special value not a number (NaN). + /// + /// + /// 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 static AndConstraint> NotBeNaN(this NullableNumericAssertions parent, + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) + { + float? actualValue = parent.Subject; + bool actualValueIsNaN = actualValue is { } value && float.IsNaN(value); + + parent.CurrentAssertionChain + .ForCondition(!actualValueIsNaN) + .BecauseOf(because, becauseArgs) + .FailWith("Did not expect {context:value} to be NaN{reason}."); + + return new AndConstraint>(parent); + } + + /// + /// Asserts that the number is not seen as the special value not a number (NaN). + /// + /// + /// 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 static AndConstraint> NotBeNaN(this NullableNumericAssertions parent, + [StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) + { + double? actualValue = parent.Subject; + bool actualValueIsNaN = actualValue is { } value && double.IsNaN(value); + + parent.CurrentAssertionChain + .ForCondition(!actualValueIsNaN) + .BecauseOf(because, becauseArgs) + .FailWith("Did not expect {context:value} to be NaN{reason}."); + + return new AndConstraint>(parent); + } + + #endregion + private static long GetMinValue(long value, ulong delta) { long minValue; + if (delta <= (ulong.MaxValue / 2)) { minValue = value - (long)delta; @@ -1508,6 +1774,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 f49d3b6a7f..319548a6ae 100644 --- a/Src/FluentAssertions/ObjectAssertionsExtensions.cs +++ b/Src/FluentAssertions/ObjectAssertionsExtensions.cs @@ -1,87 +1,20 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Runtime.Serialization; -using System.Runtime.Serialization.Formatters.Binary; using System.Xml.Serialization; using FluentAssertions.Common; using FluentAssertions.Equivalency; -using FluentAssertions.Execution; using FluentAssertions.Primitives; namespace FluentAssertions; public static class ObjectAssertionsExtensions { - /// - /// Asserts that an object can be serialized and deserialized using the binary serializer and that it stills retains - /// the values of all members. - /// - /// - /// - /// 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 static AndConstraint BeBinarySerializable(this ObjectAssertions assertions, string because = "", - params object[] becauseArgs) - { - return BeBinarySerializable(assertions, options => options, because, becauseArgs); - } - - /// - /// Asserts that an object can be serialized and deserialized using the binary serializer and that it stills retains - /// the values of all members. - /// - /// - /// - /// A reference to the configuration object that can be used - /// to influence the way the object graphs are compared. You can also provide an alternative instance of the - /// class. The global defaults are determined by the - /// class. - /// - /// - /// 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 . - /// - /// is . - public static AndConstraint BeBinarySerializable(this ObjectAssertions assertions, - Func, EquivalencyAssertionOptions> options, string because = "", - params object[] becauseArgs) - { - Guard.ThrowIfArgumentIsNull(options); - - try - { - object deserializedObject = CreateCloneUsingBinarySerializer(assertions.Subject); - - EquivalencyAssertionOptions defaultOptions = AssertionOptions.CloneDefaults() - .RespectingRuntimeTypes().IncludingFields().IncludingProperties(); - - ((T)deserializedObject).Should().BeEquivalentTo((T)assertions.Subject, _ => options(defaultOptions)); - } - catch (Exception exc) - { - Execute.Assertion - .BecauseOf(because, becauseArgs) - .FailWith("Expected {0} to be serializable{reason}, but serialization failed with:" - + Environment.NewLine + Environment.NewLine + "{1}.", - assertions.Subject, - exc.Message); - } - - return new AndConstraint(assertions); - } - /// /// Asserts that an object can be serialized and deserialized using the data contract serializer and that it stills retains /// the values of all members. /// - /// /// /// 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. @@ -90,7 +23,7 @@ public static AndConstraint BeBinarySerializable(this Objec /// Zero or more objects to format using the placeholders in . /// public static AndConstraint BeDataContractSerializable(this ObjectAssertions assertions, - string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return BeDataContractSerializable(assertions, options => options, because, becauseArgs); } @@ -99,12 +32,11 @@ public static AndConstraint BeDataContractSerializable(this Ob /// Asserts that an object can be serialized and deserialized using the data contract serializer and that it stills retains /// the values of all members. /// - /// /// - /// A reference to the configuration object that can be used + /// A reference to the configuration object that can be used /// to influence the way the object graphs are compared. You can also provide an alternative instance of the - /// class. The global defaults are determined by the - /// class. + /// class. The global defaults are determined by the + /// class. /// /// /// A formatted phrase as is supported by explaining why the assertion @@ -115,8 +47,8 @@ public static AndConstraint BeDataContractSerializable(this Ob /// /// is . public static AndConstraint BeDataContractSerializable(this ObjectAssertions assertions, - Func, EquivalencyAssertionOptions> options, string because = "", - params object[] becauseArgs) + Func, EquivalencyOptions> options, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(options); @@ -124,17 +56,17 @@ public static AndConstraint BeDataContractSerializable(this { var deserializedObject = CreateCloneUsingDataContractSerializer(assertions.Subject); - EquivalencyAssertionOptions defaultOptions = AssertionOptions.CloneDefaults() - .RespectingRuntimeTypes().IncludingFields().IncludingProperties(); + EquivalencyOptions defaultOptions = AssertionConfiguration.Current.Equivalency.CloneDefaults() + .PreferringRuntimeMemberTypes().IncludingFields().IncludingProperties(); ((T)deserializedObject).Should().BeEquivalentTo((T)assertions.Subject, _ => options(defaultOptions)); } 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}.", + + Environment.NewLine + Environment.NewLine + "{1}.", assertions.Subject, exc.Message); } @@ -142,42 +74,6 @@ public static AndConstraint BeDataContractSerializable(this return new AndConstraint(assertions); } - private static object CreateCloneUsingBinarySerializer(object subject) - { - using var stream = new MemoryStream(); - - var binaryFormatter = new BinaryFormatter - { - Binder = new SimpleBinder(subject.GetType()) - }; - -#pragma warning disable SYSLIB0011, CA2300 // BinaryFormatter is obsoleted, GH-issue 1779 tracks the upcoming removal in .NET 8.0 - binaryFormatter.Serialize(stream, subject); - stream.Position = 0; - return binaryFormatter.Deserialize(stream); -#pragma warning restore SYSLIB0011, CA2300 - } - - private sealed class SimpleBinder : SerializationBinder - { - private readonly Type type; - - public SimpleBinder(Type type) - { - this.type = type; - } - - public override Type BindToType(string assemblyName, string typeName) - { - if (type.FullName == typeName && type.Assembly.FullName == assemblyName) - { - return type; - } - - return null; - } - } - private static object CreateCloneUsingDataContractSerializer(object subject) { using var stream = new MemoryStream(); @@ -191,7 +87,6 @@ private static object CreateCloneUsingDataContractSerializer(object subject) /// Asserts that an object can be serialized and deserialized using the XML serializer and that it stills retains /// the values of all members. /// - /// /// /// 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. @@ -199,22 +94,22 @@ private static object CreateCloneUsingDataContractSerializer(object subject) /// /// Zero or more objects to format using the placeholders in . /// - public static AndConstraint BeXmlSerializable(this ObjectAssertions assertions, string because = "", - params object[] becauseArgs) + public static AndConstraint BeXmlSerializable(this ObjectAssertions assertions, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { try { object deserializedObject = CreateCloneUsingXmlSerializer(assertions.Subject); deserializedObject.Should().BeEquivalentTo(assertions.Subject, - options => options.RespectingRuntimeTypes().IncludingFields().IncludingProperties()); + options => options.PreferringRuntimeMemberTypes().IncludingFields().IncludingProperties()); } 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}.", + + Environment.NewLine + Environment.NewLine + "{1}.", assertions.Subject, exc.Message); } @@ -222,13 +117,14 @@ public static AndConstraint BeXmlSerializable(this ObjectAsser return new AndConstraint(assertions); } + [SuppressMessage("Security", "CA5369:Use XmlReader for \'XmlSerializer.Deserialize()\'")] private static object CreateCloneUsingXmlSerializer(object subject) { using var stream = new MemoryStream(); - var binaryFormatter = new XmlSerializer(subject.GetType()); - binaryFormatter.Serialize(stream, subject); + var serializer = new XmlSerializer(subject.GetType()); + serializer.Serialize(stream, subject); stream.Position = 0; - return binaryFormatter.Deserialize(stream); + return serializer.Deserialize(stream); } } 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/StringBuilderExtensions.cs b/Src/FluentAssertions/Polyfill/StringBuilderExtensions.cs similarity index 75% rename from Src/FluentAssertions/StringBuilderExtensions.cs rename to Src/FluentAssertions/Polyfill/StringBuilderExtensions.cs index 3c20ee549e..d2d901b678 100644 --- a/Src/FluentAssertions/StringBuilderExtensions.cs +++ b/Src/FluentAssertions/Polyfill/StringBuilderExtensions.cs @@ -1,12 +1,15 @@ -using System.Collections.Generic; +#if NET47 || NETSTANDARD2_0 || NETSTANDARD2_1 +using System.Collections.Generic; + +// ReSharper disable once CheckNamespace namespace System.Text; /// /// Since net6.0 StringBuilder has additional overloads taking an AppendInterpolatedStringHandler /// and optionally an IFormatProvider. /// The overload here is polyfill for older target frameworks to avoid littering the code base with #ifs -/// in order to silence analyzers about dependending on the current culture instead of an invariant culture. +/// in order to silence analyzers about depending on the current culture instead of an invariant culture. /// internal static class StringBuilderExtensions { @@ -18,3 +21,5 @@ public static StringBuilder AppendJoin(this StringBuilder stringBuilder, stri stringBuilder.Append(string.Join(separator, values)); #endif } + +#endif diff --git a/Src/FluentAssertions/SystemExtensions.cs b/Src/FluentAssertions/Polyfill/SystemExtensions.cs similarity index 74% rename from Src/FluentAssertions/SystemExtensions.cs rename to Src/FluentAssertions/Polyfill/SystemExtensions.cs index 9594e97654..a725bd4f3c 100644 --- a/Src/FluentAssertions/SystemExtensions.cs +++ b/Src/FluentAssertions/Polyfill/SystemExtensions.cs @@ -1,4 +1,7 @@ -namespace System; +#if NET47 || NETSTANDARD2_0 + +// ReSharper disable once CheckNamespace +namespace System; internal static class SystemExtensions { @@ -20,4 +23,12 @@ public static bool Contains(this string str, char value, StringComparison compar // https://source.dot.net/#System.Private.CoreLib/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs,1014 public static bool StartsWith(this string str, char value) => str.Length != 0 && str[0] == value; + + public static string[] Split(this string str, char separator, StringSplitOptions options = StringSplitOptions.None) => + str.Split([separator], options); + + public static string[] Split(this string str, string separator, StringSplitOptions options = StringSplitOptions.None) => + str.Split([separator], options); } + +#endif diff --git a/Src/FluentAssertions/Primitives/BooleanAssertions.cs b/Src/FluentAssertions/Primitives/BooleanAssertions.cs index c44bc7f2ff..45fa01878c 100644 --- a/Src/FluentAssertions/Primitives/BooleanAssertions.cs +++ b/Src/FluentAssertions/Primitives/BooleanAssertions.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using FluentAssertions.Execution; namespace FluentAssertions.Primitives; @@ -8,11 +9,12 @@ namespace FluentAssertions.Primitives; /// Contains a number of methods to assert that a is in the expected state. /// [DebuggerNonUserCode] +[SuppressMessage("Maintainability", "AV1564:Parameter in public or internal member is of type bool or bool?")] public class BooleanAssertions : BooleanAssertions { - public BooleanAssertions(bool? value) - : base(value) + public BooleanAssertions(bool? value, AssertionChain assertionChain) + : base(value, assertionChain) { } } @@ -23,11 +25,15 @@ public BooleanAssertions(bool? value) /// Contains a number of methods to assert that a is in the expected state. /// [DebuggerNonUserCode] +[SuppressMessage("Maintainability", "AV1564:Parameter in public or internal member is of type bool or bool?")] 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; } @@ -46,9 +52,9 @@ public BooleanAssertions(bool? value) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeFalse(string because = "", params object[] becauseArgs) + 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); @@ -66,9 +72,9 @@ public AndConstraint BeFalse(string because = "", params object[] b /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeTrue(string because = "", params object[] becauseArgs) + 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); @@ -87,9 +93,10 @@ public AndConstraint BeTrue(string because = "", params object[] be /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint Be(bool expected, 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); @@ -108,9 +115,10 @@ public AndConstraint Be(bool expected, string because = "", params /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBe(bool unexpected, 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); @@ -130,21 +138,20 @@ public AndConstraint NotBe(bool unexpected, string because = "", pa /// Zero or more objects to format using the placeholders in . /// public AndConstraint Imply(bool consequent, - string because = "", + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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 9d01383966..c5e57b5d73 100644 --- a/Src/FluentAssertions/Primitives/DateOnlyAssertions.cs +++ b/Src/FluentAssertions/Primitives/DateOnlyAssertions.cs @@ -1,10 +1,12 @@ -using System; +#if NET6_0_OR_GREATER + +using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using FluentAssertions.Execution; -#if NET6_0_OR_GREATER namespace FluentAssertions.Primitives; /// @@ -13,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) { } } @@ -28,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; } @@ -49,9 +54,10 @@ public DateOnlyAssertions(DateOnly? value) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint Be(DateOnly expected, 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}.", @@ -71,9 +77,10 @@ public AndConstraint Be(DateOnly expected, string because = "", par /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint Be(DateOnly? expected, 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}.", @@ -93,10 +100,10 @@ public AndConstraint Be(DateOnly? expected, string because = "", pa /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBe(DateOnly unexpected, string because = "", + 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); @@ -115,10 +122,10 @@ public AndConstraint NotBe(DateOnly unexpected, string because = "" /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBe(DateOnly? unexpected, string because = "", - params object[] becauseArgs) + 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); @@ -137,10 +144,10 @@ public AndConstraint NotBe(DateOnly? unexpected, string because = " /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeBefore(DateOnly expected, string because = "", - params object[] becauseArgs) + 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, @@ -160,8 +167,8 @@ public AndConstraint BeBefore(DateOnly expected, string because = " /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeBefore(DateOnly unexpected, string because = "", - params object[] becauseArgs) + public AndConstraint NotBeBefore(DateOnly unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return BeOnOrAfter(unexpected, because, becauseArgs); } @@ -177,10 +184,10 @@ public AndConstraint NotBeBefore(DateOnly unexpected, string becaus /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeOnOrBefore(DateOnly expected, string because = "", - params object[] becauseArgs) + 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, @@ -200,8 +207,8 @@ public AndConstraint BeOnOrBefore(DateOnly expected, string because /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeOnOrBefore(DateOnly unexpected, string because = "", - params object[] becauseArgs) + public AndConstraint NotBeOnOrBefore(DateOnly unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return BeAfter(unexpected, because, becauseArgs); } @@ -217,10 +224,10 @@ public AndConstraint NotBeOnOrBefore(DateOnly unexpected, string be /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeAfter(DateOnly expected, string because = "", - params object[] becauseArgs) + 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, @@ -240,8 +247,8 @@ public AndConstraint BeAfter(DateOnly expected, string because = "" /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeAfter(DateOnly unexpected, string because = "", - params object[] becauseArgs) + public AndConstraint NotBeAfter(DateOnly unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return BeOnOrBefore(unexpected, because, becauseArgs); } @@ -257,10 +264,10 @@ public AndConstraint NotBeAfter(DateOnly unexpected, string because /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeOnOrAfter(DateOnly expected, string because = "", - params object[] becauseArgs) + 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, @@ -280,8 +287,8 @@ public AndConstraint BeOnOrAfter(DateOnly expected, string because /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeOnOrAfter(DateOnly unexpected, string because = "", - params object[] becauseArgs) + public AndConstraint NotBeOnOrAfter(DateOnly unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return BeBefore(unexpected, because, becauseArgs); } @@ -297,18 +304,17 @@ public AndConstraint NotBeOnOrAfter(DateOnly unexpected, string bec /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveYear(int expected, 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); } @@ -324,9 +330,10 @@ public AndConstraint HaveYear(int expected, string because = "", pa /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveYear(int unexpected, 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.", @@ -350,18 +357,17 @@ public AndConstraint NotHaveYear(int unexpected, string because = " /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveMonth(int expected, 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); } @@ -377,18 +383,17 @@ public AndConstraint HaveMonth(int expected, string because = "", p /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveMonth(int unexpected, 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); } @@ -404,18 +409,17 @@ public AndConstraint NotHaveMonth(int unexpected, string because = /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveDay(int expected, 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); } @@ -431,18 +435,17 @@ public AndConstraint HaveDay(int expected, string because = "", par /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveDay(int unexpected, 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); } @@ -482,7 +485,8 @@ public AndConstraint BeOneOf(params DateOnly[] validValues) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeOneOf(IEnumerable validValues, string because = "", params object[] becauseArgs) + public AndConstraint BeOneOf(IEnumerable validValues, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return BeOneOf(validValues.Cast(), because, becauseArgs); } @@ -500,10 +504,10 @@ public AndConstraint BeOneOf(IEnumerable validValues, str /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeOneOf(IEnumerable validValues, string because = "", - params object[] becauseArgs) + 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 0606a6a0ae..b1f7ffa01a 100644 --- a/Src/FluentAssertions/Primitives/DateTimeAssertions.cs +++ b/Src/FluentAssertions/Primitives/DateTimeAssertions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using FluentAssertions.Common; using FluentAssertions.Execution; @@ -17,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) { } } @@ -36,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; } @@ -57,9 +61,10 @@ public DateTimeAssertions(DateTime? value) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint Be(DateTime expected, 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}.", @@ -79,9 +84,10 @@ public AndConstraint Be(DateTime expected, string because = "", par /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint Be(DateTime? expected, 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}.", @@ -101,10 +107,10 @@ public AndConstraint Be(DateTime? expected, string because = "", pa /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBe(DateTime unexpected, string because = "", - params object[] becauseArgs) + 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); @@ -123,10 +129,10 @@ public AndConstraint NotBe(DateTime unexpected, string because = "" /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBe(DateTime? unexpected, string because = "", - params object[] becauseArgs) + 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); @@ -156,8 +162,8 @@ public AndConstraint NotBe(DateTime? unexpected, string because = " /// Zero or more objects to format using the placeholders in . /// /// is negative. - public AndConstraint BeCloseTo(DateTime nearbyTime, TimeSpan precision, string because = "", - params object[] becauseArgs) + public AndConstraint BeCloseTo(DateTime nearbyTime, TimeSpan precision, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNegative(precision); @@ -169,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); } @@ -205,8 +210,8 @@ public AndConstraint BeCloseTo(DateTime nearbyTime, TimeSpan precis /// Zero or more objects to format using the placeholders in . /// /// is negative. - public AndConstraint NotBeCloseTo(DateTime distantTime, TimeSpan precision, string because = "", - params object[] becauseArgs) + public AndConstraint NotBeCloseTo(DateTime distantTime, TimeSpan precision, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNegative(precision); @@ -216,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( @@ -238,10 +243,10 @@ public AndConstraint NotBeCloseTo(DateTime distantTime, TimeSpan pr /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeBefore(DateTime expected, string because = "", - params object[] becauseArgs) + 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, @@ -261,8 +266,8 @@ public AndConstraint BeBefore(DateTime expected, string because = " /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeBefore(DateTime unexpected, string because = "", - params object[] becauseArgs) + public AndConstraint NotBeBefore(DateTime unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return BeOnOrAfter(unexpected, because, becauseArgs); } @@ -278,10 +283,10 @@ public AndConstraint NotBeBefore(DateTime unexpected, string becaus /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeOnOrBefore(DateTime expected, string because = "", - params object[] becauseArgs) + 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, @@ -301,8 +306,8 @@ public AndConstraint BeOnOrBefore(DateTime expected, string because /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeOnOrBefore(DateTime unexpected, string because = "", - params object[] becauseArgs) + public AndConstraint NotBeOnOrBefore(DateTime unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return BeAfter(unexpected, because, becauseArgs); } @@ -318,10 +323,10 @@ public AndConstraint NotBeOnOrBefore(DateTime unexpected, string be /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeAfter(DateTime expected, string because = "", - params object[] becauseArgs) + 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, @@ -341,8 +346,8 @@ public AndConstraint BeAfter(DateTime expected, string because = "" /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeAfter(DateTime unexpected, string because = "", - params object[] becauseArgs) + public AndConstraint NotBeAfter(DateTime unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return BeOnOrBefore(unexpected, because, becauseArgs); } @@ -358,10 +363,10 @@ public AndConstraint NotBeAfter(DateTime unexpected, string because /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeOnOrAfter(DateTime expected, string because = "", - params object[] becauseArgs) + 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, @@ -381,8 +386,8 @@ public AndConstraint BeOnOrAfter(DateTime expected, string because /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeOnOrAfter(DateTime unexpected, string because = "", - params object[] becauseArgs) + public AndConstraint NotBeOnOrAfter(DateTime unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return BeBefore(unexpected, because, becauseArgs); } @@ -398,18 +403,17 @@ public AndConstraint NotBeOnOrAfter(DateTime unexpected, string bec /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveYear(int expected, 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); } @@ -425,9 +429,10 @@ public AndConstraint HaveYear(int expected, string because = "", pa /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveYear(int unexpected, 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.", @@ -451,18 +456,17 @@ public AndConstraint NotHaveYear(int unexpected, string because = " /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveMonth(int expected, 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); } @@ -478,18 +482,17 @@ public AndConstraint HaveMonth(int expected, string because = "", p /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveMonth(int unexpected, 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); } @@ -505,18 +508,17 @@ public AndConstraint NotHaveMonth(int unexpected, string because = /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveDay(int expected, 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); } @@ -532,18 +534,17 @@ public AndConstraint HaveDay(int expected, string because = "", par /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveDay(int unexpected, 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); } @@ -559,18 +560,17 @@ public AndConstraint NotHaveDay(int unexpected, string because = "" /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveHour(int expected, 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); } @@ -586,18 +586,17 @@ public AndConstraint HaveHour(int expected, string because = "", pa /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveHour(int unexpected, 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); } @@ -613,19 +612,17 @@ public AndConstraint NotHaveHour(int unexpected, string because = " /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveMinute(int expected, string because = "", - params object[] becauseArgs) + 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); } @@ -641,19 +638,17 @@ public AndConstraint HaveMinute(int expected, string because = "", /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveMinute(int unexpected, string because = "", - params object[] becauseArgs) + 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); } @@ -669,19 +664,17 @@ public AndConstraint NotHaveMinute(int unexpected, string because = /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveSecond(int expected, string because = "", - params object[] becauseArgs) + 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); } @@ -697,19 +690,17 @@ public AndConstraint HaveSecond(int expected, string because = "", /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveSecond(int unexpected, string because = "", - params object[] becauseArgs) + 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); } @@ -723,7 +714,8 @@ public AndConstraint NotHaveSecond(int unexpected, string because = /// public DateTimeRangeAssertions BeMoreThan(TimeSpan timeSpan) { - return new DateTimeRangeAssertions((TAssertions)this, Subject, TimeSpanCondition.MoreThan, timeSpan); + return new DateTimeRangeAssertions((TAssertions)this, assertionChain, Subject, TimeSpanCondition.MoreThan, + timeSpan); } /// @@ -736,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); } /// @@ -748,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); } /// @@ -760,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); } /// @@ -772,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); } /// @@ -786,21 +782,20 @@ public DateTimeRangeAssertions BeLessThan(TimeSpan timeSpan) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeSameDateAs(DateTime expected, string because = "", - params object[] becauseArgs) + public AndConstraint BeSameDateAs(DateTime expected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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); } @@ -816,21 +811,20 @@ public AndConstraint BeSameDateAs(DateTime expected, string because /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeSameDateAs(DateTime unexpected, string because = "", - params object[] becauseArgs) + public AndConstraint NotBeSameDateAs(DateTime unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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); } @@ -870,7 +864,8 @@ public AndConstraint BeOneOf(params DateTime[] validValues) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeOneOf(IEnumerable validValues, string because = "", params object[] becauseArgs) + public AndConstraint BeOneOf(IEnumerable validValues, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return BeOneOf(validValues.Cast(), because, becauseArgs); } @@ -888,10 +883,10 @@ public AndConstraint BeOneOf(IEnumerable validValues, str /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeOneOf(IEnumerable validValues, string because = "", - params object[] becauseArgs) + 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); @@ -912,18 +907,46 @@ public AndConstraint BeOneOf(IEnumerable validValues, st /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeIn(DateTimeKind expectedKind, string because = "", params object[] becauseArgs) + 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); + } + + /// + /// Asserts that the does not represent a value in a specific kind. + /// + /// + /// The that the current value should not represent. + /// + /// + /// 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 AndConstraint NotBeIn(DateTimeKind unexpectedKind, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + assertionChain + .BecauseOf(because, becauseArgs) + .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 dafcdbbdba..83d27c8ad0 100644 --- a/Src/FluentAssertions/Primitives/DateTimeOffsetAssertions.cs +++ b/Src/FluentAssertions/Primitives/DateTimeOffsetAssertions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using FluentAssertions.Common; using FluentAssertions.Execution; @@ -18,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) { } } @@ -37,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; } @@ -58,20 +62,18 @@ public DateTimeOffsetAssertions(DateTimeOffset? value) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint Be(DateTimeOffset expected, string because = "", - params object[] becauseArgs) + 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); } @@ -87,29 +89,26 @@ public AndConstraint Be(DateTimeOffset expected, string because = " /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint Be(DateTimeOffset? expected, string because = "", - params object[] becauseArgs) + public AndConstraint Be(DateTimeOffset? expected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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); @@ -126,10 +125,10 @@ public AndConstraint Be(DateTimeOffset? expected, string because = /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBe(DateTimeOffset unexpected, string because = "", - params object[] becauseArgs) + public AndConstraint NotBe(DateTimeOffset unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject != unexpected) .BecauseOf(because, becauseArgs) .FailWith( @@ -150,10 +149,10 @@ public AndConstraint NotBe(DateTimeOffset unexpected, string becaus /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBe(DateTimeOffset? unexpected, string because = "", - params object[] becauseArgs) + public AndConstraint NotBe(DateTimeOffset? unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject != unexpected) .BecauseOf(because, becauseArgs) .FailWith( @@ -174,19 +173,17 @@ public AndConstraint NotBe(DateTimeOffset? unexpected, string becau /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeExactly(DateTimeOffset expected, string because = "", - params object[] becauseArgs) + 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); } @@ -203,28 +200,26 @@ public AndConstraint BeExactly(DateTimeOffset expected, string beca /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeExactly(DateTimeOffset? expected, string because = "", - params object[] becauseArgs) + public AndConstraint BeExactly(DateTimeOffset? expected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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); @@ -242,10 +237,10 @@ public AndConstraint BeExactly(DateTimeOffset? expected, string bec /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeExactly(DateTimeOffset unexpected, string because = "", - params object[] becauseArgs) + 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); @@ -264,10 +259,10 @@ public AndConstraint NotBeExactly(DateTimeOffset unexpected, string /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeExactly(DateTimeOffset? unexpected, string because = "", - params object[] becauseArgs) + 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) @@ -299,8 +294,7 @@ public AndConstraint NotBeExactly(DateTimeOffset? unexpected, strin /// /// is negative. public AndConstraint BeCloseTo(DateTimeOffset nearbyTime, TimeSpan precision, - string because = "", - params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNegative(precision); @@ -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); } @@ -348,8 +341,8 @@ public AndConstraint BeCloseTo(DateTimeOffset nearbyTime, TimeSpan /// Zero or more objects to format using the placeholders in . /// /// is negative. - public AndConstraint NotBeCloseTo(DateTimeOffset distantTime, TimeSpan precision, string because = "", - params object[] becauseArgs) + public AndConstraint NotBeCloseTo(DateTimeOffset distantTime, TimeSpan precision, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNegative(precision); @@ -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( @@ -381,10 +374,10 @@ public AndConstraint NotBeCloseTo(DateTimeOffset distantTime, TimeS /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeBefore(DateTimeOffset expected, string because = "", - params object[] becauseArgs) + 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, @@ -404,8 +397,8 @@ public AndConstraint BeBefore(DateTimeOffset expected, string becau /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeBefore(DateTimeOffset unexpected, string because = "", - params object[] becauseArgs) + public AndConstraint NotBeBefore(DateTimeOffset unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return BeOnOrAfter(unexpected, because, becauseArgs); } @@ -421,10 +414,10 @@ public AndConstraint NotBeBefore(DateTimeOffset unexpected, string /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeOnOrBefore(DateTimeOffset expected, string because = "", - params object[] becauseArgs) + 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, @@ -444,8 +437,8 @@ public AndConstraint BeOnOrBefore(DateTimeOffset expected, string b /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeOnOrBefore(DateTimeOffset unexpected, string because = "", - params object[] becauseArgs) + public AndConstraint NotBeOnOrBefore(DateTimeOffset unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return BeAfter(unexpected, because, becauseArgs); } @@ -461,10 +454,10 @@ public AndConstraint NotBeOnOrBefore(DateTimeOffset unexpected, str /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeAfter(DateTimeOffset expected, string because = "", - params object[] becauseArgs) + 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, @@ -484,8 +477,8 @@ public AndConstraint BeAfter(DateTimeOffset expected, string becaus /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeAfter(DateTimeOffset unexpected, string because = "", - params object[] becauseArgs) + public AndConstraint NotBeAfter(DateTimeOffset unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return BeOnOrBefore(unexpected, because, becauseArgs); } @@ -501,10 +494,10 @@ public AndConstraint NotBeAfter(DateTimeOffset unexpected, string b /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeOnOrAfter(DateTimeOffset expected, string because = "", - params object[] becauseArgs) + 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, @@ -524,8 +517,8 @@ public AndConstraint BeOnOrAfter(DateTimeOffset expected, string be /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeOnOrAfter(DateTimeOffset unexpected, string because = "", - params object[] becauseArgs) + public AndConstraint NotBeOnOrAfter(DateTimeOffset unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return BeBefore(unexpected, because, becauseArgs); } @@ -541,19 +534,17 @@ public AndConstraint NotBeOnOrAfter(DateTimeOffset unexpected, stri /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveYear(int expected, 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 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); } @@ -569,18 +560,17 @@ public AndConstraint HaveYear(int expected, string because = "", /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveYear(int unexpected, string because = "", params object[] becauseArgs) + 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); } @@ -596,19 +586,17 @@ public AndConstraint NotHaveYear(int unexpected, string because = " /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveMonth(int expected, 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 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); } @@ -624,18 +612,17 @@ public AndConstraint HaveMonth(int expected, string because = "", /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveMonth(int unexpected, 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 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); } @@ -651,19 +638,17 @@ public AndConstraint NotHaveMonth(int unexpected, string because = /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveDay(int expected, 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 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); } @@ -679,18 +664,17 @@ public AndConstraint HaveDay(int expected, string because = "", /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveDay(int unexpected, 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 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); } @@ -706,19 +690,17 @@ public AndConstraint NotHaveDay(int unexpected, string because = "" /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveHour(int expected, 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 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); } @@ -734,18 +716,17 @@ public AndConstraint HaveHour(int expected, string because = "", /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveHour(int unexpected, 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 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); } @@ -761,19 +742,17 @@ public AndConstraint NotHaveHour(int unexpected, string because = " /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveMinute(int expected, string because = "", - params object[] becauseArgs) + 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); } @@ -789,19 +768,18 @@ public AndConstraint HaveMinute(int expected, string because = "", /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveMinute(int unexpected, string because = "", - params object[] becauseArgs) + 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); } @@ -817,19 +795,17 @@ public AndConstraint NotHaveMinute(int unexpected, string because = /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveSecond(int expected, string because = "", - params object[] becauseArgs) + 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); } @@ -845,19 +821,18 @@ public AndConstraint HaveSecond(int expected, string because = "", /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveSecond(int unexpected, string because = "", - params object[] becauseArgs) + 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); } @@ -873,19 +848,17 @@ public AndConstraint NotHaveSecond(int unexpected, string because = /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveOffset(TimeSpan expected, string because = "", - params object[] becauseArgs) + 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); } @@ -901,19 +874,17 @@ public AndConstraint HaveOffset(TimeSpan expected, string because = /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveOffset(TimeSpan unexpected, string because = "", - params object[] becauseArgs) + 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); } @@ -927,7 +898,8 @@ public AndConstraint NotHaveOffset(TimeSpan unexpected, string beca /// public DateTimeOffsetRangeAssertions BeMoreThan(TimeSpan timeSpan) { - return new DateTimeOffsetRangeAssertions((TAssertions)this, Subject, TimeSpanCondition.MoreThan, timeSpan); + return new DateTimeOffsetRangeAssertions((TAssertions)this, assertionChain, Subject, + TimeSpanCondition.MoreThan, timeSpan); } /// @@ -940,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); } /// @@ -952,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); } /// @@ -964,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); } /// @@ -976,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); } /// @@ -990,21 +966,20 @@ public DateTimeOffsetRangeAssertions BeLessThan(TimeSpan timeSpan) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeSameDateAs(DateTimeOffset expected, string because = "", - params object[] becauseArgs) + public AndConstraint BeSameDateAs(DateTimeOffset expected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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); } @@ -1020,21 +995,20 @@ public AndConstraint BeSameDateAs(DateTimeOffset expected, string b /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeSameDateAs(DateTimeOffset unexpected, string because = "", - params object[] becauseArgs) + public AndConstraint NotBeSameDateAs(DateTimeOffset unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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); } @@ -1074,8 +1048,8 @@ public AndConstraint BeOneOf(params DateTimeOffset[] validValues) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeOneOf(IEnumerable validValues, string because = "", - params object[] becauseArgs) + public AndConstraint BeOneOf(IEnumerable validValues, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return BeOneOf(validValues.Cast(), because, becauseArgs); } @@ -1093,10 +1067,10 @@ public AndConstraint BeOneOf(IEnumerable validValue /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeOneOf(IEnumerable validValues, string because = "", - params object[] becauseArgs) + 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 d56cd78cb6..964b73c9db 100644 --- a/Src/FluentAssertions/Primitives/DateTimeOffsetRangeAssertions.cs +++ b/Src/FluentAssertions/Primitives/DateTimeOffsetRangeAssertions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using FluentAssertions.Execution; using FluentAssertions.Extensions; @@ -22,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() @@ -38,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; @@ -62,20 +66,20 @@ protected internal DateTimeOffsetRangeAssertions(TAssertions parentAssertions, D /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint Before(DateTimeOffset target, string because = "", - params object[] becauseArgs) + 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( @@ -100,19 +104,20 @@ public AndConstraint Before(DateTimeOffset target, string because = /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint After(DateTimeOffset target, string because = "", params object[] becauseArgs) + 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 4deabcf6af..6192cfa16b 100644 --- a/Src/FluentAssertions/Primitives/DateTimeRangeAssertions.cs +++ b/Src/FluentAssertions/Primitives/DateTimeRangeAssertions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using FluentAssertions.Execution; namespace FluentAssertions.Primitives; @@ -22,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() @@ -38,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; @@ -62,21 +66,21 @@ protected internal DateTimeRangeAssertions(TAssertions parentAssertions, DateTim /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint Before(DateTime target, string because = "", - params object[] becauseArgs) + 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( @@ -101,21 +105,21 @@ public AndConstraint Before(DateTime target, string because = "", /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint After(DateTime target, string because = "", - params object[] becauseArgs) + 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 69090e30d5..b740b3c7d3 100644 --- a/Src/FluentAssertions/Primitives/EnumAssertions.cs +++ b/Src/FluentAssertions/Primitives/EnumAssertions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Linq.Expressions; @@ -14,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) { } } @@ -29,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; } @@ -52,9 +56,10 @@ private protected EnumAssertions(TEnum? value) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint Be(TEnum expected, string because = "", params object[] becauseArgs) + 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}.", @@ -74,9 +79,10 @@ public AndConstraint Be(TEnum expected, string because = "", params /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint Be(TEnum? expected, string because = "", params object[] becauseArgs) + 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}.", @@ -96,10 +102,10 @@ public AndConstraint Be(TEnum? expected, string because = "", param /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBe(TEnum unexpected, string because = "", - params object[] becauseArgs) + 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); @@ -118,10 +124,10 @@ public AndConstraint NotBe(TEnum unexpected, string because = "", /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBe(TEnum? unexpected, string because = "", - params object[] becauseArgs) + 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); @@ -139,18 +145,17 @@ public AndConstraint NotBe(TEnum? unexpected, string because = "", /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeDefined(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); } @@ -165,18 +170,17 @@ public AndConstraint BeDefined(string because = "", params object[] /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeDefined(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); } @@ -192,9 +196,10 @@ public AndConstraint NotBeDefined(string because = "", params objec /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveValue(decimal expected, string because = "", params object[] becauseArgs) + 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}.", @@ -214,9 +219,10 @@ public AndConstraint HaveValue(decimal expected, string because = " /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveValue(decimal unexpected, string because = "", params object[] becauseArgs) + 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}.", @@ -236,10 +242,11 @@ public AndConstraint NotHaveValue(decimal unexpected, string becaus /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveSameValueAs(T expected, string because = "", params object[] becauseArgs) + 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}.", @@ -259,10 +266,11 @@ public AndConstraint HaveSameValueAs(T expected, string because /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveSameValueAs(T unexpected, string because = "", params object[] becauseArgs) + 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}.", @@ -282,10 +290,11 @@ public AndConstraint NotHaveSameValueAs(T unexpected, string bec /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveSameNameAs(T expected, string because = "", params object[] becauseArgs) + 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}.", @@ -305,10 +314,11 @@ public AndConstraint HaveSameNameAs(T expected, string because = /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveSameNameAs(T unexpected, string because = "", params object[] becauseArgs) + 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}.", @@ -328,10 +338,10 @@ public AndConstraint NotHaveSameNameAs(T unexpected, string beca /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveFlag(TEnum expectedFlag, string because = "", - params object[] becauseArgs) + 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); @@ -350,10 +360,10 @@ public AndConstraint HaveFlag(TEnum expectedFlag, string because = /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveFlag(TEnum unexpectedFlag, string because = "", - params object[] becauseArgs) + 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); @@ -376,12 +386,11 @@ public AndConstraint NotHaveFlag(TEnum unexpectedFlag, string becau /// An which can be used to chain assertions. /// is . public AndConstraint Match(Expression> predicate, - string because = "", - params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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); @@ -415,7 +424,8 @@ public AndConstraint BeOneOf(params TEnum[] validValues) /// /// is . /// is empty. - public AndConstraint BeOneOf(IEnumerable validValues, string because = "", params object[] becauseArgs) + public AndConstraint BeOneOf(IEnumerable validValues, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(validValues, nameof(validValues), "Cannot assert that an enum is one of a null list of enums"); @@ -423,7 +433,7 @@ public AndConstraint BeOneOf(IEnumerable validValues, string 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 eb9cb115a0..d30fe808df 100644 --- a/Src/FluentAssertions/Primitives/GuidAssertions.cs +++ b/Src/FluentAssertions/Primitives/GuidAssertions.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using FluentAssertions.Execution; namespace FluentAssertions.Primitives; @@ -10,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) { } } @@ -25,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; } @@ -47,9 +51,9 @@ public GuidAssertions(Guid? value) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeEmpty(string because = "", params object[] becauseArgs) + 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); @@ -67,9 +71,9 @@ public AndConstraint BeEmpty(string because = "", params object[] b /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeEmpty(string because = "", params object[] becauseArgs) + 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}."); @@ -93,7 +97,8 @@ public AndConstraint NotBeEmpty(string because = "", params object[ /// Zero or more objects to format using the placeholders in . /// /// The format of is invalid. - public AndConstraint Be(string expected, string because = "", params object[] becauseArgs) + public AndConstraint Be(string expected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { if (!Guid.TryParse(expected, out Guid expectedGuid)) { @@ -114,9 +119,10 @@ public AndConstraint Be(string expected, string because = "", param /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint Be(Guid expected, string because = "", params object[] becauseArgs) + 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); @@ -136,7 +142,8 @@ public AndConstraint Be(Guid expected, string because = "", params /// Zero or more objects to format using the placeholders in . /// /// The format of is invalid. - public AndConstraint NotBe(string unexpected, string because = "", params object[] becauseArgs) + public AndConstraint NotBe(string unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { if (!Guid.TryParse(unexpected, out Guid unexpectedGuid)) { @@ -157,9 +164,10 @@ public AndConstraint NotBe(string unexpected, string because = "", /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBe(Guid unexpected, string because = "", params object[] becauseArgs) + 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 deleted file mode 100644 index 49df342a49..0000000000 --- a/Src/FluentAssertions/Primitives/HttpResponseMessageAssertions.cs +++ /dev/null @@ -1,236 +0,0 @@ -using System.Diagnostics; -using System.Net; -using System.Net.Http; -using FluentAssertions.Execution; - -namespace FluentAssertions.Primitives; - -/// -/// Contains a number of methods to assert that a is in the expected state. -/// -[DebuggerNonUserCode] -public class HttpResponseMessageAssertions : HttpResponseMessageAssertions -{ - public HttpResponseMessageAssertions(HttpResponseMessage value) - : base(value) - { - } -} - -/// -/// Contains a number of methods to assert that a is in the expected state. -/// -[DebuggerNonUserCode] -public class HttpResponseMessageAssertions : ObjectAssertions - where TAssertions : HttpResponseMessageAssertions -{ - protected HttpResponseMessageAssertions(HttpResponseMessage value) - : base(value) - { - } - - /// - /// Asserts that the is successful (2xx). - /// - /// - /// 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 AndConstraint BeSuccessful(string because = "", params object[] becauseArgs) - { - var success = Execute.Assertion - .ForCondition(Subject is not null) - .BecauseOf(because, becauseArgs) - .FailWith("Expected HttpStatusCode to be successful (2xx){reason}, but HttpResponseMessage was ."); - - if (success) - { - Execute.Assertion - .ForCondition(Subject!.IsSuccessStatusCode) - .BecauseOf(because, becauseArgs) - .FailWith("Expected HttpStatusCode to be successful (2xx){reason}, but found {0}.", Subject.StatusCode); - } - - return new AndConstraint((TAssertions)this); - } - - /// - /// Asserts that the is redirection (3xx). - /// - /// - /// 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 AndConstraint BeRedirection(string because = "", params object[] becauseArgs) - { - var success = Execute.Assertion - .ForCondition(Subject is not null) - .BecauseOf(because, becauseArgs) - .FailWith("Expected HttpStatusCode to be redirection (3xx){reason}, but HttpResponseMessage was ."); - - if (success) - { - Execute.Assertion - .ForCondition((int)Subject!.StatusCode is >= 300 and <= 399) - .BecauseOf(because, becauseArgs) - .FailWith("Expected HttpStatusCode to be redirection (3xx){reason}, but found {0}.", Subject.StatusCode); - } - - return new AndConstraint((TAssertions)this); - } - - /// - /// Asserts that the is either client (4xx) or server error (5xx). - /// - /// - /// 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 AndConstraint HaveError(string because = "", params object[] becauseArgs) - { - var success = Execute.Assertion - .ForCondition(Subject is not null) - .BecauseOf(because, becauseArgs) - .FailWith("Expected HttpStatusCode to be an error{reason}, but HttpResponseMessage was ."); - - if (success) - { - Execute.Assertion - .ForCondition(IsClientError() || IsServerError()) - .BecauseOf(because, becauseArgs) - .FailWith("Expected HttpStatusCode to be an error{reason}, but found {0}.", Subject.StatusCode); - } - - return new AndConstraint((TAssertions)this); - } - - /// - /// Asserts that the is client error (4xx). - /// - /// - /// 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 AndConstraint HaveClientError(string because = "", params object[] becauseArgs) - { - var success = Execute.Assertion - .ForCondition(Subject is not null) - .BecauseOf(because, becauseArgs) - .FailWith("Expected HttpStatusCode to be client error (4xx){reason}, but HttpResponseMessage was ."); - - if (success) - { - Execute.Assertion - .ForCondition(IsClientError()) - .BecauseOf(because, becauseArgs) - .FailWith("Expected HttpStatusCode to be client error (4xx){reason}, but found {0}.", Subject.StatusCode); - } - - return new AndConstraint((TAssertions)this); - } - - /// - /// Asserts that the is server error (5xx). - /// - /// - /// 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 AndConstraint HaveServerError(string because = "", params object[] becauseArgs) - { - var success = Execute.Assertion - .ForCondition(Subject is not null) - .BecauseOf(because, becauseArgs) - .FailWith("Expected HttpStatusCode to be server error (5xx){reason}, but HttpResponseMessage was ."); - - if (success) - { - Execute.Assertion - .ForCondition(IsServerError()) - .BecauseOf(because, becauseArgs) - .FailWith("Expected HttpStatusCode to be server error (5xx){reason}, but found {0}.", Subject.StatusCode); - } - - return new AndConstraint((TAssertions)this); - } - - /// - /// Asserts that the is equal to the specified value. - /// - /// The expected value - /// - /// 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 AndConstraint HaveStatusCode(HttpStatusCode expected, string because = "", params object[] becauseArgs) - { - var success = Execute.Assertion - .ForCondition(Subject is not null) - .BecauseOf(because, becauseArgs) - .FailWith("Expected HttpStatusCode to be {0}{reason}, but HttpResponseMessage was .", expected); - - if (success) - { - Execute.Assertion - .ForCondition(Subject!.StatusCode == expected) - .BecauseOf(because, becauseArgs) - .FailWith("Expected HttpStatusCode to be {0}{reason}, but found {1}.", expected, Subject.StatusCode); - } - - return new AndConstraint((TAssertions)this); - } - - /// - /// Asserts that the is not equal to the specified value. - /// - /// The unexpected value - /// - /// 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 AndConstraint NotHaveStatusCode(HttpStatusCode unexpected, string because = "", - params object[] becauseArgs) - { - var success = Execute.Assertion - .ForCondition(Subject is not null) - .BecauseOf(because, becauseArgs) - .FailWith("Expected HttpStatusCode not to be {0}{reason}, but HttpResponseMessage was .", unexpected); - - if (success) - { - Execute.Assertion - .ForCondition(Subject!.StatusCode != unexpected) - .BecauseOf(because, becauseArgs) - .FailWith("Expected HttpStatusCode not to be {0}{reason}, but found {1}.", unexpected, Subject.StatusCode); - } - - return new AndConstraint((TAssertions)this); - } - - private bool IsServerError() => (int)Subject.StatusCode is >= 500 and <= 599; - - private bool IsClientError() => (int)Subject.StatusCode is >= 400 and <= 499; - - protected override string Identifier => "HTTP response message"; -} diff --git a/Src/FluentAssertions/Primitives/IStringComparisonStrategy.cs b/Src/FluentAssertions/Primitives/IStringComparisonStrategy.cs new file mode 100644 index 0000000000..01152ac4e6 --- /dev/null +++ b/Src/FluentAssertions/Primitives/IStringComparisonStrategy.cs @@ -0,0 +1,19 @@ +using FluentAssertions.Execution; + +namespace FluentAssertions.Primitives; + +/// +/// The strategy used for comparing two s. +/// +internal interface IStringComparisonStrategy +{ + /// + /// Asserts that neither the nor the strings are null. + /// + void AssertNeitherIsNull(AssertionChain assertionChain, string subject, string expected); + + /// + /// Asserts that the matches the value. + /// + void AssertForEquality(AssertionChain assertionChain, string subject, string expected); +} diff --git a/Src/FluentAssertions/Primitives/NegatedStringStartValidator.cs b/Src/FluentAssertions/Primitives/NegatedStringStartValidator.cs deleted file mode 100644 index fa719a7327..0000000000 --- a/Src/FluentAssertions/Primitives/NegatedStringStartValidator.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; - -namespace FluentAssertions.Primitives; - -internal class NegatedStringStartValidator : StringValidator -{ - private readonly StringComparison stringComparison; - - public NegatedStringStartValidator(string subject, string expected, StringComparison stringComparison, string because, - object[] becauseArgs) - : base(subject, expected, because, becauseArgs) - { - this.stringComparison = stringComparison; - } - - protected override string ExpectationDescription - { - get - { - string predicateDescription = IgnoreCase ? "start with equivalent of" : "start with"; - return "Expected {context:string} that does not " + predicateDescription + " "; - } - } - - private bool IgnoreCase - { - get - { - return stringComparison == StringComparison.OrdinalIgnoreCase; - } - } - - protected override void ValidateAgainstMismatch() - { - bool isMatch = Subject.StartsWith(Expected, stringComparison); - - if (isMatch) - { - Assertion.FailWith(ExpectationDescription + "{0}{reason}, but found {1}.", - Expected, Subject); - } - } -} diff --git a/Src/FluentAssertions/Primitives/NullableBooleanAssertions.cs b/Src/FluentAssertions/Primitives/NullableBooleanAssertions.cs index d4bab23992..c20c11af84 100644 --- a/Src/FluentAssertions/Primitives/NullableBooleanAssertions.cs +++ b/Src/FluentAssertions/Primitives/NullableBooleanAssertions.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using FluentAssertions.Execution; namespace FluentAssertions.Primitives; @@ -7,10 +8,11 @@ namespace FluentAssertions.Primitives; /// Contains a number of methods to assert that a nullable is in the expected state. /// [DebuggerNonUserCode] +[SuppressMessage("Maintainability", "AV1564:Parameter in public or internal member is of type bool or bool?")] public class NullableBooleanAssertions : NullableBooleanAssertions { - public NullableBooleanAssertions(bool? value) - : base(value) + public NullableBooleanAssertions(bool? value, AssertionChain assertionChain) + : base(value, assertionChain) { } } @@ -19,16 +21,20 @@ public NullableBooleanAssertions(bool? value) /// Contains a number of methods to assert that a nullable is in the expected state. /// [DebuggerNonUserCode] +[SuppressMessage("Maintainability", "AV1564:Parameter in public or internal member is of type bool or bool?")] 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; } /// - /// Asserts that a nullable boolean value is not . + /// Asserts that a nullable Boolean value is not . /// /// /// A formatted phrase as is supported by explaining why the assertion @@ -37,9 +43,9 @@ public NullableBooleanAssertions(bool? value) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveValue(string because = "", params object[] becauseArgs) + public AndConstraint HaveValue([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject.HasValue) .BecauseOf(because, becauseArgs) .FailWith("Expected a value{reason}."); @@ -48,7 +54,7 @@ public AndConstraint HaveValue(string because = "", params object[] } /// - /// Asserts that a nullable boolean value is not . + /// Asserts that a nullable Boolean value is not . /// /// /// A formatted phrase as is supported by explaining why the assertion @@ -57,13 +63,13 @@ public AndConstraint HaveValue(string because = "", params object[] /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeNull(string because = "", params object[] becauseArgs) + public AndConstraint NotBeNull([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return HaveValue(because, becauseArgs); } /// - /// Asserts that a nullable boolean value is . + /// Asserts that a nullable Boolean value is . /// /// /// A formatted phrase as is supported by explaining why the assertion @@ -72,9 +78,9 @@ public AndConstraint NotBeNull(string because = "", params object[] /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveValue(string because = "", params object[] becauseArgs) + 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); @@ -83,7 +89,7 @@ public AndConstraint NotHaveValue(string because = "", params objec } /// - /// Asserts that a nullable boolean value is . + /// Asserts that a nullable Boolean value is . /// /// /// A formatted phrase as is supported by explaining why the assertion @@ -92,7 +98,7 @@ public AndConstraint NotHaveValue(string because = "", params objec /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeNull(string because = "", params object[] becauseArgs) + public AndConstraint BeNull([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return NotHaveValue(because, becauseArgs); } @@ -108,9 +114,10 @@ public AndConstraint BeNull(string because = "", params object[] be /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint Be(bool? expected, 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 {0}{reason}, but found {1}.", expected, Subject); @@ -129,9 +136,10 @@ public AndConstraint Be(bool? expected, string because = "", params /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBe(bool? unexpected, 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:nullable boolean} not to be {0}{reason}, but found {1}.", unexpected, Subject); @@ -149,9 +157,9 @@ public AndConstraint NotBe(bool? unexpected, string because = "", p /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeFalse(string because = "", params object[] becauseArgs) + 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); @@ -169,9 +177,9 @@ public AndConstraint NotBeFalse(string because = "", params object[ /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeTrue(string because = "", params object[] becauseArgs) + 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 dece5ba5d6..ecdde62a34 100644 --- a/Src/FluentAssertions/Primitives/NullableDateOnlyAssertions.cs +++ b/Src/FluentAssertions/Primitives/NullableDateOnlyAssertions.cs @@ -1,8 +1,10 @@ +#if NET6_0_OR_GREATER + using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using FluentAssertions.Execution; -#if NET6_0_OR_GREATER namespace FluentAssertions.Primitives; /// @@ -11,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) { } } @@ -24,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; } /// @@ -39,9 +44,9 @@ public NullableDateOnlyAssertions(DateOnly? value) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveValue(string because = "", params object[] becauseArgs) + 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); @@ -59,7 +64,7 @@ public AndConstraint HaveValue(string because = "", params object[] /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeNull(string because = "", params object[] becauseArgs) + public AndConstraint NotBeNull([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return HaveValue(because, becauseArgs); } @@ -74,9 +79,9 @@ public AndConstraint NotBeNull(string because = "", params object[] /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveValue(string because = "", params object[] becauseArgs) + 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); @@ -94,7 +99,7 @@ public AndConstraint NotHaveValue(string because = "", params objec /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeNull(string because = "", params object[] becauseArgs) + public AndConstraint BeNull([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return NotHaveValue(because, becauseArgs); } diff --git a/Src/FluentAssertions/Primitives/NullableDateTimeAssertions.cs b/Src/FluentAssertions/Primitives/NullableDateTimeAssertions.cs index a533d7a2de..d7b8f1bc13 100644 --- a/Src/FluentAssertions/Primitives/NullableDateTimeAssertions.cs +++ b/Src/FluentAssertions/Primitives/NullableDateTimeAssertions.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using FluentAssertions.Execution; using FluentAssertions.Extensions; @@ -14,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) { } } @@ -30,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; } /// @@ -45,9 +49,9 @@ public NullableDateTimeAssertions(DateTime? expected) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveValue(string because = "", params object[] becauseArgs) + 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); @@ -65,7 +69,7 @@ public AndConstraint HaveValue(string because = "", params object[] /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeNull(string because = "", params object[] becauseArgs) + public AndConstraint NotBeNull([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return HaveValue(because, becauseArgs); } @@ -80,9 +84,9 @@ public AndConstraint NotBeNull(string because = "", params object[] /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveValue(string because = "", params object[] becauseArgs) + 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); @@ -100,7 +104,7 @@ public AndConstraint NotHaveValue(string because = "", params objec /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeNull(string because = "", params object[] becauseArgs) + public AndConstraint BeNull([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return NotHaveValue(because, becauseArgs); } diff --git a/Src/FluentAssertions/Primitives/NullableDateTimeOffsetAssertions.cs b/Src/FluentAssertions/Primitives/NullableDateTimeOffsetAssertions.cs index 50f6056b9a..3508fb2f36 100644 --- a/Src/FluentAssertions/Primitives/NullableDateTimeOffsetAssertions.cs +++ b/Src/FluentAssertions/Primitives/NullableDateTimeOffsetAssertions.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using FluentAssertions.Execution; namespace FluentAssertions.Primitives; @@ -14,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) { } } @@ -31,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; } /// @@ -46,9 +50,9 @@ public NullableDateTimeOffsetAssertions(DateTimeOffset? expected) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveValue(string because = "", params object[] becauseArgs) + 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); @@ -66,7 +70,7 @@ public AndConstraint HaveValue(string because = "", params object[] /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeNull(string because = "", params object[] becauseArgs) + public AndConstraint NotBeNull([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return HaveValue(because, becauseArgs); } @@ -81,10 +85,10 @@ public AndConstraint NotBeNull(string because = "", params object[] /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveValue(string because = "", + 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); @@ -102,7 +106,7 @@ public AndConstraint NotHaveValue(string because = "", /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeNull(string because = "", + public AndConstraint BeNull([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return NotHaveValue(because, becauseArgs); diff --git a/Src/FluentAssertions/Primitives/NullableEnumAssertions.cs b/Src/FluentAssertions/Primitives/NullableEnumAssertions.cs index 3ca6207582..7096c1ab53 100644 --- a/Src/FluentAssertions/Primitives/NullableEnumAssertions.cs +++ b/Src/FluentAssertions/Primitives/NullableEnumAssertions.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using FluentAssertions.Execution; namespace FluentAssertions.Primitives; @@ -9,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) { } } @@ -22,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; } /// @@ -37,14 +41,14 @@ public NullableEnumAssertions(TEnum? subject) /// /// Zero or more objects to format using the placeholders in . /// - public AndWhichConstraint HaveValue(string because = "", params object[] becauseArgs) + 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"); } /// @@ -57,7 +61,7 @@ public AndWhichConstraint HaveValue(string because = "", par /// /// Zero or more objects to format using the placeholders in . /// - public AndWhichConstraint NotBeNull(string because = "", params object[] becauseArgs) + public AndWhichConstraint NotBeNull([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return HaveValue(because, becauseArgs); } @@ -72,9 +76,9 @@ public AndWhichConstraint NotBeNull(string because = "", par /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveValue(string because = "", params object[] becauseArgs) + 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); @@ -92,7 +96,7 @@ public AndConstraint NotHaveValue(string because = "", params objec /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeNull(string because = "", params object[] becauseArgs) + public AndConstraint BeNull([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return NotHaveValue(because, becauseArgs); } diff --git a/Src/FluentAssertions/Primitives/NullableGuidAssertions.cs b/Src/FluentAssertions/Primitives/NullableGuidAssertions.cs index 2cf438c836..1eea1e758b 100644 --- a/Src/FluentAssertions/Primitives/NullableGuidAssertions.cs +++ b/Src/FluentAssertions/Primitives/NullableGuidAssertions.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using FluentAssertions.Execution; namespace FluentAssertions.Primitives; @@ -10,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) { } } @@ -23,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; } /// @@ -38,9 +42,9 @@ public NullableGuidAssertions(Guid? value) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveValue(string because = "", params object[] becauseArgs) + public AndConstraint HaveValue([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject.HasValue) .BecauseOf(because, becauseArgs) .FailWith("Expected a value{reason}."); @@ -58,7 +62,7 @@ public AndConstraint HaveValue(string because = "", params object[] /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeNull(string because = "", params object[] becauseArgs) + public AndConstraint NotBeNull([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return HaveValue(because, becauseArgs); } @@ -73,9 +77,9 @@ public AndConstraint NotBeNull(string because = "", params object[] /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveValue(string because = "", params object[] becauseArgs) + 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); @@ -93,7 +97,7 @@ public AndConstraint NotHaveValue(string because = "", params objec /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeNull(string because = "", params object[] becauseArgs) + public AndConstraint BeNull([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return NotHaveValue(because, becauseArgs); } @@ -109,9 +113,9 @@ public AndConstraint BeNull(string because = "", params object[] be /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint Be(Guid? expected, string because = "", params object[] becauseArgs) + 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 b3da01e2af..03ffb3d9bd 100644 --- a/Src/FluentAssertions/Primitives/NullableSimpleTimeSpanAssertions.cs +++ b/Src/FluentAssertions/Primitives/NullableSimpleTimeSpanAssertions.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using FluentAssertions.Execution; namespace FluentAssertions.Primitives; @@ -14,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) { } } @@ -31,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; } /// @@ -46,9 +50,9 @@ public NullableSimpleTimeSpanAssertions(TimeSpan? value) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveValue(string because = "", params object[] becauseArgs) + public AndConstraint HaveValue([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject.HasValue) .BecauseOf(because, becauseArgs) .FailWith("Expected a value{reason}."); @@ -66,7 +70,7 @@ public AndConstraint HaveValue(string because = "", params object[] /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeNull(string because = "", params object[] becauseArgs) + public AndConstraint NotBeNull([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return HaveValue(because, becauseArgs); } @@ -81,9 +85,9 @@ public AndConstraint NotBeNull(string because = "", params object[] /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveValue(string because = "", params object[] becauseArgs) + 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); @@ -101,7 +105,7 @@ public AndConstraint NotHaveValue(string because = "", params objec /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeNull(string because = "", params object[] becauseArgs) + public AndConstraint BeNull([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return NotHaveValue(because, becauseArgs); } @@ -117,10 +121,10 @@ public AndConstraint BeNull(string because = "", params object[] be /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint Be(TimeSpan? expected, string because = "", - params object[] becauseArgs) + 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 a9011308b7..8e6ede5140 100644 --- a/Src/FluentAssertions/Primitives/NullableTimeOnlyAssertions.cs +++ b/Src/FluentAssertions/Primitives/NullableTimeOnlyAssertions.cs @@ -1,8 +1,10 @@ +#if NET6_0_OR_GREATER + using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using FluentAssertions.Execution; -#if NET6_0_OR_GREATER namespace FluentAssertions.Primitives; /// @@ -11,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) { } } @@ -24,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; } /// @@ -39,9 +44,9 @@ public NullableTimeOnlyAssertions(TimeOnly? value) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveValue(string because = "", params object[] becauseArgs) + 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); @@ -59,7 +64,7 @@ public AndConstraint HaveValue(string because = "", params object[] /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeNull(string because = "", params object[] becauseArgs) + public AndConstraint NotBeNull([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return HaveValue(because, becauseArgs); } @@ -74,9 +79,9 @@ public AndConstraint NotBeNull(string because = "", params object[] /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveValue(string because = "", params object[] becauseArgs) + 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); @@ -94,7 +99,7 @@ public AndConstraint NotHaveValue(string because = "", params objec /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeNull(string because = "", params object[] becauseArgs) + public AndConstraint BeNull([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return NotHaveValue(because, becauseArgs); } diff --git a/Src/FluentAssertions/Primitives/ObjectAssertions.cs b/Src/FluentAssertions/Primitives/ObjectAssertions.cs index f9c9880f37..f75f84120c 100644 --- a/Src/FluentAssertions/Primitives/ObjectAssertions.cs +++ b/Src/FluentAssertions/Primitives/ObjectAssertions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using FluentAssertions.Common; using FluentAssertions.Equivalency; @@ -12,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; } /// @@ -32,11 +36,12 @@ public ObjectAssertions(object value) /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint Be(TExpectation expected, IEqualityComparer comparer, string because = "", params object[] becauseArgs) + public AndConstraint Be(TExpectation expected, IEqualityComparer comparer, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(comparer); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is TExpectation subject && comparer.Equals(subject, expected)) .WithDefaultIdentifier(Identifier) @@ -61,11 +66,12 @@ public AndConstraint Be(TExpectation expected, I /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint NotBe(TExpectation unexpected, IEqualityComparer comparer, string because = "", params object[] becauseArgs) + public AndConstraint NotBe(TExpectation unexpected, IEqualityComparer comparer, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(comparer); - Execute.Assertion + assertionChain .ForCondition(Subject is not TExpectation subject || !comparer.Equals(subject, unexpected)) .BecauseOf(because, becauseArgs) .WithDefaultIdentifier(Identifier) @@ -94,13 +100,12 @@ public AndConstraint NotBe(TExpectation unexpect /// is . public AndConstraint BeOneOf(IEnumerable validValues, IEqualityComparer comparer, - string because = "", - params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(validValues); Guard.ThrowIfArgumentIsNull(comparer); - Execute.Assertion + assertionChain .ForCondition(Subject is TExpectation subject && validValues.Contains(subject, comparer)) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:object} to be one of {0}{reason}, but found {1}.", validValues, Subject); @@ -117,9 +122,12 @@ public AndConstraint 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; } /// @@ -133,14 +141,14 @@ public ObjectAssertions(TSubject value) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint Be(TSubject expected, string because = "", params object[] becauseArgs) + 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); } @@ -160,11 +168,12 @@ public AndConstraint Be(TSubject expected, string because = "", par /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint Be(TSubject expected, IEqualityComparer comparer, string because = "", params object[] becauseArgs) + public AndConstraint Be(TSubject expected, IEqualityComparer comparer, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(comparer); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(comparer.Equals(Subject, expected)) .WithDefaultIdentifier(Identifier) @@ -185,9 +194,10 @@ public AndConstraint Be(TSubject expected, IEqualityComparer /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBe(TSubject unexpected, string because = "", params object[] becauseArgs) + public AndConstraint NotBe(TSubject unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(!ObjectExtensions.GetComparer()(Subject, unexpected)) .BecauseOf(because, becauseArgs) .WithDefaultIdentifier(Identifier) @@ -211,11 +221,12 @@ public AndConstraint NotBe(TSubject unexpected, string because = "" /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint NotBe(TSubject unexpected, IEqualityComparer comparer, string because = "", params object[] becauseArgs) + public AndConstraint NotBe(TSubject unexpected, IEqualityComparer comparer, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(comparer); - Execute.Assertion + assertionChain .ForCondition(!comparer.Equals(Subject, unexpected)) .BecauseOf(because, becauseArgs) .WithDefaultIdentifier(Identifier) @@ -232,7 +243,7 @@ public AndConstraint NotBe(TSubject unexpected, IEqualityComparer and all /// items in the collection are structurally equal. - /// Notice that actual behavior is determined by the global defaults managed by . + /// Notice that actual behavior is determined by the global defaults managed by . /// /// The expected element. /// @@ -242,8 +253,8 @@ public AndConstraint NotBe(TSubject unexpected, IEqualityComparer /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeEquivalentTo(TExpectation expectation, string because = "", - params object[] becauseArgs) + public AndConstraint BeEquivalentTo(TExpectation expectation, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return BeEquivalentTo(expectation, config => config, because, becauseArgs); } @@ -259,10 +270,10 @@ public AndConstraint BeEquivalentTo(TExpectation expe /// /// The expected element. /// - /// A reference to the configuration object that can be used + /// A reference to the configuration object that can be used /// to influence the way the object graphs are compared. You can also provide an alternative instance of the - /// class. The global defaults are determined by the - /// class. + /// class. The global defaults are determined by the + /// class. /// /// /// A formatted phrase as is supported by explaining why the assertion @@ -273,15 +284,15 @@ public AndConstraint BeEquivalentTo(TExpectation expe /// /// is . public AndConstraint BeEquivalentTo(TExpectation expectation, - Func, EquivalencyAssertionOptions> config, string because = "", - params object[] becauseArgs) + Func, EquivalencyOptions> config, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(config); - EquivalencyAssertionOptions options = config(AssertionOptions.CloneDefaults()); + EquivalencyOptions options = config(AssertionConfiguration.Current.Equivalency.CloneDefaults()); var context = new EquivalencyValidationContext(Node.From(() => - AssertionScope.Current.CallerIdentity), options) + CurrentAssertionChain.CallerIdentifier), options) { Reason = new Reason(because, becauseArgs), TraceWriter = options.TraceWriter @@ -307,7 +318,7 @@ public AndConstraint BeEquivalentTo(TExpectation expe /// irrespective of the type of those objects. Two properties are also equal if one type can be converted to another and the result is equal. /// The type of a collection property is ignored as long as the collection implements and all /// items in the collection are structurally equal. - /// Notice that actual behavior is determined by the global defaults managed by . + /// Notice that actual behavior is determined by the global defaults managed by . /// /// The unexpected element. /// @@ -319,8 +330,7 @@ public AndConstraint BeEquivalentTo(TExpectation expe /// public AndConstraint NotBeEquivalentTo( TExpectation unexpected, - string because = "", - params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return NotBeEquivalentTo(unexpected, config => config, because, becauseArgs); } @@ -336,10 +346,10 @@ public AndConstraint NotBeEquivalentTo( /// /// The unexpected element. /// - /// A reference to the configuration object that can be used + /// A reference to the configuration object that can be used /// to influence the way the object graphs are compared. You can also provide an alternative instance of the - /// class. The global defaults are determined by the - /// class. + /// class. The global defaults are determined by the + /// class. /// /// /// A formatted phrase as is supported by explaining why the assertion @@ -351,9 +361,8 @@ public AndConstraint NotBeEquivalentTo( /// is . public AndConstraint NotBeEquivalentTo( TExpectation unexpected, - Func, EquivalencyAssertionOptions> config, - string because = "", - params object[] becauseArgs) + Func, EquivalencyOptions> config, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(config); @@ -365,7 +374,7 @@ public AndConstraint NotBeEquivalentTo( hasMismatches = scope.Discard().Length > 0; } - Execute.Assertion + assertionChain .ForCondition(hasMismatches) .BecauseOf(because, becauseArgs) .WithDefaultIdentifier(Identifier) @@ -398,10 +407,10 @@ public AndConstraint BeOneOf(params TSubject[] validValues) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeOneOf(IEnumerable validValues, string because = "", - params object[] becauseArgs) + 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); @@ -429,13 +438,12 @@ public AndConstraint BeOneOf(IEnumerable validValues, str /// is . public AndConstraint BeOneOf(IEnumerable validValues, IEqualityComparer comparer, - string because = "", - params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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 33cf54b052..cbfe65d6c1 100644 --- a/Src/FluentAssertions/Primitives/ReferenceTypeAssertions.cs +++ b/Src/FluentAssertions/Primitives/ReferenceTypeAssertions.cs @@ -1,5 +1,7 @@ using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Linq.Expressions; using FluentAssertions.Common; using FluentAssertions.Execution; @@ -15,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; } @@ -35,9 +38,9 @@ protected ReferenceTypeAssertions(TSubject subject) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeNull(string because = "", params object[] becauseArgs) + public AndConstraint BeNull([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + CurrentAssertionChain .ForCondition(Subject is null) .BecauseOf(because, becauseArgs) .WithDefaultIdentifier(Identifier) @@ -56,9 +59,10 @@ public AndConstraint BeNull(string because = "", params object[] be /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeNull(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) @@ -78,9 +82,10 @@ public AndConstraint NotBeNull(string because = "", params object[] /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeSameAs(TSubject expected, string because = "", params object[] becauseArgs) + public AndConstraint BeSameAs(TSubject expected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + CurrentAssertionChain .ForCondition(ReferenceEquals(Subject, expected)) .BecauseOf(because, becauseArgs) .WithDefaultIdentifier(Identifier) @@ -100,9 +105,10 @@ public AndConstraint BeSameAs(TSubject expected, string because = " /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeSameAs(TSubject unexpected, string because = "", params object[] becauseArgs) + public AndConstraint NotBeSameAs(TSubject unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + CurrentAssertionChain .ForCondition(!ReferenceEquals(Subject, unexpected)) .BecauseOf(because, becauseArgs) .WithDefaultIdentifier(Identifier) @@ -122,7 +128,8 @@ public AndConstraint NotBeSameAs(TSubject unexpected, string becaus /// /// Zero or more objects to format using the placeholders in . /// - public AndWhichConstraint BeOfType(string because = "", params object[] becauseArgs) + public AndWhichConstraint BeOfType([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { BeOfType(typeof(T), because, becauseArgs); @@ -147,17 +154,18 @@ public AndWhichConstraint BeOfType(string because = "", param /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint BeOfType(Type expectedType, string because = "", params object[] becauseArgs) + public AndConstraint BeOfType(Type expectedType, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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(); @@ -185,7 +193,8 @@ public AndConstraint BeOfType(Type expectedType, string because = " /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeOfType(string because = "", params object[] becauseArgs) + public AndConstraint NotBeOfType([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { NotBeOfType(typeof(T), because, becauseArgs); @@ -206,17 +215,18 @@ public AndConstraint NotBeOfType(string because = "", params obj /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint NotBeOfType(Type unexpectedType, string because = "", params object[] becauseArgs) + public AndConstraint NotBeOfType(Type unexpectedType, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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(); @@ -244,18 +254,19 @@ public AndConstraint NotBeOfType(Type unexpectedType, string becaus /// /// Zero or more objects to format using the placeholders in . /// - /// An which can be used to chain assertions. - public AndWhichConstraint BeAssignableTo(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) @@ -282,23 +293,24 @@ public AndWhichConstraint BeAssignableTo(string because = "", /// /// An which can be used to chain assertions. /// is . - public AndConstraint BeAssignableTo(Type type, string because = "", params object[] becauseArgs) + public AndConstraint BeAssignableTo(Type type, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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) @@ -322,7 +334,8 @@ public AndConstraint BeAssignableTo(Type type, string because = "", /// Zero or more objects to format using the placeholders in . /// /// An which can be used to chain assertions. - public AndConstraint NotBeAssignableTo(string because = "", params object[] becauseArgs) + public AndConstraint NotBeAssignableTo([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { return NotBeAssignableTo(typeof(T), because, becauseArgs); } @@ -340,23 +353,24 @@ public AndConstraint NotBeAssignableTo(string because = "", para /// /// An which can be used to chain assertions. /// is . - public AndConstraint NotBeAssignableTo(Type type, string because = "", params object[] becauseArgs) + public AndConstraint NotBeAssignableTo(Type type, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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) @@ -379,8 +393,7 @@ public AndConstraint NotBeAssignableTo(Type type, string because = /// /// An which can be used to chain assertions. public AndConstraint Match(Expression> predicate, - string because = "", - params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return Match(predicate, because, becauseArgs); } @@ -399,13 +412,12 @@ public AndConstraint Match(Expression> predica /// An which can be used to chain assertions. /// is . public AndConstraint Match(Expression> predicate, - string because = "", - params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where T : TSubject { 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) @@ -414,6 +426,56 @@ public AndConstraint Match(Expression> predicate, return new AndConstraint((TAssertions)this); } + /// + /// Allows combining one or more assertions using the other assertion methods that this library offers on an instance of . + /// + /// + /// If multiple assertions executed by the fail, they will be raised as a single failure. + /// + /// The element inspector which must be satisfied by the . + /// An which can be used to chain assertions. + /// is . + public AndConstraint Satisfy(Action assertion) + where T : TSubject + { + Guard.ThrowIfArgumentIsNull(assertion, nameof(assertion), "Cannot verify an object against a inspector."); + + 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()); + + if (CurrentAssertionChain.Succeeded) + { + string[] failuresFromInspector; + + using (var assertionScope = new AssertionScope()) + { + assertion((T)Subject); + failuresFromInspector = assertionScope.Discard(); + } + + if (failuresFromInspector.Length > 0) + { + string failureMessage = Environment.NewLine + + string.Join(Environment.NewLine, failuresFromInspector.Select(x => x.IndentLines())); + + CurrentAssertionChain + .WithDefaultIdentifier(Identifier) + .WithExpectation("Expected {context:object} to match inspector, but the inspector was not satisfied:", + Subject, + chain => chain.FailWithPreFormatted(failureMessage)); + } + } + + return new AndConstraint((TAssertions)this); + } + /// /// Returns the type of the subject the assertion applies on. /// It should be a user-friendly name as it is included in the failure message. @@ -423,4 +485,9 @@ public AndConstraint Match(Expression> predicate, /// public override bool Equals(object obj) => throw new NotSupportedException("Equals is not part of Fluent Assertions. Did you mean BeSameAs() instead?"); + + /// + /// Provides access to the that this assertion class was initialized with. + /// + public AssertionChain CurrentAssertionChain { get; } } diff --git a/Src/FluentAssertions/Primitives/SimpleTimeSpanAssertions.cs b/Src/FluentAssertions/Primitives/SimpleTimeSpanAssertions.cs index df7af13c81..634feee5a7 100644 --- a/Src/FluentAssertions/Primitives/SimpleTimeSpanAssertions.cs +++ b/Src/FluentAssertions/Primitives/SimpleTimeSpanAssertions.cs @@ -1,6 +1,6 @@ using System; -using System.ComponentModel; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using FluentAssertions.Common; using FluentAssertions.Execution; @@ -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; } @@ -47,9 +50,9 @@ public SimpleTimeSpanAssertions(TimeSpan? value) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BePositive(string because = "", params object[] becauseArgs) + 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); @@ -67,9 +70,9 @@ public AndConstraint BePositive(string because = "", params object[ /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeNegative(string because = "", params object[] becauseArgs) + 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); @@ -89,9 +92,10 @@ public AndConstraint BeNegative(string because = "", params object[ /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint Be(TimeSpan expected, string because = "", params object[] becauseArgs) + 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); @@ -111,9 +115,10 @@ public AndConstraint Be(TimeSpan expected, string because = "", par /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBe(TimeSpan unexpected, string because = "", params object[] becauseArgs) + 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); @@ -133,9 +138,10 @@ public AndConstraint NotBe(TimeSpan unexpected, string because = "" /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeLessThan(TimeSpan expected, string because = "", params object[] becauseArgs) + 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); @@ -155,9 +161,10 @@ public AndConstraint BeLessThan(TimeSpan expected, string because = /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeLessThanOrEqualTo(TimeSpan expected, string because = "", params object[] becauseArgs) + 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); @@ -165,10 +172,6 @@ public AndConstraint BeLessThanOrEqualTo(TimeSpan expected, string return new AndConstraint((TAssertions)this); } - [EditorBrowsable(EditorBrowsableState.Never)] - public AndConstraint BeLessOrEqualTo(TimeSpan expected, string because = "", params object[] becauseArgs) => - BeLessThanOrEqualTo(expected, because, becauseArgs); - /// /// Asserts that the time difference of the current is greater than the /// specified time. @@ -181,9 +184,10 @@ public AndConstraint BeLessOrEqualTo(TimeSpan expected, string beca /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeGreaterThan(TimeSpan expected, string because = "", params object[] becauseArgs) + 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); @@ -203,10 +207,10 @@ public AndConstraint BeGreaterThan(TimeSpan expected, string becaus /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeGreaterThanOrEqualTo(TimeSpan expected, string because = "", - params object[] becauseArgs) + 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); @@ -214,10 +218,6 @@ public AndConstraint BeGreaterThanOrEqualTo(TimeSpan expected, stri return new AndConstraint((TAssertions)this); } - [EditorBrowsable(EditorBrowsableState.Never)] - public AndConstraint BeGreaterOrEqualTo(TimeSpan expected, string because = "", params object[] becauseArgs) => - BeGreaterThanOrEqualTo(expected, because, becauseArgs); - /// /// Asserts that the current is within the specified time /// from the specified value. @@ -240,15 +240,15 @@ public AndConstraint BeGreaterOrEqualTo(TimeSpan expected, string b /// Zero or more objects to format using the placeholders in . /// /// is negative. - public AndConstraint BeCloseTo(TimeSpan nearbyTime, TimeSpan precision, string because = "", - params object[] becauseArgs) + public AndConstraint BeCloseTo(TimeSpan nearbyTime, TimeSpan precision, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNegative(precision); 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}.", @@ -280,15 +280,15 @@ public AndConstraint BeCloseTo(TimeSpan nearbyTime, TimeSpan precis /// Zero or more objects to format using the placeholders in . /// /// is negative. - public AndConstraint NotBeCloseTo(TimeSpan distantTime, TimeSpan precision, string because = "", - params object[] becauseArgs) + public AndConstraint NotBeCloseTo(TimeSpan distantTime, TimeSpan precision, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNegative(precision); 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 57c2f1b021..6c4a8a9833 100644 --- a/Src/FluentAssertions/Primitives/StringAssertions.cs +++ b/Src/FluentAssertions/Primitives/StringAssertions.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text.RegularExpressions; using FluentAssertions.Common; +using FluentAssertions.Equivalency; using FluentAssertions.Execution; using JetBrains.Annotations; @@ -19,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) { } } @@ -32,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; } /// @@ -51,12 +55,14 @@ public StringAssertions(string value) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint Be(string expected, string because = "", params object[] becauseArgs) + public AndConstraint Be(string expected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - var stringEqualityValidator = - new StringEqualityValidator(Subject, expected, StringComparison.Ordinal, because, becauseArgs); + var stringEqualityValidator = new StringValidator(assertionChain, + new StringEqualityStrategy(StringComparer.Ordinal, "be"), + because, becauseArgs); - stringEqualityValidator.Validate(); + stringEqualityValidator.Validate(Subject, expected); return new AndConstraint((TAssertions)this); } @@ -85,10 +91,11 @@ public AndConstraint BeOneOf(params string[] validValues) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeOneOf(IEnumerable validValues, string because = "", params object[] becauseArgs) + public AndConstraint BeOneOf(IEnumerable validValues, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion - .ForCondition(validValues.Contains(Subject)) + assertionChain + .ForCondition(validValues.Contains(Subject, StringComparer.Ordinal)) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:string} to be one of {0}{reason}, but found {1}.", validValues, Subject); @@ -109,17 +116,53 @@ public AndConstraint BeOneOf(IEnumerable validValues, strin /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeEquivalentTo(string expected, string because = "", - params object[] becauseArgs) + public AndConstraint BeEquivalentTo(string expected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - var expectation = new StringEqualityValidator( - Subject, expected, StringComparison.OrdinalIgnoreCase, because, becauseArgs); + var expectation = new StringValidator(assertionChain, + new StringEqualityStrategy(StringComparer.OrdinalIgnoreCase, "be equivalent to"), + because, becauseArgs); - expectation.Validate(); + expectation.Validate(Subject, expected); return new AndConstraint((TAssertions)this); } + /// + /// Asserts that a string is exactly the same as another string, using the provided . + /// + /// + /// The string that the subject is expected to be equivalent to. + /// + /// + /// The equivalency options. + /// + /// + /// 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 AndConstraint BeEquivalentTo(string expected, + Func, EquivalencyOptions> config, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNull(config); + + EquivalencyOptions options = config(AssertionConfiguration.Current.Equivalency.CloneDefaults()); + + var expectation = new StringValidator(assertionChain, + new StringEqualityStrategy(options.GetStringComparerOrDefault(), "be equivalent to"), + because, becauseArgs); + + var subject = ApplyStringSettings(Subject, options); + expected = ApplyStringSettings(expected, options); + + expectation.Validate(subject, expected); + return new AndConstraint((TAssertions)this); + } + /// /// Asserts that a string is not exactly the same as another string, including any leading or trailing whitespace, with /// the exception of the casing. @@ -134,8 +177,8 @@ public AndConstraint BeEquivalentTo(string expected, string because /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeEquivalentTo(string unexpected, string because = "", - params object[] becauseArgs) + public AndConstraint NotBeEquivalentTo(string unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { bool notEquivalent; @@ -145,7 +188,45 @@ public AndConstraint NotBeEquivalentTo(string unexpected, string be 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); + + return new AndConstraint((TAssertions)this); + } + + /// + /// Asserts that a string is not exactly the same as another string, using the provided . + /// + /// + /// The string that the subject is not expected to be equivalent to. + /// + /// + /// The equivalency options. + /// + /// + /// 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 AndConstraint NotBeEquivalentTo(string unexpected, + Func, EquivalencyOptions> config, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNull(config); + + bool notEquivalent; + + using (var scope = new AssertionScope()) + { + Subject.Should().BeEquivalentTo(unexpected, config); + notEquivalent = scope.Discard().Length > 0; + } + + assertionChain .ForCondition(notEquivalent) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:string} not to be equivalent to {0}{reason}, but they are.", unexpected); @@ -165,9 +246,10 @@ public AndConstraint NotBeEquivalentTo(string unexpected, string be /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBe(string unexpected, string because = "", params object[] becauseArgs) + 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); @@ -210,7 +292,8 @@ public AndConstraint NotBe(string unexpected, string because = "", /// /// is . /// is empty. - public AndConstraint Match(string wildcardPattern, string because = "", params object[] becauseArgs) + public AndConstraint Match(string wildcardPattern, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(wildcardPattern, nameof(wildcardPattern), "Cannot match string against . Provide a wildcard pattern or use the BeNull method."); @@ -218,8 +301,11 @@ public AndConstraint Match(string wildcardPattern, string because = Guard.ThrowIfArgumentIsEmpty(wildcardPattern, nameof(wildcardPattern), "Cannot match string against an empty string. Provide a wildcard pattern or use the BeEmpty method."); - var stringWildcardMatchingValidator = new StringWildcardMatchingValidator(Subject, wildcardPattern, because, becauseArgs); - stringWildcardMatchingValidator.Validate(); + var stringWildcardMatchingValidator = new StringValidator(assertionChain, + new StringWildcardMatchingStrategy(), + because, becauseArgs); + + stringWildcardMatchingValidator.Validate(Subject, wildcardPattern); return new AndConstraint((TAssertions)this); } @@ -259,7 +345,8 @@ public AndConstraint Match(string wildcardPattern, string because = /// /// is . /// is empty. - public AndConstraint NotMatch(string wildcardPattern, string because = "", params object[] becauseArgs) + public AndConstraint NotMatch(string wildcardPattern, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(wildcardPattern, nameof(wildcardPattern), "Cannot match string against . Provide a wildcard pattern or use the NotBeNull method."); @@ -267,10 +354,14 @@ public AndConstraint NotMatch(string wildcardPattern, string becaus Guard.ThrowIfArgumentIsEmpty(wildcardPattern, nameof(wildcardPattern), "Cannot match string against an empty string. Provide a wildcard pattern or use the NotBeEmpty method."); - new StringWildcardMatchingValidator(Subject, wildcardPattern, because, becauseArgs) - { - Negate = true - }.Validate(); + var stringWildcardMatchingValidator = new StringValidator(assertionChain, + new StringWildcardMatchingStrategy + { + Negate = true + }, + because, becauseArgs); + + stringWildcardMatchingValidator.Validate(Subject, wildcardPattern); return new AndConstraint((TAssertions)this); } @@ -310,8 +401,8 @@ public AndConstraint NotMatch(string wildcardPattern, string becaus /// /// is . /// is empty. - public AndConstraint MatchEquivalentOf(string wildcardPattern, string because = "", - params object[] becauseArgs) + public AndConstraint MatchEquivalentOf(string wildcardPattern, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(wildcardPattern, nameof(wildcardPattern), "Cannot match string against . Provide a wildcard pattern or use the BeNull method."); @@ -319,14 +410,83 @@ public AndConstraint MatchEquivalentOf(string wildcardPattern, stri Guard.ThrowIfArgumentIsEmpty(wildcardPattern, nameof(wildcardPattern), "Cannot match string against an empty string. Provide a wildcard pattern or use the BeEmpty method."); - var validator = new StringWildcardMatchingValidator(Subject, wildcardPattern, because, becauseArgs) - { - IgnoreCase = true, - IgnoreNewLineDifferences = true - }; + var stringWildcardMatchingValidator = new StringValidator(assertionChain, + new StringWildcardMatchingStrategy + { + IgnoreCase = true, + IgnoreAllNewlines = true + }, + because, becauseArgs); + + stringWildcardMatchingValidator.Validate(Subject, wildcardPattern); + + return new AndConstraint((TAssertions)this); + } + + /// + /// Asserts that a string matches the , using the provided . + /// + /// + /// The pattern to match against the subject. This parameter can contain a combination of literal text and wildcard + /// (* and ?) characters, but it doesn't support regular expressions. + /// + /// + /// The equivalency options. + /// + /// + /// 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 . + /// + /// + /// can be a combination of literal and wildcard characters, + /// but it doesn't support regular expressions. The following wildcard specifiers are permitted in + /// . + /// + /// + /// Wildcard character + /// Description + /// + /// + /// * (asterisk) + /// Zero or more characters in that position. + /// + /// + /// ? (question mark) + /// Exactly one character in that position. + /// + /// + /// + /// is . + /// is empty. + public AndConstraint MatchEquivalentOf(string wildcardPattern, + Func, EquivalencyOptions> config, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNull(wildcardPattern, nameof(wildcardPattern), + "Cannot match string against . Provide a wildcard pattern or use the BeNull method."); - validator.Validate(); + Guard.ThrowIfArgumentIsEmpty(wildcardPattern, nameof(wildcardPattern), + "Cannot match string against an empty string. Provide a wildcard pattern or use the BeEmpty method."); + Guard.ThrowIfArgumentIsNull(config); + + EquivalencyOptions options = config(AssertionConfiguration.Current.Equivalency.CloneDefaults()); + + var stringWildcardMatchingValidator = new StringValidator(assertionChain, + new StringWildcardMatchingStrategy + { + IgnoreCase = options.IgnoreCase, + IgnoreNewlineStyle = options.IgnoreNewlineStyle, + }, + because, becauseArgs); + + var subject = ApplyStringSettings(Subject, options); + wildcardPattern = ApplyStringSettings(wildcardPattern, options); + + stringWildcardMatchingValidator.Validate(subject, wildcardPattern); return new AndConstraint((TAssertions)this); } @@ -365,8 +525,8 @@ public AndConstraint MatchEquivalentOf(string wildcardPattern, stri /// /// is . /// is empty. - public AndConstraint NotMatchEquivalentOf(string wildcardPattern, string because = "", - params object[] becauseArgs) + public AndConstraint NotMatchEquivalentOf(string wildcardPattern, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(wildcardPattern, nameof(wildcardPattern), "Cannot match string against . Provide a wildcard pattern or use the NotBeNull method."); @@ -374,15 +534,85 @@ public AndConstraint NotMatchEquivalentOf(string wildcardPattern, s Guard.ThrowIfArgumentIsEmpty(wildcardPattern, nameof(wildcardPattern), "Cannot match string against an empty string. Provide a wildcard pattern or use the NotBeEmpty method."); - var validator = new StringWildcardMatchingValidator(Subject, wildcardPattern, because, becauseArgs) - { - IgnoreCase = true, - IgnoreNewLineDifferences = true, - Negate = true - }; + var stringWildcardMatchingValidator = new StringValidator(assertionChain, + new StringWildcardMatchingStrategy + { + IgnoreCase = true, + IgnoreAllNewlines = true, + Negate = true + }, + because, becauseArgs); + + stringWildcardMatchingValidator.Validate(Subject, wildcardPattern); + + return new AndConstraint((TAssertions)this); + } + + /// + /// Asserts that a string does not match the , using the provided . + /// + /// + /// The pattern to match against the subject. This parameter can contain a combination of literal text and wildcard + /// (* and ?) characters, but it doesn't support regular expressions. + /// + /// + /// The equivalency options. + /// + /// + /// 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 . + /// + /// + /// can be a combination of literal and wildcard characters, + /// but it doesn't support regular expressions. The following wildcard specifiers are permitted in + /// . + /// + /// + /// Wildcard character + /// Description + /// + /// + /// * (asterisk) + /// Zero or more characters in that position. + /// + /// + /// ? (question mark) + /// Exactly one character in that position. + /// + /// + /// + /// is . + /// is empty. + public AndConstraint NotMatchEquivalentOf(string wildcardPattern, + Func, EquivalencyOptions> config, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNull(wildcardPattern, nameof(wildcardPattern), + "Cannot match string against . Provide a wildcard pattern or use the NotBeNull method."); + + Guard.ThrowIfArgumentIsEmpty(wildcardPattern, nameof(wildcardPattern), + "Cannot match string against an empty string. Provide a wildcard pattern or use the NotBeEmpty method."); + + Guard.ThrowIfArgumentIsNull(config); - validator.Validate(); + EquivalencyOptions options = config(AssertionConfiguration.Current.Equivalency.CloneDefaults()); + var stringWildcardMatchingValidator = new StringValidator(assertionChain, + new StringWildcardMatchingStrategy + { + IgnoreCase = options.IgnoreCase, + IgnoreNewlineStyle = options.IgnoreNewlineStyle, + Negate = true + }, + because, becauseArgs); + + var subject = ApplyStringSettings(Subject, options); + wildcardPattern = ApplyStringSettings(wildcardPattern, options); + + stringWildcardMatchingValidator.Validate(subject, wildcardPattern); return new AndConstraint((TAssertions)this); } @@ -407,7 +637,8 @@ public AndConstraint NotMatchEquivalentOf(string wildcardPattern, s /// /// is . public AndConstraint MatchRegex([RegexPattern][StringSyntax("Regex")] string regularExpression, - OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) + OccurrenceConstraint occurrenceConstraint, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(regularExpression, nameof(regularExpression), "Cannot match string against . Provide a regex pattern or use the BeNull method."); @@ -420,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); @@ -444,7 +675,7 @@ public AndConstraint MatchRegex([RegexPattern][StringSyntax("Regex" /// /// is . public AndConstraint MatchRegex([RegexPattern][StringSyntax("Regex")] string regularExpression, - string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(regularExpression, nameof(regularExpression), "Cannot match string against . Provide a regex pattern or use the BeNull method."); @@ -457,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); @@ -488,7 +719,8 @@ public AndConstraint MatchRegex([RegexPattern][StringSyntax("Regex" /// is . /// is empty. public AndConstraint MatchRegex(Regex regularExpression, - OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) + OccurrenceConstraint occurrenceConstraint, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(regularExpression, nameof(regularExpression), "Cannot match string against . Provide a regex pattern or use the BeNull method."); @@ -498,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; + int actual = regularExpression.Matches(Subject!).Count; - Execute.Assertion + assertionChain .ForConstraint(occurrenceConstraint, actual) .UsingLineBreaks .BecauseOf(because, becauseArgs) @@ -536,7 +768,7 @@ public AndConstraint MatchRegex(Regex regularExpression, /// is . /// is empty. public AndConstraint MatchRegex(Regex regularExpression, - string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(regularExpression, nameof(regularExpression), "Cannot match string against . Provide a regex pattern or use the BeNull method."); @@ -546,16 +778,16 @@ 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 - .ForCondition(regularExpression.IsMatch(Subject)) + assertionChain + .ForCondition(regularExpression.IsMatch(Subject!)) .BecauseOf(because, becauseArgs) .UsingLineBreaks .FailWith("Expected {context:string} to match regex {0}{reason}, but {1} does not match.", regexStr, Subject); @@ -579,7 +811,7 @@ public AndConstraint MatchRegex(Regex regularExpression, /// /// is . public AndConstraint NotMatchRegex([RegexPattern][StringSyntax("Regex")] string regularExpression, - string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(regularExpression, nameof(regularExpression), "Cannot match string against . Provide a regex pattern or use the NotBeNull method."); @@ -592,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); @@ -616,7 +848,8 @@ public AndConstraint NotMatchRegex([RegexPattern][StringSyntax("Reg /// /// is . /// is empty. - public AndConstraint NotMatchRegex(Regex regularExpression, string because = "", params object[] becauseArgs) + public AndConstraint NotMatchRegex(Regex regularExpression, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(regularExpression, nameof(regularExpression), "Cannot match string against . Provide a regex pattern or use the NotBeNull method."); @@ -626,16 +859,16 @@ public AndConstraint NotMatchRegex(Regex regularExpression, string 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 - .ForCondition(!regularExpression.IsMatch(Subject)) + assertionChain + .ForCondition(!regularExpression.IsMatch(Subject!)) .BecauseOf(because, becauseArgs) .UsingLineBreaks .FailWith("Did not expect {context:string} to match regex {0}{reason}, but {1} matches.", regexStr, Subject); @@ -657,12 +890,16 @@ public AndConstraint NotMatchRegex(Regex regularExpression, string /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint StartWith(string expected, string because = "", params object[] becauseArgs) + public AndConstraint StartWith(string expected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(expected, nameof(expected), "Cannot compare start of string with ."); - var stringStartValidator = new StringStartValidator(Subject, expected, StringComparison.Ordinal, because, becauseArgs); - stringStartValidator.Validate(); + var stringStartValidator = new StringValidator(assertionChain, + new StringStartStrategy(StringComparer.Ordinal, "start with"), + because, becauseArgs); + + stringStartValidator.Validate(Subject, expected); return new AndConstraint((TAssertions)this); } @@ -680,14 +917,23 @@ public AndConstraint StartWith(string expected, string because = "" /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint NotStartWith(string unexpected, string because = "", params object[] becauseArgs) + public AndConstraint NotStartWith(string unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(unexpected, nameof(unexpected), "Cannot compare start of string with ."); - var negatedStringStartValidator = - new NegatedStringStartValidator(Subject, unexpected, StringComparison.Ordinal, because, becauseArgs); + bool notEquivalent; + + using (var scope = new AssertionScope()) + { + Subject.Should().StartWith(unexpected); + notEquivalent = scope.Discard().Length > 0; + } - negatedStringStartValidator.Validate(); + assertionChain + .ForCondition(Subject != null && notEquivalent) + .BecauseOf(because, becauseArgs) + .FailWith("Expected {context:string} not to start with {0}{reason}, but found {1}.", unexpected, Subject); return new AndConstraint((TAssertions)this); } @@ -705,19 +951,55 @@ public AndConstraint NotStartWith(string unexpected, string because /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint StartWithEquivalentOf(string expected, string because = "", - params object[] becauseArgs) + public AndConstraint StartWithEquivalentOf(string expected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(expected, nameof(expected), "Cannot compare string start equivalence with ."); - var stringStartValidator = - new StringStartValidator(Subject, expected, StringComparison.OrdinalIgnoreCase, because, becauseArgs); + var stringStartValidator = new StringValidator(assertionChain, + new StringStartStrategy(StringComparer.OrdinalIgnoreCase, "start with equivalent of"), + because, becauseArgs); - stringStartValidator.Validate(); + stringStartValidator.Validate(Subject, expected); return new AndConstraint((TAssertions)this); } + /// + /// Asserts that a string starts with the specified , using the provided . + /// + /// The string that the subject is expected to start with. + /// + /// The equivalency options. + /// + /// + /// 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 . + /// + /// is . + public AndConstraint StartWithEquivalentOf(string expected, + Func, EquivalencyOptions> config, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNull(expected, nameof(expected), "Cannot compare string start equivalence with ."); + Guard.ThrowIfArgumentIsNull(config); + + EquivalencyOptions options = config(AssertionConfiguration.Current.Equivalency.CloneDefaults()); + + var stringStartValidator = new StringValidator(assertionChain, + new StringStartStrategy(options.GetStringComparerOrDefault(), "start with equivalent of"), + because, becauseArgs); + + var subject = ApplyStringSettings(Subject, options); + expected = ApplyStringSettings(expected, options); + + stringStartValidator.Validate(subject, expected); + return new AndConstraint((TAssertions)this); + } + /// /// Asserts that a string does not start with the specified value, /// including any leading or trailing whitespace, with the exception of the casing. @@ -731,15 +1013,61 @@ public AndConstraint StartWithEquivalentOf(string expected, string /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint NotStartWithEquivalentOf(string unexpected, string because = "", - params object[] becauseArgs) + public AndConstraint NotStartWithEquivalentOf(string unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNull(unexpected, nameof(unexpected), "Cannot compare start of string with ."); + + bool notEquivalent; + + using (var scope = new AssertionScope()) + { + Subject.Should().StartWithEquivalentOf(unexpected); + notEquivalent = scope.Discard().Length > 0; + } + + 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); + + return new AndConstraint((TAssertions)this); + } + + /// + /// Asserts that a string does not start with the specified value, using the provided . + /// + /// The string that the subject is not expected to start with. + /// + /// The equivalency options. + /// + /// + /// 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 . + /// + /// is . + public AndConstraint NotStartWithEquivalentOf(string unexpected, + Func, EquivalencyOptions> config, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(unexpected, nameof(unexpected), "Cannot compare start of string with ."); + Guard.ThrowIfArgumentIsNull(config); + + bool notEquivalent; - var negatedStringStartValidator = - new NegatedStringStartValidator(Subject, unexpected, StringComparison.OrdinalIgnoreCase, because, becauseArgs); + using (var scope = new AssertionScope()) + { + Subject.Should().StartWithEquivalentOf(unexpected, config); + notEquivalent = scope.Discard().Length > 0; + } - negatedStringStartValidator.Validate(); + 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); return new AndConstraint((TAssertions)this); } @@ -757,30 +1085,16 @@ public AndConstraint NotStartWithEquivalentOf(string unexpected, st /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint EndWith(string expected, string because = "", params object[] becauseArgs) + public AndConstraint EndWith(string expected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(expected, nameof(expected), "Cannot compare string end with ."); - bool success = Execute.Assertion - .BecauseOf(because, becauseArgs) - .ForCondition(Subject is not null) - .FailWith("Expected {context:string} {0} to end with {1}{reason}.", Subject, expected); - - if (success) - { - success = Execute.Assertion - .BecauseOf(because, becauseArgs) - .ForCondition(Subject.Length >= expected.Length) - .FailWith("Expected {context:string} to end with {0}{reason}, but {1} is too short.", expected, Subject); + var stringEndValidator = new StringValidator(assertionChain, + new StringEndStrategy(StringComparer.Ordinal, "end with"), + because, becauseArgs); - if (success) - { - Execute.Assertion - .ForCondition(Subject.EndsWith(expected, StringComparison.Ordinal)) - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context:string} {0} to end with {1}{reason}.", Subject, expected); - } - } + stringEndValidator.Validate(Subject, expected); return new AndConstraint((TAssertions)this); } @@ -798,23 +1112,24 @@ public AndConstraint EndWith(string expected, string because = "", /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint NotEndWith(string unexpected, string because = "", params object[] becauseArgs) + public AndConstraint NotEndWith(string unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(unexpected, nameof(unexpected), "Cannot compare end of string with ."); - bool success = Execute.Assertion - .ForCondition(Subject is not null) - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context:string} that does not end with {1}{reason}, but found {0}.", Subject, unexpected); + bool notEquivalent; - if (success) + using (var scope = new AssertionScope()) { - Execute.Assertion - .ForCondition(!Subject.EndsWith(unexpected, StringComparison.Ordinal)) - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context:string} {0} not to end with {1}{reason}.", Subject, unexpected); + Subject.Should().EndWith(unexpected); + notEquivalent = scope.Discard().Length > 0; } + assertionChain + .ForCondition(Subject != null && notEquivalent) + .BecauseOf(because, becauseArgs) + .FailWith("Expected {context:string} not to end with {0}{reason}, but found {1}.", unexpected, Subject); + return new AndConstraint((TAssertions)this); } @@ -831,36 +1146,52 @@ public AndConstraint NotEndWith(string unexpected, string because = /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint EndWithEquivalentOf(string expected, string because = "", params object[] becauseArgs) + public AndConstraint EndWithEquivalentOf(string expected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(expected, nameof(expected), "Cannot compare string end equivalence with ."); - bool success = Execute.Assertion - .BecauseOf(because, becauseArgs) - .ForCondition(Subject is not null) - .FailWith( - "Expected {context:string} that ends with equivalent of {0}{reason}, but found {1}.", expected, Subject); + var stringEndValidator = new StringValidator(assertionChain, + new StringEndStrategy(StringComparer.OrdinalIgnoreCase, "end with equivalent of"), + because, becauseArgs); - if (success) - { - success = Execute.Assertion - .BecauseOf(because, becauseArgs) - .ForCondition(Subject.Length >= expected.Length) - .FailWith( - "Expected {context:string} to end with equivalent of {0}{reason}, but {1} is too short.", - expected, Subject); + stringEndValidator.Validate(Subject, expected); - if (success) - { - Execute.Assertion - .ForCondition(Subject.EndsWith(expected, StringComparison.OrdinalIgnoreCase)) - .BecauseOf(because, becauseArgs) - .FailWith( - "Expected {context:string} that ends with equivalent of {0}{reason}, but found {1}.", - expected, Subject); - } - } + return new AndConstraint((TAssertions)this); + } + /// + /// Asserts that a string ends with the specified , using the provided . + /// + /// The string that the subject is expected to end with. + /// + /// The equivalency options. + /// + /// + /// 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 . + /// + /// is . + public AndConstraint EndWithEquivalentOf(string expected, + Func, EquivalencyOptions> config, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNull(expected, nameof(expected), "Cannot compare string end equivalence with ."); + Guard.ThrowIfArgumentIsNull(config); + + EquivalencyOptions options = config(AssertionConfiguration.Current.Equivalency.CloneDefaults()); + + var stringEndValidator = new StringValidator(assertionChain, + new StringEndStrategy(options.GetStringComparerOrDefault(), "end with equivalent of"), + because, becauseArgs); + + var subject = ApplyStringSettings(Subject, options); + expected = ApplyStringSettings(expected, options); + + stringEndValidator.Validate(subject, expected); return new AndConstraint((TAssertions)this); } @@ -877,27 +1208,62 @@ public AndConstraint EndWithEquivalentOf(string expected, string be /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint NotEndWithEquivalentOf(string unexpected, string because = "", params object[] becauseArgs) + public AndConstraint NotEndWithEquivalentOf(string unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(unexpected, nameof(unexpected), "Cannot compare end of string with ."); - var success = Execute.Assertion + bool notEquivalent; + + using (var scope = new AssertionScope()) + { + Subject.Should().EndWithEquivalentOf(unexpected); + notEquivalent = scope.Discard().Length > 0; + } + + assertionChain + .ForCondition(Subject != null && notEquivalent) .BecauseOf(because, becauseArgs) - .ForCondition(Subject is not null) - .FailWith( - "Expected {context:string} that does not end with equivalent of {0}{reason}, but found {1}.", - unexpected, Subject); + .FailWith("Expected {context:string} not to end with equivalent of {0}{reason}, but found {1}.", unexpected, Subject); + + return new AndConstraint((TAssertions)this); + } - if (success) + /// + /// Asserts that a string does not end with the specified , using the provided . + /// + /// The string that the subject is not expected to end with. + /// + /// The equivalency options. + /// + /// + /// 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 . + /// + /// is . + public AndConstraint NotEndWithEquivalentOf(string unexpected, + Func, EquivalencyOptions> config, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNull(unexpected, nameof(unexpected), "Cannot compare end of string with ."); + Guard.ThrowIfArgumentIsNull(config); + + bool notEquivalent; + + using (var scope = new AssertionScope()) { - Execute.Assertion - .ForCondition(!Subject.EndsWith(unexpected, StringComparison.OrdinalIgnoreCase)) - .BecauseOf(because, becauseArgs) - .FailWith( - "Expected {context:string} that does not end with equivalent of {0}{reason}, but found {1}.", - unexpected, Subject); + Subject.Should().EndWithEquivalentOf(unexpected, config); + notEquivalent = scope.Discard().Length > 0; } + 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); + return new AndConstraint((TAssertions)this); } @@ -916,12 +1282,13 @@ public AndConstraint NotEndWithEquivalentOf(string unexpected, stri /// /// is . /// is empty. - public AndConstraint Contain(string expected, string because = "", params object[] becauseArgs) + public AndConstraint Contain(string expected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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); @@ -950,15 +1317,15 @@ public AndConstraint Contain(string expected, string because = "", /// /// is . /// is empty. - public AndConstraint Contain(string expected, OccurrenceConstraint occurrenceConstraint, string because = "", - params object[] becauseArgs) + public AndConstraint Contain(string expected, OccurrenceConstraint occurrenceConstraint, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(expected, nameof(expected), "Cannot assert string containment against ."); Guard.ThrowIfArgumentIsEmpty(expected, nameof(expected), "Cannot assert string containment against an empty string."); - int actual = Subject.CountSubstring(expected, StringComparison.Ordinal); + int actual = Subject.CountSubstring(expected, StringComparer.Ordinal); - Execute.Assertion + assertionChain .ForConstraint(occurrenceConstraint, actual) .BecauseOf(because, becauseArgs) .FailWith( @@ -982,16 +1349,86 @@ public AndConstraint Contain(string expected, OccurrenceConstraint /// /// is . /// is empty. - public AndConstraint ContainEquivalentOf(string expected, string because = "", params object[] becauseArgs) + public AndConstraint ContainEquivalentOf(string expected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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 - .ForCondition(Contains(Subject, expected, StringComparison.OrdinalIgnoreCase)) - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context:string} {0} to contain the equivalent of {1}{reason}.", Subject, expected); + var stringContainValidator = new StringValidatorSupportingNull(assertionChain, + new StringContainsStrategy(StringComparer.OrdinalIgnoreCase, AtLeast.Once()), + because, becauseArgs); + + stringContainValidator.Validate(Subject, expected); + + return new AndConstraint((TAssertions)this); + } + + /// + /// Asserts that a string contains the specified , using the provided . + /// + /// The string that the subject is expected to contain. + /// + /// The equivalency options. + /// + /// + /// 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 . + /// + /// is . + /// is empty. + public AndConstraint ContainEquivalentOf(string expected, + Func, EquivalencyOptions> config, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + return ContainEquivalentOf(expected, AtLeast.Once(), config, because, becauseArgs); + } + + /// + /// Asserts that a string contains the specified , using the provided . + /// + /// The string that the subject is expected to contain. + /// + /// A constraint specifying the amount of times a substring should be present within the test subject. + /// It can be created by invoking static methods Once, Twice, Thrice, or Times(int) + /// on the classes , , , , and . + /// For example, or . + /// + /// + /// The equivalency options. + /// + /// + /// 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 . + /// + /// is . + /// is empty. + public AndConstraint ContainEquivalentOf(string expected, + OccurrenceConstraint occurrenceConstraint, + Func, EquivalencyOptions> config, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNull(expected, nameof(expected), "Cannot assert string containment against ."); + Guard.ThrowIfArgumentIsEmpty(expected, nameof(expected), "Cannot assert string containment against an empty string."); + Guard.ThrowIfArgumentIsNull(occurrenceConstraint); + Guard.ThrowIfArgumentIsNull(config); + + EquivalencyOptions options = config(AssertionConfiguration.Current.Equivalency.CloneDefaults()); + var stringContainValidator = new StringValidatorSupportingNull(assertionChain, + new StringContainsStrategy(options.GetStringComparerOrDefault(), occurrenceConstraint), + because, becauseArgs); + + var subject = ApplyStringSettings(Subject, options); + expected = ApplyStringSettings(expected, options); + + stringContainValidator.Validate(subject, expected); return new AndConstraint((TAssertions)this); } @@ -1017,20 +1454,19 @@ public AndConstraint ContainEquivalentOf(string expected, string be /// /// is . /// is empty. - public AndConstraint ContainEquivalentOf(string expected, OccurrenceConstraint occurrenceConstraint, - string because = "", params object[] becauseArgs) + public AndConstraint ContainEquivalentOf(string expected, + OccurrenceConstraint occurrenceConstraint, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(expected, nameof(expected), "Cannot assert string containment against ."); Guard.ThrowIfArgumentIsEmpty(expected, nameof(expected), "Cannot assert string containment against an empty string."); + Guard.ThrowIfArgumentIsNull(occurrenceConstraint); - int actual = Subject.CountSubstring(expected, StringComparison.OrdinalIgnoreCase); + var stringContainValidator = new StringValidatorSupportingNull(assertionChain, + new StringContainsStrategy(StringComparer.OrdinalIgnoreCase, occurrenceConstraint), + because, becauseArgs); - Execute.Assertion - .ForConstraint(occurrenceConstraint, actual) - .BecauseOf(because, becauseArgs) - .FailWith( - $"Expected {{context:string}} {{0}} to contain equivalent of {{1}} {{expectedOccurrence}}{{reason}}, but found it {actual.Times()}.", - Subject, expected); + stringContainValidator.Validate(Subject, expected); return new AndConstraint((TAssertions)this); } @@ -1048,13 +1484,14 @@ public AndConstraint ContainEquivalentOf(string expected, Occurrenc /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint ContainAll(IEnumerable values, string because = "", params object[] becauseArgs) + public AndConstraint ContainAll(IEnumerable values, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { ThrowIfValuesNullOrEmpty(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); @@ -1086,11 +1523,12 @@ public AndConstraint ContainAll(params string[] values) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint ContainAny(IEnumerable values, string because = "", params object[] becauseArgs) + public AndConstraint ContainAny(IEnumerable values, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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); @@ -1124,13 +1562,13 @@ public AndConstraint ContainAny(params string[] values) /// /// is . /// is empty. - public AndConstraint NotContain(string unexpected, string because = "", - params object[] becauseArgs) + public AndConstraint NotContain(string unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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); @@ -1152,15 +1590,16 @@ public AndConstraint NotContain(string unexpected, string because = /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotContainAll(IEnumerable values, string because = "", - params object[] becauseArgs) + public AndConstraint NotContainAll(IEnumerable values, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - ThrowIfValuesNullOrEmpty(values); + ICollection strings = values?.ConvertOrCastToCollection(); + ThrowIfValuesNullOrEmpty(strings); - var matches = values.Count(v => Contains(Subject, v, StringComparison.Ordinal)); + var matches = strings!.Count(v => Contains(Subject, v, StringComparison.Ordinal)); - Execute.Assertion - .ForCondition(matches != values.Count()) + assertionChain + .ForCondition(matches != strings.Count) .BecauseOf(because, becauseArgs) .FailWith("Did not expect {context:string} {0} to contain all of the strings: {1}{reason}.", Subject, values); @@ -1192,14 +1631,14 @@ public AndConstraint NotContainAll(params string[] values) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotContainAny(IEnumerable values, string because = "", - params object[] becauseArgs) + public AndConstraint NotContainAny(IEnumerable values, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { ThrowIfValuesNullOrEmpty(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); @@ -1230,20 +1669,69 @@ public AndConstraint NotContainAny(params string[] values) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotContainEquivalentOf(string unexpected, string because = "", - params object[] becauseArgs) + public AndConstraint NotContainEquivalentOf(string unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion - .ForCondition(!Contains(Subject, unexpected, StringComparison.OrdinalIgnoreCase)) + 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); + + bool notEquivalent; + + using (var scope = new AssertionScope()) + { + Subject.Should().ContainEquivalentOf(unexpected); + notEquivalent = scope.Discard().Length > 0; + } + + assertionChain + .ForCondition(notEquivalent) .BecauseOf(because, becauseArgs) - .FailWith("Did not expect {context:string} to contain equivalent of {0}{reason} but found {1}.", unexpected, Subject); + .FailWith("Did not expect {context:string} to contain the equivalent of {0}{reason} but found {1}.", unexpected, Subject); return new AndConstraint((TAssertions)this); } - private static bool Contains(string actual, string expected, StringComparison comparison) + /// + /// Asserts that a string does not contain the specified string, using the provided . + /// + /// The string that the subject is not expected to contain. + /// + /// The equivalency options. + /// + /// + /// 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 AndConstraint NotContainEquivalentOf(string unexpected, + Func, EquivalencyOptions> config, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - return (actual ?? string.Empty).Contains(expected ?? string.Empty, comparison); + Guard.ThrowIfArgumentIsNull(config); + + 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); + + bool notEquivalent; + + using (var scope = new AssertionScope()) + { + Subject.Should().ContainEquivalentOf(unexpected, config); + notEquivalent = scope.Discard().Length > 0; + } + + assertionChain + .ForCondition(notEquivalent) + .BecauseOf(because, becauseArgs) + .FailWith("Did not expect {context:string} to contain the equivalent of {0}{reason}, but found {1}.", unexpected, Subject); + + return new AndConstraint((TAssertions)this); } /// @@ -1256,9 +1744,9 @@ private static bool Contains(string actual, string expected, StringComparison co /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeEmpty(string because = "", params object[] becauseArgs) + 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); @@ -1276,9 +1764,9 @@ public AndConstraint BeEmpty(string because = "", params object[] b /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeEmpty(string because = "", params object[] becauseArgs) + 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}."); @@ -1297,18 +1785,19 @@ public AndConstraint NotBeEmpty(string because = "", params object[ /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveLength(int expected, string because = "", params object[] becauseArgs) + 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) + .ForCondition(Subject!.Length == expected) .FailWith("Expected {context:string} with length {0}{reason}, but found string {1} with length {2}.", expected, Subject, Subject.Length); } @@ -1326,9 +1815,9 @@ public AndConstraint HaveLength(int expected, string because = "", /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeNullOrEmpty(string because = "", params object[] becauseArgs) + 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); @@ -1346,9 +1835,9 @@ public AndConstraint NotBeNullOrEmpty(string because = "", params o /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeNullOrEmpty(string because = "", params object[] becauseArgs) + 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); @@ -1366,9 +1855,9 @@ public AndConstraint BeNullOrEmpty(string because = "", params obje /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeNullOrWhiteSpace(string because = "", params object[] becauseArgs) + 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); @@ -1386,9 +1875,9 @@ public AndConstraint NotBeNullOrWhiteSpace(string because = "", par /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeNullOrWhiteSpace(string because = "", params object[] becauseArgs) + 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); @@ -1397,12 +1886,12 @@ public AndConstraint BeNullOrWhiteSpace(string because = "", params } /// - /// Asserts that all characters in a string are in upper casing. + /// Asserts that all cased characters in a string are upper-case. That is, that the string could be the result of a call to + /// . /// /// - /// Be careful that numbers and special characters don't have casing, so - /// will always fail on a string that contains anything but alphabetic characters. - /// In those cases, we recommend using . + /// Numbers, special characters, and many Asian characters don't have casing, so + /// will ignore these and will fail only in the presence of lower-case characters. /// /// /// A formatted phrase as is supported by explaining why the assertion @@ -1411,19 +1900,24 @@ public AndConstraint BeNullOrWhiteSpace(string because = "", params /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeUpperCased(string because = "", params object[] becauseArgs) + public AndConstraint BeUpperCased([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion - .ForCondition(Subject?.All(char.IsUpper) == true) + assertionChain + .ForCondition(Subject is not null && !Subject.Any(char.IsLower)) .BecauseOf(because, becauseArgs) - .FailWith("Expected all characters in {context:string} to be upper cased{reason}, but found {0}.", Subject); + .FailWith("Expected all alphabetic characters in {context:string} to be upper-case{reason}, but found {0}.", Subject); return new AndConstraint((TAssertions)this); } /// - /// Asserts that all characters in a string are not in upper casing. + /// Asserts that of all the cased characters in a string, some are not upper-case. That is, the string could not be + /// the result of a call to . /// + /// + /// Numbers, special characters, and many Asian characters don't have casing, so + /// will ignore these and will fail only if the string contains cased characters and they are all upper-case. + /// /// /// 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. @@ -1431,23 +1925,23 @@ public AndConstraint BeUpperCased(string because = "", params objec /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeUpperCased(string because = "", params object[] becauseArgs) + public AndConstraint NotBeUpperCased([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion - .ForCondition(Subject is null || Subject.Any(ch => !char.IsUpper(ch))) + assertionChain + .ForCondition(Subject is null || HasMixedOrNoCase(Subject)) .BecauseOf(because, becauseArgs) - .FailWith("Did not expect any characters in {context:string} to be upper cased{reason}."); + .FailWith("Expected some characters in {context:string} to be lower-case{reason}."); return new AndConstraint((TAssertions)this); } /// - /// Asserts that all characters in a string are in lower casing. + /// Asserts that all cased characters in a string are lower-case. That is, that the string could be the result of a call to + /// , /// /// - /// Be careful that numbers and special characters don't have casing, so will always fail on - /// a string that contains anything but alphabetic characters. - /// In those cases, we recommend using . + /// Numbers, special characters, and many Asian characters don't have casing, so + /// will ignore these and will fail only in the presence of upper-case characters. /// /// /// A formatted phrase as is supported by explaining why the assertion @@ -1456,19 +1950,24 @@ public AndConstraint NotBeUpperCased(string because = "", params ob /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeLowerCased(string because = "", params object[] becauseArgs) + public AndConstraint BeLowerCased([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion - .ForCondition(Subject?.All(char.IsLower) == true) + assertionChain + .ForCondition(Subject is not null && !Subject.Any(char.IsUpper)) .BecauseOf(because, becauseArgs) - .FailWith("Expected all characters in {context:string} to be lower cased{reason}, but found {0}.", Subject); + .FailWith("Expected all alphabetic characters in {context:string} to be lower cased{reason}, but found {0}.", Subject); return new AndConstraint((TAssertions)this); } /// - /// Asserts that all characters in a string are not in lower casing. + /// Asserts that of all the cased characters in a string, some are not lower-case. That is, the string could not be + /// the result of a call to . /// + /// + /// Numbers, special characters, and many Asian characters don't have casing, so + /// will ignore these and will fail only if the string contains cased characters and they are all lower-case. + /// /// /// 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. @@ -1476,16 +1975,59 @@ public AndConstraint BeLowerCased(string because = "", params objec /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeLowerCased(string because = "", params object[] becauseArgs) + public AndConstraint NotBeLowerCased([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion - .ForCondition(Subject is null || Subject.Any(ch => !char.IsLower(ch))) + assertionChain + .ForCondition(Subject is null || HasMixedOrNoCase(Subject)) .BecauseOf(because, becauseArgs) - .FailWith("Did not expect any characters in {context:string} to be lower cased{reason}."); + .FailWith("Expected some characters in {context:string} to be upper-case{reason}."); + + return new AndConstraint((TAssertions)this); + } + + private static bool HasMixedOrNoCase(string value) + { + var hasUpperCase = false; + var hasLowerCase = false; + + foreach (var ch in value) + { + hasUpperCase |= char.IsUpper(ch); + hasLowerCase |= char.IsLower(ch); + + if (hasUpperCase && hasLowerCase) + { + return true; + } + } + + return !hasUpperCase && !hasLowerCase; + } + + internal AndConstraint Be(string expected, + Func, EquivalencyOptions> config, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNull(config); + + EquivalencyOptions options = config(AssertionConfiguration.Current.Equivalency.CloneDefaults()); + var expectation = new StringValidator(assertionChain, + new StringEqualityStrategy(options.GetStringComparerOrDefault(), "be"), + because, becauseArgs); + + var subject = ApplyStringSettings(Subject, options); + expected = ApplyStringSettings(expected, options); + + expectation.Validate(subject, expected); return new AndConstraint((TAssertions)this); } + private static bool Contains(string actual, string expected, StringComparison comparison) + { + return (actual ?? string.Empty).Contains(expected ?? string.Empty, comparison); + } + private static void ThrowIfValuesNullOrEmpty(IEnumerable values) { Guard.ThrowIfArgumentIsNull(values, nameof(values), "Cannot assert string containment of values in null collection"); @@ -1496,6 +2038,34 @@ private static void ThrowIfValuesNullOrEmpty(IEnumerable values) } } + /// + /// Applies the string-specific to the . + /// + /// + /// When is set, whitespace is removed from the start of the .
+ /// When is set, whitespace is removed from the end of the .
+ /// When is set, all newlines (\r\n and \r) are replaced with \n in the .
+ ///
+ private static string ApplyStringSettings(string value, IEquivalencyOptions options) + { + if (options.IgnoreLeadingWhitespace) + { + value = value.TrimStart(); + } + + if (options.IgnoreTrailingWhitespace) + { + value = value.TrimEnd(); + } + + if (options.IgnoreNewlineStyle) + { + value = value.RemoveNewlineStyle(); + } + + return value; + } + /// /// Returns the type of the subject the assertion applies on. /// diff --git a/Src/FluentAssertions/Primitives/StringContainsStrategy.cs b/Src/FluentAssertions/Primitives/StringContainsStrategy.cs new file mode 100644 index 0000000000..6411133327 --- /dev/null +++ b/Src/FluentAssertions/Primitives/StringContainsStrategy.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using FluentAssertions.Common; +using FluentAssertions.Execution; + +namespace FluentAssertions.Primitives; + +internal class StringContainsStrategy : IStringComparisonStrategy +{ + private readonly IEqualityComparer comparer; + private readonly OccurrenceConstraint occurrenceConstraint; + + public StringContainsStrategy(IEqualityComparer comparer, OccurrenceConstraint occurrenceConstraint) + { + this.comparer = comparer; + this.occurrenceConstraint = occurrenceConstraint; + } + + public void AssertForEquality(AssertionChain assertionChain, string subject, string expected) + { + int actual = subject.CountSubstring(expected, comparer); + + assertionChain + .ForConstraint(occurrenceConstraint, actual) + .FailWith( + $"Expected {{context:string}} {{0}} to contain the equivalent of {{1}} {{expectedOccurrence}}{{reason}}, but found it {actual.Times()}.", + subject, expected); + } + + public void AssertNeitherIsNull(AssertionChain assertionChain, string subject, string expected) + { + if (subject is null || expected is null) + { + assertionChain.FailWith("Expected {context:string} to contain the equivalent of {0}{reason}, but found {1}", subject, expected); + } + } +} diff --git a/Src/FluentAssertions/Primitives/StringEndStrategy.cs b/Src/FluentAssertions/Primitives/StringEndStrategy.cs new file mode 100644 index 0000000000..3a2d0044e0 --- /dev/null +++ b/Src/FluentAssertions/Primitives/StringEndStrategy.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using FluentAssertions.Common; +using FluentAssertions.Execution; + +namespace FluentAssertions.Primitives; + +internal class StringEndStrategy : IStringComparisonStrategy +{ + private readonly IEqualityComparer comparer; + private readonly string predicateDescription; + + public StringEndStrategy(IEqualityComparer comparer, string predicateDescription) + { + this.comparer = comparer; + this.predicateDescription = predicateDescription; + } + + public void AssertForEquality(AssertionChain assertionChain, string subject, string expected) + { + assertionChain + .ForCondition(subject!.Length >= expected.Length) + .FailWith($"{ExpectationDescription}, but {{1}} is too short.", expected, subject); + + if (!assertionChain.Succeeded) + { + return; + } + + int indexOfMismatch = subject.Substring(subject.Length - expected.Length).IndexOfFirstMismatch(expected, comparer); + + if (indexOfMismatch < 0) + { + return; + } + + assertionChain.FailWith( + $"{ExpectationDescription}, but {{1}} differs near {subject.IndexedSegmentAt(indexOfMismatch)}.", + expected, subject); + } + + /// + public void AssertNeitherIsNull(AssertionChain assertionChain, string subject, string expected) + { + if (subject is null || expected is null) + { + assertionChain.FailWith($"{ExpectationDescription}, but found {{1}}.", expected, subject); + } + } + + private string ExpectationDescription => $"Expected {{context:string}} to {predicateDescription} {{0}}{{reason}}"; +} diff --git a/Src/FluentAssertions/Primitives/StringEqualityStrategy.cs b/Src/FluentAssertions/Primitives/StringEqualityStrategy.cs new file mode 100644 index 0000000000..2bb4ff376f --- /dev/null +++ b/Src/FluentAssertions/Primitives/StringEqualityStrategy.cs @@ -0,0 +1,238 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FluentAssertions.Common; +using FluentAssertions.Execution; + +namespace FluentAssertions.Primitives; + +internal class StringEqualityStrategy : IStringComparisonStrategy +{ + private readonly IEqualityComparer comparer; + private readonly string predicateDescription; + + public StringEqualityStrategy(IEqualityComparer comparer, string predicateDescription) + { + this.comparer = comparer; + this.predicateDescription = predicateDescription; + } + + public void AssertForEquality(AssertionChain assertionChain, string subject, string expected) + { + ValidateAgainstSuperfluousWhitespace(assertionChain, subject, expected); + + if (expected.IsLongOrMultiline() || subject.IsLongOrMultiline()) + { + int indexOfMismatch = subject.IndexOfFirstMismatch(expected, comparer); + + if (indexOfMismatch == -1) + { + ValidateAgainstLengthDifferences(assertionChain, subject, expected); + return; + } + + string locationDescription = $"at index {indexOfMismatch}"; + var matchingString = subject[..indexOfMismatch]; + int lineNumber = matchingString.Count(c => c == '\n'); + + if (lineNumber > 0) + { + var indexOfLastNewlineBeforeMismatch = matchingString.LastIndexOf('\n'); + var column = matchingString.Length - indexOfLastNewlineBeforeMismatch; + locationDescription = $"on line {lineNumber + 1} and column {column} (index {indexOfMismatch})"; + } + + assertionChain.FailWith( + $"{ExpectationDescription}the same string{{reason}}, but they differ {locationDescription}:{Environment.NewLine}{GetMismatchSegmentForLongStrings(subject, expected, indexOfMismatch)}."); + } + else if (ValidateAgainstLengthDifferences(assertionChain, subject, expected)) + { + int indexOfMismatch = subject.IndexOfFirstMismatch(expected, comparer); + + if (indexOfMismatch != -1) + { + assertionChain.FailWith( + $"{ExpectationDescription}{{0}}{{reason}}, but {{1}} differs near " + subject.IndexedSegmentAt(indexOfMismatch) + + ".", + expected, subject); + } + } + else + { + // Then it's fine + } + } + + public void AssertNeitherIsNull(AssertionChain assertionChain, string subject, string expected) + { + if (subject is null || expected is null) + { + assertionChain.FailWith($"{ExpectationDescription}{{0}}{{reason}}, but found {{1}}.", expected, subject); + } + } + + private string ExpectationDescription => $"Expected {{context:string}} to {predicateDescription} "; + + private void ValidateAgainstSuperfluousWhitespace(AssertionChain assertion, string subject, string expected) + { + assertion + .ForCondition(!(expected.Length > subject.Length && comparer.Equals(expected.TrimEnd(), subject))) + .FailWith($"{ExpectationDescription}{{0}}{{reason}}, but it misses some extra whitespace at the end.", expected) + .Then + .ForCondition(!(subject.Length > expected.Length && comparer.Equals(subject.TrimEnd(), expected))) + .FailWith($"{ExpectationDescription}{{0}}{{reason}}, but it has unexpected whitespace at the end.", expected); + } + + private bool ValidateAgainstLengthDifferences(AssertionChain assertion, string subject, string expected) + { + assertion + .ForCondition(subject.Length == expected.Length) + .FailWith(() => + { + string mismatchSegment = GetMismatchSegmentForStringsOfDifferentLengths(subject, expected); + + string message = $"{ExpectationDescription}{{0}} with a length of {{1}}{{reason}}, but {{2}} has a length of {{3}}, differs near " + mismatchSegment + "."; + + return new FailReason(message, expected, expected.Length, subject, subject.Length); + }); + + return assertion.Succeeded; + } + + private string GetMismatchSegmentForStringsOfDifferentLengths(string subject, string expected) + { + int indexOfMismatch = subject.IndexOfFirstMismatch(expected, comparer); + + // If there is no difference it means that expected starts with subject and subject is shorter than expected + if (indexOfMismatch == -1) + { + // Subject is shorter so we point at its last character. + // We would like to point at next character as it is the real + // index of first mismatch, but we need to point at character existing in + // subject, so the next best thing is the last subject character. + indexOfMismatch = Math.Max(0, subject.Length - 1); + } + + return subject.IndexedSegmentAt(indexOfMismatch); + } + + /// + /// Get the mismatch segment between and for long strings, + /// when they differ at index . + /// + private static string GetMismatchSegmentForLongStrings(string subject, string expected, int firstIndexOfMismatch) + { + int trimStart = GetStartIndexOfPhraseToShowBeforeTheMismatchingIndex(subject, firstIndexOfMismatch); + const string prefix = " \""; + const string suffix = "\""; + const char arrowDown = '\u2193'; + const char arrowUp = '\u2191'; + + int whiteSpaceCountBeforeArrow = (firstIndexOfMismatch - trimStart) + prefix.Length; + + if (trimStart > 0) + { + whiteSpaceCountBeforeArrow++; + } + + var visibleText = subject[trimStart..firstIndexOfMismatch]; + whiteSpaceCountBeforeArrow += visibleText.Count(c => c is '\r' or '\n'); + + var sb = new StringBuilder(); + + sb.Append(' ', whiteSpaceCountBeforeArrow).Append(arrowDown).AppendLine(" (actual)"); + AppendPrefixAndEscapedPhraseToShowWithEllipsisAndSuffix(sb, prefix, subject, trimStart, suffix); + AppendPrefixAndEscapedPhraseToShowWithEllipsisAndSuffix(sb, prefix, expected, trimStart, suffix); + sb.Append(' ', whiteSpaceCountBeforeArrow).Append(arrowUp).Append(" (expected)"); + + return sb.ToString(); + } + + /// + /// Appends the , the escaped visible phrase decorated with ellipsis and the to the . + /// + /// When text phrase starts at and with a calculated length omits text on start or end, an ellipsis is added. + private static void AppendPrefixAndEscapedPhraseToShowWithEllipsisAndSuffix(StringBuilder stringBuilder, + string prefix, string text, int indexOfStartingPhrase, string suffix) + { + var subjectLength = GetLengthOfPhraseToShowOrDefaultLength(text[indexOfStartingPhrase..]); + const char ellipsis = '\u2026'; + + stringBuilder.Append(prefix); + + if (indexOfStartingPhrase > 0) + { + stringBuilder.Append(ellipsis); + } + + stringBuilder.Append(text + .Substring(indexOfStartingPhrase, subjectLength) + .Replace("\r", "\\r", StringComparison.OrdinalIgnoreCase) + .Replace("\n", "\\n", StringComparison.OrdinalIgnoreCase)); + + if (text.Length > (indexOfStartingPhrase + subjectLength)) + { + stringBuilder.Append(ellipsis); + } + + stringBuilder.AppendLine(suffix); + } + + /// + /// Calculates the start index of the visible segment from when highlighting the difference at . + /// + /// + /// Either keep the last 10 characters before or a word begin (separated by whitespace) between 15 and 5 characters before . + /// + private static int GetStartIndexOfPhraseToShowBeforeTheMismatchingIndex(string value, int indexOfFirstMismatch) + { + const int defaultCharactersToKeep = 10; + const int minCharactersToKeep = 5; + const int maxCharactersToKeep = 15; + const int lengthOfWhitespace = 1; + const int phraseLengthToCheckForWordBoundary = (maxCharactersToKeep - minCharactersToKeep) + lengthOfWhitespace; + + if (indexOfFirstMismatch <= defaultCharactersToKeep) + { + return 0; + } + + var indexToStartSearchingForWordBoundary = Math.Max(indexOfFirstMismatch - (maxCharactersToKeep + lengthOfWhitespace), 0); + + var indexOfWordBoundary = value + .IndexOf(' ', indexToStartSearchingForWordBoundary, phraseLengthToCheckForWordBoundary) - + indexToStartSearchingForWordBoundary; + + if (indexOfWordBoundary >= 0) + { + return indexToStartSearchingForWordBoundary + indexOfWordBoundary + lengthOfWhitespace; + } + + return indexOfFirstMismatch - defaultCharactersToKeep; + } + + /// + /// Calculates how many characters to keep in . + /// + /// + /// If a word end is found between 15 and 25 characters, use this word end, otherwise keep 20 characters. + /// + private static int GetLengthOfPhraseToShowOrDefaultLength(string value) + { + const int defaultLength = 20; + const int minLength = 15; + const int maxLength = 25; + const int lengthOfWhitespace = 1; + + var indexOfWordBoundary = value + .LastIndexOf(' ', Math.Min(maxLength + lengthOfWhitespace, value.Length) - 1); + + if (indexOfWordBoundary >= minLength) + { + return indexOfWordBoundary; + } + + return Math.Min(defaultLength, value.Length); + } +} diff --git a/Src/FluentAssertions/Primitives/StringEqualityValidator.cs b/Src/FluentAssertions/Primitives/StringEqualityValidator.cs deleted file mode 100644 index eb098e56a1..0000000000 --- a/Src/FluentAssertions/Primitives/StringEqualityValidator.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using FluentAssertions.Common; -using FluentAssertions.Execution; - -namespace FluentAssertions.Primitives; - -internal class StringEqualityValidator : StringValidator -{ - private readonly StringComparison comparisonMode; - - public StringEqualityValidator(string subject, string expected, StringComparison comparisonMode, string because, - object[] becauseArgs) - : base(subject, expected, because, becauseArgs) - { - this.comparisonMode = comparisonMode; - } - - protected override bool ValidateAgainstSuperfluousWhitespace() - { - return Assertion - .ForCondition(!(Expected.Length > Subject.Length && Expected.TrimEnd().Equals(Subject, comparisonMode))) - .FailWith(ExpectationDescription + "{0}{reason}, but it misses some extra whitespace at the end.", Expected) - .Then - .ForCondition(!(Subject.Length > Expected.Length && Subject.TrimEnd().Equals(Expected, comparisonMode))) - .FailWith(ExpectationDescription + "{0}{reason}, but it has unexpected whitespace at the end.", Expected); - } - - protected override bool ValidateAgainstLengthDifferences() - { - return Assertion - .ForCondition(Subject.Length == Expected.Length) - .FailWith(() => - { - string mismatchSegment = GetMismatchSegmentForStringsOfDifferentLengths(); - - string message = ExpectationDescription + - "{0} with a length of {1}{reason}, but {2} has a length of {3}, differs near " + mismatchSegment + "."; - - return new FailReason(message, Expected, Expected.Length, Subject, Subject.Length); - }); - } - - private string GetMismatchSegmentForStringsOfDifferentLengths() - { - int indexOfMismatch = Subject.IndexOfFirstMismatch(Expected, comparisonMode); - - // If there is no difference it means that expected starts with subject and subject is shorter than expected - if (indexOfMismatch == -1) - { - // Subject is shorter so we point at its last character. - // We would like to point at next character as it is the real - // index of first mismatch, but we need to point at character existing in - // subject, so the next best thing is the last subject character. - indexOfMismatch = Math.Max(0, Subject.Length - 1); - } - - return Subject.IndexedSegmentAt(indexOfMismatch); - } - - protected override void ValidateAgainstMismatch() - { - int indexOfMismatch = Subject.IndexOfFirstMismatch(Expected, comparisonMode); - - if (indexOfMismatch != -1) - { - Assertion.FailWith( - ExpectationDescription + "{0}{reason}, but {1} differs near " + Subject.IndexedSegmentAt(indexOfMismatch) + ".", - Expected, Subject); - } - } - - protected override string ExpectationDescription - { - get - { - string predicateDescription = IgnoreCase ? "be equivalent to" : "be"; - return "Expected {context:string} to " + predicateDescription + " "; - } - } - - private bool IgnoreCase - { - get - { - return comparisonMode == StringComparison.OrdinalIgnoreCase; - } - } -} diff --git a/Src/FluentAssertions/Primitives/StringStartStrategy.cs b/Src/FluentAssertions/Primitives/StringStartStrategy.cs new file mode 100644 index 0000000000..4738d90355 --- /dev/null +++ b/Src/FluentAssertions/Primitives/StringStartStrategy.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using FluentAssertions.Common; +using FluentAssertions.Execution; + +namespace FluentAssertions.Primitives; + +internal class StringStartStrategy : IStringComparisonStrategy +{ + private readonly IEqualityComparer comparer; + private readonly string predicateDescription; + + public StringStartStrategy(IEqualityComparer comparer, string predicateDescription) + { + this.comparer = comparer; + this.predicateDescription = predicateDescription; + } + + public void AssertForEquality(AssertionChain assertionChain, string subject, string expected) + { + assertionChain + .ForCondition(subject.Length >= expected.Length) + .FailWith($"{ExpectationDescription}{{0}}{{reason}}, but {{1}} is too short.", expected, subject); + + if (!assertionChain.Succeeded) + { + return; + } + + int indexOfMismatch = subject.IndexOfFirstMismatch(expected, comparer); + + if (indexOfMismatch < 0 || indexOfMismatch >= expected.Length) + { + return; + } + + assertionChain.FailWith( + $"{ExpectationDescription}{{0}}{{reason}}, but {{1}} differs near {subject.IndexedSegmentAt(indexOfMismatch)}.", + expected, subject); + } + + /// + public void AssertNeitherIsNull(AssertionChain assertionChain, string subject, string expected) + { + if (subject is null || expected is null) + { + assertionChain.FailWith($"{ExpectationDescription}{{0}}{{reason}}, but found {{1}}.", expected, subject); + } + } + + private string ExpectationDescription => $"Expected {{context:string}} to {predicateDescription} "; +} diff --git a/Src/FluentAssertions/Primitives/StringStartValidator.cs b/Src/FluentAssertions/Primitives/StringStartValidator.cs deleted file mode 100644 index 727d645a74..0000000000 --- a/Src/FluentAssertions/Primitives/StringStartValidator.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using FluentAssertions.Common; - -namespace FluentAssertions.Primitives; - -internal class StringStartValidator : StringValidator -{ - private readonly StringComparison stringComparison; - - public StringStartValidator(string subject, string expected, StringComparison stringComparison, string because, - object[] becauseArgs) - : base(subject, expected, because, becauseArgs) - { - this.stringComparison = stringComparison; - } - - protected override string ExpectationDescription - { - get - { - string predicateDescription = IgnoreCase ? "start with equivalent of" : "start with"; - return "Expected {context:string} to " + predicateDescription + " "; - } - } - - private bool IgnoreCase - { - get - { - return stringComparison == StringComparison.OrdinalIgnoreCase; - } - } - - protected override bool ValidateAgainstLengthDifferences() - { - return Assertion - .ForCondition(Subject.Length >= Expected.Length) - .FailWith(ExpectationDescription + "{0}{reason}, but {1} is too short.", Expected, Subject); - } - - protected override void ValidateAgainstMismatch() - { - bool isMismatch = !Subject.StartsWith(Expected, stringComparison); - - if (isMismatch) - { - int indexOfMismatch = Subject.IndexOfFirstMismatch(Expected, stringComparison); - - Assertion.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 0b2edd0bdf..c98ff29fda 100644 --- a/Src/FluentAssertions/Primitives/StringValidator.cs +++ b/Src/FluentAssertions/Primitives/StringValidator.cs @@ -1,79 +1,37 @@ -using System; +using FluentAssertions.Common; using FluentAssertions.Execution; namespace FluentAssertions.Primitives; -/// -/// Dedicated class for comparing two strings and generating consistent error messages. -/// -internal abstract class StringValidator +internal class StringValidator { - #region Private Definition + private readonly IStringComparisonStrategy comparisonStrategy; + private AssertionChain assertionChain; - private const int HumanReadableLength = 8; - - protected string Subject { get; } - - protected string Expected { get; } - - protected IAssertionScope Assertion { get; set; } - - #endregion - - protected StringValidator(string subject, string expected, string because, object[] becauseArgs) + public StringValidator(AssertionChain assertionChain, IStringComparisonStrategy comparisonStrategy, string because, + object[] becauseArgs) { - Assertion = Execute.Assertion.BecauseOf(because, becauseArgs); - - Subject = subject; - Expected = expected; + this.comparisonStrategy = comparisonStrategy; + this.assertionChain = assertionChain.BecauseOf(because, becauseArgs); } - public void Validate() + public void Validate(string subject, string expected) { - if (Expected is not null || Subject is not null) + if (expected is null && subject is null) { - if (ValidateAgainstNulls()) - { - if (IsLongOrMultiline(Expected) || IsLongOrMultiline(Subject)) - { - Assertion = Assertion.UsingLineBreaks; - } - - if (ValidateAgainstSuperfluousWhitespace() && ValidateAgainstLengthDifferences()) - { - ValidateAgainstMismatch(); - } - } + return; } - } - private bool ValidateAgainstNulls() - { - if (Expected is null != Subject is null) - { - Assertion.FailWith(ExpectationDescription + "{0}{reason}, but found {1}.", Expected, Subject); - return false; - } + comparisonStrategy.AssertNeitherIsNull(assertionChain, subject, expected); - return true; - } - - private static bool IsLongOrMultiline(string value) - { - return value.Length > HumanReadableLength || value.Contains(Environment.NewLine, StringComparison.Ordinal); - } - - protected virtual bool ValidateAgainstSuperfluousWhitespace() - { - return true; - } + if (assertionChain.Succeeded) + { + if (expected.IsLongOrMultiline() || subject.IsLongOrMultiline()) + { + assertionChain = assertionChain.UsingLineBreaks; + } - protected virtual bool ValidateAgainstLengthDifferences() - { - return true; + comparisonStrategy.AssertForEquality(assertionChain, subject, expected); + } } - - protected abstract void ValidateAgainstMismatch(); - - protected abstract string ExpectationDescription { get; } } diff --git a/Src/FluentAssertions/Primitives/StringValidatorSupportingNull.cs b/Src/FluentAssertions/Primitives/StringValidatorSupportingNull.cs new file mode 100644 index 0000000000..cabd1a6e37 --- /dev/null +++ b/Src/FluentAssertions/Primitives/StringValidatorSupportingNull.cs @@ -0,0 +1,29 @@ +using System.Diagnostics.CodeAnalysis; +using FluentAssertions.Common; +using FluentAssertions.Execution; + +namespace FluentAssertions.Primitives; + +internal class StringValidatorSupportingNull +{ + private readonly IStringComparisonStrategy comparisonStrategy; + private AssertionChain assertionChain; + + public StringValidatorSupportingNull(AssertionChain assertionChain, IStringComparisonStrategy comparisonStrategy, + [StringSyntax("CompositeFormat")] string because, object[] becauseArgs) + { + this.comparisonStrategy = comparisonStrategy; + this.assertionChain = assertionChain.BecauseOf(because, becauseArgs); + } + + public void Validate(string subject, string expected) + { + if (expected?.IsLongOrMultiline() == true || + subject?.IsLongOrMultiline() == true) + { + assertionChain = assertionChain.UsingLineBreaks; + } + + comparisonStrategy.AssertForEquality(assertionChain, subject, expected); + } +} diff --git a/Src/FluentAssertions/Primitives/StringWildcardMatchingStrategy.cs b/Src/FluentAssertions/Primitives/StringWildcardMatchingStrategy.cs new file mode 100644 index 0000000000..ce93197ef5 --- /dev/null +++ b/Src/FluentAssertions/Primitives/StringWildcardMatchingStrategy.cs @@ -0,0 +1,142 @@ +using System; +using System.Text; +using System.Text.RegularExpressions; +using FluentAssertions.Common; +using FluentAssertions.Execution; + +namespace FluentAssertions.Primitives; + +internal class StringWildcardMatchingStrategy : IStringComparisonStrategy +{ + public void AssertForEquality(AssertionChain assertionChain, string subject, string expected) + { + bool isMatch = IsMatch(subject, expected); + if (isMatch != Negate) + { + return; + } + + if (IsLongOrMultiline(subject) || IsLongOrMultiline(expected)) + { + expected = RenderAsIndentedBlock(expected); + subject = RenderAsIndentedBlock(subject); + + assertionChain + .FailWith($$""" + {{ExpectationDescription}} + + {0}, + + {reason}but + + {1} + + {{OutcomeDescription}}. + + """, expected.AsNonFormattable(), + subject.AsNonFormattable()); + } + else + { + assertionChain.FailWith($"{ExpectationDescription} {{0}}{{reason}}, but {{1}} {OutcomeDescription}.", expected, + subject); + } + } + + /// + public void AssertNeitherIsNull(AssertionChain assertionChain, string subject, string expected) + { + if (subject is null || expected is null) + { + assertionChain.FailWith($"{ExpectationDescription} {{0}}{{reason}}, but found {{1}}.", expected, subject); + } + } + + private bool IsMatch(string subject, string expected) + { + RegexOptions options = IgnoreCase + ? RegexOptions.IgnoreCase | RegexOptions.CultureInvariant + : RegexOptions.None; + + string input = CleanNewLines(subject); + string pattern = ConvertWildcardToRegEx(CleanNewLines(expected)); + + return Regex.IsMatch(input, pattern, options | RegexOptions.Singleline); + } + + private static string ConvertWildcardToRegEx(string wildcardExpression) + { + return "^" + + Regex.Escape(wildcardExpression) + .Replace("\\*", ".*", StringComparison.Ordinal) + .Replace("\\?", ".", StringComparison.Ordinal) + + "$"; + } + + private static string RenderAsIndentedBlock(string message) + { + string[] lines = message.Split(["\r\n", "\n", "\r"], StringSplitOptions.None); + + return " \"" + string.Join(Environment.NewLine + " ", lines) + "\""; + } + + private static bool IsLongOrMultiline(string message) + { + return message.Length > 80 || message.Contains(Environment.NewLine, StringComparison.Ordinal); + } + + private string CleanNewLines(string input) + { + if (IgnoreAllNewlines) + { + return input.RemoveNewLines(); + } + + if (IgnoreNewlineStyle) + { + return input.RemoveNewlineStyle(); + } + + return input; + } + + private string ExpectationDescription + { + get + { + var builder = new StringBuilder(); + + builder + .Append(Negate ? "Did not expect " : "Expected ") + .Append("{context:string}") + .Append(IgnoreCase ? " to match the equivalent of" : " to match"); + + return builder.ToString(); + } + } + + private string OutcomeDescription + { + get { return Negate ? "matches" : "does not"; } + } + + /// + /// Gets or sets a value indicating whether the subject should not match the pattern. + /// + public bool Negate { get; init; } + + /// + /// Gets or sets a value indicating whether the matching process should ignore any casing difference. + /// + public bool IgnoreCase { get; init; } + + /// + /// Ignores all newline differences + /// + public bool IgnoreAllNewlines { get; init; } + + /// + /// Ignores the difference between environment newline differences + /// + public bool IgnoreNewlineStyle { get; init; } +} diff --git a/Src/FluentAssertions/Primitives/StringWildcardMatchingValidator.cs b/Src/FluentAssertions/Primitives/StringWildcardMatchingValidator.cs deleted file mode 100644 index 7a8ce1e7b9..0000000000 --- a/Src/FluentAssertions/Primitives/StringWildcardMatchingValidator.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using System.Text; -using System.Text.RegularExpressions; -using FluentAssertions.Common; - -namespace FluentAssertions.Primitives; - -internal class StringWildcardMatchingValidator : StringValidator -{ - public StringWildcardMatchingValidator(string subject, string expected, string because, object[] becauseArgs) - : base(subject, expected, because, becauseArgs) - { - } - - protected override void ValidateAgainstMismatch() - { - bool isMatch = IsMatch(); - - if (!isMatch && !Negate) - { - Assertion.FailWith(ExpectationDescription + "but {1} does not.", Expected, Subject); - } - - if (isMatch && Negate) - { - Assertion.FailWith(ExpectationDescription + "but {1} matches.", Expected, Subject); - } - } - - private bool IsMatch() - { - RegexOptions options = IgnoreCase - ? RegexOptions.IgnoreCase | RegexOptions.CultureInvariant - : RegexOptions.None; - - string input = CleanNewLines(Subject); - string pattern = ConvertWildcardToRegEx(CleanNewLines(Expected)); - - return Regex.IsMatch(input, pattern, options | RegexOptions.Singleline); - } - - private static string ConvertWildcardToRegEx(string wildcardExpression) - { - return "^" - + Regex.Escape(wildcardExpression) - .Replace("\\*", ".*", StringComparison.Ordinal) - .Replace("\\?", ".", StringComparison.Ordinal) - + "$"; - } - - private string CleanNewLines(string input) - { - return IgnoreNewLineDifferences ? input.RemoveNewLines() : input; - } - - protected override string ExpectationDescription - { - get - { - var builder = new StringBuilder(); - builder - .Append(Negate ? "Did not expect " : "Expected ") - .Append("{context:string}") - .Append(IgnoreCase ? " to match the equivalent of" : " to match") - .Append(" {0}{reason}, "); - - return builder.ToString(); - } - } - - /// - /// Gets or sets a value indicating whether the subject should not match the pattern. - /// - public bool Negate { get; init; } - - /// - /// Gets or sets a value indicating whether the matching process should ignore any casing difference. - /// - public bool IgnoreCase { get; init; } - - /// - /// Ignores the difference between environment newline differences - /// - public bool IgnoreNewLineDifferences { get; init; } -} diff --git a/Src/FluentAssertions/Primitives/TimeOnlyAssertions.cs b/Src/FluentAssertions/Primitives/TimeOnlyAssertions.cs index 70c48e5383..ed92a9c0cb 100644 --- a/Src/FluentAssertions/Primitives/TimeOnlyAssertions.cs +++ b/Src/FluentAssertions/Primitives/TimeOnlyAssertions.cs @@ -1,11 +1,13 @@ -using System; +#if NET6_0_OR_GREATER + +using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using FluentAssertions.Common; using FluentAssertions.Execution; -#if NET6_0_OR_GREATER namespace FluentAssertions.Primitives; /// @@ -14,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) { } } @@ -29,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; } @@ -50,9 +55,10 @@ public TimeOnlyAssertions(TimeOnly? value) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint Be(TimeOnly expected, string because = "", params object[] becauseArgs) + 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}.", @@ -72,9 +78,10 @@ public AndConstraint Be(TimeOnly expected, string because = "", par /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint Be(TimeOnly? expected, string because = "", params object[] becauseArgs) + 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}.", @@ -94,10 +101,10 @@ public AndConstraint Be(TimeOnly? expected, string because = "", pa /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBe(TimeOnly unexpected, string because = "", - params object[] becauseArgs) + 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); @@ -116,10 +123,10 @@ public AndConstraint NotBe(TimeOnly unexpected, string because = "" /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBe(TimeOnly? unexpected, string because = "", - params object[] becauseArgs) + 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); @@ -145,8 +152,8 @@ public AndConstraint NotBe(TimeOnly? unexpected, string because = " /// Zero or more objects to format using the placeholders in . /// /// is negative. - public AndConstraint BeCloseTo(TimeOnly nearbyTime, TimeSpan precision, string because = "", - params object[] becauseArgs) + public AndConstraint BeCloseTo(TimeOnly nearbyTime, TimeSpan precision, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNegative(precision); @@ -154,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); } @@ -194,21 +200,20 @@ public AndConstraint BeCloseTo(TimeOnly nearbyTime, TimeSpan precis /// Zero or more objects to format using the placeholders in . /// /// is negative. - public AndConstraint NotBeCloseTo(TimeOnly distantTime, TimeSpan precision, string because = "", - params object[] becauseArgs) + public AndConstraint NotBeCloseTo(TimeOnly distantTime, TimeSpan precision, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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); } @@ -224,10 +229,10 @@ public AndConstraint NotBeCloseTo(TimeOnly distantTime, TimeSpan pr /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeBefore(TimeOnly expected, string because = "", - params object[] becauseArgs) + 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, @@ -247,8 +252,8 @@ public AndConstraint BeBefore(TimeOnly expected, string because = " /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeBefore(TimeOnly unexpected, string because = "", - params object[] becauseArgs) + public AndConstraint NotBeBefore(TimeOnly unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return BeOnOrAfter(unexpected, because, becauseArgs); } @@ -264,10 +269,10 @@ public AndConstraint NotBeBefore(TimeOnly unexpected, string becaus /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeOnOrBefore(TimeOnly expected, string because = "", - params object[] becauseArgs) + 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, @@ -287,8 +292,8 @@ public AndConstraint BeOnOrBefore(TimeOnly expected, string because /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeOnOrBefore(TimeOnly unexpected, string because = "", - params object[] becauseArgs) + public AndConstraint NotBeOnOrBefore(TimeOnly unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return BeAfter(unexpected, because, becauseArgs); } @@ -304,10 +309,10 @@ public AndConstraint NotBeOnOrBefore(TimeOnly unexpected, string be /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeAfter(TimeOnly expected, string because = "", - params object[] becauseArgs) + 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, @@ -327,8 +332,8 @@ public AndConstraint BeAfter(TimeOnly expected, string because = "" /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeAfter(TimeOnly unexpected, string because = "", - params object[] becauseArgs) + public AndConstraint NotBeAfter(TimeOnly unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return BeOnOrBefore(unexpected, because, becauseArgs); } @@ -344,10 +349,10 @@ public AndConstraint NotBeAfter(TimeOnly unexpected, string because /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeOnOrAfter(TimeOnly expected, string because = "", - params object[] becauseArgs) + 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, @@ -367,8 +372,8 @@ public AndConstraint BeOnOrAfter(TimeOnly expected, string because /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeOnOrAfter(TimeOnly unexpected, string because = "", - params object[] becauseArgs) + public AndConstraint NotBeOnOrAfter(TimeOnly unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return BeBefore(unexpected, because, becauseArgs); } @@ -384,18 +389,17 @@ public AndConstraint NotBeOnOrAfter(TimeOnly unexpected, string bec /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveHours(int expected, string because = "", params object[] becauseArgs) + 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); } @@ -411,9 +415,10 @@ public AndConstraint HaveHours(int expected, string because = "", p /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveHours(int unexpected, string because = "", params object[] becauseArgs) + 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.", @@ -437,18 +442,17 @@ public AndConstraint NotHaveHours(int unexpected, string because = /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveMinutes(int expected, string because = "", params object[] becauseArgs) + 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); } @@ -464,18 +468,17 @@ public AndConstraint HaveMinutes(int expected, string because = "", /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveMinutes(int unexpected, string because = "", params object[] becauseArgs) + 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); } @@ -491,18 +494,17 @@ public AndConstraint NotHaveMinutes(int unexpected, string because /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveSeconds(int expected, string because = "", params object[] becauseArgs) + 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); } @@ -518,18 +520,17 @@ public AndConstraint HaveSeconds(int expected, string because = "", /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveSeconds(int unexpected, string because = "", params object[] becauseArgs) + 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); } @@ -545,18 +546,17 @@ public AndConstraint NotHaveSeconds(int unexpected, string because /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveMilliseconds(int expected, string because = "", params object[] becauseArgs) + 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); } @@ -572,18 +572,18 @@ public AndConstraint HaveMilliseconds(int expected, string because /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveMilliseconds(int unexpected, string because = "", params object[] becauseArgs) + 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); } @@ -623,7 +623,8 @@ public AndConstraint BeOneOf(params TimeOnly[] validValues) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeOneOf(IEnumerable validValues, string because = "", params object[] becauseArgs) + public AndConstraint BeOneOf(IEnumerable validValues, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return BeOneOf(validValues.Cast(), because, becauseArgs); } @@ -641,10 +642,10 @@ public AndConstraint BeOneOf(IEnumerable validValues, str /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeOneOf(IEnumerable validValues, string because = "", - params object[] becauseArgs) + 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 5dec1d2d52..94be29f8c4 100644 --- a/Src/FluentAssertions/Specialized/ActionAssertions.cs +++ b/Src/FluentAssertions/Specialized/ActionAssertions.cs @@ -1,6 +1,8 @@ using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using FluentAssertions.Common; +using FluentAssertions.Execution; namespace FluentAssertions.Specialized; @@ -10,14 +12,109 @@ namespace FluentAssertions.Specialized; [DebuggerNonUserCode] public class ActionAssertions : DelegateAssertions { - public ActionAssertions(Action subject, IExtractExceptions extractor) - : this(subject, extractor, new Clock()) + 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, AssertionChain assertionChain, IClock clock) + : base(subject, extractor, assertionChain, clock) { + this.assertionChain = assertionChain; } - public ActionAssertions(Action subject, IExtractExceptions extractor, IClock clock) - : base(subject, extractor, clock) + /// + /// Asserts that the current does not throw any exception. + /// + /// + /// 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 AndConstraint NotThrow([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { + assertionChain + .ForCondition(Subject is not null) + .BecauseOf(because, becauseArgs) + .FailWith("Expected {context} not to throw{reason}, but found ."); + + if (assertionChain.Succeeded) + { + FailIfSubjectIsAsyncVoid(); + Exception exception = InvokeSubjectWithInterception(); + return NotThrowInternal(exception, because, becauseArgs); + } + + return new AndConstraint(this); + } + + /// + /// Asserts that the current stops throwing any exception + /// after a specified amount of time. + /// + /// + /// The delegate is invoked. If it raises an exception, + /// the invocation is repeated until it either stops raising any exceptions + /// or the specified wait time is exceeded. + /// + /// + /// The time after which the delegate should have stopped throwing any exception. + /// + /// + /// The time between subsequent invocations of the delegate. + /// + /// + /// 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 . + /// + /// or are negative. + public AndConstraint NotThrowAfter(TimeSpan waitTime, TimeSpan pollInterval, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNegative(waitTime); + Guard.ThrowIfArgumentIsNegative(pollInterval); + + assertionChain + .ForCondition(Subject is not null) + .BecauseOf(because, becauseArgs) + .FailWith("Expected {context} not to throw after {0}{reason}, but found .", waitTime); + + if (assertionChain.Succeeded) + { + FailIfSubjectIsAsyncVoid(); + + TimeSpan? invocationEndTime = null; + Exception exception = null; + ITimer timer = Clock.StartTimer(); + + while (invocationEndTime is null || invocationEndTime < waitTime) + { + exception = InvokeSubjectWithInterception(); + + if (exception is null) + { + break; + } + + Clock.Delay(pollInterval); + invocationEndTime = timer.Elapsed; + } + + assertionChain + .BecauseOf(because, becauseArgs) + .ForCondition(exception is null) + .FailWith("Did not expect any exceptions after {0}{reason}, but found {1}.", waitTime, exception); + } + + return new AndConstraint(this); } protected override void InvokeSubject() diff --git a/Src/FluentAssertions/Specialized/AsyncFunctionAssertions.cs b/Src/FluentAssertions/Specialized/AsyncFunctionAssertions.cs index 2a80ef257e..a775860198 100644 --- a/Src/FluentAssertions/Specialized/AsyncFunctionAssertions.cs +++ b/Src/FluentAssertions/Specialized/AsyncFunctionAssertions.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -11,67 +12,24 @@ namespace FluentAssertions.Specialized; /// /// Contains a number of methods to assert that an asynchronous method yields the expected result. /// +/// The type of to be handled. +/// The type of assertion to be returned. [DebuggerNonUserCode] public class AsyncFunctionAssertions : DelegateAssertionsBase, TAssertions> where TTask : Task where TAssertions : AsyncFunctionAssertions { - [Obsolete("This class is intended as base class. This ctor is accidentally public and will be removed in Version 7.")] - public AsyncFunctionAssertions(Func subject, IExtractExceptions extractor) - : this(subject, extractor, new Clock()) - { - } + private readonly AssertionChain assertionChain; - [Obsolete("This class is intended as base class. This ctor is accidentally public and will be made protected in Version 7.")] - public AsyncFunctionAssertions(Func subject, IExtractExceptions extractor, IClock clock) - : base(subject, extractor, clock) + protected AsyncFunctionAssertions(Func subject, IExtractExceptions extractor, AssertionChain assertionChain, + IClock clock) + : base(subject, extractor, assertionChain, clock) { + this.assertionChain = assertionChain; } protected override string Identifier => "async function"; - /// - /// Asserts that the current will complete within the specified time. - /// - /// The allowed time span for the operation. - /// - /// 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 async Task> CompleteWithinAsync( - TimeSpan timeSpan, string because = "", params object[] becauseArgs) - { - bool success = Execute.Assertion - .ForCondition(Subject is not null) - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context:task} to complete within {0}{reason}, but found .", timeSpan); - - if (success) - { - (TTask task, TimeSpan remainingTime) = InvokeWithTimer(timeSpan); - - success = Execute.Assertion - .ForCondition(remainingTime >= TimeSpan.Zero) - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context:task} to complete within {0}{reason}.", timeSpan); - - if (success) - { - bool completesWithinTimeout = await CompletesWithinTimeoutAsync(task, remainingTime); - - Execute.Assertion - .ForCondition(completesWithinTimeout) - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context:task} to complete within {0}{reason}.", timeSpan); - } - } - - return new AndConstraint((TAssertions)this); - } - /// /// Asserts that the current will not complete within the specified time. /// @@ -83,23 +41,23 @@ public async Task> CompleteWithinAsync( /// /// Zero or more objects to format using the placeholders in . /// - public async Task> NotCompleteWithinAsync( - TimeSpan timeSpan, string because = "", params object[] becauseArgs) + 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); if (remainingTime >= TimeSpan.Zero) { - bool completesWithinTimeout = await CompletesWithinTimeoutAsync(task, remainingTime); + bool completesWithinTimeout = await CompletesWithinTimeoutAsync(task, remainingTime, _ => Task.CompletedTask); - Execute.Assertion + assertionChain .ForCondition(!completesWithinTimeout) .BecauseOf(because, becauseArgs) .FailWith("Did not expect {context:task} to complete within {0}{reason}.", timeSpan); @@ -123,35 +81,51 @@ public async Task> NotCompleteWithinAsync( /// /// Returns an object that allows asserting additional members of the thrown exception. /// - public async Task> ThrowExactlyAsync(string because = "", - params object[] becauseArgs) + public async Task> ThrowExactlyAsync( + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TException : Exception { 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(new[] { exception as TException }); + return new ExceptionAssertions([exception as TException], assertionChain); } - return new ExceptionAssertions(Array.Empty()); + return new ExceptionAssertions([], assertionChain); + } + + /// + /// Asserts that the current throws any exception. + /// + /// + /// (Optional) 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 async Task> ThrowAsync( + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + return await ThrowAsync(because, becauseArgs); } /// @@ -165,22 +139,42 @@ public async Task> ThrowExactlyAsync /// /// Zero or more objects to format using the placeholders in . /// - public async Task> ThrowAsync(string because = "", - params object[] becauseArgs) + 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(Array.Empty()); + return new ExceptionAssertions([], assertionChain); + } + + /// + /// Asserts that the current throws any exception within a specific timeout. + /// + /// + /// The allowed time span for the operation. + /// + /// + /// (Optional) + /// 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 async Task> ThrowWithinAsync(TimeSpan timeSpan, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + return await ThrowWithinAsync(timeSpan, because, becauseArgs); } /// @@ -196,46 +190,44 @@ public async Task> ThrowAsync(string /// /// Zero or more objects to format using the placeholders in . /// - public async Task> ThrowWithinAsync( - TimeSpan timeSpan, string because = "", params object[] becauseArgs) + public async Task> ThrowWithinAsync(TimeSpan timeSpan, + [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(Array.Empty()); + return new ExceptionAssertions([], assertionChain); } private ExceptionAssertions AssertThrows( - Exception exception, TimeSpan timeSpan, string because, object[] becauseArgs) + Exception exception, TimeSpan timeSpan, + [StringSyntax("CompositeFormat")] string because, object[] becauseArgs) where TException : Exception { 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) @@ -255,6 +247,7 @@ private async Task InvokeWithInterceptionAsync(TimeSpan timeout) : default) { (TTask task, TimeSpan remainingTime) = InvokeWithTimer(timeout); + if (remainingTime < TimeSpan.Zero) { // timeout reached without exception @@ -271,7 +264,7 @@ private async Task InvokeWithInterceptionAsync(TimeSpan timeout) // Here we do not need to know whether the task completes (successfully) in timeout // or does not complete. We are only interested in the exception which is thrown, not returned. // So, we can ignore the result. - _ = await CompletesWithinTimeoutAsync(task, remainingTime); + _ = await CompletesWithinTimeoutAsync(task, remainingTime, cancelledTask => cancelledTask); } return null; @@ -282,38 +275,6 @@ private async Task InvokeWithInterceptionAsync(TimeSpan timeout) } } - /// - /// Asserts that the current does not throw any exception. - /// - /// - /// 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 async Task> NotThrowAsync(string because = "", params object[] becauseArgs) - { - bool success = Execute.Assertion - .ForCondition(Subject is not null) - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context} not to throw{reason}, but found ."); - - if (success) - { - try - { - await Subject.Invoke(); - } - catch (Exception exception) - { - return NotThrowInternal(exception, because, becauseArgs); - } - } - - return new AndConstraint((TAssertions)this); - } - /// /// Asserts that the current does not throw an exception of type . /// @@ -325,19 +286,20 @@ public async Task> NotThrowAsync(string because = "", /// /// Zero or more objects to format using the placeholders in . /// - public async Task> NotThrowAsync(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 { - await Subject.Invoke(); + await Subject!.Invoke(); } catch (Exception exception) { @@ -348,78 +310,10 @@ public async Task> NotThrowAsync(string b return new AndConstraint((TAssertions)this); } - /// - /// Asserts that the current stops throwing any exception - /// after a specified amount of time. - /// - /// - /// The is invoked. If it raises an exception, - /// the invocation is repeated until it either stops raising any exceptions - /// or the specified wait time is exceeded. - /// - /// - /// The time after which the should have stopped throwing any exception. - /// - /// - /// The time between subsequent invocations of the . - /// - /// - /// 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 . - /// - /// or are negative. - public Task> NotThrowAfterAsync(TimeSpan waitTime, TimeSpan pollInterval, string because = "", - params object[] becauseArgs) - { - Guard.ThrowIfArgumentIsNegative(waitTime); - Guard.ThrowIfArgumentIsNegative(pollInterval); - - bool success = Execute.Assertion - .ForCondition(Subject is not null) - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context} not to throw any exceptions after {0}{reason}, but found .", waitTime); - - if (success) - { - return AssertionTaskAsync(); - - async Task> AssertionTaskAsync() - { - TimeSpan? invocationEndTime = null; - Exception exception = null; - ITimer timer = Clock.StartTimer(); - - while (invocationEndTime is null || invocationEndTime < waitTime) - { - exception = await InvokeWithInterceptionAsync(Subject); - - if (exception is null) - { - return new AndConstraint((TAssertions)this); - } - - await Clock.DelayAsync(pollInterval, CancellationToken.None); - invocationEndTime = timer.Elapsed; - } - - Execute.Assertion - .BecauseOf(because, becauseArgs) - .FailWith("Did not expect any exceptions after {0}{reason}, but found {1}.", waitTime, exception); - - return new AndConstraint((TAssertions)this); - } - } - - return Task.FromResult(new AndConstraint((TAssertions)this)); - } - /// /// Invokes the subject and measures the sync execution time. /// - private protected (TTask result, TimeSpan remainingTime) InvokeWithTimer(TimeSpan timeSpan) + private protected (TTask Result, TimeSpan RemainingTime) InvokeWithTimer(TimeSpan timeSpan) { ITimer timer = Clock.StartTimer(); TTask result = Subject.Invoke(); @@ -431,7 +325,7 @@ private protected (TTask result, TimeSpan remainingTime) InvokeWithTimer(TimeSpa /// /// Monitors the specified task whether it completes withing the remaining time span. /// - private protected async Task CompletesWithinTimeoutAsync(Task target, TimeSpan remainingTime) + private protected async Task CompletesWithinTimeoutAsync(Task target, TimeSpan remainingTime, Func onTaskCanceled) { using var delayCancellationTokenSource = new CancellationTokenSource(); @@ -452,8 +346,7 @@ private protected async Task CompletesWithinTimeoutAsync(Task target, Time if (target.IsCanceled) { - // Rethrow the exception causing the task be canceled. - await target; + await onTaskCanceled(target); } // The monitored task is completed, we shall cancel the clock. @@ -461,7 +354,7 @@ private protected async Task CompletesWithinTimeoutAsync(Task target, Time return true; } - private static async Task InvokeWithInterceptionAsync(Func action) + private protected static async Task InvokeWithInterceptionAsync(Func action) { try { diff --git a/Src/FluentAssertions/Specialized/DelegateAssertions.cs b/Src/FluentAssertions/Specialized/DelegateAssertions.cs index b267bc91c0..b0586629b8 100644 --- a/Src/FluentAssertions/Specialized/DelegateAssertions.cs +++ b/Src/FluentAssertions/Specialized/DelegateAssertions.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Runtime.CompilerServices; using FluentAssertions.Common; @@ -15,46 +16,38 @@ 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; } /// - /// Asserts that the current throws an exception of type . + /// Asserts that the current throws any exception. /// /// - /// 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. + /// (Optional) + /// 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 ExceptionAssertions Throw(string because = "", params object[] becauseArgs) - where TException : Exception + public ExceptionAssertions Throw([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion - .ForCondition(Subject is not null) - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context} to throw {0}{reason}, but found .", typeof(TException)); - - if (success) - { - FailIfSubjectIsAsyncVoid(); - Exception exception = InvokeSubjectWithInterception(); - return ThrowInternal(exception, because, becauseArgs); - } - - return new ExceptionAssertions(Array.Empty()); + return Throw(because, becauseArgs); } /// - /// Asserts that the current does not throw an exception of type . + /// Asserts that the current throws an exception of type . /// /// /// A formatted phrase as is supported by explaining why the assertion @@ -63,26 +56,26 @@ public ExceptionAssertions Throw(string because = "", pa /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotThrow(string because = "", params object[] becauseArgs) + 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} not to throw {0}{reason}, but found .", typeof(TException)); + .FailWith("Expected {context} to throw {0}{reason}, but found .", typeof(TException)); - if (success) + if (assertionChain.Succeeded) { FailIfSubjectIsAsyncVoid(); Exception exception = InvokeSubjectWithInterception(); - return NotThrowInternal(exception, because, becauseArgs); + return ThrowInternal(exception, because, becauseArgs); } - return new AndConstraint((TAssertions)this); + return new ExceptionAssertions([], assertionChain); } /// - /// Asserts that the current does not throw any exception. + /// Asserts that the current does not throw an exception of type . /// /// /// A formatted phrase as is supported by explaining why the assertion @@ -91,18 +84,19 @@ public AndConstraint NotThrow(string because = "", para /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotThrow(string because = "", params object[] becauseArgs) + 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{reason}, but found ."); + .FailWith("Expected {context} not to throw {0}{reason}, but found .", typeof(TException)); - if (success) + if (assertionChain.Succeeded) { FailIfSubjectIsAsyncVoid(); Exception exception = InvokeSubjectWithInterception(); - return NotThrowInternal(exception, because, becauseArgs); + return NotThrowInternal(exception, because, becauseArgs); } return new AndConstraint((TAssertions)this); @@ -124,105 +118,41 @@ public AndConstraint NotThrow(string because = "", params object[] /// /// Returns an object that allows asserting additional members of the thrown exception. /// - public ExceptionAssertions ThrowExactly(string because = "", + public ExceptionAssertions ThrowExactly([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 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(new[] { exception as TException }); - } - - return new ExceptionAssertions(Array.Empty()); - } - - /// - /// Asserts that the current stops throwing any exception - /// after a specified amount of time. - /// - /// - /// The delegate is invoked. If it raises an exception, - /// the invocation is repeated until it either stops raising any exceptions - /// or the specified wait time is exceeded. - /// - /// - /// The time after which the delegate should have stopped throwing any exception. - /// - /// - /// The time between subsequent invocations of the delegate. - /// - /// - /// 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 . - /// - /// or are negative. - public AndConstraint NotThrowAfter(TimeSpan waitTime, TimeSpan pollInterval, string because = "", - params object[] becauseArgs) - { - Guard.ThrowIfArgumentIsNegative(waitTime); - Guard.ThrowIfArgumentIsNegative(pollInterval); - - bool success = Execute.Assertion - .ForCondition(Subject is not null) - .BecauseOf(because, becauseArgs) - .FailWith("Expected {context} not to throw after {0}{reason}, but found .", waitTime); - - if (success) - { - FailIfSubjectIsAsyncVoid(); - - TimeSpan? invocationEndTime = null; - Exception exception = null; - ITimer timer = Clock.StartTimer(); - - while (invocationEndTime is null || invocationEndTime < waitTime) - { - exception = InvokeSubjectWithInterception(); - - if (exception is null) - { - break; - } - - Clock.Delay(pollInterval); - invocationEndTime = timer.Elapsed; - } - - Execute.Assertion - .BecauseOf(because, becauseArgs) - .ForCondition(exception is null) - .FailWith("Did not expect any exceptions after {0}{reason}, but found {1}.", waitTime, exception); + return new ExceptionAssertions([exception as TException], assertionChain); } - return new AndConstraint((TAssertions)this); + return new ExceptionAssertions([], assertionChain); } protected abstract void InvokeSubject(); - private Exception InvokeSubjectWithInterception() + private protected Exception InvokeSubjectWithInterception() { Exception actualException = null; @@ -251,7 +181,7 @@ private Exception InvokeSubjectWithInterception() return actualException; } - private void FailIfSubjectIsAsyncVoid() + private protected void FailIfSubjectIsAsyncVoid() { if (Subject.GetMethodInfo().IsDecoratedWithOrInherit()) { diff --git a/Src/FluentAssertions/Specialized/DelegateAssertionsBase.cs b/Src/FluentAssertions/Specialized/DelegateAssertionsBase.cs index 878a82ed7b..fe662c0424 100644 --- a/Src/FluentAssertions/Specialized/DelegateAssertionsBase.cs +++ b/Src/FluentAssertions/Specialized/DelegateAssertionsBase.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using FluentAssertions.Common; using FluentAssertions.Execution; @@ -17,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)); } @@ -29,30 +34,30 @@ private protected DelegateAssertionsBase(TDelegate @delegate, IExtractExceptions private protected IClock Clock { get; } protected ExceptionAssertions ThrowInternal( - Exception exception, string because, object[] becauseArgs) + Exception exception, + [StringSyntax("CompositeFormat")] string because, object[] becauseArgs) where TException : Exception { 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, 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); @@ -60,12 +65,13 @@ protected AndConstraint NotThrowInternal(Exception exception, strin return new AndConstraint((TAssertions)this); } - protected AndConstraint NotThrowInternal(Exception exception, 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 90c0957002..b611fa08d5 100644 --- a/Src/FluentAssertions/Specialized/ExceptionAssertions.cs +++ b/Src/FluentAssertions/Specialized/ExceptionAssertions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; using FluentAssertions.Common; @@ -18,9 +19,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; } /// @@ -71,21 +75,49 @@ public ExceptionAssertions(IEnumerable exceptions) /// /// /// - public virtual ExceptionAssertions WithMessage(string expectedWildcardPattern, string because = "", - params object[] becauseArgs) + public virtual ExceptionAssertions WithMessage(string expectedWildcardPattern, + [StringSyntax("CompositeFormat")] 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, - becauseArgs); + AssertExceptionMessage(expectedWildcardPattern, because, becauseArgs); return this; } + private void AssertExceptionMessage(string expectedWildcardPattern, string because, object[] becauseArgs) + { + var results = new AssertionResultSet(); + + foreach (string message in Subject.Select(exc => exc.Message)) + { + using (var scope = new AssertionScope()) + { + var chain = AssertionChain.GetOrCreate(); + chain.OverrideCallerIdentifier(() => "exception message"); + chain.ReuseOnce(); + + message.Should().MatchEquivalentOf(expectedWildcardPattern, because, becauseArgs); + + results.AddSet(message, scope.Discard()); + } + + if (results.ContainsSuccessfulSet()) + { + break; + } + } + + foreach (string failure in results.GetTheFailuresForTheSetWithTheFewestFailures()) + { + assertionChain.FailWith("{0}", failure.AsNonFormattable()); + } + } + /// /// Asserts that the thrown exception contains an inner exception of type . /// @@ -97,12 +129,13 @@ public virtual ExceptionAssertions WithMessage(string expectedWildca /// /// Zero or more objects to format using the placeholders in . /// - public virtual ExceptionAssertions WithInnerException(string because = "", + public virtual ExceptionAssertions WithInnerException( + [StringSyntax("CompositeFormat")] 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); } /// @@ -116,12 +149,13 @@ public virtual ExceptionAssertions WithInnerException /// Zero or more objects to format using the placeholders in . /// - public ExceptionAssertions WithInnerException(Type innerException, string because = "", + public ExceptionAssertions WithInnerException(Type innerException, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(innerException); - return new ExceptionAssertions(AssertInnerExceptions(innerException, because, becauseArgs)); + return new ExceptionAssertions(AssertInnerExceptions(innerException, because, becauseArgs), assertionChain); } /// @@ -135,12 +169,13 @@ public ExceptionAssertions WithInnerException(Type innerException, st /// /// Zero or more objects to format using the placeholders in . /// - public virtual ExceptionAssertions WithInnerExceptionExactly(string because = "", + public virtual ExceptionAssertions WithInnerExceptionExactly( + [StringSyntax("CompositeFormat")] 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); } /// @@ -154,12 +189,14 @@ public virtual ExceptionAssertions WithInnerExceptionExactly /// Zero or more objects to format using the placeholders in . /// - public ExceptionAssertions WithInnerExceptionExactly(Type innerException, string because = "", + public ExceptionAssertions WithInnerExceptionExactly(Type innerException, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(innerException); - return new ExceptionAssertions(AssertInnerExceptionExactly(innerException, because, becauseArgs)); + return new ExceptionAssertions(AssertInnerExceptionExactly(innerException, because, becauseArgs), + assertionChain); } /// @@ -177,26 +214,27 @@ public ExceptionAssertions WithInnerExceptionExactly(Type innerExcept /// /// is . public ExceptionAssertions Where(Expression> exceptionExpression, - string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] 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:" - + Environment.NewLine + Environment.NewLine + "{1}.", + + Environment.NewLine + Environment.NewLine + "{1}.", exceptionExpression, Subject); return this; } - private IEnumerable AssertInnerExceptionExactly(Type innerException, string because = "", + private IEnumerable AssertInnerExceptionExactly(Type innerException, + [StringSyntax("CompositeFormat")] 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); @@ -205,7 +243,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); @@ -213,10 +251,11 @@ private IEnumerable AssertInnerExceptionExactly(Type innerException, return expectedExceptions; } - private IEnumerable AssertInnerExceptions(Type innerException, string because = "", + private IEnumerable AssertInnerExceptions(Type innerException, + [StringSyntax("CompositeFormat")] 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); @@ -226,7 +265,7 @@ private IEnumerable AssertInnerExceptions(Type innerException, string .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); @@ -242,7 +281,7 @@ private TException SingleSubject { string thrownExceptions = BuildExceptionsString(Subject); - Services.ThrowException( + AssertionEngine.TestFramework.Throw( $"More than one exception was thrown. FluentAssertions cannot determine which Exception was meant.{Environment.NewLine}{thrownExceptions}"); } @@ -252,45 +291,6 @@ private TException SingleSubject private static string BuildExceptionsString(IEnumerable exceptions) { - return string.Join(Environment.NewLine, - exceptions.Select( - exception => - "\t" + Formatter.ToString(exception))); - } - - private static class ExceptionMessageAssertion - { - private const string Context = "exception message"; - - public static void Execute(IEnumerable messages, string expectation, string because, params object[] becauseArgs) - { - using var _ = new AssertionScope(); - var results = new AssertionResultSet(); - - foreach (string message in messages) - { - using (var scope = new AssertionScope()) - { - scope.Context = new Lazy(() => Context); - - message.Should().MatchEquivalentOf(expectation, because, becauseArgs); - - results.AddSet(message, scope.Discard()); - } - - if (results.ContainsSuccessfulSet()) - { - break; - } - } - - foreach (string failure in results.SelectClosestMatchFor()) - { - string replacedCurlyBraces = - failure.EscapePlaceholders(); - - AssertionScope.Current.FailWith(replacedCurlyBraces); - } - } + return string.Join(Environment.NewLine, exceptions.Select(exception => "\t" + Formatter.ToString(exception))); } } diff --git a/Src/FluentAssertions/Specialized/ExecutionTimeAssertions.cs b/Src/FluentAssertions/Specialized/ExecutionTimeAssertions.cs index 16e5b24970..b87cd19fc3 100644 --- a/Src/FluentAssertions/Specialized/ExecutionTimeAssertions.cs +++ b/Src/FluentAssertions/Specialized/ExecutionTimeAssertions.cs @@ -1,5 +1,5 @@ using System; -using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using FluentAssertions.Common; using FluentAssertions.Execution; @@ -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; } /// @@ -31,7 +33,7 @@ public ExecutionTimeAssertions(ExecutionTime executionTime) /// Polling stops when condition returns the expected result. /// The rate at which the condition is re-checked. /// The elapsed time. (use this, don't measure twice) - private (bool isRunning, TimeSpan elapsed) PollUntil(Func condition, bool expectedResult, TimeSpan rate) + private (bool IsRunning, TimeSpan Elapsed) PollUntil(Func condition, bool expectedResult, TimeSpan rate) { TimeSpan elapsed = execution.ElapsedTime; bool isRunning = execution.IsRunning; @@ -69,12 +71,12 @@ public ExecutionTimeAssertions(ExecutionTime executionTime) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeLessThanOrEqualTo(TimeSpan maxDuration, string because = "", - params object[] becauseArgs) + public AndConstraint BeLessThanOrEqualTo(TimeSpan maxDuration, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { (bool isRunning, TimeSpan elapsed) = PollUntil(duration => duration <= maxDuration, expectedResult: false, rate: maxDuration); - Execute.Assertion + assertionChain .ForCondition(elapsed <= maxDuration) .BecauseOf(because, becauseArgs) .FailWith("Execution of " + @@ -87,10 +89,6 @@ public AndConstraint BeLessThanOrEqualTo(TimeSpan maxDu return new AndConstraint(this); } - [EditorBrowsable(EditorBrowsableState.Never)] - public AndConstraint BeLessOrEqualTo(TimeSpan maxDuration, string because = "", - params object[] becauseArgs) => BeLessThanOrEqualTo(maxDuration, because, becauseArgs); - /// /// Asserts that the execution time of the operation is less than a specified amount of time. /// @@ -104,12 +102,12 @@ public AndConstraint BeLessOrEqualTo(TimeSpan maxDurati /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeLessThan(TimeSpan maxDuration, string because = "", - params object[] becauseArgs) + public AndConstraint BeLessThan(TimeSpan maxDuration, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { (bool isRunning, TimeSpan elapsed) = PollUntil(duration => duration < maxDuration, expectedResult: false, rate: maxDuration); - Execute.Assertion + assertionChain .ForCondition(elapsed < maxDuration) .BecauseOf(because, becauseArgs) .FailWith("Execution of " + @@ -134,12 +132,12 @@ public AndConstraint BeLessThan(TimeSpan maxDuration, s /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeGreaterThanOrEqualTo(TimeSpan minDuration, string because = "", - params object[] becauseArgs) + public AndConstraint BeGreaterThanOrEqualTo(TimeSpan minDuration, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { (bool isRunning, TimeSpan elapsed) = PollUntil(duration => duration >= minDuration, expectedResult: true, rate: minDuration); - Execute.Assertion + assertionChain .ForCondition(elapsed >= minDuration) .BecauseOf(because, becauseArgs) .FailWith("Execution of " + @@ -152,10 +150,6 @@ public AndConstraint BeGreaterThanOrEqualTo(TimeSpan mi return new AndConstraint(this); } - [EditorBrowsable(EditorBrowsableState.Never)] - public AndConstraint BeGreaterOrEqualTo(TimeSpan minDuration, string because = "", - params object[] becauseArgs) => BeGreaterThanOrEqualTo(minDuration, because, becauseArgs); - /// /// Asserts that the execution time of the operation is greater than a specified amount of time. /// @@ -169,12 +163,12 @@ public AndConstraint BeGreaterOrEqualTo(TimeSpan minDur /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeGreaterThan(TimeSpan minDuration, string because = "", - params object[] becauseArgs) + public AndConstraint BeGreaterThan(TimeSpan minDuration, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { (bool isRunning, TimeSpan elapsed) = PollUntil(duration => duration > minDuration, expectedResult: true, rate: minDuration); - Execute.Assertion + assertionChain .ForCondition(elapsed > minDuration) .BecauseOf(because, becauseArgs) .FailWith("Execution of " + @@ -204,8 +198,8 @@ public AndConstraint BeGreaterThan(TimeSpan minDuration /// Zero or more objects to format using the placeholders in . /// /// is negative. - public AndConstraint BeCloseTo(TimeSpan expectedDuration, TimeSpan precision, string because = "", - params object[] becauseArgs) + public AndConstraint BeCloseTo(TimeSpan expectedDuration, TimeSpan precision, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNegative(precision); @@ -216,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 968e6c54a4..0000000000 --- a/Src/FluentAssertions/Specialized/FunctionAssertionHelpers.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using FluentAssertions.Common; -using FluentAssertions.Execution; - -namespace FluentAssertions.Specialized; - -internal static class FunctionAssertionHelpers -{ - internal static T NotThrow(Func subject, 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, - 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 fda57829d9..4a093c1444 100644 --- a/Src/FluentAssertions/Specialized/FunctionAssertions.cs +++ b/Src/FluentAssertions/Specialized/FunctionAssertions.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using FluentAssertions.Common; using FluentAssertions.Execution; @@ -11,14 +12,18 @@ namespace FluentAssertions.Specialized; [DebuggerNonUserCode] public class FunctionAssertions : DelegateAssertions, FunctionAssertions> { - public FunctionAssertions(Func subject, IExtractExceptions extractor) - : this(subject, extractor, new Clock()) + 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() @@ -38,21 +43,32 @@ protected override void InvokeSubject() /// /// Zero or more objects to format using the placeholders in . /// - public new AndWhichConstraint, T> NotThrow(string because = "", params object[] becauseArgs) + 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"); } /// @@ -78,21 +94,54 @@ protected override void InvokeSubject() /// Zero or more objects to format using the placeholders in . /// /// or are negative. - public new AndWhichConstraint, T> NotThrowAfter(TimeSpan waitTime, TimeSpan pollInterval, - string because = "", params object[] becauseArgs) + 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"); + } + + [SuppressMessage("Maintainability", "AV1561:Signature contains too many parameters")] + internal 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; + } + + 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 d389955d0e..f5860af3ad 100644 --- a/Src/FluentAssertions/Specialized/GenericAsyncFunctionAssertions.cs +++ b/Src/FluentAssertions/Specialized/GenericAsyncFunctionAssertions.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using FluentAssertions.Common; @@ -6,19 +7,32 @@ namespace FluentAssertions.Specialized; +/// +/// Contains a number of methods to assert that an asynchronous method yields the expected result. +/// +/// The type returned in the . public class GenericAsyncFunctionAssertions : AsyncFunctionAssertions, GenericAsyncFunctionAssertions> { - public GenericAsyncFunctionAssertions(Func> subject, IExtractExceptions extractor) - : this(subject, extractor, new Clock()) + private readonly AssertionChain assertionChain; + + /// + /// Initializes a new instance of the class. + /// + public GenericAsyncFunctionAssertions(Func> subject, IExtractExceptions extractor, AssertionChain assertionChain) + : this(subject, extractor, assertionChain, new Clock()) { + this.assertionChain = assertionChain; } - public GenericAsyncFunctionAssertions(Func> subject, IExtractExceptions extractor, IClock clock) -#pragma warning disable CS0618 // is currently obsolete to make it protected in Version 7 - : base(subject, extractor, clock) -#pragma warning restore CS0618 + /// + /// Initializes a new instance of the class with custom . + /// + public GenericAsyncFunctionAssertions(Func> subject, IExtractExceptions extractor, AssertionChain assertionChain, + IClock clock) + : base(subject, extractor, assertionChain, clock) { + this.assertionChain = assertionChain; } /// @@ -32,37 +46,37 @@ public GenericAsyncFunctionAssertions(Func> subject, IExtractExcep /// /// Zero or more objects to format using the placeholders in . /// - public new async Task, TResult>> CompleteWithinAsync( - TimeSpan timeSpan, string because = "", params object[] becauseArgs) + 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); + bool completesWithinTimeout = await CompletesWithinTimeoutAsync(task, remainingTime, _ => Task.CompletedTask); - 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)); @@ -78,20 +92,20 @@ public GenericAsyncFunctionAssertions(Func> subject, IExtractExcep /// /// Zero or more objects to format using the placeholders in . /// - public new async Task, TResult>> NotThrowAsync( - string because = "", params object[] becauseArgs) + 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); + TResult result = await Subject!.Invoke(); + return new AndWhichConstraint, TResult>(this, result, assertionChain, ".Result"); } catch (Exception exception) { @@ -125,18 +139,18 @@ public GenericAsyncFunctionAssertions(Func> subject, IExtractExcep /// Zero or more objects to format using the placeholders in . /// /// or are negative. - public new Task, TResult>> NotThrowAfterAsync( - TimeSpan waitTime, TimeSpan pollInterval, string because = "", params object[] becauseArgs) + public Task, TResult>> NotThrowAfterAsync( + TimeSpan waitTime, TimeSpan pollInterval, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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(); @@ -151,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) { @@ -161,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/JsonNodeAssertions.cs b/Src/FluentAssertions/Specialized/JsonNodeAssertions.cs new file mode 100644 index 0000000000..43bb4abab5 --- /dev/null +++ b/Src/FluentAssertions/Specialized/JsonNodeAssertions.cs @@ -0,0 +1,514 @@ +#if NET6_0_OR_GREATER + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text.Json.Nodes; +using FluentAssertions.Common; +using FluentAssertions.Configuration; +using FluentAssertions.Equivalency; +using FluentAssertions.Execution; +using FluentAssertions.Primitives; + +namespace FluentAssertions.Specialized; + +public class JsonNodeAssertions : ReferenceTypeAssertions> + where T : JsonNode +{ + /// + public JsonNodeAssertions(T subject, AssertionChain assertionChain) + : base(subject, assertionChain) + { + } + + /// + protected override string Identifier + { + get => "JSON node"; + } + + /// + /// Asserts that the current contains a property with the specified name. + /// + /// The name of the property to look for. + /// + /// 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 AndWhichConstraint, JsonNode> HaveProperty(string code, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + CurrentAssertionChain + .BecauseOf(because, becauseArgs) + .ForCondition(Subject is not null) + .FailWith("Cannot assert the existence of a property on a JSON node{reason}.") + .Then + .ForCondition(Subject?[code] != null) + .FailWith("Expected {context:JSON node} to have property {0}{reason}.", code); + + return new AndWhichConstraint, JsonNode>(this, Subject?[code]); + } + + /// + /// Asserts that the current does not have a property with the specified name. + /// + /// The name of the property that should not exist. + /// + /// 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 AndConstraint> NotHaveProperty(string code, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + CurrentAssertionChain + .BecauseOf(because, becauseArgs) + .ForCondition(Subject is not null) + .FailWith("Cannot assert the existence of a property on a JSON node{reason}.") + .Then + .ForCondition(Subject?[code] is null) + .BecauseOf(because, becauseArgs) + .FailWith("Did not expect {context:JSON node} to have property {0}{reason}.", code); + + return new AndConstraint>(this); + } + + /// + /// Asserts that the current represents a JSON array. + /// + /// + /// 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 AndWhichConstraint, IEnumerable> BeAnArray( + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + CurrentAssertionChain + .ForCondition(Subject is JsonArray) + .BecauseOf(because, becauseArgs) + .FailWith("Expected {context:JSON node} to be an array{reason}, but {0} is not.", Subject); + + return new AndWhichConstraint, IEnumerable>(this, (Subject as JsonArray)?.AsEnumerable()); + } + + /// + /// Asserts that the current does not represent a JSON array. + /// + /// + /// 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 AndConstraint> NotBeAnArray( + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + CurrentAssertionChain + .ForCondition(Subject is not JsonArray) + .BecauseOf(because, becauseArgs) + .FailWith("Did not expect {context:JSON node} to be an array{reason}, but {0} is.", Subject); + + return new AndConstraint>(this); + } + + /// + /// Asserts that the current represents a numeric value. + /// + /// + /// 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 AndWhichConstraint, string> BeNumeric( + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + CurrentAssertionChain + .ForCondition(Subject is JsonValue value && value.IsNumeric()) + .BecauseOf(because, becauseArgs) + .FailWith("Expected {context:JSON node} to be a numeric value{reason}, but {0} is not.", Subject); + + return new AndWhichConstraint, string>(this, (Subject as JsonValue)?.ToString() ?? string.Empty); + } + + /// + /// Asserts that the current represents a numeric value and returns + /// the actual value provided it can be converted to . + /// + /// + /// 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 AndWhichConstraint, TValue> BeNumeric( + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + TValue actualValue = default; + + CurrentAssertionChain + .ForCondition(Subject is JsonValue value && value.IsNumeric() && value.TryGetValue(out actualValue)) + .BecauseOf(because, becauseArgs) + .FailWith("Expected {context:JSON node} to be a numeric value{reason}, but {0} is not.", Subject); + + return new AndWhichConstraint, TValue>(this, actualValue); + } + + /// + /// Asserts that the current does not represent a numeric value. + /// + /// + /// 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 AndConstraint> NotBeNumeric( + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + CurrentAssertionChain + .ForCondition(Subject is not JsonValue value || !value.IsNumeric()) + .BecauseOf(because, becauseArgs) + .FailWith("Did not expect {context:JSON node} to be a numeric value{reason}, but {0} is.", Subject); + + return new AndConstraint>(this); + } + + /// + /// Asserts that the current represents a local . + /// + /// + /// 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 AndWhichConstraint, DateTime> BeLocalDate( + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + CurrentAssertionChain + .ForCondition(Subject is JsonValue value && value.TryGetValue(out _) && !Subject.ToString().EndsWith('Z')) + .BecauseOf(because, becauseArgs) + .FailWith("Expected {context:JSON node} to be a local date{reason}, but {0} is not.", Subject); + + var actualValue = Subject is JsonValue jsonValue && jsonValue.TryGetValue(out var result) ? result : default; + return new AndWhichConstraint, DateTime>(this, actualValue); + } + + /// + /// Asserts that the current does not represent a local . + /// + /// + /// 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 AndConstraint> NotBeLocalDate( + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + CurrentAssertionChain + .ForCondition(Subject is not JsonValue value || !value.TryGetValue(out _) || + Subject.ToString().EndsWith('Z')) + .BecauseOf(because, becauseArgs) + .FailWith("Did not expect {context:JSON node} to be a local date{reason}, but {0} is.", Subject); + + return new AndConstraint>(this); + } + + /// + /// Asserts that the current represents a UTC . + /// + /// + /// 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 AndWhichConstraint, DateTime> BeUtcDate( + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + CurrentAssertionChain + .ForCondition(Subject is JsonValue value && value.TryGetValue(out _) && Subject.ToString().EndsWith('Z')) + .BecauseOf(because, becauseArgs) + .FailWith("Expected {context} to be a UTC date{reason}, but {0} is not.", Subject); + + var actualValue = Subject is JsonValue jsonValue && jsonValue.TryGetValue(out var result) ? result : default; + return new AndWhichConstraint, DateTime>(this, actualValue); + } + + /// + /// Asserts that the current does not represent a UTC . + /// + /// + /// 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 AndConstraint> NotBeUtcDate( + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + CurrentAssertionChain + .ForCondition(Subject is not JsonValue value || !value.TryGetValue(out _) || + !Subject.ToString().EndsWith('Z')) + .BecauseOf(because, becauseArgs) + .FailWith("Did not expect {context} to be a UTC date{reason}, but {0} is.", Subject); + + return new AndConstraint>(this); + } + + /// + /// Asserts that the current represents a boolean value. + /// + /// + /// 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 AndWhichConstraint, bool> BeBool( + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + CurrentAssertionChain + .ForCondition(Subject is JsonValue value && value.TryGetValue(out bool _)) + .BecauseOf(because, becauseArgs) + .FailWith("Expected {context} to be a boolean{reason}, but {0} is not.", Subject); + + var actualValue = Subject is JsonValue jsonValue && jsonValue.TryGetValue(out var result) && result; + return new AndWhichConstraint, bool>(this, actualValue); + } + + /// + /// Asserts that the current does not represent a boolean value. + /// + /// + /// 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 AndConstraint> NotBeBool( + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + CurrentAssertionChain + .ForCondition(!(Subject is JsonValue value && value.TryGetValue(out _))) + .BecauseOf(because, becauseArgs) + .FailWith("Did not expect {context:JSON node} to be a boolean{reason}, but {0} is.", Subject); + + return new AndConstraint>(this); + } + + /// + /// Asserts that the current represents a string value. + /// + /// + /// 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 AndWhichConstraint, string> BeString( + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + CurrentAssertionChain + .ForCondition(Subject is JsonValue value && value.TryGetValue(out string _)) + .BecauseOf(because, becauseArgs) + .FailWith("Expected {context:JSON node} to be a string{reason}, but {0} is not.", Subject); + + var actualValue = Subject is JsonValue jsonValue && jsonValue.TryGetValue(out var result) ? result : null; + return new AndWhichConstraint, string>(this, actualValue); + } + + /// + /// Asserts that the current does not represent a string value. + /// + /// + /// 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 AndConstraint> NotBeString( + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + CurrentAssertionChain + .ForCondition(!(Subject is JsonValue value && value.TryGetValue(out _))) + .BecauseOf(because, becauseArgs) + .FailWith("Did not expect {context:JSON node} to be a string{reason}, but {0} is.", Subject); + + return new AndConstraint>(this); + } + + /// + /// Asserts a JSON node is equivalent to a given C# object graph. + /// + /// + /// Traverses the properties of the object graph, which can contain nested objects and collections, and compares them to + /// the properties the JSON node. Two properties are considered equal when their names and values match. The comparison is + /// recursive, so nested properties and collections are compared in the same way. The comparison is driven by the expectation, so + /// if the JSON node contains extra properties, they are ignored. If the JSON node has a property that contains an ISO 8601 date (local or UTC), it can be + /// compared to a property of the expectation. + /// + /// The default global configuration can be modified through . + /// + /// The expected object graph, such as an anonymous type. + /// + /// 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 AndConstraint> BeEquivalentTo(TExpectation expectation, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + return BeEquivalentTo(expectation, config => config, because, becauseArgs); + } + + /// + /// Asserts a JSON node is equivalent to a given C# object graph. + /// + /// + /// Traverses the properties of the object graph, which can contain nested objects and collections, and compares them to + /// the properties the JSON node. Two properties are considered equal when their names and values match. The comparison is + /// recursive, so nested properties and collections are compared in the same way. The comparison is case-sensitive, unless you use + /// . The comparison is driven by the expectation, so + /// if the JSON node contains extra properties, they are ignored. If the JSON node has a property that contains an ISO 8601 date (local or UTC), it can be + /// compared to a property of the expectation. + /// + /// The default configuration can be modified by using the many options available through the delegate. Also, + /// global options can be set through . + /// + /// The expected object graph, such as an anonymous type. + /// + /// 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 AndConstraint> BeEquivalentTo(TExpectation expectation, + Func, EquivalencyOptions> config, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNull(config); + + EquivalencyOptions options = + config(AssertionConfiguration.Current.Equivalency.CloneDefaults()); + + var context = new EquivalencyValidationContext(Node.From(() => + CurrentAssertionChain.CallerIdentifier), options) + { + Reason = new Reason(because, becauseArgs), + TraceWriter = options.TraceWriter + }; + + var comparands = new Comparands + { + Subject = Subject, + Expectation = expectation, + CompileTimeType = typeof(TExpectation), + }; + + new EquivalencyValidator().AssertEquality(comparands, context); + + return new AndConstraint>(this); + } + + /// + /// Asserts a JSON node is NOT equivalent to a given C# object graph. + /// + /// + /// Traverses the properties of the object graph, which can contain nested objects and collections, and compares them to + /// the properties the JSON node. Two properties are considered equal when their names and values match. The comparison is + /// recursive, so nested properties and collections are compared in the same way. The comparison is driven by the expectation, so + /// if the JSON node contains extra properties, they are ignored. If the JSON node has a property that contains an ISO 8601 date (local or UTC), it can be + /// compared to a property of the expectation. + /// + /// The default global configuration can be modified through . + /// + /// The expected object graph, such as an anonymous type. + /// + /// 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 AndConstraint> NotBeEquivalentTo( + TExpectation unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + return NotBeEquivalentTo(unexpected, config => config, because, becauseArgs); + } + + /// + /// Asserts a JSON node is NOT equivalent to a given C# object graph. + /// + /// + /// Traverses the properties of the object graph, which can contain nested objects and collections, and compares them to + /// the properties the JSON node. Two properties are considered equal when their names and values match. The comparison is + /// recursive, so nested properties and collections are compared in the same way. The comparison is case-sensitive, unless you use + /// . The comparison is driven by the expectation, so + /// if the JSON node contains extra properties, they are ignored. If the JSON node has a property that contains an ISO 8601 date (local or UTC), it can be + /// compared to a property of the expectation. + /// + /// The default configuration can be modified by using the many options available through the delegate. Also, + /// global options can be set through . + /// + /// The expected object graph, such as an anonymous type. + /// + /// 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 AndConstraint> NotBeEquivalentTo( + TExpectation unexpected, + Func, EquivalencyOptions> config, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNull(config); + + bool hasMismatches; + + using (var scope = new AssertionScope()) + { + BeEquivalentTo(unexpected, config); + hasMismatches = scope.Discard().Length > 0; + } + + CurrentAssertionChain + .ForCondition(hasMismatches) + .BecauseOf(because, becauseArgs) + .WithDefaultIdentifier(Identifier) + .FailWith("Did not expect {context:JSON node} to be equivalent to {0}{reason}, but they are.", unexpected); + + return new AndConstraint>(this); + } +} + +#endif diff --git a/Src/FluentAssertions/Specialized/JsonValueExtensions.cs b/Src/FluentAssertions/Specialized/JsonValueExtensions.cs new file mode 100644 index 0000000000..551c55fba7 --- /dev/null +++ b/Src/FluentAssertions/Specialized/JsonValueExtensions.cs @@ -0,0 +1,16 @@ +#if NET6_0_OR_GREATER + +using System.Text.Json; +using System.Text.Json.Nodes; + +namespace FluentAssertions.Specialized; + +internal static class JsonValueExtensions +{ + public static bool IsNumeric(this JsonValue value) + { + return value.GetValue().ValueKind == JsonValueKind.Number; + } +} + +#endif diff --git a/Src/FluentAssertions/Specialized/NonGenericAsyncFunctionAssertions.cs b/Src/FluentAssertions/Specialized/NonGenericAsyncFunctionAssertions.cs index 19bcf54d99..f68a8dac85 100644 --- a/Src/FluentAssertions/Specialized/NonGenericAsyncFunctionAssertions.cs +++ b/Src/FluentAssertions/Specialized/NonGenericAsyncFunctionAssertions.cs @@ -1,20 +1,177 @@ using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading; using System.Threading.Tasks; using FluentAssertions.Common; +using FluentAssertions.Execution; namespace FluentAssertions.Specialized; +/// +/// Contains a number of methods to assert that an asynchronous method yields the expected result. +/// public class NonGenericAsyncFunctionAssertions : AsyncFunctionAssertions { - public NonGenericAsyncFunctionAssertions(Func subject, IExtractExceptions extractor) - : this(subject, extractor, new Clock()) + private readonly AssertionChain assertionChain; + + /// + /// Initializes a new instance of the class. + /// + 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, AssertionChain assertionChain, IClock clock) + : base(subject, extractor, assertionChain, clock) + { + this.assertionChain = assertionChain; + } + + /// + /// Asserts that the current will complete within the specified time. + /// + /// The allowed time span for the operation. + /// + /// 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 async Task> CompleteWithinAsync( + TimeSpan timeSpan, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + assertionChain + .ForCondition(Subject is not null) + .BecauseOf(because, becauseArgs) + .FailWith("Expected {context:task} to complete within {0}{reason}, but found .", timeSpan); + + if (assertionChain.Succeeded) + { + (Task task, TimeSpan remainingTime) = InvokeWithTimer(timeSpan); + + assertionChain + .ForCondition(remainingTime >= TimeSpan.Zero) + .BecauseOf(because, becauseArgs) + .FailWith("Expected {context:task} to complete within {0}{reason}.", timeSpan); + + if (assertionChain.Succeeded) + { + bool completesWithinTimeout = await CompletesWithinTimeoutAsync(task, remainingTime, _ => Task.CompletedTask); + + assertionChain + .ForCondition(completesWithinTimeout) + .BecauseOf(because, becauseArgs) + .FailWith("Expected {context:task} to complete within {0}{reason}.", timeSpan); + } + } + + return new AndConstraint(this); + } + + /// + /// Asserts that the current does not throw any exception. + /// + /// + /// 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 async Task> NotThrowAsync( + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { + assertionChain + .ForCondition(Subject is not null) + .BecauseOf(because, becauseArgs) + .FailWith("Expected {context} not to throw{reason}, but found ."); + + if (assertionChain.Succeeded) + { + try + { + await Subject!.Invoke(); + } + catch (Exception exception) + { + return NotThrowInternal(exception, because, becauseArgs); + } + } + + return new AndConstraint(this); } - public NonGenericAsyncFunctionAssertions(Func subject, IExtractExceptions extractor, IClock clock) -#pragma warning disable CS0618 // is currently obsolete to make it protected in Version 7 - : base(subject, extractor, clock) -#pragma warning restore CS0618 + /// + /// Asserts that the current stops throwing any exception + /// after a specified amount of time. + /// + /// + /// The is invoked. If it raises an exception, + /// the invocation is repeated until it either stops raising any exceptions + /// or the specified wait time is exceeded. + /// + /// + /// The time after which the should have stopped throwing any exception. + /// + /// + /// The time between subsequent invocations of the . + /// + /// + /// 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 . + /// + /// or are negative. + public Task> NotThrowAfterAsync(TimeSpan waitTime, TimeSpan pollInterval, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { + Guard.ThrowIfArgumentIsNegative(waitTime); + Guard.ThrowIfArgumentIsNegative(pollInterval); + + assertionChain + .ForCondition(Subject is not null) + .BecauseOf(because, becauseArgs) + .FailWith("Expected {context} not to throw any exceptions after {0}{reason}, but found .", waitTime); + + if (assertionChain.Succeeded) + { + return AssertionTaskAsync(); + + async Task> AssertionTaskAsync() + { + TimeSpan? invocationEndTime = null; + Exception exception = null; + ITimer timer = Clock.StartTimer(); + + while (invocationEndTime is null || invocationEndTime < waitTime) + { + exception = await InvokeWithInterceptionAsync(Subject); + + if (exception is null) + { + return new AndConstraint(this); + } + + await Clock.DelayAsync(pollInterval, CancellationToken.None); + invocationEndTime = timer.Elapsed; + } + + assertionChain + .BecauseOf(because, becauseArgs) + .FailWith("Did not expect any exceptions after {0}{reason}, but found {1}.", waitTime, exception); + + return new AndConstraint(this); + } + } + + return Task.FromResult(new AndConstraint(this)); } } diff --git a/Src/FluentAssertions/Specialized/TaskCompletionSourceAssertions.cs b/Src/FluentAssertions/Specialized/TaskCompletionSourceAssertions.cs index 1ad25bb5a9..0081053767 100644 --- a/Src/FluentAssertions/Specialized/TaskCompletionSourceAssertions.cs +++ b/Src/FluentAssertions/Specialized/TaskCompletionSourceAssertions.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using FluentAssertions.Common; using FluentAssertions.Execution; @@ -11,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; } /// @@ -36,17 +40,17 @@ public TaskCompletionSourceAssertions(TaskCompletionSource tcs, IClock clock) /// Zero or more objects to format using the placeholders in . /// public async Task> CompleteWithinAsync( - TimeSpan timeSpan, string because = "", params object[] becauseArgs) + 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 + bool completesWithinTimeout = await CompletesWithinTimeoutAsync(subject!.Task, timeSpan); + assertionChain .ForCondition(completesWithinTimeout) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:task} to complete within {0}{reason}.", timeSpan); @@ -67,17 +71,17 @@ public async Task> CompleteWithinA /// Zero or more objects to format using the placeholders in . /// public async Task> NotCompleteWithinAsync( - TimeSpan timeSpan, string because = "", params object[] becauseArgs) + 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 + bool completesWithinTimeout = await CompletesWithinTimeoutAsync(subject!.Task, timeSpan); + assertionChain .ForCondition(!completesWithinTimeout) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:task} to not complete within {0}{reason}.", timeSpan); @@ -90,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; } /// @@ -115,18 +122,18 @@ public TaskCompletionSourceAssertions(TaskCompletionSource tcs, IClock clock) /// Zero or more objects to format using the placeholders in . /// public async Task, T>> CompleteWithinAsync( - TimeSpan timeSpan, string because = "", params object[] becauseArgs) + 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); + 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); @@ -134,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)); @@ -152,18 +159,18 @@ public async Task, T>> Comp /// Zero or more objects to format using the placeholders in . /// public async Task>> NotCompleteWithinAsync( - TimeSpan timeSpan, string because = "", params object[] becauseArgs) + 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); + 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 8901f99dbc..1073684a4c 100644 --- a/Src/FluentAssertions/Streams/BufferedStreamAssertions.cs +++ b/Src/FluentAssertions/Streams/BufferedStreamAssertions.cs @@ -1,7 +1,8 @@ using System.Diagnostics; using System.IO; -#if NET6_0_OR_GREATER || NETSTANDARD2_1 using FluentAssertions.Execution; +#if NET6_0_OR_GREATER || NETSTANDARD2_1 +using System.Diagnostics.CodeAnalysis; #endif namespace FluentAssertions.Streams; @@ -13,8 +14,8 @@ namespace FluentAssertions.Streams; [DebuggerNonUserCode] public class BufferedStreamAssertions : BufferedStreamAssertions { - public BufferedStreamAssertions(BufferedStream stream) - : base(stream) + public BufferedStreamAssertions(BufferedStream stream, AssertionChain assertionChain) + : base(stream, assertionChain) { } } @@ -22,14 +23,16 @@ public BufferedStreamAssertions(BufferedStream stream) public class BufferedStreamAssertions : StreamAssertions where TAssertions : BufferedStreamAssertions { - public BufferedStreamAssertions(BufferedStream stream) - : base(stream) +#if NET6_0_OR_GREATER || NETSTANDARD2_1 + + 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. /// @@ -41,19 +44,20 @@ public BufferedStreamAssertions(BufferedStream stream) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveBufferSize(int expected, string because = "", params object[] becauseArgs) + public AndConstraint HaveBufferSize(int expected, + [StringSyntax("CompositeFormat")] 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) + .ForCondition(Subject!.BufferSize == expected) .FailWith("Expected the buffer size of {context:stream} to be {0}{reason}, but it was {1}.", expected, Subject.BufferSize); } @@ -72,24 +76,32 @@ public AndConstraint HaveBufferSize(int expected, string because = /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveBufferSize(int unexpected, string because = "", params object[] becauseArgs) + public AndConstraint NotHaveBufferSize(int unexpected, + [StringSyntax("CompositeFormat")] 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) + .ForCondition(Subject!.BufferSize != unexpected) .FailWith("Expected the buffer size of {context:stream} not to be {0}{reason}, but it was.", unexpected); } return new AndConstraint((TAssertions)this); } +#else + public BufferedStreamAssertions(BufferedStream stream, AssertionChain assertionChain) + : base(stream, assertionChain) + { + } #endif + + protected override string Identifier => "buffered stream"; } diff --git a/Src/FluentAssertions/Streams/StreamAssertions.cs b/Src/FluentAssertions/Streams/StreamAssertions.cs index 0855ee12df..3116721541 100644 --- a/Src/FluentAssertions/Streams/StreamAssertions.cs +++ b/Src/FluentAssertions/Streams/StreamAssertions.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; using FluentAssertions.Execution; using FluentAssertions.Primitives; @@ -12,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) { } } @@ -25,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"; @@ -42,18 +46,18 @@ public StreamAssertions(TSubject stream) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeWritable(string because = "", params object[] becauseArgs) + 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) + .ForCondition(Subject!.CanWrite) .FailWith("Expected {context:stream} to be writable{reason}, but it was not."); } @@ -70,18 +74,18 @@ public AndConstraint BeWritable(string because = "", params object[ /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeWritable(string because = "", params object[] becauseArgs) + 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) + .ForCondition(!Subject!.CanWrite) .FailWith("Expected {context:stream} not to be writable{reason}, but it was."); } @@ -98,18 +102,18 @@ public AndConstraint NotBeWritable(string because = "", params obje /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeSeekable(string because = "", params object[] becauseArgs) + 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) + .ForCondition(Subject!.CanSeek) .FailWith("Expected {context:stream} to be seekable{reason}, but it was not."); } @@ -126,18 +130,18 @@ public AndConstraint BeSeekable(string because = "", params object[ /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeSeekable(string because = "", params object[] becauseArgs) + 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) + .ForCondition(!Subject!.CanSeek) .FailWith("Expected {context:stream} not to be seekable{reason}, but it was."); } @@ -154,18 +158,18 @@ public AndConstraint NotBeSeekable(string because = "", params obje /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeReadable(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 {context:stream} to be readable{reason}, but found a reference."); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .ForCondition(Subject.CanRead) + .ForCondition(Subject!.CanRead) .FailWith("Expected {context:stream} to be readable{reason}, but it was not."); } @@ -182,18 +186,18 @@ public AndConstraint BeReadable(string because = "", params object[ /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeReadable(string because = "", params object[] becauseArgs) + 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) + .ForCondition(!Subject!.CanRead) .FailWith("Expected {context:stream} not to be readable{reason}, but it was."); } @@ -211,26 +215,27 @@ public AndConstraint NotBeReadable(string because = "", params obje /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HavePosition(long expected, string because = "", params object[] becauseArgs) + 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; try { - position = Subject.Position; + position = Subject!.Position; } 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}", @@ -239,7 +244,7 @@ public AndConstraint HavePosition(long expected, string because = " 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}.", @@ -260,26 +265,27 @@ public AndConstraint HavePosition(long expected, string because = " /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHavePosition(long unexpected, string because = "", params object[] becauseArgs) + 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; try { - position = Subject.Position; + position = Subject!.Position; } 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}", @@ -288,7 +294,7 @@ public AndConstraint NotHavePosition(long unexpected, string becaus 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.", @@ -309,26 +315,27 @@ public AndConstraint NotHavePosition(long unexpected, string becaus /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveLength(long expected, string because = "", params object[] becauseArgs) + 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; try { - length = Subject.Length; + length = Subject!.Length; } 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}", @@ -337,7 +344,7 @@ public AndConstraint HaveLength(long expected, string because = "", 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}.", @@ -358,26 +365,27 @@ public AndConstraint HaveLength(long expected, string because = "", /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotHaveLength(long unexpected, string because = "", params object[] becauseArgs) + 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; try { - length = Subject.Length; + length = Subject!.Length; } 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}", @@ -386,7 +394,7 @@ public AndConstraint NotHaveLength(long unexpected, string because 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.", @@ -406,18 +414,18 @@ public AndConstraint NotHaveLength(long unexpected, string because /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeReadOnly(string because = "", params object[] becauseArgs) + 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) + .ForCondition(!Subject!.CanWrite && Subject.CanRead) .FailWith("Expected {context:stream} to be read-only{reason}, but it was writable or not readable."); } @@ -434,18 +442,18 @@ public AndConstraint BeReadOnly(string because = "", params object[ /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeReadOnly(string because = "", params object[] becauseArgs) + 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) + .ForCondition(Subject!.CanWrite || !Subject.CanRead) .FailWith("Expected {context:stream} not to be read-only{reason}, but it was."); } @@ -462,18 +470,18 @@ public AndConstraint NotBeReadOnly(string because = "", params obje /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeWriteOnly(string because = "", params object[] becauseArgs) + 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) + .ForCondition(Subject!.CanWrite && !Subject.CanRead) .FailWith("Expected {context:stream} to be write-only{reason}, but it was readable or not writable."); } @@ -490,18 +498,18 @@ public AndConstraint BeWriteOnly(string because = "", params object /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeWriteOnly(string because = "", params object[] becauseArgs) + 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) + .ForCondition(!Subject!.CanWrite || Subject.CanRead) .FailWith("Expected {context:stream} not to be write-only{reason}, but it was."); } diff --git a/Src/FluentAssertions/Specialized/AssemblyAssertions.cs b/Src/FluentAssertions/Types/AssemblyAssertions.cs similarity index 78% rename from Src/FluentAssertions/Specialized/AssemblyAssertions.cs rename to Src/FluentAssertions/Types/AssemblyAssertions.cs index 6b5eeb72e2..f0ba014e31 100644 --- a/Src/FluentAssertions/Specialized/AssemblyAssertions.cs +++ b/Src/FluentAssertions/Types/AssemblyAssertions.cs @@ -1,24 +1,28 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using FluentAssertions.Common; using FluentAssertions.Execution; using FluentAssertions.Primitives; -namespace FluentAssertions.Reflection; +namespace FluentAssertions.Types; /// /// Contains a number of methods to assert that an is in the expected state. /// 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; } /// @@ -33,27 +37,28 @@ public AssemblyAssertions(Assembly assembly) /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint NotReference(Assembly assembly, string because = "", params object[] becauseArgs) + public AndConstraint NotReference(Assembly assembly, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(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; + var subjectName = Subject!.GetName().Name; IEnumerable references = Subject.GetReferencedAssemblies().Select(x => x.Name); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .ForCondition(!references.Contains(assemblyName)) + .ForCondition(!references.Contains(assemblyName, StringComparer.Ordinal)) .FailWith("Expected assembly {0} not to reference assembly {1}{reason}.", subjectName, assemblyName); } @@ -72,26 +77,27 @@ public AndConstraint NotReference(Assembly assembly, string /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint Reference(Assembly assembly, string because = "", params object[] becauseArgs) + public AndConstraint Reference(Assembly assembly, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(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; + var subjectName = Subject!.GetName().Name; IEnumerable references = Subject.GetReferencedAssemblies().Select(x => x.Name); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) - .ForCondition(references.Contains(assemblyName)) + .ForCondition(references.Contains(assemblyName, StringComparer.Ordinal)) .FailWith("Expected assembly {0} to reference assembly {1}{reason}, but it does not.", subjectName, assemblyName); } @@ -112,12 +118,12 @@ public AndConstraint Reference(Assembly assembly, string bec /// /// is . /// is empty. - public AndWhichConstraint DefineType(string @namespace, string name, string because = "", - params object[] becauseArgs) + public AndWhichConstraint DefineType(string @namespace, string name, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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 .", @@ -125,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); + 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.", @@ -147,17 +153,18 @@ public AndWhichConstraint DefineType(string @namespace /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeUnsigned(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 }) + .ForCondition(Subject!.GetName().GetPublicKey() is not { Length: > 0 }) .FailWith( "Did not expect the assembly {0} to be signed{reason}, but it is.", Subject.FullName); } @@ -178,29 +185,28 @@ public AndConstraint BeUnsigned(string because = "", params /// /// is . /// is empty. - public AndConstraint BeSignedWithPublicKey(string publicKey, 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() ?? Array.Empty(); + 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..c65c9d8cfd 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,17 +14,17 @@ 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) { } - internal static string GetDescriptionFor(ConstructorInfo constructorInfo) + private protected override string SubjectDescription => GetDescriptionFor(Subject); + + protected override string Identifier => "constructor"; + + private static string GetDescriptionFor(ConstructorInfo constructorInfo) { return $"{constructorInfo.DeclaringType}({GetParameterString(constructorInfo)})"; } - - internal 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 97d80fccb2..3e214fd28c 100644 --- a/Src/FluentAssertions/Types/MemberInfoAssertions.cs +++ b/Src/FluentAssertions/Types/MemberInfoAssertions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -18,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; } /// @@ -34,7 +38,7 @@ protected MemberInfoAssertions(TSubject subject) /// Zero or more objects to format using the placeholders in . /// public AndWhichConstraint, TAttribute> BeDecoratedWith( - string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TAttribute : Attribute { return BeDecoratedWith(_ => true, because, becauseArgs); @@ -51,7 +55,7 @@ public AndWhichConstraint, TAttribut /// Zero or more objects to format using the placeholders in . /// public AndConstraint NotBeDecoratedWith( - string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TAttribute : Attribute { return NotBeDecoratedWith(_ => true, because, becauseArgs); @@ -74,25 +78,25 @@ public AndConstraint NotBeDecoratedWith( /// is . public AndWhichConstraint, TAttribute> BeDecoratedWith( Expression> isMatchingAttributePredicate, - string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TAttribute : Attribute { Guard.ThrowIfArgumentIsNull(isMatchingAttributePredicate); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith( $"Expected {Identifier} to be decorated with {typeof(TAttribute)}{{reason}}" + ", but {context:member} is ."); - IEnumerable attributes = Array.Empty(); + IEnumerable attributes = []; - if (success) + if (assertionChain.Succeeded) { attributes = Subject.GetMatchingAttributes(isMatchingAttributePredicate); - Execute.Assertion + assertionChain .ForCondition(attributes.Any()) .BecauseOf(because, becauseArgs) .FailWith( @@ -100,7 +104,7 @@ public AndWhichConstraint, TAttribut ", but that attribute was not found."); } - return new AndWhichConstraint, TAttribute>(this, attributes); + return new AndWhichConstraint, TAttribute>(this, attributes, assertionChain); } /// @@ -120,23 +124,23 @@ public AndWhichConstraint, TAttribut /// is . public AndConstraint NotBeDecoratedWith( Expression> isMatchingAttributePredicate, - string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TAttribute : Attribute { 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( @@ -149,5 +153,5 @@ public AndConstraint NotBeDecoratedWith( protected override string Identifier => "member"; - internal virtual string SubjectDescription => $"{Subject.DeclaringType}.{Subject.Name}"; + private protected virtual string SubjectDescription => $"{Subject.DeclaringType}.{Subject.Name}"; } diff --git a/Src/FluentAssertions/Types/MethodBaseAssertions.cs b/Src/FluentAssertions/Types/MethodBaseAssertions.cs index 7ab268bbb7..042c34b27e 100644 --- a/Src/FluentAssertions/Types/MethodBaseAssertions.cs +++ b/Src/FluentAssertions/Types/MethodBaseAssertions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using FluentAssertions.Common; @@ -16,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; } /// @@ -35,24 +39,32 @@ protected MethodBaseAssertions(TSubject subject) /// /// is not a value. public AndConstraint HaveAccessModifier( - CSharpAccessModifier accessModifier, string because = "", params object[] becauseArgs) + CSharpAccessModifier accessModifier, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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 + assertionChain .ForCondition(accessModifier == subjectAccessModifier) .BecauseOf(because, becauseArgs) - .FailWith( - $"Expected method {Subject.Name} to be {accessModifier}{{reason}}, but it is {subjectAccessModifier}."); + .FailWith(() => + { + var subject = assertionChain.HasOverriddenCallerIdentifier + ? assertionChain.CallerIdentifier + : "method " + Subject.ToFormattedString(); + + return new FailReason( + $"Expected {subject} to be {accessModifier}{{reason}}, but it is {subjectAccessModifier}."); + }); } return new AndConstraint((TAssertions)this); @@ -71,24 +83,31 @@ public AndConstraint HaveAccessModifier( /// /// /// is not a value. - public AndConstraint NotHaveAccessModifier(CSharpAccessModifier accessModifier, string because = "", - params object[] becauseArgs) + public AndConstraint NotHaveAccessModifier(CSharpAccessModifier accessModifier, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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 + assertionChain .ForCondition(accessModifier != subjectAccessModifier) .BecauseOf(because, becauseArgs) - .FailWith($"Expected method {Subject.Name} not to be {accessModifier}{{reason}}, but it is."); + .FailWith(() => + { + var subject = assertionChain.HasOverriddenCallerIdentifier + ? assertionChain.CallerIdentifier + : "method " + Subject.ToFormattedString(); + + return new FailReason($"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 cafd02b3de..cfd799d9d5 100644 --- a/Src/FluentAssertions/Types/MethodInfoAssertions.cs +++ b/Src/FluentAssertions/Types/MethodInfoAssertions.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Reflection; using FluentAssertions.Common; using FluentAssertions.Execution; @@ -12,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; } /// @@ -27,16 +31,16 @@ public MethodInfoAssertions(MethodInfo methodInfo) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeVirtual(string because = "", params object[] becauseArgs) + 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."); @@ -55,16 +59,16 @@ public AndConstraint BeVirtual(string because = "", params /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeVirtual(string because = "", params object[] becauseArgs) + 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."); @@ -83,16 +87,16 @@ public AndConstraint NotBeVirtual(string because = "", par /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeAsync(string because = "", params object[] becauseArgs) + 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."); @@ -111,16 +115,16 @@ public AndConstraint BeAsync(string because = "", params o /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeAsync(string because = "", params object[] becauseArgs) + 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."); @@ -139,18 +143,18 @@ public AndConstraint NotBeAsync(string because = "", param /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint> ReturnVoid(string because = "", - params object[] becauseArgs) + 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 - .ForCondition(typeof(void) == Subject.ReturnType) + 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}.", Subject.ReturnType.FullName); @@ -171,20 +175,20 @@ public AndConstraint> Ret /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint> Return(Type returnType, string because = "", - params object[] becauseArgs) + public AndConstraint> Return(Type returnType, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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 - .ForCondition(returnType == Subject.ReturnType) + 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}.", returnType, Subject.ReturnType.FullName); @@ -204,8 +208,8 @@ public AndConstraint> Ret /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint> Return(string because = "", - params object[] becauseArgs) + public AndConstraint> Return( + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return Return(typeof(TReturn), because, becauseArgs); } @@ -220,18 +224,18 @@ public AndConstraint> Ret /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint> NotReturnVoid(string because = "", - params object[] becauseArgs) + 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 - .ForCondition(typeof(void) != Subject.ReturnType) + 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."); } @@ -251,21 +255,21 @@ public AndConstraint> Not /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint> NotReturn(Type returnType, string because = "", - params object[] becauseArgs) + public AndConstraint> NotReturn(Type returnType, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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 - .ForCondition(returnType != Subject.ReturnType) + assertionChain + .ForCondition(returnType != Subject!.ReturnType) .BecauseOf(because, becauseArgs) .FailWith( "Expected the return type of method " + Subject.Name + " not to be {0}{reason}, but it is.", returnType); @@ -285,8 +289,8 @@ public AndConstraint> Not /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint> NotReturn(string because = "", - params object[] becauseArgs) + public AndConstraint> NotReturn( + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return NotReturn(typeof(TReturn), because, becauseArgs); } @@ -303,7 +307,7 @@ internal static string GetDescriptionFor(MethodInfo method) return $"{returnTypeName} {method.DeclaringType}.{method.Name}"; } - internal override string SubjectDescription => GetDescriptionFor(Subject); + private protected override string SubjectDescription => GetDescriptionFor(Subject); protected override string Identifier => "method"; } diff --git a/Src/FluentAssertions/Types/MethodInfoSelector.cs b/Src/FluentAssertions/Types/MethodInfoSelector.cs index b2d129c9b2..511309ae7a 100644 --- a/Src/FluentAssertions/Types/MethodInfoSelector.cs +++ b/Src/FluentAssertions/Types/MethodInfoSelector.cs @@ -21,7 +21,7 @@ public class MethodInfoSelector : IEnumerable /// The type from which to select methods. /// is . public MethodInfoSelector(Type type) - : this(new[] { type }) + : this([type]) { } diff --git a/Src/FluentAssertions/Types/MethodInfoSelectorAssertions.cs b/Src/FluentAssertions/Types/MethodInfoSelectorAssertions.cs index f7f0b2f37b..71b3c51213 100644 --- a/Src/FluentAssertions/Types/MethodInfoSelectorAssertions.cs +++ b/Src/FluentAssertions/Types/MethodInfoSelectorAssertions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -17,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; @@ -44,7 +48,7 @@ public MethodInfoSelectorAssertions(params MethodInfo[] methods) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeVirtual(string because = "", params object[] becauseArgs) + public AndConstraint BeVirtual([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { MethodInfo[] nonVirtualMethods = GetAllNonVirtualMethodsFromSelection(); @@ -53,7 +57,7 @@ public AndConstraint BeVirtual(string because = "" Environment.NewLine + GetDescriptionsFor(nonVirtualMethods); - Execute.Assertion + assertionChain .ForCondition(nonVirtualMethods.Length == 0) .BecauseOf(because, becauseArgs) .FailWith(failureMessage); @@ -71,7 +75,7 @@ public AndConstraint BeVirtual(string because = "" /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeVirtual(string because = "", params object[] becauseArgs) + public AndConstraint NotBeVirtual([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { MethodInfo[] virtualMethods = GetAllVirtualMethodsFromSelection(); @@ -80,7 +84,7 @@ public AndConstraint NotBeVirtual(string because = Environment.NewLine + GetDescriptionsFor(virtualMethods); - Execute.Assertion + assertionChain .ForCondition(virtualMethods.Length == 0) .BecauseOf(because, becauseArgs) .FailWith(failureMessage); @@ -108,7 +112,7 @@ private MethodInfo[] GetAllVirtualMethodsFromSelection() /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeAsync(string because = "", params object[] becauseArgs) + public AndConstraint BeAsync([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { MethodInfo[] nonAsyncMethods = SubjectMethods.Where(method => !method.IsAsync()).ToArray(); @@ -117,7 +121,7 @@ public AndConstraint BeAsync(string because = "", Environment.NewLine + GetDescriptionsFor(nonAsyncMethods); - Execute.Assertion + assertionChain .ForCondition(nonAsyncMethods.Length == 0) .BecauseOf(because, becauseArgs) .FailWith(failureMessage); @@ -135,7 +139,7 @@ public AndConstraint BeAsync(string because = "", /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeAsync(string because = "", params object[] becauseArgs) + public AndConstraint NotBeAsync([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { MethodInfo[] asyncMethods = SubjectMethods.Where(method => method.IsAsync()).ToArray(); @@ -144,7 +148,7 @@ public AndConstraint NotBeAsync(string because = " Environment.NewLine + GetDescriptionsFor(asyncMethods); - Execute.Assertion + assertionChain .ForCondition(asyncMethods.Length == 0) .BecauseOf(because, becauseArgs) .FailWith(failureMessage); @@ -162,8 +166,8 @@ public AndConstraint NotBeAsync(string because = " /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeDecoratedWith(string because = "", - params object[] becauseArgs) + public AndConstraint BeDecoratedWith( + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TAttribute : Attribute { return BeDecoratedWith(_ => true, because, becauseArgs); @@ -185,7 +189,8 @@ public AndConstraint BeDecoratedWith(s /// /// is . public AndConstraint BeDecoratedWith( - Expression> isMatchingAttributePredicate, string because = "", params object[] becauseArgs) + Expression> isMatchingAttributePredicate, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TAttribute : Attribute { Guard.ThrowIfArgumentIsNull(isMatchingAttributePredicate); @@ -197,7 +202,7 @@ public AndConstraint BeDecoratedWith( Environment.NewLine + GetDescriptionsFor(methodsWithoutAttribute); - Execute.Assertion + assertionChain .ForCondition(methodsWithoutAttribute.Length == 0) .BecauseOf(because, becauseArgs) .FailWith(failureMessage, typeof(TAttribute)); @@ -215,8 +220,8 @@ public AndConstraint BeDecoratedWith( /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeDecoratedWith(string because = "", - params object[] becauseArgs) + public AndConstraint NotBeDecoratedWith( + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TAttribute : Attribute { return NotBeDecoratedWith(_ => true, because, becauseArgs); @@ -238,7 +243,8 @@ public AndConstraint NotBeDecoratedWith /// is . public AndConstraint NotBeDecoratedWith( - Expression> isMatchingAttributePredicate, string because = "", params object[] becauseArgs) + Expression> isMatchingAttributePredicate, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TAttribute : Attribute { Guard.ThrowIfArgumentIsNull(isMatchingAttributePredicate); @@ -250,7 +256,7 @@ public AndConstraint NotBeDecoratedWith NotBeDecoratedWith /// Zero or more objects to format using the placeholders in . /// - public AndConstraint Be(CSharpAccessModifier accessModifier, string because = "", - params object[] becauseArgs) + public AndConstraint Be(CSharpAccessModifier accessModifier, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { var methods = SubjectMethods.Where(pi => pi.GetCSharpAccessModifier() != accessModifier).ToArray(); 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); @@ -296,15 +302,15 @@ public AndConstraint Be(CSharpAccessModifier acces /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBe(CSharpAccessModifier accessModifier, string because = "", - params object[] becauseArgs) + public AndConstraint NotBe(CSharpAccessModifier accessModifier, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { var methods = SubjectMethods.Where(pi => pi.GetCSharpAccessModifier() == accessModifier).ToArray(); 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 ffc98e79d6..85ada02e77 100644 --- a/Src/FluentAssertions/Types/PropertyInfoAssertions.cs +++ b/Src/FluentAssertions/Types/PropertyInfoAssertions.cs @@ -1,8 +1,10 @@ using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Reflection; using FluentAssertions.Common; using FluentAssertions.Execution; +using FluentAssertions.Formatting; namespace FluentAssertions.Types; @@ -12,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; } /// @@ -28,20 +33,23 @@ public PropertyInfoAssertions(PropertyInfo propertyInfo) /// Zero or more objects to format using the placeholders in . /// public AndConstraint BeVirtual( - 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 property to be virtual{reason}, but {context:property} is ."); + .FailWith("Expected property to be virtual{reason}, but {context:property} is .") + .Then + .ForCondition(Subject.IsVirtual()) + .BecauseOf(because, becauseArgs) + .FailWith(() => + { + var subjectDescription = assertionChain.HasOverriddenCallerIdentifier + ? assertionChain.CallerIdentifier + : "property " + Subject.ToFormattedString(); - if (success) - { - Execute.Assertion - .ForCondition(Subject.IsVirtual()) - .BecauseOf(because, becauseArgs) - .FailWith($"Expected property {GetDescriptionFor(Subject)} to be virtual{{reason}}, but it is not."); - } + return new FailReason($"Expected {subjectDescription} to be virtual{{reason}}, but it is not."); + }); return new AndConstraint(this); } @@ -56,20 +64,24 @@ public AndConstraint BeVirtual( /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeVirtual(string because = "", params object[] becauseArgs) + 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 property not to be virtual{reason}, but {context:property} is ."); + .FailWith("Expected property not to be virtual{reason}, but {context:property} is .") + .Then + .ForCondition(!Subject.IsVirtual()) + .BecauseOf(because, becauseArgs) + .FailWith(() => + { + var subjectDescription = assertionChain.HasOverriddenCallerIdentifier + ? assertionChain.CallerIdentifier + : "property " + Subject.ToFormattedString(); - if (success) - { - Execute.Assertion - .ForCondition(!Subject.IsVirtual()) - .BecauseOf(because, becauseArgs) - .FailWith($"Expected property {GetDescriptionFor(Subject)} not to be virtual{{reason}}, but it is."); - } + return new FailReason($"Expected property {subjectDescription} not to be virtual{{reason}}, but it is."); + }); return new AndConstraint(this); } @@ -85,22 +97,23 @@ public AndConstraint NotBeVirtual(string because = "", p /// Zero or more objects to format using the placeholders in . /// public AndConstraint BeWritable( - 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 property to have a setter{reason}, but {context:property} is ."); + .FailWith("Expected property to have a setter{reason}, but {context:property} is .") + .Then + .ForCondition(Subject!.CanWrite) + .BecauseOf(because, becauseArgs) + .FailWith(() => + { + var subjectDescription = assertionChain.HasOverriddenCallerIdentifier + ? assertionChain.CallerIdentifier + : "property " + Subject.ToFormattedString(); - if (success) - { - Execute.Assertion - .ForCondition(Subject.CanWrite) - .BecauseOf(because, becauseArgs) - .FailWith( - "Expected {context:property} {0} to have a setter{reason}.", - Subject); - } + return new FailReason($"Expected {subjectDescription} to have a setter{{reason}}."); + }); return new AndConstraint(this); } @@ -118,29 +131,30 @@ public AndConstraint BeWritable( /// /// /// is not a value. - public AndConstraint BeWritable(CSharpAccessModifier accessModifier, string because = "", - params object[] becauseArgs) + public AndConstraint BeWritable(CSharpAccessModifier accessModifier, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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); - - if (success) - { - Subject.GetSetMethod(nonPublic: true).Should().HaveAccessModifier(accessModifier, because, becauseArgs); - } + assertionChain.OverrideCallerIdentifier(() => "setter of " + subjectDescription); + assertionChain.ReuseOnce(); + + Subject!.GetSetMethod(nonPublic: true).Should().HaveAccessModifier(accessModifier, because, becauseArgs); } return new AndConstraint(this); @@ -157,22 +171,23 @@ public AndConstraint BeWritable(CSharpAccessModifier acc /// Zero or more objects to format using the placeholders in . /// public AndConstraint NotBeWritable( - 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 property not to have a setter{reason}, but {context:property} is ."); + .FailWith("Expected {context:property} not to have a setter{reason}, but it is .") + .Then + .ForCondition(!Subject!.CanWrite) + .BecauseOf(because, becauseArgs) + .FailWith(() => + { + var subjectDescription = assertionChain.HasOverriddenCallerIdentifier + ? assertionChain.CallerIdentifier + : "property " + Subject.ToFormattedString(); - if (success) - { - Execute.Assertion - .ForCondition(!Subject.CanWrite) - .BecauseOf(because, becauseArgs) - .FailWith( - "Expected {context:property} {0} not to have a setter{reason}.", - Subject); - } + return new FailReason($"Did not expect {subjectDescription} to have a setter{{reason}}."); + }); return new AndConstraint(this); } @@ -187,19 +202,24 @@ public AndConstraint NotBeWritable( /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeReadable(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 ."); + .FailWith("Expected property to have a getter{reason}, but {context:property} is .") + .Then + .ForCondition(Subject!.CanRead) + .BecauseOf(because, becauseArgs) + .FailWith(() => + { + var subjectDescription = assertionChain.HasOverriddenCallerIdentifier + ? assertionChain.CallerIdentifier + : "property " + Subject.ToFormattedString(); - if (success) - { - Execute.Assertion.ForCondition(Subject.CanRead) - .BecauseOf(because, becauseArgs) - .FailWith("Expected property " + Subject.Name + " to have a getter{reason}, but it does not."); - } + return new FailReason($"Expected property {subjectDescription} to have a getter{{reason}}, but it does not."); + }); return new AndConstraint(this); } @@ -217,26 +237,30 @@ public AndConstraint BeReadable(string because = "", par /// /// /// is not a value. - public AndConstraint BeReadable(CSharpAccessModifier accessModifier, string because = "", - params object[] becauseArgs) + public AndConstraint BeReadable(CSharpAccessModifier accessModifier, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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); @@ -253,22 +277,23 @@ public AndConstraint BeReadable(CSharpAccessModifier acc /// Zero or more objects to format using the placeholders in . /// public AndConstraint NotBeReadable( - 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 property not to have a getter{reason}, but {context:property} is ."); + .FailWith("Expected property not to have a getter{reason}, but {context:property} is .") + .Then + .ForCondition(!Subject!.CanRead) + .BecauseOf(because, becauseArgs) + .FailWith(() => + { + var subjectDescription = assertionChain.HasOverriddenCallerIdentifier + ? assertionChain.CallerIdentifier + : "property " + Subject.ToFormattedString(); - if (success) - { - Execute.Assertion - .ForCondition(!Subject.CanRead) - .BecauseOf(because, becauseArgs) - .FailWith( - "Expected {context:property} {0} not to have a getter{reason}.", - Subject); - } + return new FailReason($"Did not expect {subjectDescription} to have a getter{{reason}}."); + }); return new AndConstraint(this); } @@ -286,22 +311,18 @@ public AndConstraint NotBeReadable( /// /// is . public AndConstraint Return(Type propertyType, - string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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) - { - Execute.Assertion.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 to be {0}{reason}, but {context:property} is .", propertyType) + .Then.ForCondition(Subject!.PropertyType == propertyType) + .BecauseOf(because, becauseArgs) + .FailWith("Expected type of property {2} to be {0}{reason}, but it is {1}.", + propertyType, Subject.PropertyType, Subject); return new AndConstraint(this); } @@ -317,7 +338,8 @@ public AndConstraint Return(Type propertyType, /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint Return(string because = "", params object[] becauseArgs) + public AndConstraint Return([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { return Return(typeof(TReturn), because, becauseArgs); } @@ -334,22 +356,19 @@ public AndConstraint Return(string because = "" /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint NotReturn(Type propertyType, string because = "", params object[] becauseArgs) + public AndConstraint NotReturn(Type propertyType, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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) - { - Execute.Assertion - .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 not to be {0}{reason}, but {context:property} is .", propertyType) + .Then + .ForCondition(Subject!.PropertyType != propertyType) + .BecauseOf(because, becauseArgs) + .FailWith("Expected type of property {1} not to be {0}{reason}, but it is.", propertyType, Subject); return new AndConstraint(this); } @@ -365,24 +384,13 @@ public AndConstraint NotReturn(Type propertyType, string /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotReturn(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); + private protected override string SubjectDescription => Formatter.ToString(Subject); /// /// Returns the type of the subject the assertion applies on. diff --git a/Src/FluentAssertions/Types/PropertyInfoSelector.cs b/Src/FluentAssertions/Types/PropertyInfoSelector.cs index 3081c9727e..db860f3879 100644 --- a/Src/FluentAssertions/Types/PropertyInfoSelector.cs +++ b/Src/FluentAssertions/Types/PropertyInfoSelector.cs @@ -20,7 +20,7 @@ public class PropertyInfoSelector : IEnumerable /// The type from which to select properties. /// is . public PropertyInfoSelector(Type type) - : this(new[] { type }) + : this([type]) { } diff --git a/Src/FluentAssertions/Types/PropertyInfoSelectorAssertions.cs b/Src/FluentAssertions/Types/PropertyInfoSelectorAssertions.cs index 644652b364..4aca174681 100644 --- a/Src/FluentAssertions/Types/PropertyInfoSelectorAssertions.cs +++ b/Src/FluentAssertions/Types/PropertyInfoSelectorAssertions.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using FluentAssertions.Common; using FluentAssertions.Execution; +using FluentAssertions.Formatting; namespace FluentAssertions.Types; @@ -16,6 +18,8 @@ namespace FluentAssertions.Types; [DebuggerNonUserCode] public class PropertyInfoSelectorAssertions { + private readonly AssertionChain assertionChain; + /// /// Gets the object whose value is being asserted. /// @@ -26,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; @@ -43,11 +48,11 @@ public PropertyInfoSelectorAssertions(params PropertyInfo[] properties) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeVirtual(string because = "", params object[] becauseArgs) + public AndConstraint BeVirtual([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { PropertyInfo[] nonVirtualProperties = GetAllNonVirtualPropertiesFromSelection(); - Execute.Assertion + assertionChain .ForCondition(nonVirtualProperties.Length == 0) .BecauseOf(because, becauseArgs) .FailWith( @@ -67,11 +72,11 @@ public AndConstraint BeVirtual(string because = /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeVirtual(string because = "", params object[] becauseArgs) + public AndConstraint NotBeVirtual([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { PropertyInfo[] virtualProperties = GetAllVirtualPropertiesFromSelection(); - Execute.Assertion + assertionChain .ForCondition(virtualProperties.Length == 0) .BecauseOf(because, becauseArgs) .FailWith( @@ -91,11 +96,11 @@ public AndConstraint NotBeVirtual(string because /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeWritable(string because = "", params object[] becauseArgs) + public AndConstraint BeWritable([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { PropertyInfo[] readOnlyProperties = GetAllReadOnlyPropertiesFromSelection(); - Execute.Assertion + assertionChain .ForCondition(readOnlyProperties.Length == 0) .BecauseOf(because, becauseArgs) .FailWith( @@ -115,11 +120,11 @@ public AndConstraint BeWritable(string because = /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeWritable(string because = "", params object[] becauseArgs) + public AndConstraint NotBeWritable([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { PropertyInfo[] writableProperties = GetAllWritablePropertiesFromSelection(); - Execute.Assertion + assertionChain .ForCondition(writableProperties.Length == 0) .BecauseOf(because, becauseArgs) .FailWith( @@ -159,13 +164,13 @@ private PropertyInfo[] GetAllVirtualPropertiesFromSelection() /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeDecoratedWith(string because = "", - params object[] becauseArgs) + public AndConstraint BeDecoratedWith( + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TAttribute : Attribute { PropertyInfo[] propertiesWithoutAttribute = GetPropertiesWithout(); - Execute.Assertion + assertionChain .ForCondition(propertiesWithoutAttribute.Length == 0) .BecauseOf(because, becauseArgs) .FailWith( @@ -186,13 +191,13 @@ public AndConstraint BeDecoratedWith /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeDecoratedWith(string because = "", - params object[] becauseArgs) + public AndConstraint NotBeDecoratedWith( + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TAttribute : Attribute { PropertyInfo[] propertiesWithAttribute = GetPropertiesWith(); - Execute.Assertion + assertionChain .ForCondition(propertiesWithAttribute.Length == 0) .BecauseOf(because, becauseArgs) .FailWith( @@ -217,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 8c25e0bc7d..3fc3d8fdac 100644 --- a/Src/FluentAssertions/Types/TypeAssertions.cs +++ b/Src/FluentAssertions/Types/TypeAssertions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -14,14 +15,18 @@ namespace FluentAssertions.Types; /// Contains a number of methods to assert that a meets certain expectations. /// [DebuggerNonUserCode] +[SuppressMessage("Class Design", "AV1010:Member hides inherited member")] 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; } /// @@ -34,7 +39,8 @@ public TypeAssertions(Type type) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint Be(string because = "", params object[] becauseArgs) + public AndConstraint Be([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { return Be(typeof(TExpected), because, becauseArgs); } @@ -50,9 +56,10 @@ public AndConstraint Be(string because = "", params o /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint Be(Type expected, string because = "", params object[] becauseArgs) + 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)); @@ -72,7 +79,8 @@ public AndConstraint Be(Type expected, string because = "", para /// Zero or more objects to format using the placeholders in . /// /// An which can be used to chain assertions. - public new AndConstraint BeAssignableTo(string because = "", params object[] becauseArgs) + public new AndConstraint BeAssignableTo([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { return BeAssignableTo(typeof(T), because, becauseArgs); } @@ -90,7 +98,8 @@ public AndConstraint Be(Type expected, string because = "", para /// /// An which can be used to chain assertions. /// is . - public new AndConstraint BeAssignableTo(Type type, string because = "", params object[] becauseArgs) + public new AndConstraint BeAssignableTo(Type type, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(type); @@ -98,7 +107,7 @@ public AndConstraint Be(Type expected, string because = "", para ? 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); @@ -118,7 +127,8 @@ public AndConstraint Be(Type expected, string because = "", para /// Zero or more objects to format using the placeholders in . /// /// An which can be used to chain assertions. - public new AndConstraint NotBeAssignableTo(string because = "", params object[] becauseArgs) + public new AndConstraint NotBeAssignableTo([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { return NotBeAssignableTo(typeof(T), because, becauseArgs); } @@ -136,7 +146,8 @@ public AndConstraint Be(Type expected, string because = "", para /// /// An which can be used to chain assertions. /// is . - public new AndConstraint NotBeAssignableTo(Type type, string because = "", params object[] becauseArgs) + public new AndConstraint NotBeAssignableTo(Type type, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(type); @@ -144,7 +155,7 @@ public AndConstraint Be(Type expected, string because = "", para ? 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); @@ -189,7 +200,8 @@ private static string GetFailureMessageIfTypesAreDifferent(Type actual, Type exp /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBe(string because = "", params object[] becauseArgs) + public AndConstraint NotBe([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) { return NotBe(typeof(TUnexpected), because, becauseArgs); } @@ -205,11 +217,12 @@ public AndConstraint NotBe(string because = "", par /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBe(Type unexpected, string because = "", params object[] becauseArgs) + public AndConstraint NotBe(Type unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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."); @@ -228,18 +241,18 @@ public AndConstraint NotBe(Type unexpected, string because = "", /// Zero or more objects to format using the placeholders in . /// public AndWhichConstraint BeDecoratedWith( - string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TAttribute : Attribute { IEnumerable 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.", Subject, typeof(TAttribute)); - return new AndWhichConstraint(this, attributes); + return new AndWhichConstraint(this, attributes, assertionChain); } /// @@ -258,7 +271,8 @@ public AndWhichConstraint BeDecoratedWith /// is . public AndWhichConstraint BeDecoratedWith( - Expression> isMatchingAttributePredicate, string because = "", params object[] becauseArgs) + Expression> isMatchingAttributePredicate, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TAttribute : Attribute { Guard.ThrowIfArgumentIsNull(isMatchingAttributePredicate); @@ -267,14 +281,14 @@ public AndWhichConstraint BeDecoratedWith attributes = Subject.GetMatchingAttributes(isMatchingAttributePredicate); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(attributes.Any()) .FailWith( "Expected type {0} to be decorated with {1} that matches {2}{reason}, but no matching attribute was found.", Subject, typeof(TAttribute), isMatchingAttributePredicate); - return new AndWhichConstraint(this, attributes); + return new AndWhichConstraint(this, attributes, assertionChain); } /// @@ -288,18 +302,18 @@ public AndWhichConstraint BeDecoratedWith. /// public AndWhichConstraint BeDecoratedWithOrInherit( - string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TAttribute : Attribute { IEnumerable 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.", Subject, typeof(TAttribute)); - return new AndWhichConstraint(this, attributes); + return new AndWhichConstraint(this, attributes, assertionChain); } /// @@ -318,7 +332,8 @@ public AndWhichConstraint BeDecoratedWithOrInherit /// is . public AndWhichConstraint BeDecoratedWithOrInherit( - Expression> isMatchingAttributePredicate, string because = "", params object[] becauseArgs) + Expression> isMatchingAttributePredicate, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TAttribute : Attribute { Guard.ThrowIfArgumentIsNull(isMatchingAttributePredicate); @@ -327,14 +342,14 @@ public AndWhichConstraint BeDecoratedWithOrInherit attributes = Subject.GetMatchingOrInheritedAttributes(isMatchingAttributePredicate); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(attributes.Any()) .FailWith( "Expected type {0} to be decorated with or inherit {1} that matches {2}{reason}" + ", but no matching attribute was found.", Subject, typeof(TAttribute), isMatchingAttributePredicate); - return new AndWhichConstraint(this, attributes); + return new AndWhichConstraint(this, attributes, assertionChain); } /// @@ -347,10 +362,11 @@ public AndWhichConstraint BeDecoratedWithOrInherit /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeDecoratedWith(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.", @@ -375,12 +391,13 @@ public AndConstraint NotBeDecoratedWith(string becau /// /// is . public AndConstraint NotBeDecoratedWith( - Expression> isMatchingAttributePredicate, string because = "", params object[] becauseArgs) + Expression> isMatchingAttributePredicate, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TAttribute : Attribute { Guard.ThrowIfArgumentIsNull(isMatchingAttributePredicate); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(!Subject.IsDecoratedWith(isMatchingAttributePredicate)) .FailWith( @@ -402,10 +419,10 @@ public AndConstraint NotBeDecoratedWith( /// Zero or more objects to format using the placeholders in . /// public AndConstraint NotBeDecoratedWithOrInherit( - string because = "", params object[] becauseArgs) + [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.", @@ -431,12 +448,13 @@ public AndConstraint NotBeDecoratedWithOrInherit( /// /// is . public AndConstraint NotBeDecoratedWithOrInherit( - Expression> isMatchingAttributePredicate, string because = "", params object[] becauseArgs) + Expression> isMatchingAttributePredicate, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TAttribute : Attribute { Guard.ThrowIfArgumentIsNull(isMatchingAttributePredicate); - Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(!Subject.IsDecoratedWithOrInherit(isMatchingAttributePredicate)) .FailWith( @@ -458,7 +476,8 @@ public AndConstraint NotBeDecoratedWithOrInherit( /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint Implement(Type interfaceType, string because = "", params object[] becauseArgs) + public AndConstraint Implement(Type interfaceType, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(interfaceType); @@ -467,20 +486,21 @@ public AndConstraint Implement(Type interfaceType, string becaus return new AndConstraint(this); } - private bool AssertSubjectImplements(Type interfaceType, string because = "", params object[] becauseArgs) + private bool AssertSubjectImplements(Type interfaceType, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { bool containsInterface = interfaceType.IsAssignableFrom(Subject) && interfaceType != Subject; - return Execute.Assertion - .BecauseOf(because, becauseArgs) - .WithExpectation("Expected type {0} to implement interface {1}{reason}", Subject, interfaceType) + 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.") - .Then - .ClearExpectation(); + .FailWith(", but it does not.")); + + return assertionChain.Succeeded; } /// @@ -494,7 +514,8 @@ private bool AssertSubjectImplements(Type interfaceType, string because = "", pa /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint Implement(string because = "", params object[] becauseArgs) + public AndConstraint Implement([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) where TInterface : class { return Implement(typeof(TInterface), because, becauseArgs); @@ -512,22 +533,21 @@ public AndConstraint Implement(string because = "", /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint NotImplement(Type interfaceType, string because = "", params object[] becauseArgs) + public AndConstraint NotImplement(Type interfaceType, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(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) - .FailWith(", but {0} is not an interface.", interfaceType) - .Then - .ForCondition(!containsInterface) - .FailWith(", but it does.", interfaceType) - .Then - .ClearExpectation(); + .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)); return new AndConstraint(this); } @@ -543,7 +563,8 @@ public AndConstraint NotImplement(Type interfaceType, string bec /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotImplement(string because = "", params object[] becauseArgs) + public AndConstraint NotImplement([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) where TInterface : class { return NotImplement(typeof(TInterface), because, becauseArgs); @@ -561,7 +582,8 @@ public AndConstraint NotImplement(string because = " /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint BeDerivedFrom(Type baseType, string because = "", params object[] becauseArgs) + public AndConstraint BeDerivedFrom(Type baseType, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(baseType); @@ -569,16 +591,14 @@ public AndConstraint BeDerivedFrom(Type baseType, string because ? 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) - .FailWith(", but {0} is an interface.", baseType) - .Then - .ForCondition(isDerivedFrom) - .FailWith(", but it is not.") - .Then - .ClearExpectation(); + .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.")); return new AndConstraint(this); } @@ -594,7 +614,8 @@ public AndConstraint BeDerivedFrom(Type baseType, string because /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeDerivedFrom(string because = "", params object[] becauseArgs) + public AndConstraint BeDerivedFrom([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) where TBaseClass : class { return BeDerivedFrom(typeof(TBaseClass), because, becauseArgs); @@ -612,7 +633,8 @@ public AndConstraint BeDerivedFrom(string because = /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint NotBeDerivedFrom(Type baseType, string because = "", params object[] becauseArgs) + public AndConstraint NotBeDerivedFrom(Type baseType, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(baseType); @@ -620,16 +642,14 @@ public AndConstraint NotBeDerivedFrom(Type baseType, string beca ? 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) - .FailWith(", but {0} is an interface.", baseType) - .Then - .ForCondition(!isDerivedFrom) - .FailWith(", but it is.") - .Then - .ClearExpectation(); + .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.")); return new AndConstraint(this); } @@ -645,7 +665,8 @@ public AndConstraint NotBeDerivedFrom(Type baseType, string beca /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeDerivedFrom(string because = "", params object[] becauseArgs) + public AndConstraint NotBeDerivedFrom([StringSyntax("CompositeFormat")] string because = "", + params object[] becauseArgs) where TBaseClass : class { return NotBeDerivedFrom(typeof(TBaseClass), because, becauseArgs); @@ -663,18 +684,19 @@ public AndConstraint NotBeDerivedFrom(string because /// /// /// is not a class. - public AndConstraint BeSealed(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); @@ -695,18 +717,19 @@ public AndConstraint BeSealed(string because = "", params object /// /// /// is not a class. - public AndConstraint NotBeSealed(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); @@ -727,18 +750,19 @@ public AndConstraint NotBeSealed(string because = "", params obj /// /// /// is not a class. - public AndConstraint BeAbstract(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); @@ -759,18 +783,19 @@ public AndConstraint BeAbstract(string because = "", params obje /// /// /// is not a class. - public AndConstraint NotBeAbstract(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); @@ -791,18 +816,19 @@ public AndConstraint NotBeAbstract(string because = "", params o /// /// /// is not a class. - public AndConstraint BeStatic(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); @@ -823,18 +849,19 @@ public AndConstraint BeStatic(string because = "", params object /// /// /// is not a class. - public AndConstraint NotBeStatic(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); @@ -860,31 +887,40 @@ public AndConstraint NotBeStatic(string because = "", params obj /// is . /// is empty. public AndWhichConstraint HaveProperty( - Type propertyType, string name, string because = "", params object[] becauseArgs) + Type propertyType, string name, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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 + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(propertyInfo is not null) - .FailWith($"Expected {propertyType.Name} {Subject}.{name} to exist{{reason}}, but it does not.") + .FailWith(() => + { + var subjectDescription = assertionChain.HasOverriddenCallerIdentifier + ? assertionChain.CallerIdentifier + : Subject!.Name; + + return new FailReason( + $"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); @@ -906,7 +942,7 @@ public AndWhichConstraint HaveProperty( /// is . /// is empty. public AndWhichConstraint HaveProperty( - string name, string because = "", params object[] becauseArgs) + string name, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return HaveProperty(typeof(TProperty), name, because, becauseArgs); } @@ -924,24 +960,31 @@ public AndWhichConstraint HaveProperty( /// /// is . /// is empty. - public AndConstraint NotHaveProperty(string name, string because = "", params object[] becauseArgs) + public AndConstraint NotHaveProperty(string name, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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) { 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(() => + { + var subjectDescription = + assertionChain.HasOverriddenCallerIdentifier ? assertionChain.CallerIdentifier : Subject!.Name; + + return new FailReason( + $"Did not expect {subjectDescription} to have a property {propertyInfo?.Name}{{reason}}, but it does."); + }); } return new AndConstraint(this); @@ -964,32 +1007,28 @@ public AndConstraint NotHaveProperty(string name, string because /// is . /// is empty. public AndConstraint HaveExplicitProperty( - Type interfaceType, string name, string because = "", params object[] becauseArgs) + Type interfaceType, string name, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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); @@ -1011,7 +1050,8 @@ public AndConstraint HaveExplicitProperty( /// is . /// is empty. public AndConstraint HaveExplicitProperty( - string name, string because = "", params object[] becauseArgs) + string name, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TInterface : class { return HaveExplicitProperty(typeof(TInterface), name, because, becauseArgs); @@ -1034,33 +1074,29 @@ public AndConstraint HaveExplicitProperty( /// is . /// is empty. public AndConstraint NotHaveExplicitProperty( - Type interfaceType, string name, string because = "", params object[] becauseArgs) + Type interfaceType, string name, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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); @@ -1082,7 +1118,8 @@ public AndConstraint NotHaveExplicitProperty( /// is . /// is empty. public AndConstraint NotHaveExplicitProperty( - string name, string because = "", params object[] becauseArgs) + string name, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TInterface : class { return NotHaveExplicitProperty(typeof(TInterface), name, because, becauseArgs); @@ -1107,34 +1144,30 @@ public AndConstraint NotHaveExplicitProperty( /// is empty. /// is . public AndConstraint HaveExplicitMethod( - Type interfaceType, string name, IEnumerable parameterTypes, string because = "", params object[] becauseArgs) + Type interfaceType, string name, IEnumerable parameterTypes, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(interfaceType); 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); @@ -1158,7 +1191,8 @@ public AndConstraint HaveExplicitMethod( /// is empty. /// is . public AndConstraint HaveExplicitMethod( - string name, IEnumerable parameterTypes, string because = "", params object[] becauseArgs) + string name, IEnumerable parameterTypes, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TInterface : class { return HaveExplicitMethod(typeof(TInterface), name, parameterTypes, because, becauseArgs); @@ -1183,34 +1217,30 @@ public AndConstraint HaveExplicitMethod( /// is empty. /// is . public AndConstraint NotHaveExplicitMethod( - Type interfaceType, string name, IEnumerable parameterTypes, string because = "", params object[] becauseArgs) + Type interfaceType, string name, IEnumerable parameterTypes, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(interfaceType); 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); @@ -1234,7 +1264,8 @@ public AndConstraint NotHaveExplicitMethod( /// is empty. /// is . public AndConstraint NotHaveExplicitMethod( - string name, IEnumerable parameterTypes, string because = "", params object[] becauseArgs) + string name, IEnumerable parameterTypes, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TInterface : class { return NotHaveExplicitMethod(typeof(TInterface), name, parameterTypes, because, becauseArgs); @@ -1256,37 +1287,40 @@ public AndConstraint NotHaveExplicitMethod( /// is . /// is . public AndWhichConstraint HaveIndexer( - Type indexerType, IEnumerable parameterTypes, string because = "", params object[] becauseArgs) + Type indexerType, IEnumerable parameterTypes, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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}]"); } /// @@ -1303,22 +1337,23 @@ public AndWhichConstraint HaveIndexer( /// /// is . public AndConstraint NotHaveIndexer( - IEnumerable parameterTypes, string because = "", params object[] becauseArgs) + IEnumerable parameterTypes, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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( @@ -1346,12 +1381,13 @@ public AndConstraint NotHaveIndexer( /// is empty. /// is . public AndWhichConstraint HaveMethod( - string name, IEnumerable parameterTypes, string because = "", params object[] becauseArgs) + string name, IEnumerable parameterTypes, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNullOrEmpty(name); Guard.ThrowIfArgumentIsNull(parameterTypes); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith( @@ -1360,11 +1396,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( @@ -1392,24 +1428,25 @@ public AndWhichConstraint HaveMethod( /// is empty. /// is . public AndConstraint NotHaveMethod( - string name, IEnumerable parameterTypes, string because = "", params object[] becauseArgs) + string name, IEnumerable parameterTypes, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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( @@ -1433,11 +1470,12 @@ public AndConstraint NotHaveMethod( /// /// is . public AndWhichConstraint HaveConstructor( - IEnumerable parameterTypes, string because = "", params object[] becauseArgs) + IEnumerable parameterTypes, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(parameterTypes); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith( @@ -1446,11 +1484,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( @@ -1472,9 +1510,9 @@ public AndWhichConstraint HaveConstructor( /// Zero or more objects to format using the placeholders in . /// public AndWhichConstraint HaveDefaultConstructor( - string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - return HaveConstructor(Array.Empty(), because, becauseArgs); + return HaveConstructor([], because, becauseArgs); } /// @@ -1490,11 +1528,12 @@ public AndWhichConstraint HaveDefaultConstructo /// /// is . public AndWhichConstraint NotHaveConstructor( - IEnumerable parameterTypes, string because = "", params object[] becauseArgs) + IEnumerable parameterTypes, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(parameterTypes); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith( @@ -1503,11 +1542,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( @@ -1529,9 +1568,9 @@ public AndWhichConstraint NotHaveConstructor( /// Zero or more objects to format using the placeholders in . /// public AndWhichConstraint NotHaveDefaultConstructor( - string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - return NotHaveConstructor(Array.Empty(), because, becauseArgs); + return NotHaveConstructor([], because, becauseArgs); } private static string GetParameterString(IEnumerable parameterTypes) @@ -1553,20 +1592,21 @@ private static string GetParameterString(IEnumerable parameterTypes) /// /// is not a value. public AndConstraint HaveAccessModifier( - CSharpAccessModifier accessModifier, string because = "", params object[] becauseArgs) + CSharpAccessModifier accessModifier, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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( @@ -1591,20 +1631,21 @@ public AndConstraint HaveAccessModifier( /// /// is not a value. public AndConstraint NotHaveAccessModifier( - CSharpAccessModifier accessModifier, string because = "", params object[] becauseArgs) + CSharpAccessModifier accessModifier, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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) @@ -1628,7 +1669,7 @@ public AndConstraint NotHaveAccessModifier( /// Zero or more objects to format using the placeholders in . /// public AndWhichConstraint HaveImplicitConversionOperator( - string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return HaveImplicitConversionOperator(typeof(TSource), typeof(TTarget), because, becauseArgs); } @@ -1649,12 +1690,13 @@ public AndWhichConstraint HaveImplicitConversionOper /// is . /// is . public AndWhichConstraint HaveImplicitConversionOperator( - Type sourceType, Type targetType, string because = "", params object[] becauseArgs) + Type sourceType, Type targetType, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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 .", @@ -1662,18 +1704,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); } /// @@ -1690,7 +1732,7 @@ public AndWhichConstraint HaveImplicitConversionOper /// Zero or more objects to format using the placeholders in . /// public AndConstraint NotHaveImplicitConversionOperator( - string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return NotHaveImplicitConversionOperator(typeof(TSource), typeof(TTarget), because, becauseArgs); } @@ -1711,22 +1753,23 @@ public AndConstraint NotHaveImplicitConversionOperator is . /// is . public AndConstraint NotHaveImplicitConversionOperator( - Type sourceType, Type targetType, string because = "", params object[] becauseArgs) + Type sourceType, Type targetType, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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.", @@ -1750,7 +1793,7 @@ public AndConstraint NotHaveImplicitConversionOperator( /// Zero or more objects to format using the placeholders in . /// public AndWhichConstraint HaveExplicitConversionOperator( - string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return HaveExplicitConversionOperator(typeof(TSource), typeof(TTarget), because, becauseArgs); } @@ -1771,12 +1814,13 @@ public AndWhichConstraint HaveExplicitConversionOper /// is . /// is . public AndWhichConstraint HaveExplicitConversionOperator( - Type sourceType, Type targetType, string because = "", params object[] becauseArgs) + Type sourceType, Type targetType, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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 .", @@ -1784,11 +1828,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.", @@ -1812,7 +1856,7 @@ public AndWhichConstraint HaveExplicitConversionOper /// Zero or more objects to format using the placeholders in . /// public AndConstraint NotHaveExplicitConversionOperator( - string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return NotHaveExplicitConversionOperator(typeof(TSource), typeof(TTarget), because, becauseArgs); } @@ -1833,22 +1877,23 @@ public AndConstraint NotHaveExplicitConversionOperator is . /// is . public AndConstraint NotHaveExplicitConversionOperator( - Type sourceType, Type targetType, string because = "", params object[] becauseArgs) + Type sourceType, Type targetType, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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/TypeSelector.cs b/Src/FluentAssertions/Types/TypeSelector.cs index c2138ff550..e6d46458d9 100644 --- a/Src/FluentAssertions/Types/TypeSelector.cs +++ b/Src/FluentAssertions/Types/TypeSelector.cs @@ -15,7 +15,7 @@ public class TypeSelector : IEnumerable private List types; public TypeSelector(Type type) - : this(new[] { type }) + : this([type]) { } diff --git a/Src/FluentAssertions/Types/TypeSelectorAssertions.cs b/Src/FluentAssertions/Types/TypeSelectorAssertions.cs index 90ab465827..41711ae15f 100644 --- a/Src/FluentAssertions/Types/TypeSelectorAssertions.cs +++ b/Src/FluentAssertions/Types/TypeSelectorAssertions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; using FluentAssertions.Common; @@ -17,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); @@ -44,14 +48,14 @@ public TypeSelectorAssertions(params Type[] types) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeDecoratedWith(string because = "", params object[] becauseArgs) + public AndConstraint BeDecoratedWith([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TAttribute : Attribute { Type[] typesWithoutAttribute = Subject .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}," + @@ -78,7 +82,8 @@ public AndConstraint BeDecoratedWith(string /// /// is . public AndConstraint BeDecoratedWith( - Expression> isMatchingAttributePredicate, string because = "", params object[] becauseArgs) + Expression> isMatchingAttributePredicate, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TAttribute : Attribute { Guard.ThrowIfArgumentIsNull(isMatchingAttributePredicate); @@ -87,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}," + @@ -109,15 +114,15 @@ public AndConstraint BeDecoratedWith( /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeDecoratedWithOrInherit(string because = "", - params object[] becauseArgs) + public AndConstraint BeDecoratedWithOrInherit( + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TAttribute : Attribute { Type[] typesWithoutAttribute = Subject .Where(type => !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}," + @@ -144,7 +149,8 @@ public AndConstraint BeDecoratedWithOrInherit /// is . public AndConstraint BeDecoratedWithOrInherit( - Expression> isMatchingAttributePredicate, string because = "", params object[] becauseArgs) + Expression> isMatchingAttributePredicate, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TAttribute : Attribute { Guard.ThrowIfArgumentIsNull(isMatchingAttributePredicate); @@ -153,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}," + @@ -175,14 +181,14 @@ public AndConstraint BeDecoratedWithOrInherit /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeDecoratedWith(string because = "", params object[] becauseArgs) + public AndConstraint NotBeDecoratedWith([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TAttribute : Attribute { Type[] typesWithAttribute = Subject .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}," + @@ -209,7 +215,8 @@ public AndConstraint NotBeDecoratedWith(stri /// /// is . public AndConstraint NotBeDecoratedWith( - Expression> isMatchingAttributePredicate, string because = "", params object[] becauseArgs) + Expression> isMatchingAttributePredicate, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TAttribute : Attribute { Guard.ThrowIfArgumentIsNull(isMatchingAttributePredicate); @@ -218,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}," + @@ -240,15 +247,15 @@ public AndConstraint NotBeDecoratedWith( /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeDecoratedWithOrInherit(string because = "", - params object[] becauseArgs) + public AndConstraint NotBeDecoratedWithOrInherit( + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TAttribute : Attribute { Type[] typesWithAttribute = Subject .Where(type => 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}," + @@ -275,7 +282,8 @@ public AndConstraint NotBeDecoratedWithOrInherit /// is . public AndConstraint NotBeDecoratedWithOrInherit( - Expression> isMatchingAttributePredicate, string because = "", params object[] becauseArgs) + Expression> isMatchingAttributePredicate, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) where TAttribute : Attribute { Guard.ThrowIfArgumentIsNull(isMatchingAttributePredicate); @@ -284,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}," + @@ -306,11 +314,11 @@ public AndConstraint NotBeDecoratedWithOrInherit /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeSealed(string because = "", params object[] becauseArgs) + public AndConstraint BeSealed([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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)); @@ -328,11 +336,11 @@ public AndConstraint BeSealed(string because = "", param /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeSealed(string because = "", params object[] becauseArgs) + public AndConstraint NotBeSealed([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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)); @@ -353,14 +361,14 @@ public AndConstraint NotBeSealed(string because = "", pa /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeInNamespace(string @namespace, string because = "", - params object[] becauseArgs) + public AndConstraint BeInNamespace(string @namespace, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Type[] typesNotInNamespace = Subject .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}," + @@ -384,14 +392,14 @@ public AndConstraint BeInNamespace(string @namespace, st /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeInNamespace(string @namespace, string because = "", - params object[] becauseArgs) + public AndConstraint NotBeInNamespace(string @namespace, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Type[] typesInNamespace = Subject .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}," + @@ -415,14 +423,14 @@ public AndConstraint NotBeInNamespace(string @namespace, /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeUnderNamespace(string @namespace, string because = "", - params object[] becauseArgs) + public AndConstraint BeUnderNamespace(string @namespace, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Type[] typesNotUnderNamespace = Subject .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}," + @@ -447,14 +455,14 @@ public AndConstraint BeUnderNamespace(string @namespace, /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeUnderNamespace(string @namespace, string because = "", - params object[] becauseArgs) + public AndConstraint NotBeUnderNamespace(string @namespace, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Type[] typesUnderNamespace = Subject .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/Value.cs b/Src/FluentAssertions/Value.cs new file mode 100644 index 0000000000..a07827ceff --- /dev/null +++ b/Src/FluentAssertions/Value.cs @@ -0,0 +1,36 @@ +using System; +using System.Linq.Expressions; +using FluentAssertions.Common; +using FluentAssertions.Equivalency.Inlining; + +namespace FluentAssertions; + +/// +/// Provides a fluent API for defining inline assertions. +/// +public static class Value +{ + /// + /// Builds an inline assertion that expects the subject-under-test to match the specified condition. + /// + /// The type of the subject-under-test. + /// A Boolean condition to match. + public static IInlineEquivalencyAssertion ThatMatches(Expression> condition) + { + Guard.ThrowIfArgumentIsNull(condition); + return new ConditionBasedInlineAssertion(condition); + } + + /// + /// Builds an inline assertion that executes the specific assertion operation on the subject-under-test. + /// + /// The type of the subject-under-test. + /// + /// The assertion operation to execute, typically using one of the assertion APIs Fluent Assertions provides. + /// + public static IInlineEquivalencyAssertion ThatSatisfies(Action assertion) + { + Guard.ThrowIfArgumentIsNull(assertion); + return new ActionBasedInlineAssertion(assertion); + } +} diff --git a/Src/FluentAssertions/Xml/Equivalency/Node.cs b/Src/FluentAssertions/Xml/Equivalency/Node.cs index 2d6e383aee..8e19a40132 100644 --- a/Src/FluentAssertions/Xml/Equivalency/Node.cs +++ b/Src/FluentAssertions/Xml/Equivalency/Node.cs @@ -7,7 +7,7 @@ namespace FluentAssertions.Xml.Equivalency; internal sealed class Node { - private readonly List children = new(); + private readonly List children = []; private readonly string name; private int count; diff --git a/Src/FluentAssertions/Xml/Equivalency/XmlReaderValidator.cs b/Src/FluentAssertions/Xml/Equivalency/XmlReaderValidator.cs index e906bcc42e..79290986d4 100644 --- a/Src/FluentAssertions/Xml/Equivalency/XmlReaderValidator.cs +++ b/Src/FluentAssertions/Xml/Equivalency/XmlReaderValidator.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Xml; using FluentAssertions.Execution; @@ -8,39 +9,44 @@ 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, string because, object[] becauseArgs) + public XmlReaderValidator(AssertionChain assertionChain, XmlReader subjectReader, XmlReader expectationReader, + [StringSyntax("CompositeFormat")] string because, object[] becauseArgs) { - assertion = Execute.Assertion.BecauseOf(because, becauseArgs); + this.assertionChain = assertionChain; + assertionChain.BecauseOf(because, becauseArgs); this.subjectReader = subjectReader; this.expectationReader = expectationReader; } - public void Validate(bool shouldBeEquivalent) + public void AssertThatItIsEquivalent() { Failure failure = Validate(); - if (shouldBeEquivalent && failure is not null) + if (failure is not null) { - assertion.FailWith(failure.FormatString, failure.FormatParams); + assertionChain.FailWith(failure.FormatString, failure.FormatParams); } + } - if (!shouldBeEquivalent && failure is null) + public void AssertThatIsNotEquivalent() + { + Failure failure = Validate(); + + if (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."); } } -#pragma warning disable MA0051 private Failure Validate() -#pragma warning restore MA0051 { if (subjectReader is null && expectationReader is null) { @@ -79,6 +85,7 @@ private Failure Validate() #pragma warning restore IDE0010 { case XmlNodeType.Element: + { failure = ValidateStartElement(); if (failure is not null) @@ -108,24 +115,36 @@ private Failure Validate() { subjectIterator.MoveToEndElement(); } + else + { + // Both are either empty or not empty, so we can continue + } + // Both are either empty or not empty, so we can continue break; + } case XmlNodeType.EndElement: + { // No need to verify end element, if it doesn't match // the start element it isn't valid XML, so the parser // would handle that. currentNode.Pop(); currentNode = currentNode.Parent; break; + } case XmlNodeType.Text: + { failure = ValidateText(); break; + } default: + { throw new NotSupportedException( $"{expectationIterator.NodeType} found at {currentNode.GetXPath()} is not supported for equivalency comparison."); + } } if (failure is not null) @@ -161,9 +180,9 @@ private Failure ValidateAttributes() foreach (AttributeData subjectAttribute in subjectAttributes) { - AttributeData expectedAttribute = expectedAttributes.SingleOrDefault( - ea => ea.NamespaceUri == subjectAttribute.NamespaceUri - && ea.LocalName == subjectAttribute.LocalName); + AttributeData expectedAttribute = expectedAttributes.SingleOrDefault(ea => + ea.NamespaceUri == subjectAttribute.NamespaceUri + && ea.LocalName == subjectAttribute.LocalName); if (expectedAttribute is null) { diff --git a/Src/FluentAssertions/Xml/XAttributeAssertions.cs b/Src/FluentAssertions/Xml/XAttributeAssertions.cs index e5bf240bb9..5c63862d5c 100644 --- a/Src/FluentAssertions/Xml/XAttributeAssertions.cs +++ b/Src/FluentAssertions/Xml/XAttributeAssertions.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Xml.Linq; using FluentAssertions.Execution; using FluentAssertions.Primitives; @@ -11,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; } /// @@ -30,9 +34,10 @@ public XAttributeAssertions(XAttribute attribute) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint Be(XAttribute expected, string because = "", params object[] becauseArgs) + 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); @@ -52,9 +57,10 @@ public AndConstraint Be(XAttribute expected, string becaus /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBe(XAttribute unexpected, string because = "", params object[] becauseArgs) + 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); @@ -73,17 +79,18 @@ public AndConstraint NotBe(XAttribute unexpected, string b /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveValue(string expected, string because = "", params object[] becauseArgs) + 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 - .ForCondition(Subject.Value == expected) + assertionChain + .ForCondition(Subject!.Value == expected) .BecauseOf(because, becauseArgs) .FailWith("Expected {context} \"{0}\" to have value {1}{reason}, but found {2}.", Subject.Name, expected, Subject.Value); diff --git a/Src/FluentAssertions/Xml/XDocumentAssertions.cs b/Src/FluentAssertions/Xml/XDocumentAssertions.cs index 2760a6e0b9..e3ce0a033d 100644 --- a/Src/FluentAssertions/Xml/XDocumentAssertions.cs +++ b/Src/FluentAssertions/Xml/XDocumentAssertions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Xml; using System.Xml.Linq; @@ -17,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; } /// @@ -37,9 +41,10 @@ public XDocumentAssertions(XDocument document) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint Be(XDocument expected, string because = "", params object[] becauseArgs) + 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); @@ -59,9 +64,10 @@ public AndConstraint Be(XDocument expected, string because /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBe(XDocument unexpected, string because = "", params object[] becauseArgs) + 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); @@ -81,13 +87,14 @@ public AndConstraint NotBe(XDocument unexpected, string bec /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeEquivalentTo(XDocument expected, string because = "", params object[] becauseArgs) + public AndConstraint BeEquivalentTo(XDocument expected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { using (XmlReader subjectReader = Subject?.CreateReader()) using (XmlReader otherReader = expected?.CreateReader()) { - var xmlReaderValidator = new XmlReaderValidator(subjectReader, otherReader, because, becauseArgs); - xmlReaderValidator.Validate(shouldBeEquivalent: true); + var xmlReaderValidator = new XmlReaderValidator(assertionChain, subjectReader, otherReader, because, becauseArgs); + xmlReaderValidator.AssertThatItIsEquivalent(); } return new AndConstraint(this); @@ -105,14 +112,14 @@ public AndConstraint BeEquivalentTo(XDocument expected, str /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeEquivalentTo(XDocument unexpected, string because = "", - params object[] becauseArgs) + public AndConstraint NotBeEquivalentTo(XDocument unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { using (XmlReader subjectReader = Subject?.CreateReader()) using (XmlReader otherReader = unexpected?.CreateReader()) { - var xmlReaderValidator = new XmlReaderValidator(subjectReader, otherReader, because, becauseArgs); - xmlReaderValidator.Validate(shouldBeEquivalent: false); + var xmlReaderValidator = new XmlReaderValidator(assertionChain, subjectReader, otherReader, because, becauseArgs); + xmlReaderValidator.AssertThatIsNotEquivalent(); } return new AndConstraint(this); @@ -131,8 +138,8 @@ public AndConstraint NotBeEquivalentTo(XDocument unexpected /// Zero or more objects to format using the placeholders in . /// /// is . - public AndWhichConstraint HaveRoot(string expected, string because = "", - params object[] becauseArgs) + public AndWhichConstraint HaveRoot(string expected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(expected, nameof(expected), "Cannot assert the document has a root element if the expected name is ."); @@ -153,8 +160,8 @@ public AndWhichConstraint HaveRoot(string expecte /// Zero or more objects to format using the placeholders in . /// /// is . - public AndWhichConstraint HaveRoot(XName expected, string because = "", - params object[] becauseArgs) + public AndWhichConstraint HaveRoot(XName expected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { if (Subject is null) { @@ -167,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}"); } /// @@ -192,8 +199,8 @@ public AndWhichConstraint HaveRoot(XName expected /// Zero or more objects to format using the placeholders in . /// /// is . - public AndWhichConstraint HaveElement(string expected, string because = "", - params object[] becauseArgs) + public AndWhichConstraint HaveElement(string expected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(expected, nameof(expected), "Cannot assert the document has an element if the expected name is ."); @@ -220,7 +227,8 @@ public AndWhichConstraint HaveElement(string expe /// /// is . public AndWhichConstraint> HaveElement(string expected, - OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) + OccurrenceConstraint occurrenceConstraint, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(expected, nameof(expected), "Cannot assert the document has an element if the expected name is ."); @@ -243,8 +251,8 @@ public AndWhichConstraint> HaveElemen /// Zero or more objects to format using the placeholders in . /// /// is . - public AndWhichConstraint HaveElement(XName expected, string because = "", - params object[] becauseArgs) + public AndWhichConstraint HaveElement(XName expected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { if (Subject is null) { @@ -254,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( @@ -263,11 +271,11 @@ public AndWhichConstraint HaveElement(XName expec XElement xElement = null; - if (success) + if (assertionChain.Succeeded) { - xElement = Subject.Root.Element(expected); + xElement = Subject.Root!.Element(expected); - Execute.Assertion + assertionChain .ForCondition(xElement is not null) .BecauseOf(because, becauseArgs) .FailWith( @@ -275,7 +283,7 @@ public AndWhichConstraint HaveElement(XName expec expected.ToString()); } - return new AndWhichConstraint(this, xElement); + return new AndWhichConstraint(this, xElement, assertionChain, "/" + expected); } /// @@ -297,36 +305,36 @@ public AndWhichConstraint HaveElement(XName expec /// /// is . public AndWhichConstraint> HaveElement(XName expected, - OccurrenceConstraint occurrenceConstraint, string because = "", - params object[] becauseArgs) + OccurrenceConstraint occurrenceConstraint, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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 = Enumerable.Empty(); + IEnumerable xElements = []; - if (success) + if (assertionChain.Succeeded) { - var root = Subject.Root; + 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); + xElements = root!.Elements(expected); int actual = xElements.Count(); - Execute.Assertion + assertionChain .ForConstraint(occurrenceConstraint, actual) .BecauseOf(because, becauseArgs) .FailWith( @@ -336,7 +344,198 @@ public AndWhichConstraint> HaveElemen } } - return new AndWhichConstraint>(this, xElements); + return new AndWhichConstraint>(this, xElements, assertionChain, + "/" + expected); + } + + /// + /// Asserts that the of the current doesn't have the specified child element. + /// + /// + /// The name of the expected child element of the current element's . + /// + /// + /// 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 AndConstraint NotHaveElement(string unexpectedElement, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNull(unexpectedElement, nameof(unexpectedElement)); + + return NotHaveElement(XNamespace.None + unexpectedElement, because, becauseArgs); + } + + /// + /// Asserts that the of the current doesn't have the specified child element. + /// + /// + /// The full name of the expected child element of the current element's . + /// + /// + /// 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 AndConstraint NotHaveElement(XName unexpectedElement, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNull(unexpectedElement, nameof(unexpectedElement)); + + assertionChain + .BecauseOf(because, becauseArgs) + .WithExpectation("Did not expect {context:subject} to have an element {0}{reason}, ", unexpectedElement, chain => + chain + .ForCondition(Subject is not null) + .FailWith("but the element itself is .") + .Then + .ForCondition(!Subject!.Root!.Elements(unexpectedElement).Any()) + .FailWith(" but the element {0} was found.", unexpectedElement)); + + return new AndConstraint(this); + } + + /// + /// Asserts that the of the current has the specified child element + /// with the specified name. + /// + /// + /// The name of the expected child element of the current element's . + /// + /// + /// The expected value of this particular element. + /// + /// + /// 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 AndWhichConstraint HaveElementWithValue(string expectedElement, + string expectedValue, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNull(expectedElement, nameof(expectedElement)); + Guard.ThrowIfArgumentIsNull(expectedValue, nameof(expectedValue)); + + return HaveElementWithValue(XNamespace.None + expectedElement, expectedValue, because, becauseArgs); + } + + /// + /// Asserts that the of the current has the specified child element + /// with the specified name. + /// + /// + /// The full name of the expected child element of the current element's . + /// + /// + /// The expected value of this particular element. + /// + /// + /// 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 AndWhichConstraint HaveElementWithValue(XName expectedElement, + string expectedValue, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNull(expectedElement, nameof(expectedElement)); + Guard.ThrowIfArgumentIsNull(expectedValue, nameof(expectedValue)); + + IEnumerable xElements = []; + + assertionChain + .WithExpectation("Expected {context:subject} to have an element {0} with value {1}{reason}, ", + expectedElement.ToString(), expectedValue, chain => chain + .ForCondition(Subject is not null) + .BecauseOf(because, becauseArgs) + .FailWith( + "but the element itself is .", + expectedElement.ToString(), expectedValue) + .Then + .Given(() => + { + xElements = Subject!.Root!.Elements(expectedElement).ToList(); + + return xElements; + }) + .ForCondition(collection => collection.Any()) + .FailWith("but the element {0} isn't found.", expectedElement) + .Then + .ForCondition(collection => collection.Any(e => e.Value == expectedValue)) + .FailWith("but the element {0} does not have such a value.", expectedElement)); + + return new AndWhichConstraint(this, xElements.FirstOrDefault()); + } + + /// + /// Asserts that the of the current either doesn't have the + /// specified child element or doesn't have the specified . + /// + /// + /// The name of the unexpected child element of the current element's . + /// + /// + /// The unexpected value of this particular element. + /// + /// + /// 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 AndConstraint NotHaveElementWithValue(string unexpectedElement, + string unexpectedValue, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNull(unexpectedElement, nameof(unexpectedElement)); + Guard.ThrowIfArgumentIsNull(unexpectedValue, nameof(unexpectedValue)); + + return NotHaveElementWithValue(XNamespace.None + unexpectedElement, unexpectedValue, because, becauseArgs); + } + + /// + /// Asserts that the of the current either doesn't have the + /// specified child element or doesn't have the specified . + /// + /// + /// he full name of the unexpected child element of the current element's . + /// + /// + /// The unexpected value of this particular element. + /// + /// + /// 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 AndConstraint NotHaveElementWithValue(XName unexpectedElement, + string unexpectedValue, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNull(unexpectedElement, nameof(unexpectedElement)); + Guard.ThrowIfArgumentIsNull(unexpectedValue, nameof(unexpectedValue)); + + assertionChain + .BecauseOf(because, becauseArgs) + .WithExpectation("Did not expect {context:subject} to have an element {0} with value {1}{reason}, ", + unexpectedElement, unexpectedValue, chain => chain + .ForCondition(Subject is not null) + .FailWith("but the element itself is .") + .Then + .ForCondition(!Subject!.Root!.Elements(unexpectedElement) + .Any(e => e.Value == unexpectedValue)) + .FailWith("but the element {0} does have this value.", unexpectedElement)); + + return new AndConstraint(this); } /// diff --git a/Src/FluentAssertions/Xml/XElementAssertions.cs b/Src/FluentAssertions/Xml/XElementAssertions.cs index 2ab3943c89..5570c5f2ef 100644 --- a/Src/FluentAssertions/Xml/XElementAssertions.cs +++ b/Src/FluentAssertions/Xml/XElementAssertions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Xml; using System.Xml.Linq; @@ -17,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; } /// @@ -38,9 +42,10 @@ public XElementAssertions(XElement xElement) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint Be(XElement expected, string because = "", params object[] becauseArgs) + 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); @@ -61,9 +66,10 @@ public AndConstraint Be(XElement expected, string because = /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBe(XElement unexpected, string because = "", params object[] becauseArgs) + 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); @@ -84,14 +90,14 @@ public AndConstraint NotBe(XElement unexpected, string becau /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeEquivalentTo(XElement expected, string because = "", - params object[] becauseArgs) + public AndConstraint BeEquivalentTo(XElement expected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { using (XmlReader subjectReader = Subject?.CreateReader()) using (XmlReader expectedReader = expected?.CreateReader()) { - var xmlReaderValidator = new XmlReaderValidator(subjectReader, expectedReader, because, becauseArgs); - xmlReaderValidator.Validate(shouldBeEquivalent: true); + var xmlReaderValidator = new XmlReaderValidator(assertionChain, subjectReader, expectedReader, because, becauseArgs); + xmlReaderValidator.AssertThatItIsEquivalent(); } return new AndConstraint(this); @@ -110,14 +116,14 @@ public AndConstraint BeEquivalentTo(XElement expected, strin /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeEquivalentTo(XElement unexpected, string because = "", - params object[] becauseArgs) + public AndConstraint NotBeEquivalentTo(XElement unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { using (XmlReader subjectReader = Subject?.CreateReader()) using (XmlReader otherReader = unexpected?.CreateReader()) { - var xmlReaderValidator = new XmlReaderValidator(subjectReader, otherReader, because, becauseArgs); - xmlReaderValidator.Validate(shouldBeEquivalent: false); + var xmlReaderValidator = new XmlReaderValidator(assertionChain, subjectReader, otherReader, because, becauseArgs); + xmlReaderValidator.AssertThatIsNotEquivalent(); } return new AndConstraint(this); @@ -134,17 +140,18 @@ public AndConstraint NotBeEquivalentTo(XElement unexpected, /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveValue(string expected, string because = "", params object[] becauseArgs) + 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 - .ForCondition(Subject.Value == expected) + assertionChain + .ForCondition(Subject!.Value == expected) .BecauseOf(because, becauseArgs) .FailWith( "Expected {context:subject} '{0}' to have value {1}{reason}, but found {2}.", @@ -154,6 +161,123 @@ public AndConstraint HaveValue(string expected, string becau return new AndConstraint(this); } + /// + /// Asserts that the current has an attribute with the specified . + /// + /// The name of the expected attribute + /// + /// 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 . + /// + /// is . + /// is empty. + public AndConstraint HaveAttribute(string expectedName, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNullOrEmpty(expectedName); + + return HaveAttribute(XNamespace.None + expectedName, because, becauseArgs); + } + + /// + /// Asserts that the current has an attribute with the specified . + /// + /// The name of the expected attribute + /// + /// 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 . + /// + /// is . + public AndConstraint HaveAttribute(XName expectedName, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNull(expectedName); + + string expectedText = expectedName.ToString(); + + assertionChain + .WithExpectation("Expected attribute {0} in element to exist {reason}, ", expectedText, + chain => chain + .BecauseOf(because, becauseArgs) + .ForCondition(Subject is not null) + .FailWith( + "but {context:member} is .")) + .Then + .WithExpectation("Expected {context:subject} to have attribute {0}{reason}, ", expectedText, + chain => chain + .BecauseOf(because, becauseArgs) + .Given(() => Subject!.Attribute(expectedName)) + .ForCondition(attribute => attribute is not null) + .FailWith("but found no such attribute in {0}.", Subject)); + + return new AndConstraint(this); + } + + /// + /// Asserts that the current doesn't have an attribute with the specified . + /// + /// The name of the unexpected attribute + /// + /// 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 . + /// + /// is . + /// is empty. + public AndConstraint NotHaveAttribute(string unexpectedName, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNullOrEmpty(unexpectedName); + + return NotHaveAttribute(XNamespace.None + unexpectedName, because, becauseArgs); + } + + /// + /// Asserts that the current doesn't have an attribute with the specified . + /// + /// The name of the unexpected attribute + /// + /// 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 . + /// + /// is . + public AndConstraint NotHaveAttribute(XName unexpectedName, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNull(unexpectedName); + + string unexpectedText = unexpectedName.ToString(); + + assertionChain + .WithExpectation("Did not expect attribute {0} in element to exist{reason}, ", unexpectedText, + chain => chain + .BecauseOf(because, becauseArgs) + .ForCondition(Subject is not null) + .FailWith( + "but {context:member} is .", + unexpectedText)) + .Then + .WithExpectation("Did not expect {context:subject} to have attribute {0}{reason}, ", unexpectedText, + chain => chain + .BecauseOf(because, becauseArgs) + .Given(() => Subject!.Attribute(unexpectedName)) + .ForCondition(attribute => attribute is null) + .FailWith("but found such attribute in {0}.", Subject)); + + return new AndConstraint(this); + } + /// /// Asserts that the current has an attribute with the specified /// and . @@ -169,12 +293,12 @@ public AndConstraint HaveValue(string expected, string becau /// /// is . /// is empty. - public AndConstraint HaveAttribute(string expectedName, string expectedValue, string because = "", - params object[] becauseArgs) + public AndConstraint HaveAttributeWithValue(string expectedName, string expectedValue, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNullOrEmpty(expectedName); - return HaveAttribute(XNamespace.None + expectedName, expectedValue, because, becauseArgs); + return HaveAttributeWithValue(XNamespace.None + expectedName, expectedValue, because, becauseArgs); } /// @@ -191,42 +315,102 @@ public AndConstraint HaveAttribute(string expectedName, stri /// Zero or more objects to format using the placeholders in . /// /// is . - public AndConstraint HaveAttribute(XName expectedName, string expectedValue, string because = "", - params object[] becauseArgs) + public AndConstraint HaveAttributeWithValue(XName expectedName, string expectedValue, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(expectedName); 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); + .WithExpectation("Expected attribute {0} in element to have value {1}{reason}, ", + expectedText, expectedValue, + chain => chain + .ForCondition(Subject is not null) + .FailWith("but {context:member} is .")) + .Then + .WithExpectation("Expected {context:subject} to have attribute {0} with value {1}{reason}, ", + expectedText, expectedValue, + chain => chain + .BecauseOf(because, becauseArgs) + .Given(() => Subject!.Attribute(expectedName)) + .ForCondition(attr => attr is not null) + .FailWith("but found no such attribute in {0}", Subject)) + .Then + .WithExpectation("Expected attribute {0} in {context:subject} to have value {1}{reason}, ", + expectedText, expectedValue, + chain => chain + .BecauseOf(because, becauseArgs) + .Given(() => Subject!.Attribute(expectedName)) + .ForCondition(attr => attr!.Value == expectedValue) + .FailWith("but found {0}.", attr => attr.Value)); - if (success) - { - XAttribute attribute = Subject.Attribute(expectedName); + return new AndConstraint(this); + } - success = Execute.Assertion - .ForCondition(attribute is not null) - .BecauseOf(because, becauseArgs) - .FailWith( - "Expected {context:subject} to have attribute {0} with value {1}{reason}," - + " but found no such attribute in {2}", - expectedText, expectedValue, Subject); - - if (success) - { - Execute.Assertion - .ForCondition(attribute.Value == expectedValue) + /// + /// Asserts that the current doesn't have an attribute with the specified + /// and/or . + /// + /// The name of the unexpected attribute + /// The value of the unexpected attribute + /// + /// 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 . + /// + /// is . + /// is empty. + public AndConstraint NotHaveAttributeWithValue(string unexpectedName, string unexpectedValue, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNullOrEmpty(unexpectedName, nameof(unexpectedName)); + Guard.ThrowIfArgumentIsNull(unexpectedValue, nameof(unexpectedValue)); + + return NotHaveAttributeWithValue(XNamespace.None + unexpectedName, unexpectedValue, because, becauseArgs); + } + + /// + /// Asserts that the current doesn't have an attribute with the specified + /// and/or . + /// + /// The name of the unexpected attribute + /// The value of the unexpected attribute + /// + /// 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 . + /// + /// is . + public AndConstraint NotHaveAttributeWithValue(XName unexpectedName, string unexpectedValue, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNull(unexpectedName, nameof(unexpectedName)); + Guard.ThrowIfArgumentIsNull(unexpectedValue, nameof(unexpectedValue)); + + string unexpectedText = unexpectedName.ToString(); + + assertionChain + .WithExpectation("Did not expect attribute {0} in element to have value {1}{reason}, ", + unexpectedText, unexpectedValue, + chain => chain .BecauseOf(because, becauseArgs) - .FailWith( - "Expected attribute {0} in {context:subject} to have value {1}{reason}, but found {2}.", - expectedText, expectedValue, attribute.Value); - } - } + .ForCondition(Subject is not null) + .FailWith("but {context:member} is .")) + .Then + .WithExpectation("Did not expect {context:subject} to have attribute {0} with value {1}{reason}, ", + unexpectedText, unexpectedValue, + chain => chain + .BecauseOf(because, becauseArgs) + .Given(() => Subject!.Attributes() + .FirstOrDefault(a => a.Name == unexpectedName && a.Value == unexpectedValue)) + .ForCondition(attribute => attribute is null) + .FailWith("but found such attribute in {0}.", Subject)); return new AndConstraint(this); } @@ -245,8 +429,8 @@ public AndConstraint HaveAttribute(XName expectedName, strin /// /// is . /// is empty. - public AndWhichConstraint HaveElement(string expected, string because = "", - params object[] becauseArgs) + public AndWhichConstraint HaveElement(string expected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNullOrEmpty(expected); @@ -266,12 +450,12 @@ public AndWhichConstraint HaveElement(string expec /// Zero or more objects to format using the placeholders in . /// /// is . - public AndWhichConstraint HaveElement(XName expected, string because = "", - params object[] becauseArgs) + public AndWhichConstraint HaveElement(XName expected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(expected); - bool success = Execute.Assertion + assertionChain .BecauseOf(because, becauseArgs) .ForCondition(Subject is not null) .FailWith( @@ -280,11 +464,11 @@ public AndWhichConstraint HaveElement(XName expect XElement xElement = null; - if (success) + if (assertionChain.Succeeded) { - xElement = Subject.Element(expected); + xElement = Subject!.Element(expected); - Execute.Assertion + assertionChain .ForCondition(xElement is not null) .BecauseOf(because, becauseArgs) .FailWith( @@ -292,7 +476,7 @@ public AndWhichConstraint HaveElement(XName expect expected.ToString().EscapePlaceholders()); } - return new AndWhichConstraint(this, xElement); + return new AndWhichConstraint(this, xElement, assertionChain, "/" + expected); } /// @@ -314,27 +498,27 @@ public AndWhichConstraint HaveElement(XName expect /// /// is . public AndWhichConstraint> HaveElement(XName expected, - OccurrenceConstraint occurrenceConstraint, string because = "", - params object[] becauseArgs) + OccurrenceConstraint occurrenceConstraint, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { 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( "Expected {context:subject} to have an element with count of {0}{reason}, but the element itself is .", expected.ToString()); - IEnumerable xElements = Enumerable.Empty(); + IEnumerable xElements = []; - if (success) + if (assertionChain.Succeeded) { - xElements = Subject.Elements(expected); + xElements = Subject!.Elements(expected); int actual = xElements.Count(); - Execute.Assertion + assertionChain .ForConstraint(occurrenceConstraint, actual) .BecauseOf(because, becauseArgs) .FailWith( @@ -343,7 +527,7 @@ public AndWhichConstraint> HaveElement expected.ToString()); } - return new AndWhichConstraint>(this, xElements); + return new AndWhichConstraint>(this, xElements, assertionChain, "/" + expected); } /// @@ -365,7 +549,8 @@ public AndWhichConstraint> HaveElement /// /// is . public AndWhichConstraint> HaveElement(string expected, - OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) + OccurrenceConstraint occurrenceConstraint, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { Guard.ThrowIfArgumentIsNull(expected, nameof(expected), "Cannot assert the element has an element if the expected name is ."); @@ -373,6 +558,205 @@ public AndWhichConstraint> HaveElement return HaveElement(XNamespace.None + expected, occurrenceConstraint, because, becauseArgs); } + /// + /// Asserts that the of the current doesn't have the specified child element. + /// + /// + /// The name of the expected child element of the current element's . + /// + /// + /// 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 AndConstraint NotHaveElement(string unexpectedElement, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNull(unexpectedElement, nameof(unexpectedElement)); + + return NotHaveElement(XNamespace.None + unexpectedElement, because, becauseArgs); + } + + /// + /// Asserts that the of the current doesn't have the specified child element. + /// + /// + /// The full name of the expected child element of the current element's . + /// + /// + /// 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 AndConstraint NotHaveElement(XName unexpectedElement, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNull(unexpectedElement, nameof(unexpectedElement)); + + assertionChain + .ForCondition(Subject is not null) + .BecauseOf(because, becauseArgs) + .FailWith( + "Did not expect {context:subject} to have an element {0}{reason}, but the element itself is .", + unexpectedElement.ToString()); + + if (assertionChain.Succeeded) + { + assertionChain + .BecauseOf(because, becauseArgs) + .ForCondition(!Subject!.Elements(unexpectedElement).Any()) + .FailWith("Did not expect {context:subject} to have an element {0}{reason}, " + + "but the element {0} was found.", unexpectedElement); + } + + return new AndConstraint(this); + } + + /// + /// Asserts that the of the current has the specified child element + /// with the specified name. + /// + /// + /// The name of the expected child element of the current element's . + /// + /// + /// The expected value of this particular element. + /// + /// + /// 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 AndWhichConstraint HaveElementWithValue(string expectedElement, + string expectedValue, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNull(expectedElement, nameof(expectedElement)); + Guard.ThrowIfArgumentIsNull(expectedValue, nameof(expectedValue)); + + return HaveElementWithValue(XNamespace.None + expectedElement, expectedValue, because, becauseArgs); + } + + /// + /// Asserts that the of the current has the specified child element + /// with the specified name. + /// + /// + /// The full name of the expected child element of the current element's . + /// + /// + /// The expected value of this particular element. + /// + /// + /// 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 AndWhichConstraint HaveElementWithValue(XName expectedElement, + string expectedValue, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNull(expectedElement, nameof(expectedElement)); + Guard.ThrowIfArgumentIsNull(expectedValue, nameof(expectedValue)); + + IEnumerable xElements = []; + + assertionChain + .WithExpectation("Expected {context:subject} to have an element {0} with value {1}{reason}, ", + expectedElement.ToString(), expectedValue, + chain => chain + .ForCondition(Subject is not null) + .BecauseOf(because, becauseArgs) + .FailWith("but the element itself is .")) + .Then + .WithExpectation("Expected {context:subject} to have an element {0} with value {1}{reason}, ", + expectedElement, expectedValue, chain => chain + .BecauseOf(because, becauseArgs) + .Given(() => + { + xElements = Subject!.Elements(expectedElement); + + return xElements; + }) + .ForCondition(elements => elements.Any()) + .FailWith("but the element {0} isn't found.", expectedElement) + .Then + .ForCondition(elements => elements.Any(e => e.Value == expectedValue)) + .FailWith("but the element {0} does not have such a value.", expectedElement)); + + return new AndWhichConstraint(this, xElements.FirstOrDefault()); + } + + /// + /// Asserts that the of the current either doesn't have the + /// specified child element or doesn't have the specified . + /// + /// + /// The name of the unexpected child element of the current element's . + /// + /// + /// The unexpected value of this particular element. + /// + /// + /// 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 AndConstraint NotHaveElementWithValue(string unexpectedElement, + string unexpectedValue, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNull(unexpectedElement, nameof(unexpectedElement)); + Guard.ThrowIfArgumentIsNull(unexpectedValue, nameof(unexpectedValue)); + + return NotHaveElementWithValue(XNamespace.None + unexpectedElement, unexpectedValue, because, becauseArgs); + } + + /// + /// Asserts that the of the current either doesn't have the + /// specified child element or doesn't have the specified . + /// + /// + /// he full name of the unexpected child element of the current element's . + /// + /// + /// The unexpected value of this particular element. + /// + /// + /// 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 AndConstraint NotHaveElementWithValue(XName unexpectedElement, + string unexpectedValue, [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNull(unexpectedElement, nameof(unexpectedElement)); + Guard.ThrowIfArgumentIsNull(unexpectedValue, nameof(unexpectedValue)); + + assertionChain + .WithExpectation("Did not expect {context:subject} to have an element {0} with value {1}{reason}, ", + unexpectedElement.ToString(), unexpectedValue, + chain => chain + .ForCondition(Subject is not null) + .BecauseOf(because, becauseArgs) + .FailWith("but the element itself is .") + .Then + .ForCondition(!Subject!.Elements(unexpectedElement) + .Any(e => e.Value == unexpectedValue)) + .FailWith("but the element {0} does have this value.", unexpectedElement)); + + return new AndConstraint(this); + } + /// /// Returns the type of the subject the assertion applies on. /// diff --git a/Src/FluentAssertions/Xml/XmlAssertionExtensions.cs b/Src/FluentAssertions/Xml/XmlAssertionExtensions.cs deleted file mode 100644 index 771bd359be..0000000000 --- a/Src/FluentAssertions/Xml/XmlAssertionExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Diagnostics; -using System.Xml; -using FluentAssertions.Xml; - -namespace FluentAssertions; - -[DebuggerNonUserCode] -public static class XmlAssertionExtensions -{ - public static XmlNodeAssertions Should(this XmlNode actualValue) - { - return new XmlNodeAssertions(actualValue); - } - - public static XmlElementAssertions Should(this XmlElement actualValue) - { - return new XmlElementAssertions(actualValue); - } -} diff --git a/Src/FluentAssertions/Xml/XmlElementAssertions.cs b/Src/FluentAssertions/Xml/XmlElementAssertions.cs index 40d3503982..daeffcd3fd 100644 --- a/Src/FluentAssertions/Xml/XmlElementAssertions.cs +++ b/Src/FluentAssertions/Xml/XmlElementAssertions.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Xml; using FluentAssertions.Common; using FluentAssertions.Execution; @@ -12,13 +13,15 @@ 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; } /// @@ -33,10 +36,10 @@ public XmlElementAssertions(XmlElement xmlElement) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveInnerText(string expected, string because = "", - params object[] becauseArgs) + public AndConstraint HaveInnerText(string expected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { - Execute.Assertion + assertionChain .ForCondition(Subject.InnerText == expected) .BecauseOf(because, becauseArgs) .FailWith( @@ -60,8 +63,8 @@ public AndConstraint HaveInnerText(string expected, string /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint HaveAttribute(string expectedName, string expectedValue, string because = "", - params object[] becauseArgs) + public AndConstraint HaveAttribute(string expectedName, string expectedValue, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return HaveAttributeWithNamespace(expectedName, string.Empty, expectedValue, because, becauseArgs); } @@ -85,7 +88,7 @@ public AndConstraint HaveAttributeWithNamespace( string expectedName, string expectedNamespace, string expectedValue, - string because = "", params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { XmlAttribute attribute = Subject.Attributes[expectedName, expectedNamespace]; @@ -93,7 +96,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( @@ -101,10 +104,10 @@ public AndConstraint HaveAttributeWithNamespace( + " with value {1}{reason}, but found no such attribute in {2}", expectedFormattedName, expectedValue, Subject); - if (success) + if (assertionChain.Succeeded) { - Execute.Assertion - .ForCondition(attribute.Value == expectedValue) + assertionChain + .ForCondition(attribute!.Value == expectedValue) .BecauseOf(because, becauseArgs) .FailWith( "Expected attribute {0} in {context:subject} to have value {1}{reason}, but found {2}.", @@ -128,8 +131,7 @@ public AndConstraint HaveAttributeWithNamespace( /// public AndWhichConstraint HaveElement( string expectedName, - string because = "", - params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { return HaveElementWithNamespace(expectedName, null, because, becauseArgs); } @@ -150,8 +152,7 @@ public AndWhichConstraint HaveElement( public AndWhichConstraint HaveElementWithNamespace( string expectedName, string expectedNamespace, - string because = "", - params object[] becauseArgs) + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { XmlElement element = expectedNamespace == null ? Subject[expectedName] : Subject[expectedName, expectedNamespace]; @@ -159,14 +160,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 2ea98b0b18..f903b7ec0a 100644 --- a/Src/FluentAssertions/Xml/XmlNodeAssertions.cs +++ b/Src/FluentAssertions/Xml/XmlNodeAssertions.cs @@ -1,5 +1,7 @@ using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Xml; +using FluentAssertions.Execution; using FluentAssertions.Primitives; using FluentAssertions.Xml.Equivalency; @@ -11,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) { } } @@ -25,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; } /// @@ -41,13 +46,14 @@ public XmlNodeAssertions(TSubject xmlNode) /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint BeEquivalentTo(XmlNode expected, string because = "", params object[] becauseArgs) + public AndConstraint BeEquivalentTo(XmlNode expected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { using (var subjectReader = new XmlNodeReader(Subject)) using (var expectedReader = new XmlNodeReader(expected)) { - var xmlReaderValidator = new XmlReaderValidator(subjectReader, expectedReader, because, becauseArgs); - xmlReaderValidator.Validate(shouldBeEquivalent: true); + var xmlReaderValidator = new XmlReaderValidator(assertionChain, subjectReader, expectedReader, because, becauseArgs); + xmlReaderValidator.AssertThatItIsEquivalent(); } return new AndConstraint((TAssertions)this); @@ -65,13 +71,14 @@ public AndConstraint BeEquivalentTo(XmlNode expected, string becaus /// /// Zero or more objects to format using the placeholders in . /// - public AndConstraint NotBeEquivalentTo(XmlNode unexpected, string because = "", params object[] becauseArgs) + public AndConstraint NotBeEquivalentTo(XmlNode unexpected, + [StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs) { using (var subjectReader = new XmlNodeReader(Subject)) using (var unexpectedReader = new XmlNodeReader(unexpected)) { - var xmlReaderValidator = new XmlReaderValidator(subjectReader, unexpectedReader, because, becauseArgs); - xmlReaderValidator.Validate(shouldBeEquivalent: false); + var xmlReaderValidator = new XmlReaderValidator(assertionChain, subjectReader, unexpectedReader, because, becauseArgs); + xmlReaderValidator.AssertThatIsNotEquivalent(); } return new AndConstraint((TAssertions)this); diff --git a/Src/FluentAssertions/XmlAssertionExtensions.cs b/Src/FluentAssertions/XmlAssertionExtensions.cs new file mode 100644 index 0000000000..79da764778 --- /dev/null +++ b/Src/FluentAssertions/XmlAssertionExtensions.cs @@ -0,0 +1,21 @@ +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Xml; +using FluentAssertions.Execution; +using FluentAssertions.Xml; + +namespace FluentAssertions; + +[DebuggerNonUserCode] +public static class XmlAssertionExtensions +{ + public static XmlNodeAssertions Should([NotNull] this XmlNode actualValue) + { + return new XmlNodeAssertions(actualValue, AssertionChain.GetOrCreate()); + } + + public static XmlElementAssertions Should([NotNull] this XmlElement actualValue) + { + return new XmlElementAssertions(actualValue, AssertionChain.GetOrCreate()); + } +} diff --git a/Tests/.editorconfig b/Tests/.editorconfig index d7c1e5c6d3..15629ab354 100644 --- a/Tests/.editorconfig +++ b/Tests/.editorconfig @@ -1,5 +1,9 @@ [*.cs] + + + + # IDE0051: Private member is unused dotnet_diagnostic.IDE0051.severity = none # IDE0070: GetHashCode implementation can be simplified @@ -21,6 +25,11 @@ dotnet_diagnostic.CA1024.severity = none dotnet_diagnostic.CA1028.severity = none # CA1032: Implement standard exception constructors dotnet_diagnostic.CA1032.severity = none + +# Purpose: Do not nest types. Alternatively, change its accessibility so that it is not externally visible +# Reason: We have a lot of nested types to group tests, so we disable this rule +dotnet_diagnostic.CA1034.severity = none + # CA1036: Override methods on comparable types dotnet_diagnostic.CA1036.severity = none # CA1040: Avoid empty interfaces @@ -35,12 +44,19 @@ dotnet_diagnostic.CA1052.severity = none # CA1062: Validate arguments of public methods dotnet_diagnostic.CA1062.severity = none + +# Purpose: Implement IDisposable correctly +# Reason: Not important in test classes +dotnet_diagnostic.CA1063.severity = none + # CA1064: Exceptions should be public dotnet_diagnostic.CA1064.severity = none # CA1307: Specify StringComparison dotnet_diagnostic.CA1307.severity = none # CA1506 Rewrite or refactor the code to decrease its class coupling dotnet_diagnostic.CA1506.severity = none +# CA1515 CA1515: Consider making public types internal +dotnet_diagnostic.CA1515.severity = none # CA1707: Remove the underscores from member name dotnet_diagnostic.CA1707.severity = none # CA1711: Rename type name so that it does not end in 'Enum' @@ -51,12 +67,22 @@ dotnet_diagnostic.CA1714.severity = none dotnet_diagnostic.CA1716.severity = none # CA1720: Identifiers should not contain type names dotnet_diagnostic.CA1720.severity = none + +# Purpose: The type name Interfaces conflicts in whole or in part with the namespace name +# Reason: We don't care about this in tests +dotnet_diagnostic.CA1724.severity = none + # CA1812: Type is an internal class that is apparently never instantiated. dotnet_diagnostic.CA1812.severity = none # CA1813 Avoid unsealed attributes dotnet_diagnostic.CA1813.severity = none # CA1814: Prefer jagged arrays over multidimensional dotnet_diagnostic.CA1814.severity = none + +# Purpose: Call GC.SuppressFinalize correctly +# Reason: Not important in test classes +dotnet_diagnostic.CA1816.severity = none + # CA1822: Member does not access instance data and can be marked as static dotnet_diagnostic.CA1822.severity = none # CA1825: Avoid unnecessary zero-length array allocations. Use Array.Empty() instead @@ -73,6 +99,8 @@ dotnet_diagnostic.CA2201.severity = none dotnet_diagnostic.CA2208.severity = none # CA2227: Collection properties should be read only dotnet_diagnostic.CA2227.severity = none +# CA2263: Prefer generic overload when type is known +dotnet_diagnostic.CA2263.severity = none # CA5394 Random is an insecure random number generator dotnet_diagnostic.CA5394.severity = none @@ -80,6 +108,7 @@ dotnet_diagnostic.CA5394.severity = none dotnet_diagnostic.AV1000.severity = none # AV1008: Class should be non-static or its name should be suffixed with Extensions dotnet_diagnostic.AV1008.severity = none + # AV1115: Member or local function contains the word 'and', which suggests doing multiple things dotnet_diagnostic.AV1115.severity = none # AV1135: Do not return null for strings, collections or tasks @@ -159,6 +188,10 @@ resharper_expression_is_always_null_highlighting = none # ReSharper properties resharper_keep_user_linebreaks = true +# Purpose: Highlighting for classes that are never instantiated +# Reason: We have many test classes that are never instantiated directly +resharper_class_never_instantiated_global_highlighting = none + # Make class static dotnet_diagnostic.RCS1102.severity = none @@ -183,6 +216,10 @@ dotnet_diagnostic.RCS1213.severity = none # Implement IComparable when implementing IComparable. Disabled since we don't want to be so strict in tests. dotnet_diagnostic.RCS1241.severity = none +# Purpose: Use an overload that has a IEqualityComparer or IComparer parameter +# Reason: Not important in tests +dotnet_diagnostic.MA0002.severity = none + # Use Array.Empty(). We don't care. dotnet_diagnostic.MA0005.severity = none diff --git a/Tests/Approval.Tests/Approval.Tests.csproj b/Tests/Approval.Tests/Approval.Tests.csproj index 5ec29e2d20..ac8f84b5c2 100644 --- a/Tests/Approval.Tests/Approval.Tests.csproj +++ b/Tests/Approval.Tests/Approval.Tests.csproj @@ -5,15 +5,15 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + diff --git a/Tests/Approval.Tests/Approval.Tests.v3.ncrunchproject b/Tests/Approval.Tests/Approval.Tests.v3.ncrunchproject new file mode 100644 index 0000000000..7b5b2139ff --- /dev/null +++ b/Tests/Approval.Tests/Approval.Tests.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net47.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net47.verified.txt index bc72e1fbec..05bc6d44f4 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net47.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net47.verified.txt @@ -10,17 +10,30 @@ namespace FluentAssertions public System.Collections.Generic.IEnumerable OfType(System.Exception actualException) where T : System.Exception { } } - public class AndConstraint + public class AndConstraint { - public AndConstraint(T parentConstraint) { } - public T And { get; } + 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) { } + 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 AssertionConfiguration + { + public static FluentAssertions.Configuration.GlobalConfiguration Current { get; } + } + public static class AssertionEngine + { + public static FluentAssertions.Configuration.GlobalConfiguration Configuration { get; } + public static FluentAssertions.Execution.ITestFramework TestFramework { get; set; } + public static void ResetToDefaults() { } } public static class AssertionExtensions { @@ -37,7 +50,8 @@ namespace FluentAssertions public static FluentAssertions.Specialized.MemberExecutionTime ExecutionTimeOf(this T subject, System.Linq.Expressions.Expression> action, FluentAssertions.Common.StartTimer createTimer = null) { } public static System.Action Invoking(this T subject, System.Action action) { } public static System.Func Invoking(this T subject, System.Func action) { } - public static FluentAssertions.Events.IMonitor Monitor(this T eventSource, System.Func utcNow = null) { } + public static FluentAssertions.Events.IMonitor Monitor(this T eventSource) { } + public static FluentAssertions.Events.IMonitor Monitor(this T eventSource, System.Action configureOptions) { } public static FluentAssertions.Specialized.ExecutionTimeAssertions Should(this FluentAssertions.Specialized.ExecutionTime executionTime) { } [System.Obsolete("You are asserting the \'AndConstraint\' itself. Remove the \'Should()\' method direct" + "ly following \'And\'", true)] @@ -57,58 +71,53 @@ namespace FluentAssertions [System.Obsolete("You are asserting the \'AndConstraint\' itself. Remove the \'Should()\' method direct" + "ly following \'And\'", true)] public static void Should(this FluentAssertions.Types.TypeSelectorAssertions _) { } - public static FluentAssertions.Specialized.ActionAssertions Should(this System.Action action) { } - public static FluentAssertions.Collections.StringCollectionAssertions Should(this System.Collections.Generic.IEnumerable @this) { } - public static FluentAssertions.Data.DataColumnAssertions Should(this System.Data.DataColumn actualValue) { } - public static FluentAssertions.Collections.GenericCollectionAssertions Should(this System.Data.DataColumnCollection actualValue) { } - public static FluentAssertions.Collections.GenericCollectionAssertions Should(this System.Data.DataRowCollection actualValue) { } - public static FluentAssertions.Collections.GenericCollectionAssertions Should(this System.Data.DataTableCollection actualValue) { } + public static FluentAssertions.Specialized.ActionAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Action action) { } + public static FluentAssertions.Collections.StringCollectionAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Collections.Generic.IEnumerable @this) { } public static FluentAssertions.Primitives.DateTimeAssertions Should(this System.DateTime actualValue) { } - public static FluentAssertions.Primitives.NullableDateTimeAssertions Should(this System.DateTime? actualValue) { } + public static FluentAssertions.Primitives.NullableDateTimeAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.DateTime? actualValue) { } public static FluentAssertions.Primitives.DateTimeOffsetAssertions Should(this System.DateTimeOffset actualValue) { } - public static FluentAssertions.Primitives.NullableDateTimeOffsetAssertions Should(this System.DateTimeOffset? actualValue) { } - public static FluentAssertions.Specialized.NonGenericAsyncFunctionAssertions Should(this System.Func action) { } + public static FluentAssertions.Primitives.NullableDateTimeOffsetAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.DateTimeOffset? actualValue) { } + 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(this System.IO.BufferedStream actualValue) { } - public static FluentAssertions.Streams.StreamAssertions Should(this System.IO.Stream actualValue) { } - public static FluentAssertions.Primitives.HttpResponseMessageAssertions Should(this System.Net.Http.HttpResponseMessage actualValue) { } - public static FluentAssertions.Reflection.AssemblyAssertions Should(this System.Reflection.Assembly assembly) { } - public static FluentAssertions.Types.ConstructorInfoAssertions Should(this System.Reflection.ConstructorInfo constructorInfo) { } - public static FluentAssertions.Types.MethodInfoAssertions Should(this System.Reflection.MethodInfo methodInfo) { } - public static FluentAssertions.Types.PropertyInfoAssertions Should(this System.Reflection.PropertyInfo propertyInfo) { } + public static FluentAssertions.Primitives.NullableGuidAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] 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.Types.AssemblyAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Reflection.Assembly assembly) { } + public static FluentAssertions.Types.ConstructorInfoAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Reflection.ConstructorInfo constructorInfo) { } + public static FluentAssertions.Types.MethodInfoAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Reflection.MethodInfo methodInfo) { } + public static FluentAssertions.Types.PropertyInfoAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Reflection.PropertyInfo propertyInfo) { } public static FluentAssertions.Primitives.SimpleTimeSpanAssertions Should(this System.TimeSpan actualValue) { } - public static FluentAssertions.Primitives.NullableSimpleTimeSpanAssertions Should(this System.TimeSpan? actualValue) { } - public static FluentAssertions.Types.TypeAssertions Should(this System.Type subject) { } - public static FluentAssertions.Xml.XAttributeAssertions Should(this System.Xml.Linq.XAttribute actualValue) { } - public static FluentAssertions.Xml.XDocumentAssertions Should(this System.Xml.Linq.XDocument actualValue) { } - public static FluentAssertions.Xml.XElementAssertions Should(this System.Xml.Linq.XElement actualValue) { } + public static FluentAssertions.Primitives.NullableSimpleTimeSpanAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.TimeSpan? actualValue) { } + public static FluentAssertions.Types.TypeAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Type subject) { } + public static FluentAssertions.Xml.XAttributeAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Xml.Linq.XAttribute actualValue) { } + public static FluentAssertions.Xml.XDocumentAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Xml.Linq.XDocument actualValue) { } + public static FluentAssertions.Xml.XElementAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Xml.Linq.XElement actualValue) { } public static FluentAssertions.Primitives.BooleanAssertions Should(this bool actualValue) { } - public static FluentAssertions.Primitives.NullableBooleanAssertions Should(this bool? actualValue) { } + public static FluentAssertions.Primitives.NullableBooleanAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this bool? actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this byte actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this byte? actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this byte? actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this decimal actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this decimal? actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this decimal? actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this double actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this double? actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this double? actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this float actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this float? actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this float? actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this int actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this int? actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this int? actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this long actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this long? actualValue) { } - public static FluentAssertions.Primitives.ObjectAssertions Should(this object actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this long? actualValue) { } + public static FluentAssertions.Primitives.ObjectAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this object actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this sbyte actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this sbyte? actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this sbyte? actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this short actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this short? actualValue) { } - public static FluentAssertions.Primitives.StringAssertions Should(this string actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this short? actualValue) { } + public static FluentAssertions.Primitives.StringAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this string actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this uint actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this uint? actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this uint? actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this ulong actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this ulong? actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this ulong? actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this ushort actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this ushort? actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this ushort? actualValue) { } [System.Obsolete("You are asserting the \'AndConstraint\' itself. Remove the \'Should()\' method direct" + "ly following \'And\'", true)] public static void Should(this FluentAssertions.Primitives.BooleanAssertions _) @@ -137,14 +146,14 @@ namespace FluentAssertions "ly following \'And\'", true)] public static void Should(this FluentAssertions.Primitives.SimpleTimeSpanAssertions _) where TAssertions : FluentAssertions.Primitives.SimpleTimeSpanAssertions { } - public static FluentAssertions.Collections.GenericCollectionAssertions Should(this System.Collections.Generic.IEnumerable actualValue) { } - public static FluentAssertions.Specialized.GenericAsyncFunctionAssertions Should(this System.Func> action) { } - public static FluentAssertions.Specialized.FunctionAssertions Should(this System.Func func) { } - public static FluentAssertions.Numeric.ComparableTypeAssertions Should(this System.IComparable comparableValue) { } + public static FluentAssertions.Collections.GenericCollectionAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Collections.Generic.IEnumerable actualValue) { } + public static FluentAssertions.Specialized.GenericAsyncFunctionAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Func> action) { } + public static FluentAssertions.Specialized.FunctionAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Func func) { } + public static FluentAssertions.Numeric.ComparableTypeAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.IComparable comparableValue) { } public static FluentAssertions.Specialized.TaskCompletionSourceAssertions Should(this System.Threading.Tasks.TaskCompletionSource tcs) { } [System.Obsolete("You are asserting the \'AndConstraint\' itself. Remove the \'Should()\' method direct" + "ly following \'And\'", true)] - public static void Should(this FluentAssertions.Numeric.NumericAssertions _) + public static void Should(this FluentAssertions.Numeric.NumericAssertionsBase _) where TSubject : struct, System.IComparable where TAssertions : FluentAssertions.Numeric.NumericAssertions { } [System.Obsolete("You are asserting the \'AndConstraint\' itself. Remove the \'Should()\' method direct" + @@ -156,18 +165,11 @@ namespace FluentAssertions "ly following \'And\'", true)] public static void Should(this FluentAssertions.Primitives.ReferenceTypeAssertions _) where TAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions { } - public static FluentAssertions.Collections.GenericDictionaryAssertions, TKey, TValue> Should(this System.Collections.Generic.IDictionary actualValue) { } - public static FluentAssertions.Collections.GenericDictionaryAssertions>, TKey, TValue> Should(this System.Collections.Generic.IEnumerable> actualValue) { } - public static FluentAssertions.Collections.GenericDictionaryAssertions Should(this TCollection actualValue) + public static FluentAssertions.Collections.GenericDictionaryAssertions, TKey, TValue> Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Collections.Generic.IDictionary actualValue) { } + public static FluentAssertions.Collections.GenericDictionaryAssertions>, TKey, TValue> Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Collections.Generic.IEnumerable> actualValue) { } + public static FluentAssertions.Collections.GenericDictionaryAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this TCollection actualValue) where TCollection : System.Collections.Generic.IEnumerable> { } } - public static class AssertionOptions - { - public static FluentAssertions.EquivalencyPlan EquivalencyPlan { get; } - public static FluentAssertions.Formatting.FormattingOptions FormattingOptions { get; } - public static void AssertEquivalencyUsing(System.Func defaultsConfigurer) { } - public static FluentAssertions.Equivalency.EquivalencyAssertionOptions CloneDefaults() { } - } public static class AsyncAssertionsExtensions { public static System.Threading.Tasks.Task, T>> WithResult(this System.Threading.Tasks.Task, T>> task, T expected, string because = "", params object[] becauseArgs) { } @@ -190,6 +192,7 @@ namespace FluentAssertions public static class CallerIdentifier { public static System.Action Logger { get; set; } + public static string[] DetermineCallerIdentities() { } public static string DetermineCallerIdentity() { } } [System.AttributeUsage(System.AttributeTargets.Method)] @@ -197,68 +200,18 @@ namespace FluentAssertions { public CustomAssertionAttribute() { } } - public static class DataColumnCollectionAssertionExtensions - { - public static FluentAssertions.AndConstraint> BeSameAs(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataColumnCollection expected, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> HaveSameCount(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataColumnCollection otherCollection, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> NotBeSameAs(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataColumnCollection unexpected, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> NotHaveSameCount(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataColumnCollection otherCollection, string because = "", params object[] becauseArgs) { } - } - public static class DataRowAssertionExtensions - { - public static FluentAssertions.Data.DataRowAssertions Should(this TDataRow actualValue) - where TDataRow : System.Data.DataRow { } - } - public static class DataRowCollectionAssertionExtensions - { - public static FluentAssertions.AndConstraint> BeSameAs(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataRowCollection expected, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> HaveSameCount(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataRowCollection otherCollection, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> NotBeSameAs(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataRowCollection unexpected, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> NotHaveSameCount(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataRowCollection otherCollection, string because = "", params object[] becauseArgs) { } - } - public static class DataSetAssertionExtensions - { - public static FluentAssertions.Data.DataSetAssertions Should(this TDataSet actualValue) - where TDataSet : System.Data.DataSet { } - } - public static class DataTableAssertionExtensions + [System.AttributeUsage(System.AttributeTargets.Assembly)] + public sealed class CustomAssertionsAssemblyAttribute : System.Attribute { - public static FluentAssertions.Data.DataTableAssertions Should(this TDataTable actualValue) - where TDataTable : System.Data.DataTable { } - } - public static class DataTableCollectionAssertionExtensions - { - public static FluentAssertions.AndConstraint> BeSameAs(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataTableCollection expected, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> HaveSameCount(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataSet otherDataSet, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> HaveSameCount(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataTableCollection otherCollection, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> NotBeSameAs(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataTableCollection unexpected, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> NotHaveSameCount(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataSet otherDataSet, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> NotHaveSameCount(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataTableCollection otherCollection, string because = "", params object[] becauseArgs) { } + public CustomAssertionsAssemblyAttribute() { } } public static class EnumAssertionsExtensions { public static FluentAssertions.Primitives.EnumAssertions Should(this TEnum @enum) where TEnum : struct, System.Enum { } - public static FluentAssertions.Primitives.NullableEnumAssertions Should(this TEnum? @enum) + public static FluentAssertions.Primitives.NullableEnumAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this TEnum? @enum) where TEnum : struct, System.Enum { } } - public class EquivalencyPlan : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable - { - public EquivalencyPlan() { } - public void Add() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } - public void AddAfter() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } - public void Clear() { } - public System.Collections.Generic.IEnumerator GetEnumerator() { } - public void Insert() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } - public void InsertBefore() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } - public void Remove() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep { } - public void Reset() { } - } public static class EventRaisingExtensions { public static FluentAssertions.Events.IEventRecording WithArgs(this FluentAssertions.Events.IEventRecording eventRecording, System.Linq.Expressions.Expression> predicate) { } @@ -308,6 +261,10 @@ namespace FluentAssertions public static FluentAssertions.OccurrenceConstraint Times(int expected) { } public static FluentAssertions.OccurrenceConstraint Twice() { } } + public static class License + { + public static bool Accepted { get; set; } + } public static class MoreThan { public static FluentAssertions.OccurrenceConstraint Once() { } @@ -327,13 +284,17 @@ namespace FluentAssertions public static FluentAssertions.AndConstraint> BeApproximately(this FluentAssertions.Numeric.NumericAssertions parent, double expectedValue, double precision, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> BeApproximately(this FluentAssertions.Numeric.NumericAssertions parent, float expectedValue, float precision, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, byte nearbyValue, byte delta, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, short nearbyValue, ushort delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, int nearbyValue, uint delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, long nearbyValue, ulong delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, sbyte nearbyValue, byte delta, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, ushort nearbyValue, ushort delta, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, short nearbyValue, ushort delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, uint nearbyValue, uint delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, ulong nearbyValue, ulong delta, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, ushort nearbyValue, ushort delta, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> BeNaN(this FluentAssertions.Numeric.NullableNumericAssertions parent, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> BeNaN(this FluentAssertions.Numeric.NullableNumericAssertions parent, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> BeNaN(this FluentAssertions.Numeric.NumericAssertions parent, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> BeNaN(this FluentAssertions.Numeric.NumericAssertions parent, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeApproximately(this FluentAssertions.Numeric.NullableNumericAssertions parent, decimal unexpectedValue, decimal precision, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeApproximately(this FluentAssertions.Numeric.NullableNumericAssertions parent, decimal? unexpectedValue, decimal precision, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeApproximately(this FluentAssertions.Numeric.NullableNumericAssertions parent, double unexpectedValue, double precision, string because = "", params object[] becauseArgs) { } @@ -344,20 +305,22 @@ namespace FluentAssertions public static FluentAssertions.AndConstraint> NotBeApproximately(this FluentAssertions.Numeric.NumericAssertions parent, double unexpectedValue, double precision, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeApproximately(this FluentAssertions.Numeric.NumericAssertions parent, float unexpectedValue, float precision, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, byte distantValue, byte delta, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, short distantValue, ushort delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, int distantValue, uint delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, long distantValue, ulong delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, sbyte distantValue, byte delta, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, ushort distantValue, ushort delta, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, short distantValue, ushort delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, uint distantValue, uint delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, ulong distantValue, ulong delta, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, ushort distantValue, ushort delta, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> NotBeNaN(this FluentAssertions.Numeric.NullableNumericAssertions parent, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> NotBeNaN(this FluentAssertions.Numeric.NullableNumericAssertions parent, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> NotBeNaN(this FluentAssertions.Numeric.NumericAssertions parent, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> NotBeNaN(this FluentAssertions.Numeric.NumericAssertions parent, string because = "", params object[] becauseArgs) { } } public static class ObjectAssertionsExtensions { - public static FluentAssertions.AndConstraint BeBinarySerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint BeBinarySerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> options, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint BeDataContractSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint BeDataContractSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> options, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint BeDataContractSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> options, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint BeXmlSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, string because = "", params object[] becauseArgs) { } } public abstract class OccurrenceConstraint @@ -396,33 +359,38 @@ namespace FluentAssertions public static FluentAssertions.Types.TypeSelector Types(this System.Reflection.Assembly assembly) { } public static FluentAssertions.Types.TypeSelector Types(this System.Type type) { } } + public static class Value + { + public static FluentAssertions.Equivalency.Inlining.IInlineEquivalencyAssertion ThatMatches(System.Linq.Expressions.Expression> condition) { } + public static FluentAssertions.Equivalency.Inlining.IInlineEquivalencyAssertion ThatSatisfies(System.Action assertion) { } + } public static class XmlAssertionExtensions { - public static FluentAssertions.Xml.XmlElementAssertions Should(this System.Xml.XmlElement actualValue) { } - public static FluentAssertions.Xml.XmlNodeAssertions Should(this System.Xml.XmlNode actualValue) { } + public static FluentAssertions.Xml.XmlElementAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Xml.XmlElement actualValue) { } + public static FluentAssertions.Xml.XmlNodeAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Xml.XmlNode actualValue) { } } } 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) { } public FluentAssertions.AndConstraint AllBeEquivalentTo(TExpectation expectation, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint AllBeEquivalentTo(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint AllBeEquivalentTo(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint AllBeOfType(System.Type expectedType, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint> AllBeOfType(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint AllSatisfy(System.Action expected, string because = "", params object[] becauseArgs) { } @@ -431,7 +399,7 @@ namespace FluentAssertions.Collections protected void AssertSubjectEquality(System.Collections.Generic.IEnumerable expectation, System.Func equalityComparison, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEmpty(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEquivalentTo(System.Collections.Generic.IEnumerable expectation, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeEquivalentTo(System.Collections.Generic.IEnumerable expectation, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeEquivalentTo(System.Collections.Generic.IEnumerable expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint> BeInAscendingOrder(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint> BeInAscendingOrder(System.Collections.Generic.IComparer comparer, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint> BeInAscendingOrder(System.Func comparison, string because = "", params object[] becauseArgs) { } @@ -448,7 +416,7 @@ namespace FluentAssertions.Collections public FluentAssertions.AndWhichConstraint Contain(System.Linq.Expressions.Expression> predicate, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint Contain(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint ContainEquivalentOf(TExpectation expectation, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint ContainEquivalentOf(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint ContainEquivalentOf(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint ContainInConsecutiveOrder(params T[] expected) { } public FluentAssertions.AndConstraint ContainInConsecutiveOrder(System.Collections.Generic.IEnumerable expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint ContainInOrder(params T[] expected) { } @@ -463,12 +431,10 @@ namespace FluentAssertions.Collections public FluentAssertions.AndConstraint Equal(System.Collections.Generic.IEnumerable expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Equal(System.Collections.Generic.IEnumerable expectation, System.Func equalityComparison, string because = "", params object[] becauseArgs) { } public override bool Equals(object obj) { } - public FluentAssertions.AndConstraint HaveCount(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveCount(System.Linq.Expressions.Expression> countPredicate, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint HaveCountGreaterOrEqualTo(int expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveCount(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveCountGreaterThan(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveCountGreaterThanOrEqualTo(int expected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint HaveCountLessOrEqualTo(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveCountLessThan(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveCountLessThanOrEqualTo(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint HaveElementAt(int index, T element, string because = "", params object[] becauseArgs) { } @@ -478,7 +444,7 @@ namespace FluentAssertions.Collections public FluentAssertions.AndConstraint IntersectWith(System.Collections.Generic.IEnumerable otherCollection, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEmpty(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEquivalentTo(System.Collections.Generic.IEnumerable unexpected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint NotBeEquivalentTo(System.Collections.Generic.IEnumerable unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotBeEquivalentTo(System.Collections.Generic.IEnumerable unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeInAscendingOrder(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeInAscendingOrder(System.Collections.Generic.IComparer comparer, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeInAscendingOrder(System.Func comparison, string because = "", params object[] becauseArgs) { } @@ -493,9 +459,9 @@ 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.EquivalencyAssertionOptions> config, 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) { } public FluentAssertions.AndConstraint NotContainInConsecutiveOrder(System.Collections.Generic.IEnumerable unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotContainInOrder(params T[] unexpected) { } @@ -524,16 +490,16 @@ 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.EquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeEquivalentTo(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Contain(params System.Collections.Generic.KeyValuePair[] expected) { } public FluentAssertions.AndConstraint Contain(System.Collections.Generic.IEnumerable> expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Contain(System.Collections.Generic.KeyValuePair expected, string because = "", params object[] becauseArgs) { } @@ -542,8 +508,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) { } @@ -561,42 +527,31 @@ 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.EquivalencyAssertionOptions> config, 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) { } public FluentAssertions.AndConstraint BeEquivalentTo(System.Collections.Generic.IEnumerable expectation, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeEquivalentTo(System.Collections.Generic.IEnumerable expectation, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeEquivalentTo(System.Collections.Generic.IEnumerable expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint ContainMatch(string wildcardPattern, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Equal(System.Collections.Generic.IEnumerable expected) { } public FluentAssertions.AndConstraint Equal(params string[] expected) { } public FluentAssertions.AndConstraint NotContainMatch(string wildcardPattern, string because = "", params object[] becauseArgs) { } } - public class SubsequentOrderingAssertions : FluentAssertions.Collections.SubsequentOrderingGenericCollectionAssertions, T, FluentAssertions.Collections.SubsequentOrderingAssertions> - { - public SubsequentOrderingAssertions(System.Collections.Generic.IEnumerable actualValue, System.Linq.IOrderedEnumerable previousOrderedEnumerable) { } - } - public class SubsequentOrderingGenericCollectionAssertions : FluentAssertions.Collections.SubsequentOrderingGenericCollectionAssertions> - where TCollection : System.Collections.Generic.IEnumerable + public class SubsequentOrderingAssertions : FluentAssertions.Collections.GenericCollectionAssertions, T> { - public SubsequentOrderingGenericCollectionAssertions(TCollection actualValue, System.Linq.IOrderedEnumerable previousOrderedEnumerable) { } - } - 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 SubsequentOrderingAssertions(System.Collections.Generic.IEnumerable 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) { } @@ -622,14 +577,6 @@ namespace FluentAssertions.Common InvalidForCSharp = 5, PrivateProtected = 6, } - public class Configuration - { - public Configuration(FluentAssertions.Common.IConfigurationStore store) { } - public string TestFrameworkName { get; set; } - public string ValueFormatterAssembly { get; set; } - public FluentAssertions.Common.ValueFormatterDetectionMode ValueFormatterDetectionMode { get; set; } - public static FluentAssertions.Common.Configuration Current { get; } - } public static class DateTimeExtensions { public static System.DateTimeOffset ToDateTimeOffset(this System.DateTime dateTime) { } @@ -641,31 +588,10 @@ namespace FluentAssertions.Common System.Threading.Tasks.Task DelayAsync(System.TimeSpan delay, System.Threading.CancellationToken cancellationToken); FluentAssertions.Common.ITimer StartTimer(); } - public interface ICollectionWrapper - where TCollection : System.Collections.ICollection - { - TCollection UnderlyingCollection { get; } - } - public interface IConfigurationStore - { - string GetSetting(string name); - } - public interface IReflector - { - System.Collections.Generic.IEnumerable GetAllTypesFromAppDomain(System.Func predicate); - } public interface ITimer : System.IDisposable { System.TimeSpan Elapsed { get; } } - public static class Services - { - public static FluentAssertions.Common.Configuration Configuration { get; } - public static FluentAssertions.Common.IConfigurationStore ConfigurationStore { get; set; } - public static FluentAssertions.Common.IReflector Reflector { get; set; } - public static System.Action ThrowException { get; set; } - public static void ResetToDefaults() { } - } public delegate FluentAssertions.Common.ITimer StartTimer(); public enum ValueFormatterDetectionMode { @@ -674,82 +600,36 @@ namespace FluentAssertions.Common Scan = 2, } } -namespace FluentAssertions.Data +namespace FluentAssertions.Configuration { - public class DataColumnAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions + public class GlobalConfiguration { - public DataColumnAssertions(System.Data.DataColumn dataColumn) { } - protected override string Identifier { get; } - public FluentAssertions.AndConstraint BeEquivalentTo(System.Data.DataColumn expectation, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeEquivalentTo(System.Data.DataColumn expectation, System.Func, FluentAssertions.Data.IDataEquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } + public GlobalConfiguration() { } + public FluentAssertions.Configuration.GlobalEquivalencyOptions Equivalency { get; set; } + public FluentAssertions.Configuration.GlobalFormattingOptions Formatting { get; set; } + public FluentAssertions.Configuration.TestFramework? TestFramework { get; set; } } - public class DataRowAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions> - where TDataRow : System.Data.DataRow + public class GlobalEquivalencyOptions { - public DataRowAssertions(TDataRow dataRow) { } - protected override string Identifier { get; } - public FluentAssertions.AndConstraint> BeEquivalentTo(System.Data.DataRow expectation, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint> BeEquivalentTo(System.Data.DataRow expectation, System.Func, FluentAssertions.Data.IDataEquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint, System.Data.DataColumn> HaveColumn(string expectedColumnName, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint> HaveColumns(params string[] expectedColumnNames) { } - public FluentAssertions.AndConstraint> HaveColumns(System.Collections.Generic.IEnumerable expectedColumnNames, string because = "", params object[] becauseArgs) { } + public GlobalEquivalencyOptions() { } + public FluentAssertions.Equivalency.EquivalencyPlan Plan { get; } + public FluentAssertions.Equivalency.EquivalencyOptions CloneDefaults() { } + public void Modify(System.Func configureOptions) { } } - public class DataSetAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions> - where TDataSet : System.Data.DataSet + public class GlobalFormattingOptions : FluentAssertions.Formatting.FormattingOptions { - public DataSetAssertions(TDataSet dataSet) { } - protected override string Identifier { get; } - public FluentAssertions.AndConstraint> BeEquivalentTo(System.Data.DataSet expectation, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint> BeEquivalentTo(System.Data.DataSet expectation, System.Func, FluentAssertions.Data.IDataEquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint, System.Data.DataTable> HaveTable(string expectedTableName, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint> HaveTableCount(int expected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint> HaveTables(params string[] expectedTableNames) { } - public FluentAssertions.AndConstraint> HaveTables(System.Collections.Generic.IEnumerable expectedTableNames, string because = "", params object[] becauseArgs) { } + public GlobalFormattingOptions() { } + public string ValueFormatterAssembly { get; set; } + public FluentAssertions.Common.ValueFormatterDetectionMode ValueFormatterDetectionMode { get; set; } } - public class DataTableAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions> - where TDataTable : System.Data.DataTable + public enum TestFramework { - public DataTableAssertions(TDataTable dataTable) { } - protected override string Identifier { get; } - public FluentAssertions.AndConstraint> BeEquivalentTo(System.Data.DataTable expectation, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint> BeEquivalentTo(System.Data.DataTable expectation, System.Func, FluentAssertions.Data.IDataEquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint, System.Data.DataColumn> HaveColumn(string expectedColumnName, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint> HaveColumns(params string[] expectedColumnNames) { } - public FluentAssertions.AndConstraint> HaveColumns(System.Collections.Generic.IEnumerable expectedColumnNames, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint> HaveRowCount(int expected, string because = "", params object[] becauseArgs) { } - } - public interface IDataEquivalencyAssertionOptions : FluentAssertions.Equivalency.IEquivalencyAssertionOptions - { - FluentAssertions.Data.IDataEquivalencyAssertionOptions AllowingMismatchedTypes(); - FluentAssertions.Data.IDataEquivalencyAssertionOptions Excluding(System.Linq.Expressions.Expression> predicate); - FluentAssertions.Data.IDataEquivalencyAssertionOptions Excluding(System.Linq.Expressions.Expression> expression); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingColumn(System.Data.DataColumn column); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingColumn(string tableName, string columnName); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingColumnInAllTables(string columnName); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingColumns(System.Collections.Generic.IEnumerable columns); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingColumns(params System.Data.DataColumn[] columns); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingColumns(string tableName, System.Collections.Generic.IEnumerable columnNames); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingColumns(string tableName, params string[] columnNames); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingColumnsInAllTables(System.Collections.Generic.IEnumerable columnNames); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingColumnsInAllTables(params string[] columnNames); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingOriginalData(); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingRelated(System.Linq.Expressions.Expression> expression); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingRelated(System.Linq.Expressions.Expression> expression); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingRelated(System.Linq.Expressions.Expression> expression); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingRelated(System.Linq.Expressions.Expression> expression); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingRelated(System.Linq.Expressions.Expression> expression); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingRelated(System.Linq.Expressions.Expression> expression); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingRelated(System.Linq.Expressions.Expression> expression); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingTable(string tableName); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingTables(System.Collections.Generic.IEnumerable tableNames); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingTables(params string[] tableNames); - FluentAssertions.Data.IDataEquivalencyAssertionOptions IgnoringUnmatchedColumns(); - FluentAssertions.Data.IDataEquivalencyAssertionOptions UsingRowMatchMode(FluentAssertions.Data.RowMatchMode rowMatchMode); - } - public enum RowMatchMode - { - Index = 0, - PrimaryKey = 1, + XUnit2 = 0, + XUnit3 = 1, + TUnit = 2, + MsTest = 3, + NUnit = 4, + MSpec = 5, } } namespace FluentAssertions.Equivalency @@ -762,7 +642,7 @@ namespace FluentAssertions.Equivalency public object Expectation { get; set; } public System.Type RuntimeType { get; } public object Subject { get; set; } - public System.Type GetExpectedType(FluentAssertions.Equivalency.IEquivalencyAssertionOptions options) { } + public System.Type GetExpectedType(FluentAssertions.Equivalency.IEquivalencyOptions options) { } public override string ToString() { } } public class ConversionSelector @@ -792,41 +672,58 @@ namespace FluentAssertions.Equivalency ForceEquals = 2, ForceMembers = 3, } - public class EquivalencyAssertionOptions : FluentAssertions.Equivalency.SelfReferenceEquivalencyAssertionOptions + public class EquivalencyOptions : FluentAssertions.Equivalency.SelfReferenceEquivalencyOptions { - public EquivalencyAssertionOptions() { } + public EquivalencyOptions() { } } - public class EquivalencyAssertionOptions : FluentAssertions.Equivalency.SelfReferenceEquivalencyAssertionOptions> + public class EquivalencyOptions : FluentAssertions.Equivalency.SelfReferenceEquivalencyOptions> { - public EquivalencyAssertionOptions() { } - public EquivalencyAssertionOptions(FluentAssertions.Equivalency.IEquivalencyAssertionOptions defaults) { } - public FluentAssertions.Equivalency.EquivalencyAssertionOptions> AsCollection() { } - public FluentAssertions.Equivalency.EquivalencyAssertionOptions Excluding(System.Linq.Expressions.Expression> expression) { } + public EquivalencyOptions() { } + public EquivalencyOptions(FluentAssertions.Equivalency.IEquivalencyOptions defaults) { } + public FluentAssertions.Equivalency.EquivalencyOptions> AsCollection() { } + public FluentAssertions.Equivalency.EquivalencyOptions Excluding(System.Linq.Expressions.Expression> expression) { } public FluentAssertions.Equivalency.NestedExclusionOptionBuilder For(System.Linq.Expressions.Expression>> expression) { } - public FluentAssertions.Equivalency.EquivalencyAssertionOptions Including(System.Linq.Expressions.Expression> expression) { } - public FluentAssertions.Equivalency.EquivalencyAssertionOptions WithMapping(string expectationMemberPath, string subjectMemberPath) { } - public FluentAssertions.Equivalency.EquivalencyAssertionOptions WithMapping(System.Linq.Expressions.Expression> expectationMemberPath, System.Linq.Expressions.Expression> subjectMemberPath) { } - public FluentAssertions.Equivalency.EquivalencyAssertionOptions WithMapping(System.Linq.Expressions.Expression> expectationMember, System.Linq.Expressions.Expression> subjectMember) { } - public FluentAssertions.Equivalency.EquivalencyAssertionOptions WithMapping(string expectationMemberName, string subjectMemberName) { } - public FluentAssertions.Equivalency.EquivalencyAssertionOptions WithStrictOrderingFor(System.Linq.Expressions.Expression> expression) { } - public FluentAssertions.Equivalency.EquivalencyAssertionOptions WithoutStrictOrderingFor(System.Linq.Expressions.Expression> expression) { } + public FluentAssertions.Equivalency.EquivalencyOptions Including(System.Linq.Expressions.Expression> expression) { } + public FluentAssertions.Equivalency.EquivalencyOptions WithMapping(string expectationMemberPath, string subjectMemberPath) { } + public FluentAssertions.Equivalency.EquivalencyOptions WithMapping(System.Linq.Expressions.Expression> expectationMemberPath, System.Linq.Expressions.Expression> subjectMemberPath) { } + public FluentAssertions.Equivalency.EquivalencyOptions WithMapping(System.Linq.Expressions.Expression> expectationMember, System.Linq.Expressions.Expression> subjectMember) { } + public FluentAssertions.Equivalency.EquivalencyOptions WithMapping(string expectationMemberName, string subjectMemberName) { } + public FluentAssertions.Equivalency.EquivalencyOptions WithStrictOrderingFor(System.Linq.Expressions.Expression> expression) { } + public FluentAssertions.Equivalency.EquivalencyOptions WithoutStrictOrderingFor(System.Linq.Expressions.Expression> expression) { } + } + public class EquivalencyPlan : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable + { + public EquivalencyPlan() { } + public void Add() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } + public void AddAfter() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } + public void Clear() { } + public System.Collections.Generic.IEnumerator GetEnumerator() { } + public void Insert() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } + public void InsertBefore() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } + public void Remove() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep { } + public void Reset() { } } public enum EquivalencyResult { ContinueWithNext = 0, - AssertionCompleted = 1, + EquivalencyProven = 1, } public abstract class EquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { protected EquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } - protected abstract FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator); + 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 nestedValidator); } public class EquivalencyValidationContext : FluentAssertions.Equivalency.IEquivalencyValidationContext { - public EquivalencyValidationContext(FluentAssertions.Equivalency.INode root, FluentAssertions.Equivalency.IEquivalencyAssertionOptions options) { } + public EquivalencyValidationContext(FluentAssertions.Equivalency.INode root, FluentAssertions.Equivalency.IEquivalencyOptions options) { } public FluentAssertions.Equivalency.INode CurrentNode { get; } - public FluentAssertions.Equivalency.IEquivalencyAssertionOptions Options { get; } + public FluentAssertions.Equivalency.IEquivalencyOptions Options { get; } public FluentAssertions.Execution.Reason Reason { get; set; } public FluentAssertions.Equivalency.Tracing.ITraceWriter TraceWriter { get; set; } public FluentAssertions.Equivalency.Tracing.Tracer Tracer { get; } @@ -837,24 +734,6 @@ namespace FluentAssertions.Equivalency public bool IsCyclicReference(object expectation) { } public override string ToString() { } } - public class EquivalencyValidator : FluentAssertions.Equivalency.IEquivalencyValidator - { - public EquivalencyValidator() { } - public void AssertEquality(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.EquivalencyValidationContext context) { } - public void RecursivelyAssertEquality(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context) { } - } - public class Field : FluentAssertions.Equivalency.Node, FluentAssertions.Equivalency.IMember, FluentAssertions.Equivalency.INode - { - public Field(System.Reflection.FieldInfo fieldInfo, FluentAssertions.Equivalency.INode parent) { } - public Field(System.Type reflectedType, System.Reflection.FieldInfo fieldInfo, FluentAssertions.Equivalency.INode parent) { } - public System.Type DeclaringType { get; set; } - public override string Description { get; } - public FluentAssertions.Common.CSharpAccessModifier GetterAccessibility { get; } - public bool IsBrowsable { get; } - public System.Type ReflectedType { get; } - public FluentAssertions.Common.CSharpAccessModifier SetterAccessibility { get; } - public object GetValue(object obj) { } - } public delegate string GetSubjectId(); public interface IAssertionContext { @@ -864,7 +743,7 @@ namespace FluentAssertions.Equivalency FluentAssertions.Equivalency.INode SelectedNode { get; } TSubject Subject { get; } } - public interface IEquivalencyAssertionOptions + public interface IEquivalencyOptions { bool AllowInfiniteRecursion { get; } bool? CompareRecordsByValue { get; } @@ -872,7 +751,12 @@ namespace FluentAssertions.Equivalency FluentAssertions.Equivalency.CyclicReferenceHandling CyclicReferenceHandling { get; } FluentAssertions.Equivalency.EnumEquivalencyHandling EnumEquivalencyHandling { get; } bool ExcludeNonBrowsableOnExpectation { get; } + bool IgnoreCase { get; } + bool IgnoreJsonPropertyCasing { get; } + bool IgnoreLeadingWhitespace { get; } + bool IgnoreNewlineStyle { get; } bool IgnoreNonBrowsableOnSubject { get; } + bool IgnoreTrailingWhitespace { get; } FluentAssertions.Equivalency.MemberVisibility IncludedFields { get; } FluentAssertions.Equivalency.MemberVisibility IncludedProperties { get; } bool IsRecursive { get; } @@ -886,12 +770,12 @@ namespace FluentAssertions.Equivalency } public interface IEquivalencyStep { - FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator); + FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes); } public interface IEquivalencyValidationContext { FluentAssertions.Equivalency.INode CurrentNode { get; } - FluentAssertions.Equivalency.IEquivalencyAssertionOptions Options { get; } + FluentAssertions.Equivalency.IEquivalencyOptions Options { get; } FluentAssertions.Execution.Reason Reason { get; } FluentAssertions.Equivalency.Tracing.Tracer Tracer { get; } FluentAssertions.Equivalency.IEquivalencyValidationContext AsCollectionItem(string index); @@ -900,10 +784,6 @@ namespace FluentAssertions.Equivalency FluentAssertions.Equivalency.IEquivalencyValidationContext Clone(); bool IsCyclicReference(object expectation); } - public interface IEquivalencyValidator - { - void RecursivelyAssertEquality(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context); - } public interface IMember : FluentAssertions.Equivalency.INode { System.Type DeclaringType { get; } @@ -924,7 +804,7 @@ namespace FluentAssertions.Equivalency } public interface IMemberMatchingRule { - FluentAssertions.Equivalency.IMember Match(FluentAssertions.Equivalency.IMember expectedMember, object subject, FluentAssertions.Equivalency.INode parent, FluentAssertions.Equivalency.IEquivalencyAssertionOptions 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 { @@ -934,15 +814,14 @@ namespace FluentAssertions.Equivalency public interface INode { int Depth { get; } - string Description { get; } + FluentAssertions.Equivalency.Pathway Expectation { get; } FluentAssertions.Equivalency.GetSubjectId GetSubjectId { get; } bool IsRoot { get; } - string Name { get; set; } System.Type ParentType { get; } - string Path { get; } - string PathAndName { get; } bool RootIsCollection { get; } + FluentAssertions.Equivalency.Pathway Subject { get; } System.Type Type { get; } + void AdjustForRemappedSubject(FluentAssertions.Equivalency.IMember subjectMember); } public interface IObjectInfo { @@ -957,13 +836,17 @@ namespace FluentAssertions.Equivalency { FluentAssertions.Equivalency.OrderStrictness Evaluate(FluentAssertions.Equivalency.IObjectInfo objectInfo); } + public interface IValidateChildNodeEquivalency + { + void AssertEquivalencyOf(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context); + } public static class MemberFactory { public static FluentAssertions.Equivalency.IMember Create(System.Reflection.MemberInfo memberInfo, FluentAssertions.Equivalency.INode parent) { } } public class MemberSelectionContext { - public MemberSelectionContext(System.Type compileTimeType, System.Type runtimeType, FluentAssertions.Equivalency.IEquivalencyAssertionOptions options) { } + public MemberSelectionContext(System.Type compileTimeType, System.Type runtimeType, FluentAssertions.Equivalency.IEquivalencyOptions options) { } public FluentAssertions.Equivalency.MemberVisibility IncludedFields { get; } public FluentAssertions.Equivalency.MemberVisibility IncludedProperties { get; } public System.Type Type { get; } @@ -979,29 +862,9 @@ namespace FluentAssertions.Equivalency } public class NestedExclusionOptionBuilder { - public FluentAssertions.Equivalency.EquivalencyAssertionOptions Exclude(System.Linq.Expressions.Expression> expression) { } + public FluentAssertions.Equivalency.EquivalencyOptions Exclude(System.Linq.Expressions.Expression> expression) { } public FluentAssertions.Equivalency.NestedExclusionOptionBuilder For(System.Linq.Expressions.Expression>> expression) { } } - public class Node : FluentAssertions.Equivalency.INode - { - public Node() { } - public int Depth { get; } - public virtual string Description { get; } - public FluentAssertions.Equivalency.GetSubjectId GetSubjectId { get; set; } - public bool IsRoot { get; } - public string Name { get; set; } - public System.Type ParentType { get; set; } - public string Path { get; set; } - public string PathAndName { get; } - public bool RootIsCollection { get; set; } - public System.Type Type { get; set; } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - public override string ToString() { } - public static FluentAssertions.Equivalency.INode From(FluentAssertions.Equivalency.GetSubjectId getSubjectId) { } - public static FluentAssertions.Equivalency.INode FromCollectionItem(string index, FluentAssertions.Equivalency.INode parent) { } - public static FluentAssertions.Equivalency.INode FromDictionaryItem(object key, FluentAssertions.Equivalency.INode parent) { } - } public enum OrderStrictness { Strict = 0, @@ -1016,24 +879,28 @@ namespace FluentAssertions.Equivalency public System.Collections.Generic.IEnumerator GetEnumerator() { } public bool IsOrderingStrictFor(FluentAssertions.Equivalency.IObjectInfo objectInfo) { } } - public class Property : FluentAssertions.Equivalency.Node, FluentAssertions.Equivalency.IMember, FluentAssertions.Equivalency.INode + public class Pathway : System.IEquatable { - public Property(System.Reflection.PropertyInfo propertyInfo, FluentAssertions.Equivalency.INode parent) { } - public Property(System.Type reflectedType, System.Reflection.PropertyInfo propertyInfo, FluentAssertions.Equivalency.INode parent) { } - public System.Type DeclaringType { get; } - public override string Description { get; } - public FluentAssertions.Common.CSharpAccessModifier GetterAccessibility { get; } - public bool IsBrowsable { get; } - public System.Type ReflectedType { get; } - public FluentAssertions.Common.CSharpAccessModifier SetterAccessibility { get; } - public object GetValue(object obj) { } + public Pathway(FluentAssertions.Equivalency.Pathway parent, string name, FluentAssertions.Equivalency.Pathway.GetDescription getDescription) { } + public Pathway(string path, string name, FluentAssertions.Equivalency.Pathway.GetDescription getDescription) { } + public string Description { get; } + public string Name { get; } + public string Path { get; } + public string PathAndName { get; } + public override string ToString() { } + public delegate string GetDescription(string pathAndName); } - public abstract class SelfReferenceEquivalencyAssertionOptions : FluentAssertions.Equivalency.IEquivalencyAssertionOptions - where TSelf : FluentAssertions.Equivalency.SelfReferenceEquivalencyAssertionOptions + public abstract class SelfReferenceEquivalencyOptions : FluentAssertions.Equivalency.IEquivalencyOptions + where TSelf : FluentAssertions.Equivalency.SelfReferenceEquivalencyOptions { - protected SelfReferenceEquivalencyAssertionOptions(FluentAssertions.Equivalency.IEquivalencyAssertionOptions defaults) { } + protected SelfReferenceEquivalencyOptions(FluentAssertions.Equivalency.IEquivalencyOptions defaults) { } public bool? CompareRecordsByValue { get; } public FluentAssertions.Equivalency.ConversionSelector ConversionSelector { get; } + public bool IgnoreCase { get; } + public bool IgnoreJsonPropertyCasing { get; set; } + public bool IgnoreLeadingWhitespace { get; } + public bool IgnoreNewlineStyle { get; } + public bool IgnoreTrailingWhitespace { get; } [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] protected FluentAssertions.Equivalency.OrderingRuleCollection OrderingRules { get; } public FluentAssertions.Equivalency.Tracing.ITraceWriter TraceWriter { get; } @@ -1049,13 +916,18 @@ namespace FluentAssertions.Equivalency public TSelf ComparingRecordsByMembers() { } public TSelf ComparingRecordsByValue() { } public TSelf Excluding(System.Linq.Expressions.Expression> predicate) { } + public TSelf ExcludingExplicitlyImplementedProperties() { } public TSelf ExcludingFields() { } + public TSelf ExcludingMembersNamed(params string[] memberNames) { } public TSelf ExcludingMissingMembers() { } - public TSelf ExcludingNestedObjects() { } public TSelf ExcludingNonBrowsableMembers() { } public TSelf ExcludingProperties() { } + public TSelf IgnoringCase() { } public TSelf IgnoringCyclicReferences() { } + public TSelf IgnoringLeadingWhitespace() { } + public TSelf IgnoringNewlineStyle() { } public TSelf IgnoringNonBrowsableMembersOnSubject() { } + public TSelf IgnoringTrailingWhitespace() { } public TSelf Including(System.Linq.Expressions.Expression> predicate) { } public TSelf IncludingAllDeclaredProperties() { } public TSelf IncludingAllRuntimeProperties() { } @@ -1064,15 +936,16 @@ namespace FluentAssertions.Equivalency public TSelf IncludingInternalProperties() { } public TSelf IncludingNestedObjects() { } public TSelf IncludingProperties() { } - public TSelf RespectingDeclaredTypes() { } - public TSelf RespectingRuntimeTypes() { } + public TSelf PreferringDeclaredMemberTypes() { } + public TSelf PreferringRuntimeMemberTypes() { } public TSelf ThrowingOnMissingMembers() { } public override string ToString() { } public TSelf Using(FluentAssertions.Equivalency.IEquivalencyStep equivalencyStep) { } public TSelf Using(FluentAssertions.Equivalency.IMemberMatchingRule matchingRule) { } public TSelf Using(FluentAssertions.Equivalency.IMemberSelectionRule selectionRule) { } public TSelf Using(FluentAssertions.Equivalency.IOrderingRule orderingRule) { } - public FluentAssertions.Equivalency.SelfReferenceEquivalencyAssertionOptions.Restriction Using(System.Action> action) { } + public TSelf Using(System.Collections.Generic.IEqualityComparer comparer) { } + public FluentAssertions.Equivalency.SelfReferenceEquivalencyOptions.Restriction Using(System.Action> action) { } public TSelf Using(System.Collections.Generic.IEqualityComparer comparer) { } public TSelf Using() where TEqualityComparer : System.Collections.Generic.IEqualityComparer, new () { } @@ -1080,12 +953,16 @@ namespace FluentAssertions.Equivalency public TSelf WithAutoConversionFor(System.Linq.Expressions.Expression> predicate) { } public TSelf WithStrictOrdering() { } public TSelf WithStrictOrderingFor(System.Linq.Expressions.Expression> predicate) { } + public TSelf WithStrictTyping() { } + public TSelf WithStrictTypingFor(System.Linq.Expressions.Expression> predicate) { } public TSelf WithTracing(FluentAssertions.Equivalency.Tracing.ITraceWriter writer = null) { } public TSelf WithoutAutoConversionFor(System.Linq.Expressions.Expression> predicate) { } - public void WithoutMatchingRules() { } - public void WithoutSelectionRules() { } + public TSelf WithoutMatchingRules() { } + public TSelf WithoutRecursing() { } + public TSelf WithoutSelectionRules() { } public TSelf WithoutStrictOrdering() { } public TSelf WithoutStrictOrderingFor(System.Linq.Expressions.Expression> predicate) { } + public TSelf WithoutStrictTyping() { } public class Restriction { public Restriction(TSelf options, System.Action> action) { } @@ -1102,135 +979,117 @@ namespace FluentAssertions.Equivalency public static bool WhichSetterHas(this FluentAssertions.Equivalency.IMemberInfo memberInfo, FluentAssertions.Common.CSharpAccessModifier accessModifier) { } } } +namespace FluentAssertions.Equivalency.Inlining +{ + public interface IInlineEquivalencyAssertion + { + void Execute(FluentAssertions.Execution.AssertionChain assertionChain, FluentAssertions.Equivalency.Comparands comparands); + } + public class InlineEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep + { + public InlineEquivalencyStep() { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } + } +} namespace FluentAssertions.Equivalency.Steps { public class AssertionRuleEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { - public AssertionRuleEquivalencyStep(System.Linq.Expressions.Expression> predicate, System.Action> assertion) { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + 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() { } } public class AutoConversionStep : FluentAssertions.Equivalency.IEquivalencyStep { public AutoConversionStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } public override string ToString() { } } - public class ConstraintCollectionEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep - { - public ConstraintCollectionEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } - } - public class ConstraintEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep - { - public ConstraintEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } - } - public class DataColumnEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep + public class DateAndTimeEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { - public DataColumnEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } - } - public class DataRelationEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep - { - public DataRelationEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } - } - public class DataRowCollectionEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep - { - public DataRowCollectionEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } - } - public class DataRowEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep - { - public DataRowEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } - } - public class DataSetEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep - { - public DataSetEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } - } - public class DataTableEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep - { - public DataTableEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public DateAndTimeEquivalencyStep() { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class DictionaryEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep { public DictionaryEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nestedValidator) { } } public class EnumEqualityStep : FluentAssertions.Equivalency.IEquivalencyStep { public EnumEqualityStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class EnumerableEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public EnumerableEquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class EqualityComparerEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public EqualityComparerEquivalencyStep(System.Collections.Generic.IEqualityComparer comparer) { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } public override string ToString() { } } public class GenericDictionaryEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public GenericDictionaryEquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class GenericEnumerableEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public GenericEnumerableEquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class ReferenceEqualityEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public ReferenceEqualityEquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class RunAllUserStepsEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public RunAllUserStepsEquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class SimpleEqualityEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public SimpleEqualityEquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class StringEqualityEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public StringEqualityEquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class StructuralEqualityEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public StructuralEqualityEquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } + } + public class TypeEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep + { + public TypeEquivalencyStep() { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class ValueTypeEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public ValueTypeEquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class XAttributeEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep { public XAttributeEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + 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.IEquivalencyValidator nestedValidator) { } + 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.IEquivalencyValidator nestedValidator) { } + protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nestedValidator) { } } } namespace FluentAssertions.Equivalency.Tracing @@ -1260,7 +1119,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) { } @@ -1274,6 +1133,12 @@ namespace FluentAssertions.Events public string EventName { get; } public System.Type HandlerType { get; } } + public class EventMonitorOptions + { + public EventMonitorOptions() { } + public FluentAssertions.Events.EventMonitorOptions IgnoringEventAccessorExceptions() { } + public FluentAssertions.Events.EventMonitorOptions RecordingEventsWithBrokenAccessor() { } + } public interface IEventRecording : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable { System.Type EventHandlerType { get; } @@ -1300,75 +1165,60 @@ namespace FluentAssertions.Events } namespace FluentAssertions.Execution { - [System.Serializable] + 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 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() { } + } 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.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 { @@ -1378,28 +1228,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.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; } @@ -1411,6 +1246,12 @@ namespace FluentAssertions.Execution { object Clone(); } + public interface ITestFramework + { + bool IsAvailable { get; } + [System.Diagnostics.CodeAnalysis.DoesNotReturn] + void Throw(string message); + } public class Reason { public Reason(string formattedMessage, object[] arguments) { } @@ -1418,6 +1259,14 @@ namespace FluentAssertions.Execution public string FormattedMessage { get; set; } } } +namespace FluentAssertions.Extensibility +{ + [System.AttributeUsage(System.AttributeTargets.Assembly, AllowMultiple=true)] + public sealed class AssertionEngineInitializerAttribute : System.Attribute + { + public AssertionEngineInitializerAttribute(System.Type type, string methodName) { } + } +} namespace FluentAssertions.Extensions { public static class FluentDateTimeExtensions @@ -1523,7 +1372,6 @@ namespace FluentAssertions.Formatting public class DefaultValueFormatter : FluentAssertions.Formatting.IValueFormatter { public DefaultValueFormatter() { } - protected virtual int SpacesPerIndentionLevel { get; } public virtual bool CanHandle(object value) { } public void Format(object value, FluentAssertions.Formatting.FormattedObjectGraph formattedGraph, FluentAssertions.Formatting.FormattingContext context, FluentAssertions.Formatting.FormatChild formatChild) { } protected virtual System.Reflection.MemberInfo[] GetMembers(System.Type type) { } @@ -1575,7 +1423,8 @@ namespace FluentAssertions.Formatting public static int SpacesPerIndentation { get; } public void AddFragment(string fragment) { } public void AddFragmentOnNewLine(string fragment) { } - public void AddLine(string line) { } + public void AddLine(string content) { } + public void AddLineOrFragment(string fragment) { } public override string ToString() { } public System.IDisposable WithIndentation() { } } @@ -1597,6 +1446,8 @@ namespace FluentAssertions.Formatting public int MaxDepth { get; set; } public int MaxLines { get; set; } public bool UseLineBreaks { get; set; } + public void AddFormatter(FluentAssertions.Formatting.IValueFormatter formatter) { } + public void RemoveFormatter(FluentAssertions.Formatting.IValueFormatter formatter) { } } public class GuidValueFormatter : FluentAssertions.Formatting.IValueFormatter { @@ -1630,8 +1481,12 @@ namespace FluentAssertions.Formatting public class MaxLinesExceededException : System.Exception { public MaxLinesExceededException() { } - 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 { @@ -1739,21 +1594,19 @@ 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) { } - public FluentAssertions.AndConstraint BeEquivalentTo(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeGreaterOrEqualTo(T expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeEquivalentTo(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeGreaterThan(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeGreaterThanOrEqualTo(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeInRange(T minimumValue, T maximumValue, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeLessOrEqualTo(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeLessThan(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeLessThanOrEqualTo(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOneOf(params T[] validValues) { } @@ -1766,37 +1619,32 @@ 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 + public class NullableNumericAssertions : FluentAssertions.Numeric.NumericAssertionsBase where T : struct, System.IComparable where TAssertions : FluentAssertions.Numeric.NullableNumericAssertions { - public NullableNumericAssertions(T? value) { } + public NullableNumericAssertions(T? value, FluentAssertions.Execution.AssertionChain assertionChain) { } + public override T? Subject { get; } 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) { } public FluentAssertions.AndConstraint NotBeNull(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveValue(string because = "", params object[] becauseArgs) { } } - public class NumericAssertions : FluentAssertions.Numeric.NumericAssertions> - where T : struct, System.IComparable - { - public NumericAssertions(T value) { } - } - public class NumericAssertions + public abstract class NumericAssertionsBase where T : struct, System.IComparable - where TAssertions : FluentAssertions.Numeric.NumericAssertions + where TAssertions : FluentAssertions.Numeric.NumericAssertionsBase { - public NumericAssertions(T value) { } - public T? Subject { get; } + protected NumericAssertionsBase(FluentAssertions.Execution.AssertionChain assertionChain) { } + public FluentAssertions.Execution.AssertionChain CurrentAssertionChain { get; } + public abstract TSubject Subject { get; } public FluentAssertions.AndConstraint Be(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Be(T? expected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeGreaterOrEqualTo(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeGreaterThan(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeGreaterThanOrEqualTo(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeInRange(T minimumValue, T maximumValue, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeLessOrEqualTo(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeLessThan(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeLessThanOrEqualTo(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeNegative(string because = "", params object[] becauseArgs) { } @@ -1811,17 +1659,29 @@ namespace FluentAssertions.Numeric public FluentAssertions.AndConstraint NotBeInRange(T minimumValue, T maximumValue, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeOfType(System.Type unexpectedType, string because = "", params object[] becauseArgs) { } } + public class NumericAssertions : FluentAssertions.Numeric.NumericAssertions> + where T : struct, System.IComparable + { + public NumericAssertions(T value, FluentAssertions.Execution.AssertionChain assertionChain) { } + } + public class NumericAssertions : FluentAssertions.Numeric.NumericAssertionsBase + where T : struct, System.IComparable + where TAssertions : FluentAssertions.Numeric.NumericAssertions + { + public NumericAssertions(T value, FluentAssertions.Execution.AssertionChain assertionChain) { } + public override T Subject { get; } + } } 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) { } @@ -1832,12 +1692,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) { } @@ -1852,7 +1712,7 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint BeOnOrAfter(System.DateTime expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOnOrBefore(System.DateTime expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOneOf(params System.DateTime[] validValues) { } - public FluentAssertions.AndConstraint BeOneOf(params System.Nullable[] validValues) { } + public FluentAssertions.AndConstraint BeOneOf(params System.DateTime?[] validValues) { } public FluentAssertions.AndConstraint BeOneOf(System.Collections.Generic.IEnumerable validValues, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOneOf(System.Collections.Generic.IEnumerable validValues, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeSameDateAs(System.DateTime expected, string because = "", params object[] becauseArgs) { } @@ -1869,6 +1729,7 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint NotBeAfter(System.DateTime unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeBefore(System.DateTime unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeCloseTo(System.DateTime distantTime, System.TimeSpan precision, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotBeIn(System.DateTimeKind unexpectedKind, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeOnOrAfter(System.DateTime unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeOnOrBefore(System.DateTime unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeSameDateAs(System.DateTime unexpected, string because = "", params object[] becauseArgs) { } @@ -1881,12 +1742,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) { } @@ -1902,7 +1763,7 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint BeOnOrAfter(System.DateTimeOffset expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOnOrBefore(System.DateTimeOffset expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOneOf(params System.DateTimeOffset[] validValues) { } - public FluentAssertions.AndConstraint BeOneOf(params System.Nullable[] validValues) { } + public FluentAssertions.AndConstraint BeOneOf(params System.DateTimeOffset?[] validValues) { } public FluentAssertions.AndConstraint BeOneOf(System.Collections.Generic.IEnumerable validValues, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOneOf(System.Collections.Generic.IEnumerable validValues, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeSameDateAs(System.DateTimeOffset expected, string because = "", params object[] becauseArgs) { } @@ -1936,7 +1797,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) { } @@ -1944,7 +1805,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) { } @@ -1952,13 +1813,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) { } @@ -1985,12 +1846,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) { } @@ -2000,31 +1861,14 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint NotBe(string unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEmpty(string because = "", params object[] becauseArgs) { } } - public class HttpResponseMessageAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions - { - public HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { } - } - public class HttpResponseMessageAssertions : FluentAssertions.Primitives.ObjectAssertions - where TAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions - { - protected HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { } - protected override string Identifier { get; } - public FluentAssertions.AndConstraint BeRedirection(string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeSuccessful(string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint HaveClientError(string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint HaveError(string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint HaveServerError(string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint HaveStatusCode(System.Net.HttpStatusCode expected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint NotHaveStatusCode(System.Net.HttpStatusCode unexpected, string because = "", params object[] becauseArgs) { } - } 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) { } @@ -2036,12 +1880,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) { } @@ -2049,12 +1893,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) { } @@ -2063,13 +1907,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) { } @@ -2077,12 +1921,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) { } @@ -2091,12 +1935,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) { } @@ -2105,7 +1949,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) { } @@ -2113,12 +1957,12 @@ 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) { } public FluentAssertions.AndConstraint BeEquivalentTo(TExpectation expectation, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeEquivalentTo(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeEquivalentTo(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOneOf(params TSubject[] validValues) { } public FluentAssertions.AndConstraint BeOneOf(System.Collections.Generic.IEnumerable validValues, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOneOf(System.Collections.Generic.IEnumerable validValues, System.Collections.Generic.IEqualityComparer comparer, string because = "", params object[] becauseArgs) { } @@ -2126,12 +1970,13 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint NotBe(TSubject unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBe(TSubject unexpected, System.Collections.Generic.IEqualityComparer comparer, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEquivalentTo(TExpectation unexpected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint NotBeEquivalentTo(TExpectation unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotBeEquivalentTo(TExpectation unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } } 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) { } @@ -2150,22 +1995,22 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint NotBeOfType(System.Type unexpectedType, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeOfType(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeSameAs(TSubject unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint Satisfy(System.Action assertion) + where T : TSubject { } } 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) { } - public FluentAssertions.AndConstraint BeGreaterOrEqualTo(System.TimeSpan expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeGreaterThan(System.TimeSpan expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeGreaterThanOrEqualTo(System.TimeSpan expected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeLessOrEqualTo(System.TimeSpan expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeLessThan(System.TimeSpan expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeLessThanOrEqualTo(System.TimeSpan expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeNegative(string because = "", params object[] becauseArgs) { } @@ -2176,16 +2021,17 @@ 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) { } public FluentAssertions.AndConstraint BeEquivalentTo(string expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeEquivalentTo(string expected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeLowerCased(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeNullOrEmpty(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeNullOrWhiteSpace(string because = "", params object[] becauseArgs) { } @@ -2200,18 +2046,23 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint ContainAny(System.Collections.Generic.IEnumerable values, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint ContainEquivalentOf(string expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint ContainEquivalentOf(string expected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint ContainEquivalentOf(string expected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint ContainEquivalentOf(string expected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint EndWith(string expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint EndWithEquivalentOf(string expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint EndWithEquivalentOf(string expected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveLength(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Match(string wildcardPattern, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint MatchEquivalentOf(string wildcardPattern, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint MatchRegex(string regularExpression, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint MatchEquivalentOf(string wildcardPattern, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint MatchRegex(System.Text.RegularExpressions.Regex regularExpression, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint MatchRegex(string regularExpression, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint MatchRegex(string regularExpression, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint MatchRegex(System.Text.RegularExpressions.Regex regularExpression, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint MatchRegex(string regularExpression, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBe(string unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEmpty(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEquivalentTo(string unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotBeEquivalentTo(string unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeLowerCased(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeNullOrEmpty(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeNullOrWhiteSpace(string because = "", params object[] becauseArgs) { } @@ -2222,16 +2073,21 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint NotContainAny(params string[] values) { } public FluentAssertions.AndConstraint NotContainAny(System.Collections.Generic.IEnumerable values, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotContainEquivalentOf(string unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotContainEquivalentOf(string unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotEndWith(string unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotEndWithEquivalentOf(string unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotEndWithEquivalentOf(string unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotMatch(string wildcardPattern, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotMatchEquivalentOf(string wildcardPattern, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint NotMatchRegex(string regularExpression, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotMatchEquivalentOf(string wildcardPattern, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotMatchRegex(System.Text.RegularExpressions.Regex regularExpression, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotMatchRegex(string regularExpression, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotStartWith(string unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotStartWithEquivalentOf(string unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotStartWithEquivalentOf(string unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint StartWith(string expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint StartWithEquivalentOf(string expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint StartWithEquivalentOf(string expected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } } public enum TimeSpanCondition { @@ -2242,49 +2098,32 @@ namespace FluentAssertions.Primitives LessThan = 4, } } -namespace FluentAssertions.Reflection -{ - public class AssemblyAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions - { - public AssemblyAssertions(System.Reflection.Assembly assembly) { } - 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) { } - public FluentAssertions.AndWhichConstraint DefineType(string @namespace, string name, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint NotReference(System.Reflection.Assembly assembly, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint Reference(System.Reflection.Assembly assembly, string because = "", params object[] becauseArgs) { } - } -} 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) { } + public FluentAssertions.AndConstraint NotThrowAfter(System.TimeSpan waitTime, System.TimeSpan pollInterval, string because = "", params object[] becauseArgs) { } } public class AsyncFunctionAssertions : FluentAssertions.Specialized.DelegateAssertionsBase, TAssertions> where TTask : System.Threading.Tasks.Task where TAssertions : FluentAssertions.Specialized.AsyncFunctionAssertions { - [System.Obsolete("This class is intended as base class. This ctor is accidentally public and will b" + - "e removed in Version 7.")] - public AsyncFunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor) { } - [System.Obsolete("This class is intended as base class. This ctor is accidentally public and will b" + - "e made protected in Version 7.")] - public 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> CompleteWithinAsync(System.TimeSpan timeSpan, string because = "", params object[] becauseArgs) { } public System.Threading.Tasks.Task> NotCompleteWithinAsync(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 System.Threading.Tasks.Task> NotThrowAsync(string because = "", params object[] becauseArgs) where TException : System.Exception { } + public System.Threading.Tasks.Task> ThrowAsync(string because = "", params object[] becauseArgs) { } public System.Threading.Tasks.Task> ThrowAsync(string because = "", params object[] becauseArgs) where TException : System.Exception { } public System.Threading.Tasks.Task> ThrowExactlyAsync(string because = "", params object[] becauseArgs) where TException : System.Exception { } + public System.Threading.Tasks.Task> ThrowWithinAsync(System.TimeSpan timeSpan, string because = "", params object[] becauseArgs) { } public System.Threading.Tasks.Task> ThrowWithinAsync(System.TimeSpan timeSpan, string because = "", params object[] becauseArgs) where TException : System.Exception { } } @@ -2302,12 +2141,11 @@ 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) { } public FluentAssertions.AndConstraint NotThrow(string because = "", params object[] becauseArgs) where TException : System.Exception { } - public FluentAssertions.AndConstraint NotThrowAfter(System.TimeSpan waitTime, System.TimeSpan pollInterval, string because = "", params object[] becauseArgs) { } + public FluentAssertions.Specialized.ExceptionAssertions Throw(string because = "", params object[] becauseArgs) { } public FluentAssertions.Specialized.ExceptionAssertions Throw(string because = "", params object[] becauseArgs) where TException : System.Exception { } public FluentAssertions.Specialized.ExceptionAssertions ThrowExactly(string because = "", params object[] becauseArgs) @@ -2316,7 +2154,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; } @@ -2338,20 +2176,18 @@ 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 BeGreaterOrEqualTo(System.TimeSpan minDuration, 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) { } - public FluentAssertions.AndConstraint BeLessOrEqualTo(System.TimeSpan maxDuration, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeLessThan(System.TimeSpan maxDuration, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeLessThanOrEqualTo(System.TimeSpan maxDuration, string because = "", params object[] becauseArgs) { } public override bool Equals(object obj) { } } 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) { } @@ -2359,8 +2195,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) { } @@ -2376,8 +2212,11 @@ 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 TaskCompletionSourceAssertionsBase { @@ -2386,8 +2225,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) { } } @@ -2396,23 +2235,23 @@ 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 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) { } @@ -2436,16 +2275,26 @@ namespace FluentAssertions.Types { public static FluentAssertions.Types.TypeSelector From(System.Reflection.Assembly assembly) { } } + public class AssemblyAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions + { + 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) { } + public FluentAssertions.AndWhichConstraint DefineType(string @namespace, string name, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotReference(System.Reflection.Assembly assembly, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint Reference(System.Reflection.Assembly assembly, string because = "", params object[] becauseArgs) { } + } 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; } } 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; } public FluentAssertions.AndWhichConstraint, TAttribute> BeDecoratedWith(string because = "", params object[] becauseArgs) where TAttribute : System.Attribute { } @@ -2460,13 +2309,13 @@ 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; } public FluentAssertions.AndConstraint BeAsync(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeVirtual(string because = "", params object[] becauseArgs) { } @@ -2510,7 +2359,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) { } @@ -2531,7 +2380,7 @@ 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; } public FluentAssertions.AndConstraint BeReadable(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeReadable(FluentAssertions.Common.CSharpAccessModifier accessModifier, string because = "", params object[] becauseArgs) { } @@ -2573,7 +2422,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) @@ -2588,13 +2437,13 @@ 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) { } public FluentAssertions.AndConstraint BeAbstract(string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeAssignableTo(System.Type type, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeAssignableTo(string because = "", params object[] becauseArgs) { } + public new FluentAssertions.AndConstraint BeAssignableTo(System.Type type, string because = "", params object[] becauseArgs) { } + public new FluentAssertions.AndConstraint BeAssignableTo(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint BeDecoratedWith(string because = "", params object[] becauseArgs) where TAttribute : System.Attribute { } public FluentAssertions.AndWhichConstraint BeDecoratedWith(System.Linq.Expressions.Expression> isMatchingAttributePredicate, string because = "", params object[] becauseArgs) @@ -2631,8 +2480,8 @@ namespace FluentAssertions.Types public FluentAssertions.AndConstraint NotBe(System.Type unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBe(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeAbstract(string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint NotBeAssignableTo(System.Type type, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint NotBeAssignableTo(string because = "", params object[] becauseArgs) { } + public new FluentAssertions.AndConstraint NotBeAssignableTo(System.Type type, string because = "", params object[] becauseArgs) { } + public new FluentAssertions.AndConstraint NotBeAssignableTo(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeDecoratedWith(string because = "", params object[] becauseArgs) where TAttribute : System.Attribute { } public FluentAssertions.AndConstraint NotBeDecoratedWith(System.Linq.Expressions.Expression> isMatchingAttributePredicate, string because = "", params object[] becauseArgs) @@ -2706,7 +2555,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 { } @@ -2737,7 +2586,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) { } @@ -2745,38 +2594,56 @@ 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) { } - public FluentAssertions.AndWhichConstraint HaveElement(string expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint HaveElement(System.Xml.Linq.XName expected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint> HaveElement(string expected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint HaveElement(string expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint> HaveElement(System.Xml.Linq.XName expected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint HaveRoot(string expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint> HaveElement(string expected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint HaveElementWithValue(System.Xml.Linq.XName expectedElement, string expectedValue, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint HaveElementWithValue(string expectedElement, string expectedValue, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint HaveRoot(System.Xml.Linq.XName expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint HaveRoot(string expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBe(System.Xml.Linq.XDocument unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEquivalentTo(System.Xml.Linq.XDocument unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveElement(System.Xml.Linq.XName unexpectedElement, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveElement(string unexpectedElement, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveElementWithValue(System.Xml.Linq.XName unexpectedElement, string unexpectedValue, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveElementWithValue(string unexpectedElement, string unexpectedValue, string because = "", params object[] becauseArgs) { } } 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) { } - public FluentAssertions.AndConstraint HaveAttribute(string expectedName, string expectedValue, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint HaveAttribute(System.Xml.Linq.XName expectedName, string expectedValue, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint HaveElement(string expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveAttribute(System.Xml.Linq.XName expectedName, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveAttribute(string expectedName, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveAttributeWithValue(System.Xml.Linq.XName expectedName, string expectedValue, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveAttributeWithValue(string expectedName, string expectedValue, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint HaveElement(System.Xml.Linq.XName expected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint> HaveElement(string expected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint HaveElement(string expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint> HaveElement(System.Xml.Linq.XName expected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint> HaveElement(string expected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint HaveElementWithValue(System.Xml.Linq.XName expectedElement, string expectedValue, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint HaveElementWithValue(string expectedElement, string expectedValue, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveValue(string expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBe(System.Xml.Linq.XElement unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEquivalentTo(System.Xml.Linq.XElement unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveAttribute(System.Xml.Linq.XName unexpectedName, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveAttribute(string unexpectedName, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveAttributeWithValue(System.Xml.Linq.XName unexpectedName, string unexpectedValue, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveAttributeWithValue(string unexpectedName, string unexpectedValue, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveElement(System.Xml.Linq.XName unexpectedElement, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveElement(string unexpectedElement, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveElementWithValue(System.Xml.Linq.XName unexpectedElement, string unexpectedValue, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveElementWithValue(string unexpectedElement, string unexpectedValue, string because = "", params object[] becauseArgs) { } } 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) { } @@ -2786,13 +2653,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 5adccdf720..ecbbf3f829 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net6.0.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net6.0.verified.txt @@ -10,17 +10,30 @@ namespace FluentAssertions public System.Collections.Generic.IEnumerable OfType(System.Exception actualException) where T : System.Exception { } } - public class AndConstraint + public class AndConstraint { - public AndConstraint(T parentConstraint) { } - public T And { get; } + 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) { } + 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 AssertionConfiguration + { + public static FluentAssertions.Configuration.GlobalConfiguration Current { get; } + } + public static class AssertionEngine + { + public static FluentAssertions.Configuration.GlobalConfiguration Configuration { get; } + public static FluentAssertions.Execution.ITestFramework TestFramework { get; set; } + public static void ResetToDefaults() { } } public static class AssertionExtensions { @@ -37,7 +50,8 @@ namespace FluentAssertions public static FluentAssertions.Specialized.MemberExecutionTime ExecutionTimeOf(this T subject, System.Linq.Expressions.Expression> action, FluentAssertions.Common.StartTimer createTimer = null) { } public static System.Action Invoking(this T subject, System.Action action) { } public static System.Func Invoking(this T subject, System.Func action) { } - public static FluentAssertions.Events.IMonitor Monitor(this T eventSource, System.Func utcNow = null) { } + public static FluentAssertions.Events.IMonitor Monitor(this T eventSource) { } + public static FluentAssertions.Events.IMonitor Monitor(this T eventSource, System.Action configureOptions) { } public static FluentAssertions.Specialized.ExecutionTimeAssertions Should(this FluentAssertions.Specialized.ExecutionTime executionTime) { } [System.Obsolete("You are asserting the \'AndConstraint\' itself. Remove the \'Should()\' method direct" + "ly following \'And\'", true)] @@ -57,63 +71,58 @@ namespace FluentAssertions [System.Obsolete("You are asserting the \'AndConstraint\' itself. Remove the \'Should()\' method direct" + "ly following \'And\'", true)] public static void Should(this FluentAssertions.Types.TypeSelectorAssertions _) { } - public static FluentAssertions.Specialized.ActionAssertions Should(this System.Action action) { } - public static FluentAssertions.Collections.StringCollectionAssertions Should(this System.Collections.Generic.IEnumerable @this) { } - public static FluentAssertions.Data.DataColumnAssertions Should(this System.Data.DataColumn actualValue) { } - public static FluentAssertions.Collections.GenericCollectionAssertions Should(this System.Data.DataColumnCollection actualValue) { } - public static FluentAssertions.Collections.GenericCollectionAssertions Should(this System.Data.DataRowCollection actualValue) { } - public static FluentAssertions.Collections.GenericCollectionAssertions Should(this System.Data.DataTableCollection actualValue) { } + public static FluentAssertions.Specialized.ActionAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Action action) { } + public static FluentAssertions.Collections.StringCollectionAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Collections.Generic.IEnumerable @this) { } public static FluentAssertions.Primitives.DateOnlyAssertions Should(this System.DateOnly actualValue) { } - public static FluentAssertions.Primitives.NullableDateOnlyAssertions Should(this System.DateOnly? actualValue) { } + public static FluentAssertions.Primitives.NullableDateOnlyAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.DateOnly? actualValue) { } public static FluentAssertions.Primitives.DateTimeAssertions Should(this System.DateTime actualValue) { } - public static FluentAssertions.Primitives.NullableDateTimeAssertions Should(this System.DateTime? actualValue) { } + public static FluentAssertions.Primitives.NullableDateTimeAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.DateTime? actualValue) { } public static FluentAssertions.Primitives.DateTimeOffsetAssertions Should(this System.DateTimeOffset actualValue) { } - public static FluentAssertions.Primitives.NullableDateTimeOffsetAssertions Should(this System.DateTimeOffset? actualValue) { } - public static FluentAssertions.Specialized.NonGenericAsyncFunctionAssertions Should(this System.Func action) { } + public static FluentAssertions.Primitives.NullableDateTimeOffsetAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.DateTimeOffset? actualValue) { } + 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(this System.IO.BufferedStream actualValue) { } - public static FluentAssertions.Streams.StreamAssertions Should(this System.IO.Stream actualValue) { } - public static FluentAssertions.Primitives.HttpResponseMessageAssertions Should(this System.Net.Http.HttpResponseMessage actualValue) { } - public static FluentAssertions.Reflection.AssemblyAssertions Should(this System.Reflection.Assembly assembly) { } - public static FluentAssertions.Types.ConstructorInfoAssertions Should(this System.Reflection.ConstructorInfo constructorInfo) { } - public static FluentAssertions.Types.MethodInfoAssertions Should(this System.Reflection.MethodInfo methodInfo) { } - public static FluentAssertions.Types.PropertyInfoAssertions Should(this System.Reflection.PropertyInfo propertyInfo) { } + public static FluentAssertions.Primitives.NullableGuidAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] 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.Types.AssemblyAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Reflection.Assembly assembly) { } + public static FluentAssertions.Types.ConstructorInfoAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Reflection.ConstructorInfo constructorInfo) { } + public static FluentAssertions.Types.MethodInfoAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Reflection.MethodInfo methodInfo) { } + public static FluentAssertions.Types.PropertyInfoAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Reflection.PropertyInfo propertyInfo) { } public static FluentAssertions.Specialized.TaskCompletionSourceAssertions Should(this System.Threading.Tasks.TaskCompletionSource tcs) { } public static FluentAssertions.Primitives.TimeOnlyAssertions Should(this System.TimeOnly actualValue) { } - public static FluentAssertions.Primitives.NullableTimeOnlyAssertions Should(this System.TimeOnly? actualValue) { } + public static FluentAssertions.Primitives.NullableTimeOnlyAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.TimeOnly? actualValue) { } public static FluentAssertions.Primitives.SimpleTimeSpanAssertions Should(this System.TimeSpan actualValue) { } - public static FluentAssertions.Primitives.NullableSimpleTimeSpanAssertions Should(this System.TimeSpan? actualValue) { } - public static FluentAssertions.Types.TypeAssertions Should(this System.Type subject) { } - public static FluentAssertions.Xml.XAttributeAssertions Should(this System.Xml.Linq.XAttribute actualValue) { } - public static FluentAssertions.Xml.XDocumentAssertions Should(this System.Xml.Linq.XDocument actualValue) { } - public static FluentAssertions.Xml.XElementAssertions Should(this System.Xml.Linq.XElement actualValue) { } + public static FluentAssertions.Primitives.NullableSimpleTimeSpanAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.TimeSpan? actualValue) { } + public static FluentAssertions.Types.TypeAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Type subject) { } + public static FluentAssertions.Xml.XAttributeAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Xml.Linq.XAttribute actualValue) { } + public static FluentAssertions.Xml.XDocumentAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Xml.Linq.XDocument actualValue) { } + public static FluentAssertions.Xml.XElementAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Xml.Linq.XElement actualValue) { } public static FluentAssertions.Primitives.BooleanAssertions Should(this bool actualValue) { } - public static FluentAssertions.Primitives.NullableBooleanAssertions Should(this bool? actualValue) { } + public static FluentAssertions.Primitives.NullableBooleanAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this bool? actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this byte actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this byte? actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this byte? actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this decimal actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this decimal? actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this decimal? actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this double actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this double? actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this double? actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this float actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this float? actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this float? actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this int actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this int? actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this int? actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this long actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this long? actualValue) { } - public static FluentAssertions.Primitives.ObjectAssertions Should(this object actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this long? actualValue) { } + public static FluentAssertions.Primitives.ObjectAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this object actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this sbyte actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this sbyte? actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this sbyte? actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this short actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this short? actualValue) { } - public static FluentAssertions.Primitives.StringAssertions Should(this string actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this short? actualValue) { } + public static FluentAssertions.Primitives.StringAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this string actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this uint actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this uint? actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this uint? actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this ulong actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this ulong? actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this ulong? actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this ushort actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this ushort? actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this ushort? actualValue) { } [System.Obsolete("You are asserting the \'AndConstraint\' itself. Remove the \'Should()\' method direct" + "ly following \'And\'", true)] public static void Should(this FluentAssertions.Primitives.BooleanAssertions _) @@ -150,14 +159,14 @@ namespace FluentAssertions "ly following \'And\'", true)] public static void Should(this FluentAssertions.Primitives.TimeOnlyAssertions _) where TAssertions : FluentAssertions.Primitives.TimeOnlyAssertions { } - public static FluentAssertions.Collections.GenericCollectionAssertions Should(this System.Collections.Generic.IEnumerable actualValue) { } - public static FluentAssertions.Specialized.GenericAsyncFunctionAssertions Should(this System.Func> action) { } - public static FluentAssertions.Specialized.FunctionAssertions Should(this System.Func func) { } - public static FluentAssertions.Numeric.ComparableTypeAssertions Should(this System.IComparable comparableValue) { } + public static FluentAssertions.Collections.GenericCollectionAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Collections.Generic.IEnumerable actualValue) { } + public static FluentAssertions.Specialized.GenericAsyncFunctionAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Func> action) { } + public static FluentAssertions.Specialized.FunctionAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Func func) { } + public static FluentAssertions.Numeric.ComparableTypeAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.IComparable comparableValue) { } public static FluentAssertions.Specialized.TaskCompletionSourceAssertions Should(this System.Threading.Tasks.TaskCompletionSource tcs) { } [System.Obsolete("You are asserting the \'AndConstraint\' itself. Remove the \'Should()\' method direct" + "ly following \'And\'", true)] - public static void Should(this FluentAssertions.Numeric.NumericAssertions _) + public static void Should(this FluentAssertions.Numeric.NumericAssertionsBase _) where TSubject : struct, System.IComparable where TAssertions : FluentAssertions.Numeric.NumericAssertions { } [System.Obsolete("You are asserting the \'AndConstraint\' itself. Remove the \'Should()\' method direct" + @@ -169,18 +178,11 @@ namespace FluentAssertions "ly following \'And\'", true)] public static void Should(this FluentAssertions.Primitives.ReferenceTypeAssertions _) where TAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions { } - public static FluentAssertions.Collections.GenericDictionaryAssertions, TKey, TValue> Should(this System.Collections.Generic.IDictionary actualValue) { } - public static FluentAssertions.Collections.GenericDictionaryAssertions>, TKey, TValue> Should(this System.Collections.Generic.IEnumerable> actualValue) { } - public static FluentAssertions.Collections.GenericDictionaryAssertions Should(this TCollection actualValue) + public static FluentAssertions.Collections.GenericDictionaryAssertions, TKey, TValue> Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Collections.Generic.IDictionary actualValue) { } + public static FluentAssertions.Collections.GenericDictionaryAssertions>, TKey, TValue> Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Collections.Generic.IEnumerable> actualValue) { } + public static FluentAssertions.Collections.GenericDictionaryAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this TCollection actualValue) where TCollection : System.Collections.Generic.IEnumerable> { } } - public static class AssertionOptions - { - public static FluentAssertions.EquivalencyPlan EquivalencyPlan { get; } - public static FluentAssertions.Formatting.FormattingOptions FormattingOptions { get; } - public static void AssertEquivalencyUsing(System.Func defaultsConfigurer) { } - public static FluentAssertions.Equivalency.EquivalencyAssertionOptions CloneDefaults() { } - } public static class AsyncAssertionsExtensions { public static System.Threading.Tasks.Task, T>> WithResult(this System.Threading.Tasks.Task, T>> task, T expected, string because = "", params object[] becauseArgs) { } @@ -203,6 +205,7 @@ namespace FluentAssertions public static class CallerIdentifier { public static System.Action Logger { get; set; } + public static string[] DetermineCallerIdentities() { } public static string DetermineCallerIdentity() { } } [System.AttributeUsage(System.AttributeTargets.Method)] @@ -210,68 +213,18 @@ namespace FluentAssertions { public CustomAssertionAttribute() { } } - public static class DataColumnCollectionAssertionExtensions - { - public static FluentAssertions.AndConstraint> BeSameAs(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataColumnCollection expected, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> HaveSameCount(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataColumnCollection otherCollection, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> NotBeSameAs(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataColumnCollection unexpected, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> NotHaveSameCount(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataColumnCollection otherCollection, string because = "", params object[] becauseArgs) { } - } - public static class DataRowAssertionExtensions - { - public static FluentAssertions.Data.DataRowAssertions Should(this TDataRow actualValue) - where TDataRow : System.Data.DataRow { } - } - public static class DataRowCollectionAssertionExtensions - { - public static FluentAssertions.AndConstraint> BeSameAs(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataRowCollection expected, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> HaveSameCount(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataRowCollection otherCollection, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> NotBeSameAs(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataRowCollection unexpected, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> NotHaveSameCount(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataRowCollection otherCollection, string because = "", params object[] becauseArgs) { } - } - public static class DataSetAssertionExtensions + [System.AttributeUsage(System.AttributeTargets.Assembly)] + public sealed class CustomAssertionsAssemblyAttribute : System.Attribute { - public static FluentAssertions.Data.DataSetAssertions Should(this TDataSet actualValue) - where TDataSet : System.Data.DataSet { } - } - public static class DataTableAssertionExtensions - { - public static FluentAssertions.Data.DataTableAssertions Should(this TDataTable actualValue) - where TDataTable : System.Data.DataTable { } - } - public static class DataTableCollectionAssertionExtensions - { - public static FluentAssertions.AndConstraint> BeSameAs(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataTableCollection expected, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> HaveSameCount(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataSet otherDataSet, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> HaveSameCount(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataTableCollection otherCollection, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> NotBeSameAs(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataTableCollection unexpected, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> NotHaveSameCount(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataSet otherDataSet, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> NotHaveSameCount(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataTableCollection otherCollection, string because = "", params object[] becauseArgs) { } + public CustomAssertionsAssemblyAttribute() { } } public static class EnumAssertionsExtensions { public static FluentAssertions.Primitives.EnumAssertions Should(this TEnum @enum) where TEnum : struct, System.Enum { } - public static FluentAssertions.Primitives.NullableEnumAssertions Should(this TEnum? @enum) + public static FluentAssertions.Primitives.NullableEnumAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this TEnum? @enum) where TEnum : struct, System.Enum { } } - public class EquivalencyPlan : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable - { - public EquivalencyPlan() { } - public void Add() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } - public void AddAfter() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } - public void Clear() { } - public System.Collections.Generic.IEnumerator GetEnumerator() { } - public void Insert() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } - public void InsertBefore() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } - public void Remove() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep { } - public void Reset() { } - } public static class EventRaisingExtensions { public static FluentAssertions.Events.IEventRecording WithArgs(this FluentAssertions.Events.IEventRecording eventRecording, System.Linq.Expressions.Expression> predicate) { } @@ -315,12 +268,22 @@ namespace FluentAssertions public static System.Action Invoking(System.Action action) { } public static System.Func Invoking(System.Func func) { } } + public static class JsonAssertionExtensions + { + public static FluentAssertions.Collections.GenericCollectionAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Text.Json.Nodes.JsonArray jsonArray) { } + public static FluentAssertions.Specialized.JsonNodeAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this T jsonNode) + where T : System.Text.Json.Nodes.JsonNode { } + } public static class LessThan { public static FluentAssertions.OccurrenceConstraint Thrice() { } public static FluentAssertions.OccurrenceConstraint Times(int expected) { } public static FluentAssertions.OccurrenceConstraint Twice() { } } + public static class License + { + public static bool Accepted { get; set; } + } public static class MoreThan { public static FluentAssertions.OccurrenceConstraint Once() { } @@ -340,13 +303,17 @@ namespace FluentAssertions public static FluentAssertions.AndConstraint> BeApproximately(this FluentAssertions.Numeric.NumericAssertions parent, double expectedValue, double precision, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> BeApproximately(this FluentAssertions.Numeric.NumericAssertions parent, float expectedValue, float precision, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, byte nearbyValue, byte delta, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, short nearbyValue, ushort delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, int nearbyValue, uint delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, long nearbyValue, ulong delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, sbyte nearbyValue, byte delta, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, ushort nearbyValue, ushort delta, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, short nearbyValue, ushort delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, uint nearbyValue, uint delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, ulong nearbyValue, ulong delta, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, ushort nearbyValue, ushort delta, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> BeNaN(this FluentAssertions.Numeric.NullableNumericAssertions parent, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> BeNaN(this FluentAssertions.Numeric.NullableNumericAssertions parent, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> BeNaN(this FluentAssertions.Numeric.NumericAssertions parent, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> BeNaN(this FluentAssertions.Numeric.NumericAssertions parent, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeApproximately(this FluentAssertions.Numeric.NullableNumericAssertions parent, decimal unexpectedValue, decimal precision, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeApproximately(this FluentAssertions.Numeric.NullableNumericAssertions parent, decimal? unexpectedValue, decimal precision, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeApproximately(this FluentAssertions.Numeric.NullableNumericAssertions parent, double unexpectedValue, double precision, string because = "", params object[] becauseArgs) { } @@ -357,20 +324,22 @@ namespace FluentAssertions public static FluentAssertions.AndConstraint> NotBeApproximately(this FluentAssertions.Numeric.NumericAssertions parent, double unexpectedValue, double precision, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeApproximately(this FluentAssertions.Numeric.NumericAssertions parent, float unexpectedValue, float precision, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, byte distantValue, byte delta, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, short distantValue, ushort delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, int distantValue, uint delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, long distantValue, ulong delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, sbyte distantValue, byte delta, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, ushort distantValue, ushort delta, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, short distantValue, ushort delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, uint distantValue, uint delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, ulong distantValue, ulong delta, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, ushort distantValue, ushort delta, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> NotBeNaN(this FluentAssertions.Numeric.NullableNumericAssertions parent, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> NotBeNaN(this FluentAssertions.Numeric.NullableNumericAssertions parent, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> NotBeNaN(this FluentAssertions.Numeric.NumericAssertions parent, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> NotBeNaN(this FluentAssertions.Numeric.NumericAssertions parent, string because = "", params object[] becauseArgs) { } } public static class ObjectAssertionsExtensions { - public static FluentAssertions.AndConstraint BeBinarySerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint BeBinarySerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> options, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint BeDataContractSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint BeDataContractSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> options, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint BeDataContractSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> options, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint BeXmlSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, string because = "", params object[] becauseArgs) { } } public abstract class OccurrenceConstraint @@ -409,33 +378,38 @@ namespace FluentAssertions public static FluentAssertions.Types.TypeSelector Types(this System.Reflection.Assembly assembly) { } public static FluentAssertions.Types.TypeSelector Types(this System.Type type) { } } + public static class Value + { + public static FluentAssertions.Equivalency.Inlining.IInlineEquivalencyAssertion ThatMatches(System.Linq.Expressions.Expression> condition) { } + public static FluentAssertions.Equivalency.Inlining.IInlineEquivalencyAssertion ThatSatisfies(System.Action assertion) { } + } public static class XmlAssertionExtensions { - public static FluentAssertions.Xml.XmlElementAssertions Should(this System.Xml.XmlElement actualValue) { } - public static FluentAssertions.Xml.XmlNodeAssertions Should(this System.Xml.XmlNode actualValue) { } + public static FluentAssertions.Xml.XmlElementAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Xml.XmlElement actualValue) { } + public static FluentAssertions.Xml.XmlNodeAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Xml.XmlNode actualValue) { } } } 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) { } public FluentAssertions.AndConstraint AllBeEquivalentTo(TExpectation expectation, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint AllBeEquivalentTo(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint AllBeEquivalentTo(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint AllBeOfType(System.Type expectedType, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint> AllBeOfType(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint AllSatisfy(System.Action expected, string because = "", params object[] becauseArgs) { } @@ -444,7 +418,7 @@ namespace FluentAssertions.Collections protected void AssertSubjectEquality(System.Collections.Generic.IEnumerable expectation, System.Func equalityComparison, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEmpty(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEquivalentTo(System.Collections.Generic.IEnumerable expectation, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeEquivalentTo(System.Collections.Generic.IEnumerable expectation, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeEquivalentTo(System.Collections.Generic.IEnumerable expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint> BeInAscendingOrder(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint> BeInAscendingOrder(System.Collections.Generic.IComparer comparer, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint> BeInAscendingOrder(System.Func comparison, string because = "", params object[] becauseArgs) { } @@ -461,7 +435,7 @@ namespace FluentAssertions.Collections public FluentAssertions.AndWhichConstraint Contain(System.Linq.Expressions.Expression> predicate, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint Contain(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint ContainEquivalentOf(TExpectation expectation, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint ContainEquivalentOf(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint ContainEquivalentOf(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint ContainInConsecutiveOrder(params T[] expected) { } public FluentAssertions.AndConstraint ContainInConsecutiveOrder(System.Collections.Generic.IEnumerable expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint ContainInOrder(params T[] expected) { } @@ -476,12 +450,10 @@ namespace FluentAssertions.Collections public FluentAssertions.AndConstraint Equal(System.Collections.Generic.IEnumerable expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Equal(System.Collections.Generic.IEnumerable expectation, System.Func equalityComparison, string because = "", params object[] becauseArgs) { } public override bool Equals(object obj) { } - public FluentAssertions.AndConstraint HaveCount(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveCount(System.Linq.Expressions.Expression> countPredicate, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint HaveCountGreaterOrEqualTo(int expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveCount(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveCountGreaterThan(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveCountGreaterThanOrEqualTo(int expected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint HaveCountLessOrEqualTo(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveCountLessThan(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveCountLessThanOrEqualTo(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint HaveElementAt(int index, T element, string because = "", params object[] becauseArgs) { } @@ -491,7 +463,7 @@ namespace FluentAssertions.Collections public FluentAssertions.AndConstraint IntersectWith(System.Collections.Generic.IEnumerable otherCollection, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEmpty(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEquivalentTo(System.Collections.Generic.IEnumerable unexpected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint NotBeEquivalentTo(System.Collections.Generic.IEnumerable unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotBeEquivalentTo(System.Collections.Generic.IEnumerable unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeInAscendingOrder(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeInAscendingOrder(System.Collections.Generic.IComparer comparer, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeInAscendingOrder(System.Func comparison, string because = "", params object[] becauseArgs) { } @@ -506,9 +478,9 @@ 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.EquivalencyAssertionOptions> config, 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) { } public FluentAssertions.AndConstraint NotContainInConsecutiveOrder(System.Collections.Generic.IEnumerable unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotContainInOrder(params T[] unexpected) { } @@ -537,16 +509,16 @@ 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.EquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeEquivalentTo(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Contain(params System.Collections.Generic.KeyValuePair[] expected) { } public FluentAssertions.AndConstraint Contain(System.Collections.Generic.IEnumerable> expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Contain(System.Collections.Generic.KeyValuePair expected, string because = "", params object[] becauseArgs) { } @@ -555,8 +527,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) { } @@ -574,42 +546,31 @@ 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.EquivalencyAssertionOptions> config, 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) { } public FluentAssertions.AndConstraint BeEquivalentTo(System.Collections.Generic.IEnumerable expectation, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeEquivalentTo(System.Collections.Generic.IEnumerable expectation, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeEquivalentTo(System.Collections.Generic.IEnumerable expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint ContainMatch(string wildcardPattern, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Equal(System.Collections.Generic.IEnumerable expected) { } public FluentAssertions.AndConstraint Equal(params string[] expected) { } public FluentAssertions.AndConstraint NotContainMatch(string wildcardPattern, string because = "", params object[] becauseArgs) { } } - public class SubsequentOrderingAssertions : FluentAssertions.Collections.SubsequentOrderingGenericCollectionAssertions, T, FluentAssertions.Collections.SubsequentOrderingAssertions> - { - public SubsequentOrderingAssertions(System.Collections.Generic.IEnumerable actualValue, System.Linq.IOrderedEnumerable previousOrderedEnumerable) { } - } - public class SubsequentOrderingGenericCollectionAssertions : FluentAssertions.Collections.SubsequentOrderingGenericCollectionAssertions> - where TCollection : System.Collections.Generic.IEnumerable + public class SubsequentOrderingAssertions : FluentAssertions.Collections.GenericCollectionAssertions, T> { - public SubsequentOrderingGenericCollectionAssertions(TCollection actualValue, System.Linq.IOrderedEnumerable previousOrderedEnumerable) { } - } - 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 SubsequentOrderingAssertions(System.Collections.Generic.IEnumerable 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) { } @@ -635,14 +596,6 @@ namespace FluentAssertions.Common InvalidForCSharp = 5, PrivateProtected = 6, } - public class Configuration - { - public Configuration(FluentAssertions.Common.IConfigurationStore store) { } - public string TestFrameworkName { get; set; } - public string ValueFormatterAssembly { get; set; } - public FluentAssertions.Common.ValueFormatterDetectionMode ValueFormatterDetectionMode { get; set; } - public static FluentAssertions.Common.Configuration Current { get; } - } public static class DateTimeExtensions { public static System.DateTimeOffset ToDateTimeOffset(this System.DateTime dateTime) { } @@ -654,31 +607,10 @@ namespace FluentAssertions.Common System.Threading.Tasks.Task DelayAsync(System.TimeSpan delay, System.Threading.CancellationToken cancellationToken); FluentAssertions.Common.ITimer StartTimer(); } - public interface ICollectionWrapper - where TCollection : System.Collections.ICollection - { - TCollection UnderlyingCollection { get; } - } - public interface IConfigurationStore - { - string GetSetting(string name); - } - public interface IReflector - { - System.Collections.Generic.IEnumerable GetAllTypesFromAppDomain(System.Func predicate); - } public interface ITimer : System.IDisposable { System.TimeSpan Elapsed { get; } } - public static class Services - { - public static FluentAssertions.Common.Configuration Configuration { get; } - public static FluentAssertions.Common.IConfigurationStore ConfigurationStore { get; set; } - public static FluentAssertions.Common.IReflector Reflector { get; set; } - public static System.Action ThrowException { get; set; } - public static void ResetToDefaults() { } - } public delegate FluentAssertions.Common.ITimer StartTimer(); public enum ValueFormatterDetectionMode { @@ -687,82 +619,36 @@ namespace FluentAssertions.Common Scan = 2, } } -namespace FluentAssertions.Data +namespace FluentAssertions.Configuration { - public class DataColumnAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions + public class GlobalConfiguration { - public DataColumnAssertions(System.Data.DataColumn dataColumn) { } - protected override string Identifier { get; } - public FluentAssertions.AndConstraint BeEquivalentTo(System.Data.DataColumn expectation, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeEquivalentTo(System.Data.DataColumn expectation, System.Func, FluentAssertions.Data.IDataEquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } + public GlobalConfiguration() { } + public FluentAssertions.Configuration.GlobalEquivalencyOptions Equivalency { get; set; } + public FluentAssertions.Configuration.GlobalFormattingOptions Formatting { get; set; } + public FluentAssertions.Configuration.TestFramework? TestFramework { get; set; } } - public class DataRowAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions> - where TDataRow : System.Data.DataRow + public class GlobalEquivalencyOptions { - public DataRowAssertions(TDataRow dataRow) { } - protected override string Identifier { get; } - public FluentAssertions.AndConstraint> BeEquivalentTo(System.Data.DataRow expectation, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint> BeEquivalentTo(System.Data.DataRow expectation, System.Func, FluentAssertions.Data.IDataEquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint, System.Data.DataColumn> HaveColumn(string expectedColumnName, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint> HaveColumns(params string[] expectedColumnNames) { } - public FluentAssertions.AndConstraint> HaveColumns(System.Collections.Generic.IEnumerable expectedColumnNames, string because = "", params object[] becauseArgs) { } + public GlobalEquivalencyOptions() { } + public FluentAssertions.Equivalency.EquivalencyPlan Plan { get; } + public FluentAssertions.Equivalency.EquivalencyOptions CloneDefaults() { } + public void Modify(System.Func configureOptions) { } } - public class DataSetAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions> - where TDataSet : System.Data.DataSet + public class GlobalFormattingOptions : FluentAssertions.Formatting.FormattingOptions { - public DataSetAssertions(TDataSet dataSet) { } - protected override string Identifier { get; } - public FluentAssertions.AndConstraint> BeEquivalentTo(System.Data.DataSet expectation, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint> BeEquivalentTo(System.Data.DataSet expectation, System.Func, FluentAssertions.Data.IDataEquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint, System.Data.DataTable> HaveTable(string expectedTableName, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint> HaveTableCount(int expected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint> HaveTables(params string[] expectedTableNames) { } - public FluentAssertions.AndConstraint> HaveTables(System.Collections.Generic.IEnumerable expectedTableNames, string because = "", params object[] becauseArgs) { } + public GlobalFormattingOptions() { } + public string ValueFormatterAssembly { get; set; } + public FluentAssertions.Common.ValueFormatterDetectionMode ValueFormatterDetectionMode { get; set; } } - public class DataTableAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions> - where TDataTable : System.Data.DataTable + public enum TestFramework { - public DataTableAssertions(TDataTable dataTable) { } - protected override string Identifier { get; } - public FluentAssertions.AndConstraint> BeEquivalentTo(System.Data.DataTable expectation, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint> BeEquivalentTo(System.Data.DataTable expectation, System.Func, FluentAssertions.Data.IDataEquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint, System.Data.DataColumn> HaveColumn(string expectedColumnName, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint> HaveColumns(params string[] expectedColumnNames) { } - public FluentAssertions.AndConstraint> HaveColumns(System.Collections.Generic.IEnumerable expectedColumnNames, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint> HaveRowCount(int expected, string because = "", params object[] becauseArgs) { } - } - public interface IDataEquivalencyAssertionOptions : FluentAssertions.Equivalency.IEquivalencyAssertionOptions - { - FluentAssertions.Data.IDataEquivalencyAssertionOptions AllowingMismatchedTypes(); - FluentAssertions.Data.IDataEquivalencyAssertionOptions Excluding(System.Linq.Expressions.Expression> predicate); - FluentAssertions.Data.IDataEquivalencyAssertionOptions Excluding(System.Linq.Expressions.Expression> expression); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingColumn(System.Data.DataColumn column); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingColumn(string tableName, string columnName); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingColumnInAllTables(string columnName); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingColumns(System.Collections.Generic.IEnumerable columns); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingColumns(params System.Data.DataColumn[] columns); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingColumns(string tableName, System.Collections.Generic.IEnumerable columnNames); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingColumns(string tableName, params string[] columnNames); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingColumnsInAllTables(System.Collections.Generic.IEnumerable columnNames); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingColumnsInAllTables(params string[] columnNames); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingOriginalData(); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingRelated(System.Linq.Expressions.Expression> expression); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingRelated(System.Linq.Expressions.Expression> expression); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingRelated(System.Linq.Expressions.Expression> expression); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingRelated(System.Linq.Expressions.Expression> expression); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingRelated(System.Linq.Expressions.Expression> expression); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingRelated(System.Linq.Expressions.Expression> expression); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingRelated(System.Linq.Expressions.Expression> expression); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingTable(string tableName); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingTables(System.Collections.Generic.IEnumerable tableNames); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingTables(params string[] tableNames); - FluentAssertions.Data.IDataEquivalencyAssertionOptions IgnoringUnmatchedColumns(); - FluentAssertions.Data.IDataEquivalencyAssertionOptions UsingRowMatchMode(FluentAssertions.Data.RowMatchMode rowMatchMode); - } - public enum RowMatchMode - { - Index = 0, - PrimaryKey = 1, + XUnit2 = 0, + XUnit3 = 1, + TUnit = 2, + MsTest = 3, + NUnit = 4, + MSpec = 5, } } namespace FluentAssertions.Equivalency @@ -775,7 +661,7 @@ namespace FluentAssertions.Equivalency public object Expectation { get; set; } public System.Type RuntimeType { get; } public object Subject { get; set; } - public System.Type GetExpectedType(FluentAssertions.Equivalency.IEquivalencyAssertionOptions options) { } + public System.Type GetExpectedType(FluentAssertions.Equivalency.IEquivalencyOptions options) { } public override string ToString() { } } public class ConversionSelector @@ -805,41 +691,58 @@ namespace FluentAssertions.Equivalency ForceEquals = 2, ForceMembers = 3, } - public class EquivalencyAssertionOptions : FluentAssertions.Equivalency.SelfReferenceEquivalencyAssertionOptions + public class EquivalencyOptions : FluentAssertions.Equivalency.SelfReferenceEquivalencyOptions { - public EquivalencyAssertionOptions() { } + public EquivalencyOptions() { } } - public class EquivalencyAssertionOptions : FluentAssertions.Equivalency.SelfReferenceEquivalencyAssertionOptions> + public class EquivalencyOptions : FluentAssertions.Equivalency.SelfReferenceEquivalencyOptions> { - public EquivalencyAssertionOptions() { } - public EquivalencyAssertionOptions(FluentAssertions.Equivalency.IEquivalencyAssertionOptions defaults) { } - public FluentAssertions.Equivalency.EquivalencyAssertionOptions> AsCollection() { } - public FluentAssertions.Equivalency.EquivalencyAssertionOptions Excluding(System.Linq.Expressions.Expression> expression) { } + public EquivalencyOptions() { } + public EquivalencyOptions(FluentAssertions.Equivalency.IEquivalencyOptions defaults) { } + public FluentAssertions.Equivalency.EquivalencyOptions> AsCollection() { } + public FluentAssertions.Equivalency.EquivalencyOptions Excluding(System.Linq.Expressions.Expression> expression) { } public FluentAssertions.Equivalency.NestedExclusionOptionBuilder For(System.Linq.Expressions.Expression>> expression) { } - public FluentAssertions.Equivalency.EquivalencyAssertionOptions Including(System.Linq.Expressions.Expression> expression) { } - public FluentAssertions.Equivalency.EquivalencyAssertionOptions WithMapping(string expectationMemberPath, string subjectMemberPath) { } - public FluentAssertions.Equivalency.EquivalencyAssertionOptions WithMapping(System.Linq.Expressions.Expression> expectationMemberPath, System.Linq.Expressions.Expression> subjectMemberPath) { } - public FluentAssertions.Equivalency.EquivalencyAssertionOptions WithMapping(System.Linq.Expressions.Expression> expectationMember, System.Linq.Expressions.Expression> subjectMember) { } - public FluentAssertions.Equivalency.EquivalencyAssertionOptions WithMapping(string expectationMemberName, string subjectMemberName) { } - public FluentAssertions.Equivalency.EquivalencyAssertionOptions WithStrictOrderingFor(System.Linq.Expressions.Expression> expression) { } - public FluentAssertions.Equivalency.EquivalencyAssertionOptions WithoutStrictOrderingFor(System.Linq.Expressions.Expression> expression) { } + public FluentAssertions.Equivalency.EquivalencyOptions Including(System.Linq.Expressions.Expression> expression) { } + public FluentAssertions.Equivalency.EquivalencyOptions WithMapping(string expectationMemberPath, string subjectMemberPath) { } + public FluentAssertions.Equivalency.EquivalencyOptions WithMapping(System.Linq.Expressions.Expression> expectationMemberPath, System.Linq.Expressions.Expression> subjectMemberPath) { } + public FluentAssertions.Equivalency.EquivalencyOptions WithMapping(System.Linq.Expressions.Expression> expectationMember, System.Linq.Expressions.Expression> subjectMember) { } + public FluentAssertions.Equivalency.EquivalencyOptions WithMapping(string expectationMemberName, string subjectMemberName) { } + public FluentAssertions.Equivalency.EquivalencyOptions WithStrictOrderingFor(System.Linq.Expressions.Expression> expression) { } + public FluentAssertions.Equivalency.EquivalencyOptions WithoutStrictOrderingFor(System.Linq.Expressions.Expression> expression) { } + } + public class EquivalencyPlan : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable + { + public EquivalencyPlan() { } + public void Add() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } + public void AddAfter() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } + public void Clear() { } + public System.Collections.Generic.IEnumerator GetEnumerator() { } + public void Insert() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } + public void InsertBefore() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } + public void Remove() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep { } + public void Reset() { } } public enum EquivalencyResult { ContinueWithNext = 0, - AssertionCompleted = 1, + EquivalencyProven = 1, } public abstract class EquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { protected EquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } - protected abstract FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator); + 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 nestedValidator); } public class EquivalencyValidationContext : FluentAssertions.Equivalency.IEquivalencyValidationContext { - public EquivalencyValidationContext(FluentAssertions.Equivalency.INode root, FluentAssertions.Equivalency.IEquivalencyAssertionOptions options) { } + public EquivalencyValidationContext(FluentAssertions.Equivalency.INode root, FluentAssertions.Equivalency.IEquivalencyOptions options) { } public FluentAssertions.Equivalency.INode CurrentNode { get; } - public FluentAssertions.Equivalency.IEquivalencyAssertionOptions Options { get; } + public FluentAssertions.Equivalency.IEquivalencyOptions Options { get; } public FluentAssertions.Execution.Reason Reason { get; set; } public FluentAssertions.Equivalency.Tracing.ITraceWriter TraceWriter { get; set; } public FluentAssertions.Equivalency.Tracing.Tracer Tracer { get; } @@ -850,24 +753,6 @@ namespace FluentAssertions.Equivalency public bool IsCyclicReference(object expectation) { } public override string ToString() { } } - public class EquivalencyValidator : FluentAssertions.Equivalency.IEquivalencyValidator - { - public EquivalencyValidator() { } - public void AssertEquality(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.EquivalencyValidationContext context) { } - public void RecursivelyAssertEquality(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context) { } - } - public class Field : FluentAssertions.Equivalency.Node, FluentAssertions.Equivalency.IMember, FluentAssertions.Equivalency.INode - { - public Field(System.Reflection.FieldInfo fieldInfo, FluentAssertions.Equivalency.INode parent) { } - public Field(System.Type reflectedType, System.Reflection.FieldInfo fieldInfo, FluentAssertions.Equivalency.INode parent) { } - public System.Type DeclaringType { get; set; } - public override string Description { get; } - public FluentAssertions.Common.CSharpAccessModifier GetterAccessibility { get; } - public bool IsBrowsable { get; } - public System.Type ReflectedType { get; } - public FluentAssertions.Common.CSharpAccessModifier SetterAccessibility { get; } - public object GetValue(object obj) { } - } public delegate string GetSubjectId(); public interface IAssertionContext { @@ -877,7 +762,7 @@ namespace FluentAssertions.Equivalency FluentAssertions.Equivalency.INode SelectedNode { get; } TSubject Subject { get; } } - public interface IEquivalencyAssertionOptions + public interface IEquivalencyOptions { bool AllowInfiniteRecursion { get; } bool? CompareRecordsByValue { get; } @@ -885,7 +770,12 @@ namespace FluentAssertions.Equivalency FluentAssertions.Equivalency.CyclicReferenceHandling CyclicReferenceHandling { get; } FluentAssertions.Equivalency.EnumEquivalencyHandling EnumEquivalencyHandling { get; } bool ExcludeNonBrowsableOnExpectation { get; } + bool IgnoreCase { get; } + bool IgnoreJsonPropertyCasing { get; } + bool IgnoreLeadingWhitespace { get; } + bool IgnoreNewlineStyle { get; } bool IgnoreNonBrowsableOnSubject { get; } + bool IgnoreTrailingWhitespace { get; } FluentAssertions.Equivalency.MemberVisibility IncludedFields { get; } FluentAssertions.Equivalency.MemberVisibility IncludedProperties { get; } bool IsRecursive { get; } @@ -899,12 +789,12 @@ namespace FluentAssertions.Equivalency } public interface IEquivalencyStep { - FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator); + FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes); } public interface IEquivalencyValidationContext { FluentAssertions.Equivalency.INode CurrentNode { get; } - FluentAssertions.Equivalency.IEquivalencyAssertionOptions Options { get; } + FluentAssertions.Equivalency.IEquivalencyOptions Options { get; } FluentAssertions.Execution.Reason Reason { get; } FluentAssertions.Equivalency.Tracing.Tracer Tracer { get; } FluentAssertions.Equivalency.IEquivalencyValidationContext AsCollectionItem(string index); @@ -913,10 +803,6 @@ namespace FluentAssertions.Equivalency FluentAssertions.Equivalency.IEquivalencyValidationContext Clone(); bool IsCyclicReference(object expectation); } - public interface IEquivalencyValidator - { - void RecursivelyAssertEquality(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context); - } public interface IMember : FluentAssertions.Equivalency.INode { System.Type DeclaringType { get; } @@ -937,7 +823,7 @@ namespace FluentAssertions.Equivalency } public interface IMemberMatchingRule { - FluentAssertions.Equivalency.IMember Match(FluentAssertions.Equivalency.IMember expectedMember, object subject, FluentAssertions.Equivalency.INode parent, FluentAssertions.Equivalency.IEquivalencyAssertionOptions 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 { @@ -947,15 +833,14 @@ namespace FluentAssertions.Equivalency public interface INode { int Depth { get; } - string Description { get; } + FluentAssertions.Equivalency.Pathway Expectation { get; } FluentAssertions.Equivalency.GetSubjectId GetSubjectId { get; } bool IsRoot { get; } - string Name { get; set; } System.Type ParentType { get; } - string Path { get; } - string PathAndName { get; } bool RootIsCollection { get; } + FluentAssertions.Equivalency.Pathway Subject { get; } System.Type Type { get; } + void AdjustForRemappedSubject(FluentAssertions.Equivalency.IMember subjectMember); } public interface IObjectInfo { @@ -970,13 +855,17 @@ namespace FluentAssertions.Equivalency { FluentAssertions.Equivalency.OrderStrictness Evaluate(FluentAssertions.Equivalency.IObjectInfo objectInfo); } + public interface IValidateChildNodeEquivalency + { + void AssertEquivalencyOf(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context); + } public static class MemberFactory { public static FluentAssertions.Equivalency.IMember Create(System.Reflection.MemberInfo memberInfo, FluentAssertions.Equivalency.INode parent) { } } public class MemberSelectionContext { - public MemberSelectionContext(System.Type compileTimeType, System.Type runtimeType, FluentAssertions.Equivalency.IEquivalencyAssertionOptions options) { } + public MemberSelectionContext(System.Type compileTimeType, System.Type runtimeType, FluentAssertions.Equivalency.IEquivalencyOptions options) { } public FluentAssertions.Equivalency.MemberVisibility IncludedFields { get; } public FluentAssertions.Equivalency.MemberVisibility IncludedProperties { get; } public System.Type Type { get; } @@ -992,29 +881,9 @@ namespace FluentAssertions.Equivalency } public class NestedExclusionOptionBuilder { - public FluentAssertions.Equivalency.EquivalencyAssertionOptions Exclude(System.Linq.Expressions.Expression> expression) { } + public FluentAssertions.Equivalency.EquivalencyOptions Exclude(System.Linq.Expressions.Expression> expression) { } public FluentAssertions.Equivalency.NestedExclusionOptionBuilder For(System.Linq.Expressions.Expression>> expression) { } } - public class Node : FluentAssertions.Equivalency.INode - { - public Node() { } - public int Depth { get; } - public virtual string Description { get; } - public FluentAssertions.Equivalency.GetSubjectId GetSubjectId { get; set; } - public bool IsRoot { get; } - public string Name { get; set; } - public System.Type ParentType { get; set; } - public string Path { get; set; } - public string PathAndName { get; } - public bool RootIsCollection { get; set; } - public System.Type Type { get; set; } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - public override string ToString() { } - public static FluentAssertions.Equivalency.INode From(FluentAssertions.Equivalency.GetSubjectId getSubjectId) { } - public static FluentAssertions.Equivalency.INode FromCollectionItem(string index, FluentAssertions.Equivalency.INode parent) { } - public static FluentAssertions.Equivalency.INode FromDictionaryItem(object key, FluentAssertions.Equivalency.INode parent) { } - } public enum OrderStrictness { Strict = 0, @@ -1029,24 +898,28 @@ namespace FluentAssertions.Equivalency public System.Collections.Generic.IEnumerator GetEnumerator() { } public bool IsOrderingStrictFor(FluentAssertions.Equivalency.IObjectInfo objectInfo) { } } - public class Property : FluentAssertions.Equivalency.Node, FluentAssertions.Equivalency.IMember, FluentAssertions.Equivalency.INode + public class Pathway : System.IEquatable { - public Property(System.Reflection.PropertyInfo propertyInfo, FluentAssertions.Equivalency.INode parent) { } - public Property(System.Type reflectedType, System.Reflection.PropertyInfo propertyInfo, FluentAssertions.Equivalency.INode parent) { } - public System.Type DeclaringType { get; } - public override string Description { get; } - public FluentAssertions.Common.CSharpAccessModifier GetterAccessibility { get; } - public bool IsBrowsable { get; } - public System.Type ReflectedType { get; } - public FluentAssertions.Common.CSharpAccessModifier SetterAccessibility { get; } - public object GetValue(object obj) { } + public Pathway(FluentAssertions.Equivalency.Pathway parent, string name, FluentAssertions.Equivalency.Pathway.GetDescription getDescription) { } + public Pathway(string path, string name, FluentAssertions.Equivalency.Pathway.GetDescription getDescription) { } + public string Description { get; } + public string Name { get; } + public string Path { get; } + public string PathAndName { get; } + public override string ToString() { } + public delegate string GetDescription(string pathAndName); } - public abstract class SelfReferenceEquivalencyAssertionOptions : FluentAssertions.Equivalency.IEquivalencyAssertionOptions - where TSelf : FluentAssertions.Equivalency.SelfReferenceEquivalencyAssertionOptions + public abstract class SelfReferenceEquivalencyOptions : FluentAssertions.Equivalency.IEquivalencyOptions + where TSelf : FluentAssertions.Equivalency.SelfReferenceEquivalencyOptions { - protected SelfReferenceEquivalencyAssertionOptions(FluentAssertions.Equivalency.IEquivalencyAssertionOptions defaults) { } + protected SelfReferenceEquivalencyOptions(FluentAssertions.Equivalency.IEquivalencyOptions defaults) { } public bool? CompareRecordsByValue { get; } public FluentAssertions.Equivalency.ConversionSelector ConversionSelector { get; } + public bool IgnoreCase { get; } + public bool IgnoreJsonPropertyCasing { get; set; } + public bool IgnoreLeadingWhitespace { get; } + public bool IgnoreNewlineStyle { get; } + public bool IgnoreTrailingWhitespace { get; } [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] protected FluentAssertions.Equivalency.OrderingRuleCollection OrderingRules { get; } public FluentAssertions.Equivalency.Tracing.ITraceWriter TraceWriter { get; } @@ -1062,13 +935,19 @@ namespace FluentAssertions.Equivalency public TSelf ComparingRecordsByMembers() { } public TSelf ComparingRecordsByValue() { } public TSelf Excluding(System.Linq.Expressions.Expression> predicate) { } + public TSelf ExcludingExplicitlyImplementedProperties() { } public TSelf ExcludingFields() { } + public TSelf ExcludingMembersNamed(params string[] memberNames) { } public TSelf ExcludingMissingMembers() { } - public TSelf ExcludingNestedObjects() { } public TSelf ExcludingNonBrowsableMembers() { } public TSelf ExcludingProperties() { } + public TSelf IgnoringCase() { } public TSelf IgnoringCyclicReferences() { } + public TSelf IgnoringJsonPropertyCasing() { } + public TSelf IgnoringLeadingWhitespace() { } + public TSelf IgnoringNewlineStyle() { } public TSelf IgnoringNonBrowsableMembersOnSubject() { } + public TSelf IgnoringTrailingWhitespace() { } public TSelf Including(System.Linq.Expressions.Expression> predicate) { } public TSelf IncludingAllDeclaredProperties() { } public TSelf IncludingAllRuntimeProperties() { } @@ -1077,15 +956,16 @@ namespace FluentAssertions.Equivalency public TSelf IncludingInternalProperties() { } public TSelf IncludingNestedObjects() { } public TSelf IncludingProperties() { } - public TSelf RespectingDeclaredTypes() { } - public TSelf RespectingRuntimeTypes() { } + public TSelf PreferringDeclaredMemberTypes() { } + public TSelf PreferringRuntimeMemberTypes() { } public TSelf ThrowingOnMissingMembers() { } public override string ToString() { } public TSelf Using(FluentAssertions.Equivalency.IEquivalencyStep equivalencyStep) { } public TSelf Using(FluentAssertions.Equivalency.IMemberMatchingRule matchingRule) { } public TSelf Using(FluentAssertions.Equivalency.IMemberSelectionRule selectionRule) { } public TSelf Using(FluentAssertions.Equivalency.IOrderingRule orderingRule) { } - public FluentAssertions.Equivalency.SelfReferenceEquivalencyAssertionOptions.Restriction Using(System.Action> action) { } + public TSelf Using(System.Collections.Generic.IEqualityComparer comparer) { } + public FluentAssertions.Equivalency.SelfReferenceEquivalencyOptions.Restriction Using(System.Action> action) { } public TSelf Using(System.Collections.Generic.IEqualityComparer comparer) { } public TSelf Using() where TEqualityComparer : System.Collections.Generic.IEqualityComparer, new () { } @@ -1093,12 +973,16 @@ namespace FluentAssertions.Equivalency public TSelf WithAutoConversionFor(System.Linq.Expressions.Expression> predicate) { } public TSelf WithStrictOrdering() { } public TSelf WithStrictOrderingFor(System.Linq.Expressions.Expression> predicate) { } + public TSelf WithStrictTyping() { } + public TSelf WithStrictTypingFor(System.Linq.Expressions.Expression> predicate) { } public TSelf WithTracing(FluentAssertions.Equivalency.Tracing.ITraceWriter writer = null) { } public TSelf WithoutAutoConversionFor(System.Linq.Expressions.Expression> predicate) { } - public void WithoutMatchingRules() { } - public void WithoutSelectionRules() { } + public TSelf WithoutMatchingRules() { } + public TSelf WithoutRecursing() { } + public TSelf WithoutSelectionRules() { } public TSelf WithoutStrictOrdering() { } public TSelf WithoutStrictOrderingFor(System.Linq.Expressions.Expression> predicate) { } + public TSelf WithoutStrictTyping() { } public class Restriction { public Restriction(TSelf options, System.Action> action) { } @@ -1115,135 +999,122 @@ namespace FluentAssertions.Equivalency public static bool WhichSetterHas(this FluentAssertions.Equivalency.IMemberInfo memberInfo, FluentAssertions.Common.CSharpAccessModifier accessModifier) { } } } +namespace FluentAssertions.Equivalency.Inlining +{ + public interface IInlineEquivalencyAssertion + { + void Execute(FluentAssertions.Execution.AssertionChain assertionChain, FluentAssertions.Equivalency.Comparands comparands); + } + public class InlineEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep + { + public InlineEquivalencyStep() { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } + } +} namespace FluentAssertions.Equivalency.Steps { public class AssertionRuleEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { - public AssertionRuleEquivalencyStep(System.Linq.Expressions.Expression> predicate, System.Action> assertion) { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + 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() { } } public class AutoConversionStep : FluentAssertions.Equivalency.IEquivalencyStep { public AutoConversionStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } public override string ToString() { } } - public class ConstraintCollectionEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep - { - public ConstraintCollectionEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } - } - public class ConstraintEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep + public class DateAndTimeEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { - public ConstraintEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } - } - public class DataColumnEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep - { - public DataColumnEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } - } - public class DataRelationEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep - { - public DataRelationEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } - } - public class DataRowCollectionEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep - { - public DataRowCollectionEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } - } - public class DataRowEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep - { - public DataRowEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } - } - public class DataSetEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep - { - public DataSetEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } - } - public class DataTableEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep - { - public DataTableEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public DateAndTimeEquivalencyStep() { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class DictionaryEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep { public DictionaryEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nestedValidator) { } } public class EnumEqualityStep : FluentAssertions.Equivalency.IEquivalencyStep { public EnumEqualityStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class EnumerableEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public EnumerableEquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class EqualityComparerEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public EqualityComparerEquivalencyStep(System.Collections.Generic.IEqualityComparer comparer) { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } public override string ToString() { } } public class GenericDictionaryEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public GenericDictionaryEquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class GenericEnumerableEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public GenericEnumerableEquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } + } + public class JsonConversionStep : FluentAssertions.Equivalency.IEquivalencyStep + { + public JsonConversionStep() { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class ReferenceEqualityEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public ReferenceEqualityEquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class RunAllUserStepsEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public RunAllUserStepsEquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class SimpleEqualityEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public SimpleEqualityEquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class StringEqualityEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public StringEqualityEquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class StructuralEqualityEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public StructuralEqualityEquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } + } + public class TypeEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep + { + public TypeEquivalencyStep() { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class ValueTypeEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public ValueTypeEquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class XAttributeEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep { public XAttributeEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + 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.IEquivalencyValidator nestedValidator) { } + 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.IEquivalencyValidator nestedValidator) { } + protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nestedValidator) { } } } namespace FluentAssertions.Equivalency.Tracing @@ -1273,7 +1144,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) { } @@ -1287,6 +1158,12 @@ namespace FluentAssertions.Events public string EventName { get; } public System.Type HandlerType { get; } } + public class EventMonitorOptions + { + public EventMonitorOptions() { } + public FluentAssertions.Events.EventMonitorOptions IgnoringEventAccessorExceptions() { } + public FluentAssertions.Events.EventMonitorOptions RecordingEventsWithBrokenAccessor() { } + } public interface IEventRecording : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable { System.Type EventHandlerType { get; } @@ -1313,75 +1190,60 @@ namespace FluentAssertions.Events } namespace FluentAssertions.Execution { - [System.Serializable] + 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 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() { } + } 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.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 { @@ -1391,28 +1253,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.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; } @@ -1424,6 +1271,12 @@ namespace FluentAssertions.Execution { object Clone(); } + public interface ITestFramework + { + bool IsAvailable { get; } + [System.Diagnostics.CodeAnalysis.DoesNotReturn] + void Throw(string message); + } public class Reason { public Reason(string formattedMessage, object[] arguments) { } @@ -1431,6 +1284,14 @@ namespace FluentAssertions.Execution public string FormattedMessage { get; set; } } } +namespace FluentAssertions.Extensibility +{ + [System.AttributeUsage(System.AttributeTargets.Assembly, AllowMultiple=true)] + public sealed class AssertionEngineInitializerAttribute : System.Attribute + { + public AssertionEngineInitializerAttribute(System.Type type, string methodName) { } + } +} namespace FluentAssertions.Extensions { public static class FluentDateTimeExtensions @@ -1542,7 +1403,6 @@ namespace FluentAssertions.Formatting public class DefaultValueFormatter : FluentAssertions.Formatting.IValueFormatter { public DefaultValueFormatter() { } - protected virtual int SpacesPerIndentionLevel { get; } public virtual bool CanHandle(object value) { } public void Format(object value, FluentAssertions.Formatting.FormattedObjectGraph formattedGraph, FluentAssertions.Formatting.FormattingContext context, FluentAssertions.Formatting.FormatChild formatChild) { } protected virtual System.Reflection.MemberInfo[] GetMembers(System.Type type) { } @@ -1594,7 +1454,8 @@ namespace FluentAssertions.Formatting public static int SpacesPerIndentation { get; } public void AddFragment(string fragment) { } public void AddFragmentOnNewLine(string fragment) { } - public void AddLine(string line) { } + public void AddLine(string content) { } + public void AddLineOrFragment(string fragment) { } public override string ToString() { } public System.IDisposable WithIndentation() { } } @@ -1616,6 +1477,8 @@ namespace FluentAssertions.Formatting public int MaxDepth { get; set; } public int MaxLines { get; set; } public bool UseLineBreaks { get; set; } + public void AddFormatter(FluentAssertions.Formatting.IValueFormatter formatter) { } + public void RemoveFormatter(FluentAssertions.Formatting.IValueFormatter formatter) { } } public class GuidValueFormatter : FluentAssertions.Formatting.IValueFormatter { @@ -1646,11 +1509,21 @@ namespace FluentAssertions.Formatting public bool CanHandle(object value) { } public void Format(object value, FluentAssertions.Formatting.FormattedObjectGraph formattedGraph, FluentAssertions.Formatting.FormattingContext context, FluentAssertions.Formatting.FormatChild formatChild) { } } + public class JsonNodeFormatter : FluentAssertions.Formatting.IValueFormatter + { + public JsonNodeFormatter() { } + public bool CanHandle(object value) { } + public void Format(object value, FluentAssertions.Formatting.FormattedObjectGraph formattedGraph, FluentAssertions.Formatting.FormattingContext context, FluentAssertions.Formatting.FormatChild formatChild) { } + } public class MaxLinesExceededException : System.Exception { public MaxLinesExceededException() { } - 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 { @@ -1764,21 +1637,19 @@ 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) { } - public FluentAssertions.AndConstraint BeEquivalentTo(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeGreaterOrEqualTo(T expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeEquivalentTo(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeGreaterThan(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeGreaterThanOrEqualTo(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeInRange(T minimumValue, T maximumValue, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeLessOrEqualTo(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeLessThan(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeLessThanOrEqualTo(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOneOf(params T[] validValues) { } @@ -1791,37 +1662,32 @@ 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 + public class NullableNumericAssertions : FluentAssertions.Numeric.NumericAssertionsBase where T : struct, System.IComparable where TAssertions : FluentAssertions.Numeric.NullableNumericAssertions { - public NullableNumericAssertions(T? value) { } + public NullableNumericAssertions(T? value, FluentAssertions.Execution.AssertionChain assertionChain) { } + public override T? Subject { get; } 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) { } public FluentAssertions.AndConstraint NotBeNull(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveValue(string because = "", params object[] becauseArgs) { } } - public class NumericAssertions : FluentAssertions.Numeric.NumericAssertions> + public abstract class NumericAssertionsBase where T : struct, System.IComparable + where TAssertions : FluentAssertions.Numeric.NumericAssertionsBase { - public NumericAssertions(T value) { } - } - public class NumericAssertions - where T : struct, System.IComparable - where TAssertions : FluentAssertions.Numeric.NumericAssertions - { - public NumericAssertions(T value) { } - public T? Subject { get; } + protected NumericAssertionsBase(FluentAssertions.Execution.AssertionChain assertionChain) { } + public FluentAssertions.Execution.AssertionChain CurrentAssertionChain { get; } + public abstract TSubject Subject { get; } public FluentAssertions.AndConstraint Be(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Be(T? expected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeGreaterOrEqualTo(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeGreaterThan(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeGreaterThanOrEqualTo(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeInRange(T minimumValue, T maximumValue, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeLessOrEqualTo(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeLessThan(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeLessThanOrEqualTo(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeNegative(string because = "", params object[] becauseArgs) { } @@ -1836,17 +1702,29 @@ namespace FluentAssertions.Numeric public FluentAssertions.AndConstraint NotBeInRange(T minimumValue, T maximumValue, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeOfType(System.Type unexpectedType, string because = "", params object[] becauseArgs) { } } + public class NumericAssertions : FluentAssertions.Numeric.NumericAssertions> + where T : struct, System.IComparable + { + public NumericAssertions(T value, FluentAssertions.Execution.AssertionChain assertionChain) { } + } + public class NumericAssertions : FluentAssertions.Numeric.NumericAssertionsBase + where T : struct, System.IComparable + where TAssertions : FluentAssertions.Numeric.NumericAssertions + { + public NumericAssertions(T value, FluentAssertions.Execution.AssertionChain assertionChain) { } + public override T Subject { get; } + } } 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) { } @@ -1857,12 +1735,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) { } @@ -1871,7 +1749,7 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint BeOnOrAfter(System.DateOnly expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOnOrBefore(System.DateOnly expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOneOf(params System.DateOnly[] validValues) { } - public FluentAssertions.AndConstraint BeOneOf(params System.Nullable[] validValues) { } + public FluentAssertions.AndConstraint BeOneOf(params System.DateOnly?[] validValues) { } public FluentAssertions.AndConstraint BeOneOf(System.Collections.Generic.IEnumerable validValues, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOneOf(System.Collections.Generic.IEnumerable validValues, string because = "", params object[] becauseArgs) { } public override bool Equals(object obj) { } @@ -1890,12 +1768,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) { } @@ -1910,7 +1788,7 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint BeOnOrAfter(System.DateTime expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOnOrBefore(System.DateTime expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOneOf(params System.DateTime[] validValues) { } - public FluentAssertions.AndConstraint BeOneOf(params System.Nullable[] validValues) { } + public FluentAssertions.AndConstraint BeOneOf(params System.DateTime?[] validValues) { } public FluentAssertions.AndConstraint BeOneOf(System.Collections.Generic.IEnumerable validValues, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOneOf(System.Collections.Generic.IEnumerable validValues, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeSameDateAs(System.DateTime expected, string because = "", params object[] becauseArgs) { } @@ -1927,6 +1805,7 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint NotBeAfter(System.DateTime unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeBefore(System.DateTime unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeCloseTo(System.DateTime distantTime, System.TimeSpan precision, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotBeIn(System.DateTimeKind unexpectedKind, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeOnOrAfter(System.DateTime unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeOnOrBefore(System.DateTime unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeSameDateAs(System.DateTime unexpected, string because = "", params object[] becauseArgs) { } @@ -1939,12 +1818,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) { } @@ -1960,7 +1839,7 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint BeOnOrAfter(System.DateTimeOffset expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOnOrBefore(System.DateTimeOffset expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOneOf(params System.DateTimeOffset[] validValues) { } - public FluentAssertions.AndConstraint BeOneOf(params System.Nullable[] validValues) { } + public FluentAssertions.AndConstraint BeOneOf(params System.DateTimeOffset?[] validValues) { } public FluentAssertions.AndConstraint BeOneOf(System.Collections.Generic.IEnumerable validValues, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOneOf(System.Collections.Generic.IEnumerable validValues, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeSameDateAs(System.DateTimeOffset expected, string because = "", params object[] becauseArgs) { } @@ -1994,7 +1873,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) { } @@ -2002,7 +1881,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) { } @@ -2010,13 +1889,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) { } @@ -2043,12 +1922,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) { } @@ -2058,31 +1937,14 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint NotBe(string unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEmpty(string because = "", params object[] becauseArgs) { } } - public class HttpResponseMessageAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions - { - public HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { } - } - public class HttpResponseMessageAssertions : FluentAssertions.Primitives.ObjectAssertions - where TAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions - { - protected HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { } - protected override string Identifier { get; } - public FluentAssertions.AndConstraint BeRedirection(string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeSuccessful(string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint HaveClientError(string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint HaveError(string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint HaveServerError(string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint HaveStatusCode(System.Net.HttpStatusCode expected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint NotHaveStatusCode(System.Net.HttpStatusCode unexpected, string because = "", params object[] becauseArgs) { } - } 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) { } @@ -2094,12 +1956,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) { } @@ -2107,12 +1969,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) { } @@ -2120,12 +1982,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) { } @@ -2134,13 +1996,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) { } @@ -2148,12 +2010,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) { } @@ -2162,12 +2024,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) { } @@ -2176,12 +2038,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) { } @@ -2189,7 +2051,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) { } @@ -2197,12 +2059,12 @@ 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) { } public FluentAssertions.AndConstraint BeEquivalentTo(TExpectation expectation, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeEquivalentTo(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeEquivalentTo(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOneOf(params TSubject[] validValues) { } public FluentAssertions.AndConstraint BeOneOf(System.Collections.Generic.IEnumerable validValues, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOneOf(System.Collections.Generic.IEnumerable validValues, System.Collections.Generic.IEqualityComparer comparer, string because = "", params object[] becauseArgs) { } @@ -2210,12 +2072,13 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint NotBe(TSubject unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBe(TSubject unexpected, System.Collections.Generic.IEqualityComparer comparer, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEquivalentTo(TExpectation unexpected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint NotBeEquivalentTo(TExpectation unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotBeEquivalentTo(TExpectation unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } } 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) { } @@ -2234,22 +2097,22 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint NotBeOfType(System.Type unexpectedType, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeOfType(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeSameAs(TSubject unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint Satisfy(System.Action assertion) + where T : TSubject { } } 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) { } - public FluentAssertions.AndConstraint BeGreaterOrEqualTo(System.TimeSpan expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeGreaterThan(System.TimeSpan expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeGreaterThanOrEqualTo(System.TimeSpan expected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeLessOrEqualTo(System.TimeSpan expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeLessThan(System.TimeSpan expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeLessThanOrEqualTo(System.TimeSpan expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeNegative(string because = "", params object[] becauseArgs) { } @@ -2260,16 +2123,17 @@ 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) { } public FluentAssertions.AndConstraint BeEquivalentTo(string expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeEquivalentTo(string expected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeLowerCased(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeNullOrEmpty(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeNullOrWhiteSpace(string because = "", params object[] becauseArgs) { } @@ -2284,18 +2148,23 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint ContainAny(System.Collections.Generic.IEnumerable values, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint ContainEquivalentOf(string expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint ContainEquivalentOf(string expected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint ContainEquivalentOf(string expected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint ContainEquivalentOf(string expected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint EndWith(string expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint EndWithEquivalentOf(string expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint EndWithEquivalentOf(string expected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveLength(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Match(string wildcardPattern, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint MatchEquivalentOf(string wildcardPattern, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint MatchRegex(string regularExpression, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint MatchEquivalentOf(string wildcardPattern, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint MatchRegex(System.Text.RegularExpressions.Regex regularExpression, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint MatchRegex(string regularExpression, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint MatchRegex(string regularExpression, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint MatchRegex(System.Text.RegularExpressions.Regex regularExpression, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint MatchRegex(string regularExpression, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBe(string unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEmpty(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEquivalentTo(string unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotBeEquivalentTo(string unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeLowerCased(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeNullOrEmpty(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeNullOrWhiteSpace(string because = "", params object[] becauseArgs) { } @@ -2306,25 +2175,30 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint NotContainAny(params string[] values) { } public FluentAssertions.AndConstraint NotContainAny(System.Collections.Generic.IEnumerable values, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotContainEquivalentOf(string unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotContainEquivalentOf(string unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotEndWith(string unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotEndWithEquivalentOf(string unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotEndWithEquivalentOf(string unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotMatch(string wildcardPattern, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotMatchEquivalentOf(string wildcardPattern, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint NotMatchRegex(string regularExpression, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotMatchEquivalentOf(string wildcardPattern, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotMatchRegex(System.Text.RegularExpressions.Regex regularExpression, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotMatchRegex(string regularExpression, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotStartWith(string unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotStartWithEquivalentOf(string unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotStartWithEquivalentOf(string unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint StartWith(string expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint StartWithEquivalentOf(string expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint StartWithEquivalentOf(string expected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } } 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) { } @@ -2333,8 +2207,8 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint BeCloseTo(System.TimeOnly nearbyTime, System.TimeSpan precision, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOnOrAfter(System.TimeOnly expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOnOrBefore(System.TimeOnly expected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeOneOf(params System.Nullable[] validValues) { } public FluentAssertions.AndConstraint BeOneOf(params System.TimeOnly[] validValues) { } + public FluentAssertions.AndConstraint BeOneOf(params System.TimeOnly?[] validValues) { } public FluentAssertions.AndConstraint BeOneOf(System.Collections.Generic.IEnumerable validValues, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOneOf(System.Collections.Generic.IEnumerable validValues, string because = "", params object[] becauseArgs) { } public override bool Equals(object obj) { } @@ -2363,49 +2237,32 @@ namespace FluentAssertions.Primitives LessThan = 4, } } -namespace FluentAssertions.Reflection -{ - public class AssemblyAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions - { - public AssemblyAssertions(System.Reflection.Assembly assembly) { } - 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) { } - public FluentAssertions.AndWhichConstraint DefineType(string @namespace, string name, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint NotReference(System.Reflection.Assembly assembly, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint Reference(System.Reflection.Assembly assembly, string because = "", params object[] becauseArgs) { } - } -} 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) { } + public FluentAssertions.AndConstraint NotThrowAfter(System.TimeSpan waitTime, System.TimeSpan pollInterval, string because = "", params object[] becauseArgs) { } } public class AsyncFunctionAssertions : FluentAssertions.Specialized.DelegateAssertionsBase, TAssertions> where TTask : System.Threading.Tasks.Task where TAssertions : FluentAssertions.Specialized.AsyncFunctionAssertions { - [System.Obsolete("This class is intended as base class. This ctor is accidentally public and will b" + - "e removed in Version 7.")] - public AsyncFunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor) { } - [System.Obsolete("This class is intended as base class. This ctor is accidentally public and will b" + - "e made protected in Version 7.")] - public 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> CompleteWithinAsync(System.TimeSpan timeSpan, string because = "", params object[] becauseArgs) { } public System.Threading.Tasks.Task> NotCompleteWithinAsync(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 System.Threading.Tasks.Task> NotThrowAsync(string because = "", params object[] becauseArgs) where TException : System.Exception { } + public System.Threading.Tasks.Task> ThrowAsync(string because = "", params object[] becauseArgs) { } public System.Threading.Tasks.Task> ThrowAsync(string because = "", params object[] becauseArgs) where TException : System.Exception { } public System.Threading.Tasks.Task> ThrowExactlyAsync(string because = "", params object[] becauseArgs) where TException : System.Exception { } + public System.Threading.Tasks.Task> ThrowWithinAsync(System.TimeSpan timeSpan, string because = "", params object[] becauseArgs) { } public System.Threading.Tasks.Task> ThrowWithinAsync(System.TimeSpan timeSpan, string because = "", params object[] becauseArgs) where TException : System.Exception { } } @@ -2423,12 +2280,11 @@ 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) { } public FluentAssertions.AndConstraint NotThrow(string because = "", params object[] becauseArgs) where TException : System.Exception { } - public FluentAssertions.AndConstraint NotThrowAfter(System.TimeSpan waitTime, System.TimeSpan pollInterval, string because = "", params object[] becauseArgs) { } + public FluentAssertions.Specialized.ExceptionAssertions Throw(string because = "", params object[] becauseArgs) { } public FluentAssertions.Specialized.ExceptionAssertions Throw(string because = "", params object[] becauseArgs) where TException : System.Exception { } public FluentAssertions.Specialized.ExceptionAssertions ThrowExactly(string because = "", params object[] becauseArgs) @@ -2437,7 +2293,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; } @@ -2459,20 +2315,18 @@ 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 BeGreaterOrEqualTo(System.TimeSpan minDuration, 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) { } - public FluentAssertions.AndConstraint BeLessOrEqualTo(System.TimeSpan maxDuration, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeLessThan(System.TimeSpan maxDuration, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeLessThanOrEqualTo(System.TimeSpan maxDuration, string because = "", params object[] becauseArgs) { } public override bool Equals(object obj) { } } 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) { } @@ -2480,8 +2334,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) { } @@ -2491,19 +2345,47 @@ namespace FluentAssertions.Specialized System.Collections.Generic.IEnumerable OfType(System.Exception actualException) where T : System.Exception; } + public class JsonNodeAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions> + where T : System.Text.Json.Nodes.JsonNode + { + public JsonNodeAssertions(T subject, FluentAssertions.Execution.AssertionChain assertionChain) { } + protected override string Identifier { get; } + public FluentAssertions.AndWhichConstraint, System.Collections.Generic.IEnumerable> BeAnArray(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint, bool> BeBool(string because = "", params object[] becauseArgs) { } + 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) { } + public FluentAssertions.AndWhichConstraint, System.DateTime> BeLocalDate(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint, string> BeNumeric(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint, TValue> BeNumeric(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint, string> BeString(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint, System.DateTime> BeUtcDate(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint, System.Text.Json.Nodes.JsonNode> HaveProperty(string code, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint> NotBeAnArray(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint> NotBeBool(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint> NotBeEquivalentTo(TExpectation unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint> NotBeEquivalentTo(TExpectation unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint> NotBeLocalDate(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint> NotBeNumeric(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint> NotBeString(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint> NotBeUtcDate(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint> NotHaveProperty(string code, string because = "", params object[] becauseArgs) { } + } public class MemberExecutionTime : FluentAssertions.Specialized.ExecutionTime { public MemberExecutionTime(T subject, System.Linq.Expressions.Expression> action, FluentAssertions.Common.StartTimer createTimer) { } } 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) { } } @@ -2514,8 +2396,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) { } } @@ -2524,25 +2406,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) { } @@ -2566,16 +2448,26 @@ namespace FluentAssertions.Types { public static FluentAssertions.Types.TypeSelector From(System.Reflection.Assembly assembly) { } } + public class AssemblyAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions + { + 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) { } + public FluentAssertions.AndWhichConstraint DefineType(string @namespace, string name, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotReference(System.Reflection.Assembly assembly, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint Reference(System.Reflection.Assembly assembly, string because = "", params object[] becauseArgs) { } + } 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; } } 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; } public FluentAssertions.AndWhichConstraint, TAttribute> BeDecoratedWith(string because = "", params object[] becauseArgs) where TAttribute : System.Attribute { } @@ -2590,13 +2482,13 @@ 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; } public FluentAssertions.AndConstraint BeAsync(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeVirtual(string because = "", params object[] becauseArgs) { } @@ -2640,7 +2532,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) { } @@ -2661,7 +2553,7 @@ 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; } public FluentAssertions.AndConstraint BeReadable(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeReadable(FluentAssertions.Common.CSharpAccessModifier accessModifier, string because = "", params object[] becauseArgs) { } @@ -2703,7 +2595,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) @@ -2718,13 +2610,13 @@ 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) { } public FluentAssertions.AndConstraint BeAbstract(string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeAssignableTo(System.Type type, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeAssignableTo(string because = "", params object[] becauseArgs) { } + public new FluentAssertions.AndConstraint BeAssignableTo(System.Type type, string because = "", params object[] becauseArgs) { } + public new FluentAssertions.AndConstraint BeAssignableTo(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint BeDecoratedWith(string because = "", params object[] becauseArgs) where TAttribute : System.Attribute { } public FluentAssertions.AndWhichConstraint BeDecoratedWith(System.Linq.Expressions.Expression> isMatchingAttributePredicate, string because = "", params object[] becauseArgs) @@ -2761,8 +2653,8 @@ namespace FluentAssertions.Types public FluentAssertions.AndConstraint NotBe(System.Type unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBe(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeAbstract(string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint NotBeAssignableTo(System.Type type, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint NotBeAssignableTo(string because = "", params object[] becauseArgs) { } + public new FluentAssertions.AndConstraint NotBeAssignableTo(System.Type type, string because = "", params object[] becauseArgs) { } + public new FluentAssertions.AndConstraint NotBeAssignableTo(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeDecoratedWith(string because = "", params object[] becauseArgs) where TAttribute : System.Attribute { } public FluentAssertions.AndConstraint NotBeDecoratedWith(System.Linq.Expressions.Expression> isMatchingAttributePredicate, string because = "", params object[] becauseArgs) @@ -2836,7 +2728,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 { } @@ -2867,7 +2759,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) { } @@ -2875,38 +2767,56 @@ 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) { } - public FluentAssertions.AndWhichConstraint HaveElement(string expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint HaveElement(System.Xml.Linq.XName expected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint> HaveElement(string expected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint HaveElement(string expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint> HaveElement(System.Xml.Linq.XName expected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint HaveRoot(string expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint> HaveElement(string expected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint HaveElementWithValue(System.Xml.Linq.XName expectedElement, string expectedValue, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint HaveElementWithValue(string expectedElement, string expectedValue, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint HaveRoot(System.Xml.Linq.XName expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint HaveRoot(string expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBe(System.Xml.Linq.XDocument unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEquivalentTo(System.Xml.Linq.XDocument unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveElement(System.Xml.Linq.XName unexpectedElement, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveElement(string unexpectedElement, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveElementWithValue(System.Xml.Linq.XName unexpectedElement, string unexpectedValue, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveElementWithValue(string unexpectedElement, string unexpectedValue, string because = "", params object[] becauseArgs) { } } 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) { } - public FluentAssertions.AndConstraint HaveAttribute(string expectedName, string expectedValue, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint HaveAttribute(System.Xml.Linq.XName expectedName, string expectedValue, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint HaveElement(string expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveAttribute(System.Xml.Linq.XName expectedName, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveAttribute(string expectedName, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveAttributeWithValue(System.Xml.Linq.XName expectedName, string expectedValue, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveAttributeWithValue(string expectedName, string expectedValue, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint HaveElement(System.Xml.Linq.XName expected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint> HaveElement(string expected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint HaveElement(string expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint> HaveElement(System.Xml.Linq.XName expected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint> HaveElement(string expected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint HaveElementWithValue(System.Xml.Linq.XName expectedElement, string expectedValue, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint HaveElementWithValue(string expectedElement, string expectedValue, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveValue(string expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBe(System.Xml.Linq.XElement unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEquivalentTo(System.Xml.Linq.XElement unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveAttribute(System.Xml.Linq.XName unexpectedName, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveAttribute(string unexpectedName, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveAttributeWithValue(System.Xml.Linq.XName unexpectedName, string unexpectedValue, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveAttributeWithValue(string unexpectedName, string unexpectedValue, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveElement(System.Xml.Linq.XName unexpectedElement, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveElement(string unexpectedElement, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveElementWithValue(System.Xml.Linq.XName unexpectedElement, string unexpectedValue, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveElementWithValue(string unexpectedElement, string unexpectedValue, string because = "", params object[] becauseArgs) { } } 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) { } @@ -2916,13 +2826,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 7995d53641..ee065b0c6e 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.0.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.0.verified.txt @@ -10,17 +10,30 @@ namespace FluentAssertions public System.Collections.Generic.IEnumerable OfType(System.Exception actualException) where T : System.Exception { } } - public class AndConstraint + public class AndConstraint { - public AndConstraint(T parentConstraint) { } - public T And { get; } + 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) { } + 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 AssertionConfiguration + { + public static FluentAssertions.Configuration.GlobalConfiguration Current { get; } + } + public static class AssertionEngine + { + public static FluentAssertions.Configuration.GlobalConfiguration Configuration { get; } + public static FluentAssertions.Execution.ITestFramework TestFramework { get; set; } + public static void ResetToDefaults() { } } public static class AssertionExtensions { @@ -56,58 +69,53 @@ namespace FluentAssertions [System.Obsolete("You are asserting the \'AndConstraint\' itself. Remove the \'Should()\' method direct" + "ly following \'And\'", true)] public static void Should(this FluentAssertions.Types.TypeSelectorAssertions _) { } - public static FluentAssertions.Specialized.ActionAssertions Should(this System.Action action) { } - public static FluentAssertions.Collections.StringCollectionAssertions Should(this System.Collections.Generic.IEnumerable @this) { } - public static FluentAssertions.Data.DataColumnAssertions Should(this System.Data.DataColumn actualValue) { } - public static FluentAssertions.Collections.GenericCollectionAssertions Should(this System.Data.DataColumnCollection actualValue) { } - public static FluentAssertions.Collections.GenericCollectionAssertions Should(this System.Data.DataRowCollection actualValue) { } - public static FluentAssertions.Collections.GenericCollectionAssertions Should(this System.Data.DataTableCollection actualValue) { } + public static FluentAssertions.Specialized.ActionAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Action action) { } + public static FluentAssertions.Collections.StringCollectionAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Collections.Generic.IEnumerable @this) { } public static FluentAssertions.Primitives.DateTimeAssertions Should(this System.DateTime actualValue) { } - public static FluentAssertions.Primitives.NullableDateTimeAssertions Should(this System.DateTime? actualValue) { } + public static FluentAssertions.Primitives.NullableDateTimeAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.DateTime? actualValue) { } public static FluentAssertions.Primitives.DateTimeOffsetAssertions Should(this System.DateTimeOffset actualValue) { } - public static FluentAssertions.Primitives.NullableDateTimeOffsetAssertions Should(this System.DateTimeOffset? actualValue) { } - public static FluentAssertions.Specialized.NonGenericAsyncFunctionAssertions Should(this System.Func action) { } + public static FluentAssertions.Primitives.NullableDateTimeOffsetAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.DateTimeOffset? actualValue) { } + 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(this System.IO.BufferedStream actualValue) { } - public static FluentAssertions.Streams.StreamAssertions Should(this System.IO.Stream actualValue) { } - public static FluentAssertions.Primitives.HttpResponseMessageAssertions Should(this System.Net.Http.HttpResponseMessage actualValue) { } - public static FluentAssertions.Reflection.AssemblyAssertions Should(this System.Reflection.Assembly assembly) { } - public static FluentAssertions.Types.ConstructorInfoAssertions Should(this System.Reflection.ConstructorInfo constructorInfo) { } - public static FluentAssertions.Types.MethodInfoAssertions Should(this System.Reflection.MethodInfo methodInfo) { } - public static FluentAssertions.Types.PropertyInfoAssertions Should(this System.Reflection.PropertyInfo propertyInfo) { } + public static FluentAssertions.Primitives.NullableGuidAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] 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.Types.AssemblyAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Reflection.Assembly assembly) { } + public static FluentAssertions.Types.ConstructorInfoAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Reflection.ConstructorInfo constructorInfo) { } + public static FluentAssertions.Types.MethodInfoAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Reflection.MethodInfo methodInfo) { } + public static FluentAssertions.Types.PropertyInfoAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Reflection.PropertyInfo propertyInfo) { } public static FluentAssertions.Primitives.SimpleTimeSpanAssertions Should(this System.TimeSpan actualValue) { } - public static FluentAssertions.Primitives.NullableSimpleTimeSpanAssertions Should(this System.TimeSpan? actualValue) { } - public static FluentAssertions.Types.TypeAssertions Should(this System.Type subject) { } - public static FluentAssertions.Xml.XAttributeAssertions Should(this System.Xml.Linq.XAttribute actualValue) { } - public static FluentAssertions.Xml.XDocumentAssertions Should(this System.Xml.Linq.XDocument actualValue) { } - public static FluentAssertions.Xml.XElementAssertions Should(this System.Xml.Linq.XElement actualValue) { } + public static FluentAssertions.Primitives.NullableSimpleTimeSpanAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.TimeSpan? actualValue) { } + public static FluentAssertions.Types.TypeAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Type subject) { } + public static FluentAssertions.Xml.XAttributeAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Xml.Linq.XAttribute actualValue) { } + public static FluentAssertions.Xml.XDocumentAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Xml.Linq.XDocument actualValue) { } + public static FluentAssertions.Xml.XElementAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Xml.Linq.XElement actualValue) { } public static FluentAssertions.Primitives.BooleanAssertions Should(this bool actualValue) { } - public static FluentAssertions.Primitives.NullableBooleanAssertions Should(this bool? actualValue) { } + public static FluentAssertions.Primitives.NullableBooleanAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this bool? actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this byte actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this byte? actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this byte? actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this decimal actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this decimal? actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this decimal? actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this double actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this double? actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this double? actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this float actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this float? actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this float? actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this int actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this int? actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this int? actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this long actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this long? actualValue) { } - public static FluentAssertions.Primitives.ObjectAssertions Should(this object actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this long? actualValue) { } + public static FluentAssertions.Primitives.ObjectAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this object actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this sbyte actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this sbyte? actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this sbyte? actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this short actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this short? actualValue) { } - public static FluentAssertions.Primitives.StringAssertions Should(this string actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this short? actualValue) { } + public static FluentAssertions.Primitives.StringAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this string actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this uint actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this uint? actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this uint? actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this ulong actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this ulong? actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this ulong? actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this ushort actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this ushort? actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this ushort? actualValue) { } [System.Obsolete("You are asserting the \'AndConstraint\' itself. Remove the \'Should()\' method direct" + "ly following \'And\'", true)] public static void Should(this FluentAssertions.Primitives.BooleanAssertions _) @@ -136,14 +144,14 @@ namespace FluentAssertions "ly following \'And\'", true)] public static void Should(this FluentAssertions.Primitives.SimpleTimeSpanAssertions _) where TAssertions : FluentAssertions.Primitives.SimpleTimeSpanAssertions { } - public static FluentAssertions.Collections.GenericCollectionAssertions Should(this System.Collections.Generic.IEnumerable actualValue) { } - public static FluentAssertions.Specialized.GenericAsyncFunctionAssertions Should(this System.Func> action) { } - public static FluentAssertions.Specialized.FunctionAssertions Should(this System.Func func) { } - public static FluentAssertions.Numeric.ComparableTypeAssertions Should(this System.IComparable comparableValue) { } + public static FluentAssertions.Collections.GenericCollectionAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Collections.Generic.IEnumerable actualValue) { } + public static FluentAssertions.Specialized.GenericAsyncFunctionAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Func> action) { } + public static FluentAssertions.Specialized.FunctionAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Func func) { } + public static FluentAssertions.Numeric.ComparableTypeAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.IComparable comparableValue) { } public static FluentAssertions.Specialized.TaskCompletionSourceAssertions Should(this System.Threading.Tasks.TaskCompletionSource tcs) { } [System.Obsolete("You are asserting the \'AndConstraint\' itself. Remove the \'Should()\' method direct" + "ly following \'And\'", true)] - public static void Should(this FluentAssertions.Numeric.NumericAssertions _) + public static void Should(this FluentAssertions.Numeric.NumericAssertionsBase _) where TSubject : struct, System.IComparable where TAssertions : FluentAssertions.Numeric.NumericAssertions { } [System.Obsolete("You are asserting the \'AndConstraint\' itself. Remove the \'Should()\' method direct" + @@ -155,18 +163,11 @@ namespace FluentAssertions "ly following \'And\'", true)] public static void Should(this FluentAssertions.Primitives.ReferenceTypeAssertions _) where TAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions { } - public static FluentAssertions.Collections.GenericDictionaryAssertions, TKey, TValue> Should(this System.Collections.Generic.IDictionary actualValue) { } - public static FluentAssertions.Collections.GenericDictionaryAssertions>, TKey, TValue> Should(this System.Collections.Generic.IEnumerable> actualValue) { } - public static FluentAssertions.Collections.GenericDictionaryAssertions Should(this TCollection actualValue) + public static FluentAssertions.Collections.GenericDictionaryAssertions, TKey, TValue> Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Collections.Generic.IDictionary actualValue) { } + public static FluentAssertions.Collections.GenericDictionaryAssertions>, TKey, TValue> Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Collections.Generic.IEnumerable> actualValue) { } + public static FluentAssertions.Collections.GenericDictionaryAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this TCollection actualValue) where TCollection : System.Collections.Generic.IEnumerable> { } } - public static class AssertionOptions - { - public static FluentAssertions.EquivalencyPlan EquivalencyPlan { get; } - public static FluentAssertions.Formatting.FormattingOptions FormattingOptions { get; } - public static void AssertEquivalencyUsing(System.Func defaultsConfigurer) { } - public static FluentAssertions.Equivalency.EquivalencyAssertionOptions CloneDefaults() { } - } public static class AsyncAssertionsExtensions { public static System.Threading.Tasks.Task, T>> WithResult(this System.Threading.Tasks.Task, T>> task, T expected, string because = "", params object[] becauseArgs) { } @@ -189,6 +190,7 @@ namespace FluentAssertions public static class CallerIdentifier { public static System.Action Logger { get; set; } + public static string[] DetermineCallerIdentities() { } public static string DetermineCallerIdentity() { } } [System.AttributeUsage(System.AttributeTargets.Method)] @@ -196,68 +198,18 @@ namespace FluentAssertions { public CustomAssertionAttribute() { } } - public static class DataColumnCollectionAssertionExtensions - { - public static FluentAssertions.AndConstraint> BeSameAs(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataColumnCollection expected, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> HaveSameCount(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataColumnCollection otherCollection, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> NotBeSameAs(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataColumnCollection unexpected, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> NotHaveSameCount(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataColumnCollection otherCollection, string because = "", params object[] becauseArgs) { } - } - public static class DataRowAssertionExtensions - { - public static FluentAssertions.Data.DataRowAssertions Should(this TDataRow actualValue) - where TDataRow : System.Data.DataRow { } - } - public static class DataRowCollectionAssertionExtensions - { - public static FluentAssertions.AndConstraint> BeSameAs(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataRowCollection expected, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> HaveSameCount(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataRowCollection otherCollection, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> NotBeSameAs(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataRowCollection unexpected, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> NotHaveSameCount(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataRowCollection otherCollection, string because = "", params object[] becauseArgs) { } - } - public static class DataSetAssertionExtensions - { - public static FluentAssertions.Data.DataSetAssertions Should(this TDataSet actualValue) - where TDataSet : System.Data.DataSet { } - } - public static class DataTableAssertionExtensions + [System.AttributeUsage(System.AttributeTargets.Assembly)] + public sealed class CustomAssertionsAssemblyAttribute : System.Attribute { - public static FluentAssertions.Data.DataTableAssertions Should(this TDataTable actualValue) - where TDataTable : System.Data.DataTable { } - } - public static class DataTableCollectionAssertionExtensions - { - public static FluentAssertions.AndConstraint> BeSameAs(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataTableCollection expected, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> HaveSameCount(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataSet otherDataSet, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> HaveSameCount(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataTableCollection otherCollection, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> NotBeSameAs(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataTableCollection unexpected, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> NotHaveSameCount(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataSet otherDataSet, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> NotHaveSameCount(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataTableCollection otherCollection, string because = "", params object[] becauseArgs) { } + public CustomAssertionsAssemblyAttribute() { } } public static class EnumAssertionsExtensions { public static FluentAssertions.Primitives.EnumAssertions Should(this TEnum @enum) where TEnum : struct, System.Enum { } - public static FluentAssertions.Primitives.NullableEnumAssertions Should(this TEnum? @enum) + public static FluentAssertions.Primitives.NullableEnumAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this TEnum? @enum) where TEnum : struct, System.Enum { } } - public class EquivalencyPlan : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable - { - public EquivalencyPlan() { } - public void Add() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } - public void AddAfter() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } - public void Clear() { } - public System.Collections.Generic.IEnumerator GetEnumerator() { } - public void Insert() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } - public void InsertBefore() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } - public void Remove() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep { } - public void Reset() { } - } public static class Exactly { public static FluentAssertions.OccurrenceConstraint Once() { } @@ -301,6 +253,10 @@ namespace FluentAssertions public static FluentAssertions.OccurrenceConstraint Times(int expected) { } public static FluentAssertions.OccurrenceConstraint Twice() { } } + public static class License + { + public static bool Accepted { get; set; } + } public static class MoreThan { public static FluentAssertions.OccurrenceConstraint Once() { } @@ -320,13 +276,17 @@ namespace FluentAssertions public static FluentAssertions.AndConstraint> BeApproximately(this FluentAssertions.Numeric.NumericAssertions parent, double expectedValue, double precision, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> BeApproximately(this FluentAssertions.Numeric.NumericAssertions parent, float expectedValue, float precision, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, byte nearbyValue, byte delta, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, short nearbyValue, ushort delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, int nearbyValue, uint delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, long nearbyValue, ulong delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, sbyte nearbyValue, byte delta, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, ushort nearbyValue, ushort delta, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, short nearbyValue, ushort delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, uint nearbyValue, uint delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, ulong nearbyValue, ulong delta, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, ushort nearbyValue, ushort delta, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> BeNaN(this FluentAssertions.Numeric.NullableNumericAssertions parent, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> BeNaN(this FluentAssertions.Numeric.NullableNumericAssertions parent, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> BeNaN(this FluentAssertions.Numeric.NumericAssertions parent, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> BeNaN(this FluentAssertions.Numeric.NumericAssertions parent, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeApproximately(this FluentAssertions.Numeric.NullableNumericAssertions parent, decimal unexpectedValue, decimal precision, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeApproximately(this FluentAssertions.Numeric.NullableNumericAssertions parent, decimal? unexpectedValue, decimal precision, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeApproximately(this FluentAssertions.Numeric.NullableNumericAssertions parent, double unexpectedValue, double precision, string because = "", params object[] becauseArgs) { } @@ -337,20 +297,22 @@ namespace FluentAssertions public static FluentAssertions.AndConstraint> NotBeApproximately(this FluentAssertions.Numeric.NumericAssertions parent, double unexpectedValue, double precision, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeApproximately(this FluentAssertions.Numeric.NumericAssertions parent, float unexpectedValue, float precision, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, byte distantValue, byte delta, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, short distantValue, ushort delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, int distantValue, uint delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, long distantValue, ulong delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, sbyte distantValue, byte delta, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, ushort distantValue, ushort delta, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, short distantValue, ushort delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, uint distantValue, uint delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, ulong distantValue, ulong delta, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, ushort distantValue, ushort delta, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> NotBeNaN(this FluentAssertions.Numeric.NullableNumericAssertions parent, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> NotBeNaN(this FluentAssertions.Numeric.NullableNumericAssertions parent, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> NotBeNaN(this FluentAssertions.Numeric.NumericAssertions parent, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> NotBeNaN(this FluentAssertions.Numeric.NumericAssertions parent, string because = "", params object[] becauseArgs) { } } public static class ObjectAssertionsExtensions { - public static FluentAssertions.AndConstraint BeBinarySerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint BeBinarySerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> options, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint BeDataContractSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint BeDataContractSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> options, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint BeDataContractSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> options, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint BeXmlSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, string because = "", params object[] becauseArgs) { } } public abstract class OccurrenceConstraint @@ -389,33 +351,38 @@ namespace FluentAssertions public static FluentAssertions.Types.TypeSelector Types(this System.Reflection.Assembly assembly) { } public static FluentAssertions.Types.TypeSelector Types(this System.Type type) { } } + public static class Value + { + public static FluentAssertions.Equivalency.Inlining.IInlineEquivalencyAssertion ThatMatches(System.Linq.Expressions.Expression> condition) { } + public static FluentAssertions.Equivalency.Inlining.IInlineEquivalencyAssertion ThatSatisfies(System.Action assertion) { } + } public static class XmlAssertionExtensions { - public static FluentAssertions.Xml.XmlElementAssertions Should(this System.Xml.XmlElement actualValue) { } - public static FluentAssertions.Xml.XmlNodeAssertions Should(this System.Xml.XmlNode actualValue) { } + public static FluentAssertions.Xml.XmlElementAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Xml.XmlElement actualValue) { } + public static FluentAssertions.Xml.XmlNodeAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Xml.XmlNode actualValue) { } } } 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) { } public FluentAssertions.AndConstraint AllBeEquivalentTo(TExpectation expectation, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint AllBeEquivalentTo(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint AllBeEquivalentTo(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint AllBeOfType(System.Type expectedType, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint> AllBeOfType(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint AllSatisfy(System.Action expected, string because = "", params object[] becauseArgs) { } @@ -424,7 +391,7 @@ namespace FluentAssertions.Collections protected void AssertSubjectEquality(System.Collections.Generic.IEnumerable expectation, System.Func equalityComparison, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEmpty(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEquivalentTo(System.Collections.Generic.IEnumerable expectation, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeEquivalentTo(System.Collections.Generic.IEnumerable expectation, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeEquivalentTo(System.Collections.Generic.IEnumerable expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint> BeInAscendingOrder(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint> BeInAscendingOrder(System.Collections.Generic.IComparer comparer, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint> BeInAscendingOrder(System.Func comparison, string because = "", params object[] becauseArgs) { } @@ -441,7 +408,7 @@ namespace FluentAssertions.Collections public FluentAssertions.AndWhichConstraint Contain(System.Linq.Expressions.Expression> predicate, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint Contain(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint ContainEquivalentOf(TExpectation expectation, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint ContainEquivalentOf(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint ContainEquivalentOf(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint ContainInConsecutiveOrder(params T[] expected) { } public FluentAssertions.AndConstraint ContainInConsecutiveOrder(System.Collections.Generic.IEnumerable expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint ContainInOrder(params T[] expected) { } @@ -456,12 +423,10 @@ namespace FluentAssertions.Collections public FluentAssertions.AndConstraint Equal(System.Collections.Generic.IEnumerable expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Equal(System.Collections.Generic.IEnumerable expectation, System.Func equalityComparison, string because = "", params object[] becauseArgs) { } public override bool Equals(object obj) { } - public FluentAssertions.AndConstraint HaveCount(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveCount(System.Linq.Expressions.Expression> countPredicate, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint HaveCountGreaterOrEqualTo(int expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveCount(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveCountGreaterThan(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveCountGreaterThanOrEqualTo(int expected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint HaveCountLessOrEqualTo(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveCountLessThan(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveCountLessThanOrEqualTo(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint HaveElementAt(int index, T element, string because = "", params object[] becauseArgs) { } @@ -471,7 +436,7 @@ namespace FluentAssertions.Collections public FluentAssertions.AndConstraint IntersectWith(System.Collections.Generic.IEnumerable otherCollection, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEmpty(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEquivalentTo(System.Collections.Generic.IEnumerable unexpected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint NotBeEquivalentTo(System.Collections.Generic.IEnumerable unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotBeEquivalentTo(System.Collections.Generic.IEnumerable unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeInAscendingOrder(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeInAscendingOrder(System.Collections.Generic.IComparer comparer, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeInAscendingOrder(System.Func comparison, string because = "", params object[] becauseArgs) { } @@ -486,9 +451,9 @@ 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.EquivalencyAssertionOptions> config, 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) { } public FluentAssertions.AndConstraint NotContainInConsecutiveOrder(System.Collections.Generic.IEnumerable unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotContainInOrder(params T[] unexpected) { } @@ -517,16 +482,16 @@ 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.EquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeEquivalentTo(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Contain(params System.Collections.Generic.KeyValuePair[] expected) { } public FluentAssertions.AndConstraint Contain(System.Collections.Generic.IEnumerable> expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Contain(System.Collections.Generic.KeyValuePair expected, string because = "", params object[] becauseArgs) { } @@ -535,8 +500,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) { } @@ -554,42 +519,31 @@ 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.EquivalencyAssertionOptions> config, 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) { } public FluentAssertions.AndConstraint BeEquivalentTo(System.Collections.Generic.IEnumerable expectation, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeEquivalentTo(System.Collections.Generic.IEnumerable expectation, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeEquivalentTo(System.Collections.Generic.IEnumerable expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint ContainMatch(string wildcardPattern, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Equal(System.Collections.Generic.IEnumerable expected) { } public FluentAssertions.AndConstraint Equal(params string[] expected) { } public FluentAssertions.AndConstraint NotContainMatch(string wildcardPattern, string because = "", params object[] becauseArgs) { } } - public class SubsequentOrderingAssertions : FluentAssertions.Collections.SubsequentOrderingGenericCollectionAssertions, T, FluentAssertions.Collections.SubsequentOrderingAssertions> - { - public SubsequentOrderingAssertions(System.Collections.Generic.IEnumerable actualValue, System.Linq.IOrderedEnumerable previousOrderedEnumerable) { } - } - public class SubsequentOrderingGenericCollectionAssertions : FluentAssertions.Collections.SubsequentOrderingGenericCollectionAssertions> - where TCollection : System.Collections.Generic.IEnumerable - { - public SubsequentOrderingGenericCollectionAssertions(TCollection actualValue, System.Linq.IOrderedEnumerable previousOrderedEnumerable) { } - } - public class SubsequentOrderingGenericCollectionAssertions : FluentAssertions.Collections.GenericCollectionAssertions - where TCollection : System.Collections.Generic.IEnumerable - where TAssertions : FluentAssertions.Collections.SubsequentOrderingGenericCollectionAssertions + public class SubsequentOrderingAssertions : FluentAssertions.Collections.GenericCollectionAssertions, T> { - public SubsequentOrderingGenericCollectionAssertions(TCollection actualValue, System.Linq.IOrderedEnumerable previousOrderedEnumerable) { } + public SubsequentOrderingAssertions(System.Collections.Generic.IEnumerable 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) { } @@ -615,14 +569,6 @@ namespace FluentAssertions.Common InvalidForCSharp = 5, PrivateProtected = 6, } - public class Configuration - { - public Configuration(FluentAssertions.Common.IConfigurationStore store) { } - public string TestFrameworkName { get; set; } - public string ValueFormatterAssembly { get; set; } - public FluentAssertions.Common.ValueFormatterDetectionMode ValueFormatterDetectionMode { get; set; } - public static FluentAssertions.Common.Configuration Current { get; } - } public static class DateTimeExtensions { public static System.DateTimeOffset ToDateTimeOffset(this System.DateTime dateTime) { } @@ -634,31 +580,10 @@ namespace FluentAssertions.Common System.Threading.Tasks.Task DelayAsync(System.TimeSpan delay, System.Threading.CancellationToken cancellationToken); FluentAssertions.Common.ITimer StartTimer(); } - public interface ICollectionWrapper - where TCollection : System.Collections.ICollection - { - TCollection UnderlyingCollection { get; } - } - public interface IConfigurationStore - { - string GetSetting(string name); - } - public interface IReflector - { - System.Collections.Generic.IEnumerable GetAllTypesFromAppDomain(System.Func predicate); - } public interface ITimer : System.IDisposable { System.TimeSpan Elapsed { get; } } - public static class Services - { - public static FluentAssertions.Common.Configuration Configuration { get; } - public static FluentAssertions.Common.IConfigurationStore ConfigurationStore { get; set; } - public static FluentAssertions.Common.IReflector Reflector { get; set; } - public static System.Action ThrowException { get; set; } - public static void ResetToDefaults() { } - } public delegate FluentAssertions.Common.ITimer StartTimer(); public enum ValueFormatterDetectionMode { @@ -667,82 +592,36 @@ namespace FluentAssertions.Common Scan = 2, } } -namespace FluentAssertions.Data +namespace FluentAssertions.Configuration { - public class DataColumnAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions + public class GlobalConfiguration { - public DataColumnAssertions(System.Data.DataColumn dataColumn) { } - protected override string Identifier { get; } - public FluentAssertions.AndConstraint BeEquivalentTo(System.Data.DataColumn expectation, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeEquivalentTo(System.Data.DataColumn expectation, System.Func, FluentAssertions.Data.IDataEquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } + public GlobalConfiguration() { } + public FluentAssertions.Configuration.GlobalEquivalencyOptions Equivalency { get; set; } + public FluentAssertions.Configuration.GlobalFormattingOptions Formatting { get; set; } + public FluentAssertions.Configuration.TestFramework? TestFramework { get; set; } } - public class DataRowAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions> - where TDataRow : System.Data.DataRow + public class GlobalEquivalencyOptions { - public DataRowAssertions(TDataRow dataRow) { } - protected override string Identifier { get; } - public FluentAssertions.AndConstraint> BeEquivalentTo(System.Data.DataRow expectation, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint> BeEquivalentTo(System.Data.DataRow expectation, System.Func, FluentAssertions.Data.IDataEquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint, System.Data.DataColumn> HaveColumn(string expectedColumnName, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint> HaveColumns(params string[] expectedColumnNames) { } - public FluentAssertions.AndConstraint> HaveColumns(System.Collections.Generic.IEnumerable expectedColumnNames, string because = "", params object[] becauseArgs) { } + public GlobalEquivalencyOptions() { } + public FluentAssertions.Equivalency.EquivalencyPlan Plan { get; } + public FluentAssertions.Equivalency.EquivalencyOptions CloneDefaults() { } + public void Modify(System.Func configureOptions) { } } - public class DataSetAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions> - where TDataSet : System.Data.DataSet + public class GlobalFormattingOptions : FluentAssertions.Formatting.FormattingOptions { - public DataSetAssertions(TDataSet dataSet) { } - protected override string Identifier { get; } - public FluentAssertions.AndConstraint> BeEquivalentTo(System.Data.DataSet expectation, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint> BeEquivalentTo(System.Data.DataSet expectation, System.Func, FluentAssertions.Data.IDataEquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint, System.Data.DataTable> HaveTable(string expectedTableName, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint> HaveTableCount(int expected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint> HaveTables(params string[] expectedTableNames) { } - public FluentAssertions.AndConstraint> HaveTables(System.Collections.Generic.IEnumerable expectedTableNames, string because = "", params object[] becauseArgs) { } + public GlobalFormattingOptions() { } + public string ValueFormatterAssembly { get; set; } + public FluentAssertions.Common.ValueFormatterDetectionMode ValueFormatterDetectionMode { get; set; } } - public class DataTableAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions> - where TDataTable : System.Data.DataTable + public enum TestFramework { - public DataTableAssertions(TDataTable dataTable) { } - protected override string Identifier { get; } - public FluentAssertions.AndConstraint> BeEquivalentTo(System.Data.DataTable expectation, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint> BeEquivalentTo(System.Data.DataTable expectation, System.Func, FluentAssertions.Data.IDataEquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint, System.Data.DataColumn> HaveColumn(string expectedColumnName, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint> HaveColumns(params string[] expectedColumnNames) { } - public FluentAssertions.AndConstraint> HaveColumns(System.Collections.Generic.IEnumerable expectedColumnNames, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint> HaveRowCount(int expected, string because = "", params object[] becauseArgs) { } - } - public interface IDataEquivalencyAssertionOptions : FluentAssertions.Equivalency.IEquivalencyAssertionOptions - { - FluentAssertions.Data.IDataEquivalencyAssertionOptions AllowingMismatchedTypes(); - FluentAssertions.Data.IDataEquivalencyAssertionOptions Excluding(System.Linq.Expressions.Expression> predicate); - FluentAssertions.Data.IDataEquivalencyAssertionOptions Excluding(System.Linq.Expressions.Expression> expression); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingColumn(System.Data.DataColumn column); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingColumn(string tableName, string columnName); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingColumnInAllTables(string columnName); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingColumns(System.Collections.Generic.IEnumerable columns); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingColumns(params System.Data.DataColumn[] columns); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingColumns(string tableName, System.Collections.Generic.IEnumerable columnNames); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingColumns(string tableName, params string[] columnNames); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingColumnsInAllTables(System.Collections.Generic.IEnumerable columnNames); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingColumnsInAllTables(params string[] columnNames); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingOriginalData(); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingRelated(System.Linq.Expressions.Expression> expression); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingRelated(System.Linq.Expressions.Expression> expression); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingRelated(System.Linq.Expressions.Expression> expression); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingRelated(System.Linq.Expressions.Expression> expression); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingRelated(System.Linq.Expressions.Expression> expression); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingRelated(System.Linq.Expressions.Expression> expression); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingRelated(System.Linq.Expressions.Expression> expression); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingTable(string tableName); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingTables(System.Collections.Generic.IEnumerable tableNames); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingTables(params string[] tableNames); - FluentAssertions.Data.IDataEquivalencyAssertionOptions IgnoringUnmatchedColumns(); - FluentAssertions.Data.IDataEquivalencyAssertionOptions UsingRowMatchMode(FluentAssertions.Data.RowMatchMode rowMatchMode); - } - public enum RowMatchMode - { - Index = 0, - PrimaryKey = 1, + XUnit2 = 0, + XUnit3 = 1, + TUnit = 2, + MsTest = 3, + NUnit = 4, + MSpec = 5, } } namespace FluentAssertions.Equivalency @@ -755,7 +634,7 @@ namespace FluentAssertions.Equivalency public object Expectation { get; set; } public System.Type RuntimeType { get; } public object Subject { get; set; } - public System.Type GetExpectedType(FluentAssertions.Equivalency.IEquivalencyAssertionOptions options) { } + public System.Type GetExpectedType(FluentAssertions.Equivalency.IEquivalencyOptions options) { } public override string ToString() { } } public class ConversionSelector @@ -785,41 +664,58 @@ namespace FluentAssertions.Equivalency ForceEquals = 2, ForceMembers = 3, } - public class EquivalencyAssertionOptions : FluentAssertions.Equivalency.SelfReferenceEquivalencyAssertionOptions + public class EquivalencyOptions : FluentAssertions.Equivalency.SelfReferenceEquivalencyOptions { - public EquivalencyAssertionOptions() { } + public EquivalencyOptions() { } } - public class EquivalencyAssertionOptions : FluentAssertions.Equivalency.SelfReferenceEquivalencyAssertionOptions> + public class EquivalencyOptions : FluentAssertions.Equivalency.SelfReferenceEquivalencyOptions> { - public EquivalencyAssertionOptions() { } - public EquivalencyAssertionOptions(FluentAssertions.Equivalency.IEquivalencyAssertionOptions defaults) { } - public FluentAssertions.Equivalency.EquivalencyAssertionOptions> AsCollection() { } - public FluentAssertions.Equivalency.EquivalencyAssertionOptions Excluding(System.Linq.Expressions.Expression> expression) { } + public EquivalencyOptions() { } + public EquivalencyOptions(FluentAssertions.Equivalency.IEquivalencyOptions defaults) { } + public FluentAssertions.Equivalency.EquivalencyOptions> AsCollection() { } + public FluentAssertions.Equivalency.EquivalencyOptions Excluding(System.Linq.Expressions.Expression> expression) { } public FluentAssertions.Equivalency.NestedExclusionOptionBuilder For(System.Linq.Expressions.Expression>> expression) { } - public FluentAssertions.Equivalency.EquivalencyAssertionOptions Including(System.Linq.Expressions.Expression> expression) { } - public FluentAssertions.Equivalency.EquivalencyAssertionOptions WithMapping(string expectationMemberPath, string subjectMemberPath) { } - public FluentAssertions.Equivalency.EquivalencyAssertionOptions WithMapping(System.Linq.Expressions.Expression> expectationMemberPath, System.Linq.Expressions.Expression> subjectMemberPath) { } - public FluentAssertions.Equivalency.EquivalencyAssertionOptions WithMapping(System.Linq.Expressions.Expression> expectationMember, System.Linq.Expressions.Expression> subjectMember) { } - public FluentAssertions.Equivalency.EquivalencyAssertionOptions WithMapping(string expectationMemberName, string subjectMemberName) { } - public FluentAssertions.Equivalency.EquivalencyAssertionOptions WithStrictOrderingFor(System.Linq.Expressions.Expression> expression) { } - public FluentAssertions.Equivalency.EquivalencyAssertionOptions WithoutStrictOrderingFor(System.Linq.Expressions.Expression> expression) { } + public FluentAssertions.Equivalency.EquivalencyOptions Including(System.Linq.Expressions.Expression> expression) { } + public FluentAssertions.Equivalency.EquivalencyOptions WithMapping(string expectationMemberPath, string subjectMemberPath) { } + public FluentAssertions.Equivalency.EquivalencyOptions WithMapping(System.Linq.Expressions.Expression> expectationMemberPath, System.Linq.Expressions.Expression> subjectMemberPath) { } + public FluentAssertions.Equivalency.EquivalencyOptions WithMapping(System.Linq.Expressions.Expression> expectationMember, System.Linq.Expressions.Expression> subjectMember) { } + public FluentAssertions.Equivalency.EquivalencyOptions WithMapping(string expectationMemberName, string subjectMemberName) { } + public FluentAssertions.Equivalency.EquivalencyOptions WithStrictOrderingFor(System.Linq.Expressions.Expression> expression) { } + public FluentAssertions.Equivalency.EquivalencyOptions WithoutStrictOrderingFor(System.Linq.Expressions.Expression> expression) { } + } + public class EquivalencyPlan : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable + { + public EquivalencyPlan() { } + public void Add() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } + public void AddAfter() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } + public void Clear() { } + public System.Collections.Generic.IEnumerator GetEnumerator() { } + public void Insert() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } + public void InsertBefore() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } + public void Remove() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep { } + public void Reset() { } } public enum EquivalencyResult { ContinueWithNext = 0, - AssertionCompleted = 1, + EquivalencyProven = 1, } public abstract class EquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { protected EquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } - protected abstract FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator); + 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 nestedValidator); } public class EquivalencyValidationContext : FluentAssertions.Equivalency.IEquivalencyValidationContext { - public EquivalencyValidationContext(FluentAssertions.Equivalency.INode root, FluentAssertions.Equivalency.IEquivalencyAssertionOptions options) { } + public EquivalencyValidationContext(FluentAssertions.Equivalency.INode root, FluentAssertions.Equivalency.IEquivalencyOptions options) { } public FluentAssertions.Equivalency.INode CurrentNode { get; } - public FluentAssertions.Equivalency.IEquivalencyAssertionOptions Options { get; } + public FluentAssertions.Equivalency.IEquivalencyOptions Options { get; } public FluentAssertions.Execution.Reason Reason { get; set; } public FluentAssertions.Equivalency.Tracing.ITraceWriter TraceWriter { get; set; } public FluentAssertions.Equivalency.Tracing.Tracer Tracer { get; } @@ -830,24 +726,6 @@ namespace FluentAssertions.Equivalency public bool IsCyclicReference(object expectation) { } public override string ToString() { } } - public class EquivalencyValidator : FluentAssertions.Equivalency.IEquivalencyValidator - { - public EquivalencyValidator() { } - public void AssertEquality(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.EquivalencyValidationContext context) { } - public void RecursivelyAssertEquality(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context) { } - } - public class Field : FluentAssertions.Equivalency.Node, FluentAssertions.Equivalency.IMember, FluentAssertions.Equivalency.INode - { - public Field(System.Reflection.FieldInfo fieldInfo, FluentAssertions.Equivalency.INode parent) { } - public Field(System.Type reflectedType, System.Reflection.FieldInfo fieldInfo, FluentAssertions.Equivalency.INode parent) { } - public System.Type DeclaringType { get; set; } - public override string Description { get; } - public FluentAssertions.Common.CSharpAccessModifier GetterAccessibility { get; } - public bool IsBrowsable { get; } - public System.Type ReflectedType { get; } - public FluentAssertions.Common.CSharpAccessModifier SetterAccessibility { get; } - public object GetValue(object obj) { } - } public delegate string GetSubjectId(); public interface IAssertionContext { @@ -857,7 +735,7 @@ namespace FluentAssertions.Equivalency FluentAssertions.Equivalency.INode SelectedNode { get; } TSubject Subject { get; } } - public interface IEquivalencyAssertionOptions + public interface IEquivalencyOptions { bool AllowInfiniteRecursion { get; } bool? CompareRecordsByValue { get; } @@ -865,7 +743,12 @@ namespace FluentAssertions.Equivalency FluentAssertions.Equivalency.CyclicReferenceHandling CyclicReferenceHandling { get; } FluentAssertions.Equivalency.EnumEquivalencyHandling EnumEquivalencyHandling { get; } bool ExcludeNonBrowsableOnExpectation { get; } + bool IgnoreCase { get; } + bool IgnoreJsonPropertyCasing { get; } + bool IgnoreLeadingWhitespace { get; } + bool IgnoreNewlineStyle { get; } bool IgnoreNonBrowsableOnSubject { get; } + bool IgnoreTrailingWhitespace { get; } FluentAssertions.Equivalency.MemberVisibility IncludedFields { get; } FluentAssertions.Equivalency.MemberVisibility IncludedProperties { get; } bool IsRecursive { get; } @@ -879,12 +762,12 @@ namespace FluentAssertions.Equivalency } public interface IEquivalencyStep { - FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator); + FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes); } public interface IEquivalencyValidationContext { FluentAssertions.Equivalency.INode CurrentNode { get; } - FluentAssertions.Equivalency.IEquivalencyAssertionOptions Options { get; } + FluentAssertions.Equivalency.IEquivalencyOptions Options { get; } FluentAssertions.Execution.Reason Reason { get; } FluentAssertions.Equivalency.Tracing.Tracer Tracer { get; } FluentAssertions.Equivalency.IEquivalencyValidationContext AsCollectionItem(string index); @@ -893,10 +776,6 @@ namespace FluentAssertions.Equivalency FluentAssertions.Equivalency.IEquivalencyValidationContext Clone(); bool IsCyclicReference(object expectation); } - public interface IEquivalencyValidator - { - void RecursivelyAssertEquality(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context); - } public interface IMember : FluentAssertions.Equivalency.INode { System.Type DeclaringType { get; } @@ -917,7 +796,7 @@ namespace FluentAssertions.Equivalency } public interface IMemberMatchingRule { - FluentAssertions.Equivalency.IMember Match(FluentAssertions.Equivalency.IMember expectedMember, object subject, FluentAssertions.Equivalency.INode parent, FluentAssertions.Equivalency.IEquivalencyAssertionOptions 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 { @@ -927,15 +806,14 @@ namespace FluentAssertions.Equivalency public interface INode { int Depth { get; } - string Description { get; } + FluentAssertions.Equivalency.Pathway Expectation { get; } FluentAssertions.Equivalency.GetSubjectId GetSubjectId { get; } bool IsRoot { get; } - string Name { get; set; } System.Type ParentType { get; } - string Path { get; } - string PathAndName { get; } bool RootIsCollection { get; } + FluentAssertions.Equivalency.Pathway Subject { get; } System.Type Type { get; } + void AdjustForRemappedSubject(FluentAssertions.Equivalency.IMember subjectMember); } public interface IObjectInfo { @@ -950,13 +828,17 @@ namespace FluentAssertions.Equivalency { FluentAssertions.Equivalency.OrderStrictness Evaluate(FluentAssertions.Equivalency.IObjectInfo objectInfo); } + public interface IValidateChildNodeEquivalency + { + void AssertEquivalencyOf(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context); + } public static class MemberFactory { public static FluentAssertions.Equivalency.IMember Create(System.Reflection.MemberInfo memberInfo, FluentAssertions.Equivalency.INode parent) { } } public class MemberSelectionContext { - public MemberSelectionContext(System.Type compileTimeType, System.Type runtimeType, FluentAssertions.Equivalency.IEquivalencyAssertionOptions options) { } + public MemberSelectionContext(System.Type compileTimeType, System.Type runtimeType, FluentAssertions.Equivalency.IEquivalencyOptions options) { } public FluentAssertions.Equivalency.MemberVisibility IncludedFields { get; } public FluentAssertions.Equivalency.MemberVisibility IncludedProperties { get; } public System.Type Type { get; } @@ -972,29 +854,9 @@ namespace FluentAssertions.Equivalency } public class NestedExclusionOptionBuilder { - public FluentAssertions.Equivalency.EquivalencyAssertionOptions Exclude(System.Linq.Expressions.Expression> expression) { } + public FluentAssertions.Equivalency.EquivalencyOptions Exclude(System.Linq.Expressions.Expression> expression) { } public FluentAssertions.Equivalency.NestedExclusionOptionBuilder For(System.Linq.Expressions.Expression>> expression) { } } - public class Node : FluentAssertions.Equivalency.INode - { - public Node() { } - public int Depth { get; } - public virtual string Description { get; } - public FluentAssertions.Equivalency.GetSubjectId GetSubjectId { get; set; } - public bool IsRoot { get; } - public string Name { get; set; } - public System.Type ParentType { get; set; } - public string Path { get; set; } - public string PathAndName { get; } - public bool RootIsCollection { get; set; } - public System.Type Type { get; set; } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - public override string ToString() { } - public static FluentAssertions.Equivalency.INode From(FluentAssertions.Equivalency.GetSubjectId getSubjectId) { } - public static FluentAssertions.Equivalency.INode FromCollectionItem(string index, FluentAssertions.Equivalency.INode parent) { } - public static FluentAssertions.Equivalency.INode FromDictionaryItem(object key, FluentAssertions.Equivalency.INode parent) { } - } public enum OrderStrictness { Strict = 0, @@ -1009,24 +871,28 @@ namespace FluentAssertions.Equivalency public System.Collections.Generic.IEnumerator GetEnumerator() { } public bool IsOrderingStrictFor(FluentAssertions.Equivalency.IObjectInfo objectInfo) { } } - public class Property : FluentAssertions.Equivalency.Node, FluentAssertions.Equivalency.IMember, FluentAssertions.Equivalency.INode + public class Pathway : System.IEquatable { - public Property(System.Reflection.PropertyInfo propertyInfo, FluentAssertions.Equivalency.INode parent) { } - public Property(System.Type reflectedType, System.Reflection.PropertyInfo propertyInfo, FluentAssertions.Equivalency.INode parent) { } - public System.Type DeclaringType { get; } - public override string Description { get; } - public FluentAssertions.Common.CSharpAccessModifier GetterAccessibility { get; } - public bool IsBrowsable { get; } - public System.Type ReflectedType { get; } - public FluentAssertions.Common.CSharpAccessModifier SetterAccessibility { get; } - public object GetValue(object obj) { } + public Pathway(FluentAssertions.Equivalency.Pathway parent, string name, FluentAssertions.Equivalency.Pathway.GetDescription getDescription) { } + public Pathway(string path, string name, FluentAssertions.Equivalency.Pathway.GetDescription getDescription) { } + public string Description { get; } + public string Name { get; } + public string Path { get; } + public string PathAndName { get; } + public override string ToString() { } + public delegate string GetDescription(string pathAndName); } - public abstract class SelfReferenceEquivalencyAssertionOptions : FluentAssertions.Equivalency.IEquivalencyAssertionOptions - where TSelf : FluentAssertions.Equivalency.SelfReferenceEquivalencyAssertionOptions + public abstract class SelfReferenceEquivalencyOptions : FluentAssertions.Equivalency.IEquivalencyOptions + where TSelf : FluentAssertions.Equivalency.SelfReferenceEquivalencyOptions { - protected SelfReferenceEquivalencyAssertionOptions(FluentAssertions.Equivalency.IEquivalencyAssertionOptions defaults) { } + protected SelfReferenceEquivalencyOptions(FluentAssertions.Equivalency.IEquivalencyOptions defaults) { } public bool? CompareRecordsByValue { get; } public FluentAssertions.Equivalency.ConversionSelector ConversionSelector { get; } + public bool IgnoreCase { get; } + public bool IgnoreJsonPropertyCasing { get; set; } + public bool IgnoreLeadingWhitespace { get; } + public bool IgnoreNewlineStyle { get; } + public bool IgnoreTrailingWhitespace { get; } [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] protected FluentAssertions.Equivalency.OrderingRuleCollection OrderingRules { get; } public FluentAssertions.Equivalency.Tracing.ITraceWriter TraceWriter { get; } @@ -1042,13 +908,18 @@ namespace FluentAssertions.Equivalency public TSelf ComparingRecordsByMembers() { } public TSelf ComparingRecordsByValue() { } public TSelf Excluding(System.Linq.Expressions.Expression> predicate) { } + public TSelf ExcludingExplicitlyImplementedProperties() { } public TSelf ExcludingFields() { } + public TSelf ExcludingMembersNamed(params string[] memberNames) { } public TSelf ExcludingMissingMembers() { } - public TSelf ExcludingNestedObjects() { } public TSelf ExcludingNonBrowsableMembers() { } public TSelf ExcludingProperties() { } + public TSelf IgnoringCase() { } public TSelf IgnoringCyclicReferences() { } + public TSelf IgnoringLeadingWhitespace() { } + public TSelf IgnoringNewlineStyle() { } public TSelf IgnoringNonBrowsableMembersOnSubject() { } + public TSelf IgnoringTrailingWhitespace() { } public TSelf Including(System.Linq.Expressions.Expression> predicate) { } public TSelf IncludingAllDeclaredProperties() { } public TSelf IncludingAllRuntimeProperties() { } @@ -1057,15 +928,16 @@ namespace FluentAssertions.Equivalency public TSelf IncludingInternalProperties() { } public TSelf IncludingNestedObjects() { } public TSelf IncludingProperties() { } - public TSelf RespectingDeclaredTypes() { } - public TSelf RespectingRuntimeTypes() { } + public TSelf PreferringDeclaredMemberTypes() { } + public TSelf PreferringRuntimeMemberTypes() { } public TSelf ThrowingOnMissingMembers() { } public override string ToString() { } public TSelf Using(FluentAssertions.Equivalency.IEquivalencyStep equivalencyStep) { } public TSelf Using(FluentAssertions.Equivalency.IMemberMatchingRule matchingRule) { } public TSelf Using(FluentAssertions.Equivalency.IMemberSelectionRule selectionRule) { } public TSelf Using(FluentAssertions.Equivalency.IOrderingRule orderingRule) { } - public FluentAssertions.Equivalency.SelfReferenceEquivalencyAssertionOptions.Restriction Using(System.Action> action) { } + public TSelf Using(System.Collections.Generic.IEqualityComparer comparer) { } + public FluentAssertions.Equivalency.SelfReferenceEquivalencyOptions.Restriction Using(System.Action> action) { } public TSelf Using(System.Collections.Generic.IEqualityComparer comparer) { } public TSelf Using() where TEqualityComparer : System.Collections.Generic.IEqualityComparer, new () { } @@ -1073,12 +945,16 @@ namespace FluentAssertions.Equivalency public TSelf WithAutoConversionFor(System.Linq.Expressions.Expression> predicate) { } public TSelf WithStrictOrdering() { } public TSelf WithStrictOrderingFor(System.Linq.Expressions.Expression> predicate) { } + public TSelf WithStrictTyping() { } + public TSelf WithStrictTypingFor(System.Linq.Expressions.Expression> predicate) { } public TSelf WithTracing(FluentAssertions.Equivalency.Tracing.ITraceWriter writer = null) { } public TSelf WithoutAutoConversionFor(System.Linq.Expressions.Expression> predicate) { } - public void WithoutMatchingRules() { } - public void WithoutSelectionRules() { } + public TSelf WithoutMatchingRules() { } + public TSelf WithoutRecursing() { } + public TSelf WithoutSelectionRules() { } public TSelf WithoutStrictOrdering() { } public TSelf WithoutStrictOrderingFor(System.Linq.Expressions.Expression> predicate) { } + public TSelf WithoutStrictTyping() { } public class Restriction { public Restriction(TSelf options, System.Action> action) { } @@ -1095,135 +971,117 @@ namespace FluentAssertions.Equivalency public static bool WhichSetterHas(this FluentAssertions.Equivalency.IMemberInfo memberInfo, FluentAssertions.Common.CSharpAccessModifier accessModifier) { } } } +namespace FluentAssertions.Equivalency.Inlining +{ + public interface IInlineEquivalencyAssertion + { + void Execute(FluentAssertions.Execution.AssertionChain assertionChain, FluentAssertions.Equivalency.Comparands comparands); + } + public class InlineEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep + { + public InlineEquivalencyStep() { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } + } +} namespace FluentAssertions.Equivalency.Steps { public class AssertionRuleEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { - public AssertionRuleEquivalencyStep(System.Linq.Expressions.Expression> predicate, System.Action> assertion) { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + 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() { } } public class AutoConversionStep : FluentAssertions.Equivalency.IEquivalencyStep { public AutoConversionStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } public override string ToString() { } } - public class ConstraintCollectionEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep - { - public ConstraintCollectionEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } - } - public class ConstraintEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep - { - public ConstraintEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } - } - public class DataColumnEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep - { - public DataColumnEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } - } - public class DataRelationEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep - { - public DataRelationEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } - } - public class DataRowCollectionEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep - { - public DataRowCollectionEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } - } - public class DataRowEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep + public class DateAndTimeEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { - public DataRowEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } - } - public class DataSetEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep - { - public DataSetEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } - } - public class DataTableEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep - { - public DataTableEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public DateAndTimeEquivalencyStep() { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class DictionaryEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep { public DictionaryEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nestedValidator) { } } public class EnumEqualityStep : FluentAssertions.Equivalency.IEquivalencyStep { public EnumEqualityStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class EnumerableEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public EnumerableEquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class EqualityComparerEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public EqualityComparerEquivalencyStep(System.Collections.Generic.IEqualityComparer comparer) { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } public override string ToString() { } } public class GenericDictionaryEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public GenericDictionaryEquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class GenericEnumerableEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public GenericEnumerableEquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class ReferenceEqualityEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public ReferenceEqualityEquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class RunAllUserStepsEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public RunAllUserStepsEquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class SimpleEqualityEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public SimpleEqualityEquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class StringEqualityEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public StringEqualityEquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class StructuralEqualityEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public StructuralEqualityEquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } + } + public class TypeEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep + { + public TypeEquivalencyStep() { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class ValueTypeEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public ValueTypeEquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class XAttributeEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep { public XAttributeEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + 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.IEquivalencyValidator nestedValidator) { } + 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.IEquivalencyValidator nestedValidator) { } + protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nestedValidator) { } } } namespace FluentAssertions.Equivalency.Tracing @@ -1251,75 +1109,60 @@ namespace FluentAssertions.Equivalency.Tracing } namespace FluentAssertions.Execution { - [System.Serializable] + 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 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() { } + } 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.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 { @@ -1329,28 +1172,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.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; } @@ -1362,6 +1190,12 @@ namespace FluentAssertions.Execution { object Clone(); } + public interface ITestFramework + { + bool IsAvailable { get; } + [System.Diagnostics.CodeAnalysis.DoesNotReturn] + void Throw(string message); + } public class Reason { public Reason(string formattedMessage, object[] arguments) { } @@ -1369,6 +1203,14 @@ namespace FluentAssertions.Execution public string FormattedMessage { get; set; } } } +namespace FluentAssertions.Extensibility +{ + [System.AttributeUsage(System.AttributeTargets.Assembly, AllowMultiple=true)] + public sealed class AssertionEngineInitializerAttribute : System.Attribute + { + public AssertionEngineInitializerAttribute(System.Type type, string methodName) { } + } +} namespace FluentAssertions.Extensions { public static class FluentDateTimeExtensions @@ -1474,7 +1316,6 @@ namespace FluentAssertions.Formatting public class DefaultValueFormatter : FluentAssertions.Formatting.IValueFormatter { public DefaultValueFormatter() { } - protected virtual int SpacesPerIndentionLevel { get; } public virtual bool CanHandle(object value) { } public void Format(object value, FluentAssertions.Formatting.FormattedObjectGraph formattedGraph, FluentAssertions.Formatting.FormattingContext context, FluentAssertions.Formatting.FormatChild formatChild) { } protected virtual System.Reflection.MemberInfo[] GetMembers(System.Type type) { } @@ -1526,7 +1367,8 @@ namespace FluentAssertions.Formatting public static int SpacesPerIndentation { get; } public void AddFragment(string fragment) { } public void AddFragmentOnNewLine(string fragment) { } - public void AddLine(string line) { } + public void AddLine(string content) { } + public void AddLineOrFragment(string fragment) { } public override string ToString() { } public System.IDisposable WithIndentation() { } } @@ -1548,6 +1390,8 @@ namespace FluentAssertions.Formatting public int MaxDepth { get; set; } public int MaxLines { get; set; } public bool UseLineBreaks { get; set; } + public void AddFormatter(FluentAssertions.Formatting.IValueFormatter formatter) { } + public void RemoveFormatter(FluentAssertions.Formatting.IValueFormatter formatter) { } } public class GuidValueFormatter : FluentAssertions.Formatting.IValueFormatter { @@ -1581,8 +1425,12 @@ namespace FluentAssertions.Formatting public class MaxLinesExceededException : System.Exception { public MaxLinesExceededException() { } - 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 { @@ -1690,21 +1538,19 @@ 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) { } - public FluentAssertions.AndConstraint BeEquivalentTo(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeGreaterOrEqualTo(T expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeEquivalentTo(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeGreaterThan(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeGreaterThanOrEqualTo(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeInRange(T minimumValue, T maximumValue, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeLessOrEqualTo(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeLessThan(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeLessThanOrEqualTo(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOneOf(params T[] validValues) { } @@ -1717,37 +1563,32 @@ 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 + public class NullableNumericAssertions : FluentAssertions.Numeric.NumericAssertionsBase where T : struct, System.IComparable where TAssertions : FluentAssertions.Numeric.NullableNumericAssertions { - public NullableNumericAssertions(T? value) { } + public NullableNumericAssertions(T? value, FluentAssertions.Execution.AssertionChain assertionChain) { } + public override T? Subject { get; } 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) { } public FluentAssertions.AndConstraint NotBeNull(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveValue(string because = "", params object[] becauseArgs) { } } - public class NumericAssertions : FluentAssertions.Numeric.NumericAssertions> - where T : struct, System.IComparable - { - public NumericAssertions(T value) { } - } - public class NumericAssertions + public abstract class NumericAssertionsBase where T : struct, System.IComparable - where TAssertions : FluentAssertions.Numeric.NumericAssertions + where TAssertions : FluentAssertions.Numeric.NumericAssertionsBase { - public NumericAssertions(T value) { } - public T? Subject { get; } + protected NumericAssertionsBase(FluentAssertions.Execution.AssertionChain assertionChain) { } + public FluentAssertions.Execution.AssertionChain CurrentAssertionChain { get; } + public abstract TSubject Subject { get; } public FluentAssertions.AndConstraint Be(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Be(T? expected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeGreaterOrEqualTo(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeGreaterThan(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeGreaterThanOrEqualTo(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeInRange(T minimumValue, T maximumValue, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeLessOrEqualTo(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeLessThan(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeLessThanOrEqualTo(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeNegative(string because = "", params object[] becauseArgs) { } @@ -1762,17 +1603,29 @@ namespace FluentAssertions.Numeric public FluentAssertions.AndConstraint NotBeInRange(T minimumValue, T maximumValue, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeOfType(System.Type unexpectedType, string because = "", params object[] becauseArgs) { } } + public class NumericAssertions : FluentAssertions.Numeric.NumericAssertions> + where T : struct, System.IComparable + { + public NumericAssertions(T value, FluentAssertions.Execution.AssertionChain assertionChain) { } + } + public class NumericAssertions : FluentAssertions.Numeric.NumericAssertionsBase + where T : struct, System.IComparable + where TAssertions : FluentAssertions.Numeric.NumericAssertions + { + public NumericAssertions(T value, FluentAssertions.Execution.AssertionChain assertionChain) { } + public override T Subject { get; } + } } 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) { } @@ -1783,12 +1636,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) { } @@ -1803,7 +1656,7 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint BeOnOrAfter(System.DateTime expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOnOrBefore(System.DateTime expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOneOf(params System.DateTime[] validValues) { } - public FluentAssertions.AndConstraint BeOneOf(params System.Nullable[] validValues) { } + public FluentAssertions.AndConstraint BeOneOf(params System.DateTime?[] validValues) { } public FluentAssertions.AndConstraint BeOneOf(System.Collections.Generic.IEnumerable validValues, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOneOf(System.Collections.Generic.IEnumerable validValues, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeSameDateAs(System.DateTime expected, string because = "", params object[] becauseArgs) { } @@ -1820,6 +1673,7 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint NotBeAfter(System.DateTime unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeBefore(System.DateTime unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeCloseTo(System.DateTime distantTime, System.TimeSpan precision, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotBeIn(System.DateTimeKind unexpectedKind, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeOnOrAfter(System.DateTime unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeOnOrBefore(System.DateTime unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeSameDateAs(System.DateTime unexpected, string because = "", params object[] becauseArgs) { } @@ -1832,12 +1686,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) { } @@ -1853,7 +1707,7 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint BeOnOrAfter(System.DateTimeOffset expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOnOrBefore(System.DateTimeOffset expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOneOf(params System.DateTimeOffset[] validValues) { } - public FluentAssertions.AndConstraint BeOneOf(params System.Nullable[] validValues) { } + public FluentAssertions.AndConstraint BeOneOf(params System.DateTimeOffset?[] validValues) { } public FluentAssertions.AndConstraint BeOneOf(System.Collections.Generic.IEnumerable validValues, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOneOf(System.Collections.Generic.IEnumerable validValues, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeSameDateAs(System.DateTimeOffset expected, string because = "", params object[] becauseArgs) { } @@ -1887,7 +1741,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) { } @@ -1895,7 +1749,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) { } @@ -1903,13 +1757,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) { } @@ -1936,12 +1790,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) { } @@ -1951,31 +1805,14 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint NotBe(string unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEmpty(string because = "", params object[] becauseArgs) { } } - public class HttpResponseMessageAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions - { - public HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { } - } - public class HttpResponseMessageAssertions : FluentAssertions.Primitives.ObjectAssertions - where TAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions - { - protected HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { } - protected override string Identifier { get; } - public FluentAssertions.AndConstraint BeRedirection(string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeSuccessful(string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint HaveClientError(string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint HaveError(string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint HaveServerError(string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint HaveStatusCode(System.Net.HttpStatusCode expected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint NotHaveStatusCode(System.Net.HttpStatusCode unexpected, string because = "", params object[] becauseArgs) { } - } 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) { } @@ -1987,12 +1824,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) { } @@ -2000,12 +1837,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) { } @@ -2014,13 +1851,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) { } @@ -2028,12 +1865,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) { } @@ -2042,12 +1879,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) { } @@ -2056,7 +1893,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) { } @@ -2064,12 +1901,12 @@ 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) { } public FluentAssertions.AndConstraint BeEquivalentTo(TExpectation expectation, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeEquivalentTo(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeEquivalentTo(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOneOf(params TSubject[] validValues) { } public FluentAssertions.AndConstraint BeOneOf(System.Collections.Generic.IEnumerable validValues, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOneOf(System.Collections.Generic.IEnumerable validValues, System.Collections.Generic.IEqualityComparer comparer, string because = "", params object[] becauseArgs) { } @@ -2077,12 +1914,13 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint NotBe(TSubject unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBe(TSubject unexpected, System.Collections.Generic.IEqualityComparer comparer, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEquivalentTo(TExpectation unexpected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint NotBeEquivalentTo(TExpectation unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotBeEquivalentTo(TExpectation unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } } 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) { } @@ -2101,22 +1939,22 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint NotBeOfType(System.Type unexpectedType, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeOfType(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeSameAs(TSubject unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint Satisfy(System.Action assertion) + where T : TSubject { } } 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) { } - public FluentAssertions.AndConstraint BeGreaterOrEqualTo(System.TimeSpan expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeGreaterThan(System.TimeSpan expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeGreaterThanOrEqualTo(System.TimeSpan expected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeLessOrEqualTo(System.TimeSpan expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeLessThan(System.TimeSpan expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeLessThanOrEqualTo(System.TimeSpan expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeNegative(string because = "", params object[] becauseArgs) { } @@ -2127,16 +1965,17 @@ 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) { } public FluentAssertions.AndConstraint BeEquivalentTo(string expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeEquivalentTo(string expected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeLowerCased(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeNullOrEmpty(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeNullOrWhiteSpace(string because = "", params object[] becauseArgs) { } @@ -2151,18 +1990,23 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint ContainAny(System.Collections.Generic.IEnumerable values, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint ContainEquivalentOf(string expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint ContainEquivalentOf(string expected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint ContainEquivalentOf(string expected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint ContainEquivalentOf(string expected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint EndWith(string expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint EndWithEquivalentOf(string expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint EndWithEquivalentOf(string expected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveLength(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Match(string wildcardPattern, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint MatchEquivalentOf(string wildcardPattern, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint MatchRegex(string regularExpression, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint MatchEquivalentOf(string wildcardPattern, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint MatchRegex(System.Text.RegularExpressions.Regex regularExpression, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint MatchRegex(string regularExpression, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint MatchRegex(string regularExpression, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint MatchRegex(System.Text.RegularExpressions.Regex regularExpression, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint MatchRegex(string regularExpression, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBe(string unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEmpty(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEquivalentTo(string unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotBeEquivalentTo(string unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeLowerCased(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeNullOrEmpty(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeNullOrWhiteSpace(string because = "", params object[] becauseArgs) { } @@ -2173,16 +2017,21 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint NotContainAny(params string[] values) { } public FluentAssertions.AndConstraint NotContainAny(System.Collections.Generic.IEnumerable values, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotContainEquivalentOf(string unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotContainEquivalentOf(string unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotEndWith(string unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotEndWithEquivalentOf(string unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotEndWithEquivalentOf(string unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotMatch(string wildcardPattern, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotMatchEquivalentOf(string wildcardPattern, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint NotMatchRegex(string regularExpression, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotMatchEquivalentOf(string wildcardPattern, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotMatchRegex(System.Text.RegularExpressions.Regex regularExpression, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotMatchRegex(string regularExpression, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotStartWith(string unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotStartWithEquivalentOf(string unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotStartWithEquivalentOf(string unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint StartWith(string expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint StartWithEquivalentOf(string expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint StartWithEquivalentOf(string expected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } } public enum TimeSpanCondition { @@ -2193,49 +2042,32 @@ namespace FluentAssertions.Primitives LessThan = 4, } } -namespace FluentAssertions.Reflection -{ - public class AssemblyAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions - { - public AssemblyAssertions(System.Reflection.Assembly assembly) { } - 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) { } - public FluentAssertions.AndWhichConstraint DefineType(string @namespace, string name, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint NotReference(System.Reflection.Assembly assembly, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint Reference(System.Reflection.Assembly assembly, string because = "", params object[] becauseArgs) { } - } -} 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) { } + public FluentAssertions.AndConstraint NotThrowAfter(System.TimeSpan waitTime, System.TimeSpan pollInterval, string because = "", params object[] becauseArgs) { } } public class AsyncFunctionAssertions : FluentAssertions.Specialized.DelegateAssertionsBase, TAssertions> where TTask : System.Threading.Tasks.Task where TAssertions : FluentAssertions.Specialized.AsyncFunctionAssertions { - [System.Obsolete("This class is intended as base class. This ctor is accidentally public and will b" + - "e removed in Version 7.")] - public AsyncFunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor) { } - [System.Obsolete("This class is intended as base class. This ctor is accidentally public and will b" + - "e made protected in Version 7.")] - public 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> CompleteWithinAsync(System.TimeSpan timeSpan, string because = "", params object[] becauseArgs) { } public System.Threading.Tasks.Task> NotCompleteWithinAsync(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 System.Threading.Tasks.Task> NotThrowAsync(string because = "", params object[] becauseArgs) where TException : System.Exception { } + public System.Threading.Tasks.Task> ThrowAsync(string because = "", params object[] becauseArgs) { } public System.Threading.Tasks.Task> ThrowAsync(string because = "", params object[] becauseArgs) where TException : System.Exception { } public System.Threading.Tasks.Task> ThrowExactlyAsync(string because = "", params object[] becauseArgs) where TException : System.Exception { } + public System.Threading.Tasks.Task> ThrowWithinAsync(System.TimeSpan timeSpan, string because = "", params object[] becauseArgs) { } public System.Threading.Tasks.Task> ThrowWithinAsync(System.TimeSpan timeSpan, string because = "", params object[] becauseArgs) where TException : System.Exception { } } @@ -2253,12 +2085,11 @@ 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) { } public FluentAssertions.AndConstraint NotThrow(string because = "", params object[] becauseArgs) where TException : System.Exception { } - public FluentAssertions.AndConstraint NotThrowAfter(System.TimeSpan waitTime, System.TimeSpan pollInterval, string because = "", params object[] becauseArgs) { } + public FluentAssertions.Specialized.ExceptionAssertions Throw(string because = "", params object[] becauseArgs) { } public FluentAssertions.Specialized.ExceptionAssertions Throw(string because = "", params object[] becauseArgs) where TException : System.Exception { } public FluentAssertions.Specialized.ExceptionAssertions ThrowExactly(string because = "", params object[] becauseArgs) @@ -2267,7 +2098,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; } @@ -2289,20 +2120,18 @@ 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 BeGreaterOrEqualTo(System.TimeSpan minDuration, 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) { } - public FluentAssertions.AndConstraint BeLessOrEqualTo(System.TimeSpan maxDuration, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeLessThan(System.TimeSpan maxDuration, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeLessThanOrEqualTo(System.TimeSpan maxDuration, string because = "", params object[] becauseArgs) { } public override bool Equals(object obj) { } } 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) { } @@ -2310,8 +2139,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) { } @@ -2327,8 +2156,11 @@ 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 TaskCompletionSourceAssertionsBase { @@ -2337,8 +2169,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) { } } @@ -2347,23 +2179,23 @@ 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 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) { } @@ -2387,16 +2219,26 @@ namespace FluentAssertions.Types { public static FluentAssertions.Types.TypeSelector From(System.Reflection.Assembly assembly) { } } + public class AssemblyAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions + { + 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) { } + public FluentAssertions.AndWhichConstraint DefineType(string @namespace, string name, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotReference(System.Reflection.Assembly assembly, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint Reference(System.Reflection.Assembly assembly, string because = "", params object[] becauseArgs) { } + } 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; } } 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; } public FluentAssertions.AndWhichConstraint, TAttribute> BeDecoratedWith(string because = "", params object[] becauseArgs) where TAttribute : System.Attribute { } @@ -2411,13 +2253,13 @@ 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; } public FluentAssertions.AndConstraint BeAsync(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeVirtual(string because = "", params object[] becauseArgs) { } @@ -2461,7 +2303,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) { } @@ -2482,7 +2324,7 @@ 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; } public FluentAssertions.AndConstraint BeReadable(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeReadable(FluentAssertions.Common.CSharpAccessModifier accessModifier, string because = "", params object[] becauseArgs) { } @@ -2524,7 +2366,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) @@ -2539,13 +2381,13 @@ 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) { } public FluentAssertions.AndConstraint BeAbstract(string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeAssignableTo(System.Type type, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeAssignableTo(string because = "", params object[] becauseArgs) { } + public new FluentAssertions.AndConstraint BeAssignableTo(System.Type type, string because = "", params object[] becauseArgs) { } + public new FluentAssertions.AndConstraint BeAssignableTo(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint BeDecoratedWith(string because = "", params object[] becauseArgs) where TAttribute : System.Attribute { } public FluentAssertions.AndWhichConstraint BeDecoratedWith(System.Linq.Expressions.Expression> isMatchingAttributePredicate, string because = "", params object[] becauseArgs) @@ -2582,8 +2424,8 @@ namespace FluentAssertions.Types public FluentAssertions.AndConstraint NotBe(System.Type unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBe(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeAbstract(string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint NotBeAssignableTo(System.Type type, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint NotBeAssignableTo(string because = "", params object[] becauseArgs) { } + public new FluentAssertions.AndConstraint NotBeAssignableTo(System.Type type, string because = "", params object[] becauseArgs) { } + public new FluentAssertions.AndConstraint NotBeAssignableTo(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeDecoratedWith(string because = "", params object[] becauseArgs) where TAttribute : System.Attribute { } public FluentAssertions.AndConstraint NotBeDecoratedWith(System.Linq.Expressions.Expression> isMatchingAttributePredicate, string because = "", params object[] becauseArgs) @@ -2657,7 +2499,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 { } @@ -2688,7 +2530,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) { } @@ -2696,38 +2538,56 @@ 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) { } - public FluentAssertions.AndWhichConstraint HaveElement(string expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint HaveElement(System.Xml.Linq.XName expected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint> HaveElement(string expected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint HaveElement(string expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint> HaveElement(System.Xml.Linq.XName expected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint HaveRoot(string expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint> HaveElement(string expected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint HaveElementWithValue(System.Xml.Linq.XName expectedElement, string expectedValue, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint HaveElementWithValue(string expectedElement, string expectedValue, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint HaveRoot(System.Xml.Linq.XName expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint HaveRoot(string expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBe(System.Xml.Linq.XDocument unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEquivalentTo(System.Xml.Linq.XDocument unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveElement(System.Xml.Linq.XName unexpectedElement, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveElement(string unexpectedElement, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveElementWithValue(System.Xml.Linq.XName unexpectedElement, string unexpectedValue, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveElementWithValue(string unexpectedElement, string unexpectedValue, string because = "", params object[] becauseArgs) { } } 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) { } - public FluentAssertions.AndConstraint HaveAttribute(string expectedName, string expectedValue, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint HaveAttribute(System.Xml.Linq.XName expectedName, string expectedValue, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint HaveElement(string expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveAttribute(System.Xml.Linq.XName expectedName, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveAttribute(string expectedName, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveAttributeWithValue(System.Xml.Linq.XName expectedName, string expectedValue, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveAttributeWithValue(string expectedName, string expectedValue, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint HaveElement(System.Xml.Linq.XName expected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint> HaveElement(string expected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint HaveElement(string expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint> HaveElement(System.Xml.Linq.XName expected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint> HaveElement(string expected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint HaveElementWithValue(System.Xml.Linq.XName expectedElement, string expectedValue, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint HaveElementWithValue(string expectedElement, string expectedValue, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveValue(string expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBe(System.Xml.Linq.XElement unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEquivalentTo(System.Xml.Linq.XElement unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveAttribute(System.Xml.Linq.XName unexpectedName, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveAttribute(string unexpectedName, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveAttributeWithValue(System.Xml.Linq.XName unexpectedName, string unexpectedValue, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveAttributeWithValue(string unexpectedName, string unexpectedValue, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveElement(System.Xml.Linq.XName unexpectedElement, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveElement(string unexpectedElement, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveElementWithValue(System.Xml.Linq.XName unexpectedElement, string unexpectedValue, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveElementWithValue(string unexpectedElement, string unexpectedValue, string because = "", params object[] becauseArgs) { } } 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) { } @@ -2737,13 +2597,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 3df6aee5fb..f9e20dbb4d 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.1.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.1.verified.txt @@ -10,17 +10,30 @@ namespace FluentAssertions public System.Collections.Generic.IEnumerable OfType(System.Exception actualException) where T : System.Exception { } } - public class AndConstraint + public class AndConstraint { - public AndConstraint(T parentConstraint) { } - public T And { get; } + 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) { } + 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 AssertionConfiguration + { + public static FluentAssertions.Configuration.GlobalConfiguration Current { get; } + } + public static class AssertionEngine + { + public static FluentAssertions.Configuration.GlobalConfiguration Configuration { get; } + public static FluentAssertions.Execution.ITestFramework TestFramework { get; set; } + public static void ResetToDefaults() { } } public static class AssertionExtensions { @@ -37,7 +50,8 @@ namespace FluentAssertions public static FluentAssertions.Specialized.MemberExecutionTime ExecutionTimeOf(this T subject, System.Linq.Expressions.Expression> action, FluentAssertions.Common.StartTimer createTimer = null) { } public static System.Action Invoking(this T subject, System.Action action) { } public static System.Func Invoking(this T subject, System.Func action) { } - public static FluentAssertions.Events.IMonitor Monitor(this T eventSource, System.Func utcNow = null) { } + public static FluentAssertions.Events.IMonitor Monitor(this T eventSource) { } + public static FluentAssertions.Events.IMonitor Monitor(this T eventSource, System.Action configureOptions) { } public static FluentAssertions.Specialized.ExecutionTimeAssertions Should(this FluentAssertions.Specialized.ExecutionTime executionTime) { } [System.Obsolete("You are asserting the \'AndConstraint\' itself. Remove the \'Should()\' method direct" + "ly following \'And\'", true)] @@ -57,58 +71,53 @@ namespace FluentAssertions [System.Obsolete("You are asserting the \'AndConstraint\' itself. Remove the \'Should()\' method direct" + "ly following \'And\'", true)] public static void Should(this FluentAssertions.Types.TypeSelectorAssertions _) { } - public static FluentAssertions.Specialized.ActionAssertions Should(this System.Action action) { } - public static FluentAssertions.Collections.StringCollectionAssertions Should(this System.Collections.Generic.IEnumerable @this) { } - public static FluentAssertions.Data.DataColumnAssertions Should(this System.Data.DataColumn actualValue) { } - public static FluentAssertions.Collections.GenericCollectionAssertions Should(this System.Data.DataColumnCollection actualValue) { } - public static FluentAssertions.Collections.GenericCollectionAssertions Should(this System.Data.DataRowCollection actualValue) { } - public static FluentAssertions.Collections.GenericCollectionAssertions Should(this System.Data.DataTableCollection actualValue) { } + public static FluentAssertions.Specialized.ActionAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Action action) { } + public static FluentAssertions.Collections.StringCollectionAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Collections.Generic.IEnumerable @this) { } public static FluentAssertions.Primitives.DateTimeAssertions Should(this System.DateTime actualValue) { } - public static FluentAssertions.Primitives.NullableDateTimeAssertions Should(this System.DateTime? actualValue) { } + public static FluentAssertions.Primitives.NullableDateTimeAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.DateTime? actualValue) { } public static FluentAssertions.Primitives.DateTimeOffsetAssertions Should(this System.DateTimeOffset actualValue) { } - public static FluentAssertions.Primitives.NullableDateTimeOffsetAssertions Should(this System.DateTimeOffset? actualValue) { } - public static FluentAssertions.Specialized.NonGenericAsyncFunctionAssertions Should(this System.Func action) { } + public static FluentAssertions.Primitives.NullableDateTimeOffsetAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.DateTimeOffset? actualValue) { } + 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(this System.IO.BufferedStream actualValue) { } - public static FluentAssertions.Streams.StreamAssertions Should(this System.IO.Stream actualValue) { } - public static FluentAssertions.Primitives.HttpResponseMessageAssertions Should(this System.Net.Http.HttpResponseMessage actualValue) { } - public static FluentAssertions.Reflection.AssemblyAssertions Should(this System.Reflection.Assembly assembly) { } - public static FluentAssertions.Types.ConstructorInfoAssertions Should(this System.Reflection.ConstructorInfo constructorInfo) { } - public static FluentAssertions.Types.MethodInfoAssertions Should(this System.Reflection.MethodInfo methodInfo) { } - public static FluentAssertions.Types.PropertyInfoAssertions Should(this System.Reflection.PropertyInfo propertyInfo) { } + public static FluentAssertions.Primitives.NullableGuidAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] 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.Types.AssemblyAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Reflection.Assembly assembly) { } + public static FluentAssertions.Types.ConstructorInfoAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Reflection.ConstructorInfo constructorInfo) { } + public static FluentAssertions.Types.MethodInfoAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Reflection.MethodInfo methodInfo) { } + public static FluentAssertions.Types.PropertyInfoAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Reflection.PropertyInfo propertyInfo) { } public static FluentAssertions.Primitives.SimpleTimeSpanAssertions Should(this System.TimeSpan actualValue) { } - public static FluentAssertions.Primitives.NullableSimpleTimeSpanAssertions Should(this System.TimeSpan? actualValue) { } - public static FluentAssertions.Types.TypeAssertions Should(this System.Type subject) { } - public static FluentAssertions.Xml.XAttributeAssertions Should(this System.Xml.Linq.XAttribute actualValue) { } - public static FluentAssertions.Xml.XDocumentAssertions Should(this System.Xml.Linq.XDocument actualValue) { } - public static FluentAssertions.Xml.XElementAssertions Should(this System.Xml.Linq.XElement actualValue) { } + public static FluentAssertions.Primitives.NullableSimpleTimeSpanAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.TimeSpan? actualValue) { } + public static FluentAssertions.Types.TypeAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Type subject) { } + public static FluentAssertions.Xml.XAttributeAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Xml.Linq.XAttribute actualValue) { } + public static FluentAssertions.Xml.XDocumentAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Xml.Linq.XDocument actualValue) { } + public static FluentAssertions.Xml.XElementAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Xml.Linq.XElement actualValue) { } public static FluentAssertions.Primitives.BooleanAssertions Should(this bool actualValue) { } - public static FluentAssertions.Primitives.NullableBooleanAssertions Should(this bool? actualValue) { } + public static FluentAssertions.Primitives.NullableBooleanAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this bool? actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this byte actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this byte? actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this byte? actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this decimal actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this decimal? actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this decimal? actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this double actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this double? actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this double? actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this float actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this float? actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this float? actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this int actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this int? actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this int? actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this long actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this long? actualValue) { } - public static FluentAssertions.Primitives.ObjectAssertions Should(this object actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this long? actualValue) { } + public static FluentAssertions.Primitives.ObjectAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this object actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this sbyte actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this sbyte? actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this sbyte? actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this short actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this short? actualValue) { } - public static FluentAssertions.Primitives.StringAssertions Should(this string actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this short? actualValue) { } + public static FluentAssertions.Primitives.StringAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this string actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this uint actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this uint? actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this uint? actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this ulong actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this ulong? actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this ulong? actualValue) { } public static FluentAssertions.Numeric.NumericAssertions Should(this ushort actualValue) { } - public static FluentAssertions.Numeric.NullableNumericAssertions Should(this ushort? actualValue) { } + public static FluentAssertions.Numeric.NullableNumericAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this ushort? actualValue) { } [System.Obsolete("You are asserting the \'AndConstraint\' itself. Remove the \'Should()\' method direct" + "ly following \'And\'", true)] public static void Should(this FluentAssertions.Primitives.BooleanAssertions _) @@ -137,14 +146,14 @@ namespace FluentAssertions "ly following \'And\'", true)] public static void Should(this FluentAssertions.Primitives.SimpleTimeSpanAssertions _) where TAssertions : FluentAssertions.Primitives.SimpleTimeSpanAssertions { } - public static FluentAssertions.Collections.GenericCollectionAssertions Should(this System.Collections.Generic.IEnumerable actualValue) { } - public static FluentAssertions.Specialized.GenericAsyncFunctionAssertions Should(this System.Func> action) { } - public static FluentAssertions.Specialized.FunctionAssertions Should(this System.Func func) { } - public static FluentAssertions.Numeric.ComparableTypeAssertions Should(this System.IComparable comparableValue) { } + public static FluentAssertions.Collections.GenericCollectionAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Collections.Generic.IEnumerable actualValue) { } + public static FluentAssertions.Specialized.GenericAsyncFunctionAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Func> action) { } + public static FluentAssertions.Specialized.FunctionAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Func func) { } + public static FluentAssertions.Numeric.ComparableTypeAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.IComparable comparableValue) { } public static FluentAssertions.Specialized.TaskCompletionSourceAssertions Should(this System.Threading.Tasks.TaskCompletionSource tcs) { } [System.Obsolete("You are asserting the \'AndConstraint\' itself. Remove the \'Should()\' method direct" + "ly following \'And\'", true)] - public static void Should(this FluentAssertions.Numeric.NumericAssertions _) + public static void Should(this FluentAssertions.Numeric.NumericAssertionsBase _) where TSubject : struct, System.IComparable where TAssertions : FluentAssertions.Numeric.NumericAssertions { } [System.Obsolete("You are asserting the \'AndConstraint\' itself. Remove the \'Should()\' method direct" + @@ -156,18 +165,11 @@ namespace FluentAssertions "ly following \'And\'", true)] public static void Should(this FluentAssertions.Primitives.ReferenceTypeAssertions _) where TAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions { } - public static FluentAssertions.Collections.GenericDictionaryAssertions, TKey, TValue> Should(this System.Collections.Generic.IDictionary actualValue) { } - public static FluentAssertions.Collections.GenericDictionaryAssertions>, TKey, TValue> Should(this System.Collections.Generic.IEnumerable> actualValue) { } - public static FluentAssertions.Collections.GenericDictionaryAssertions Should(this TCollection actualValue) + public static FluentAssertions.Collections.GenericDictionaryAssertions, TKey, TValue> Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Collections.Generic.IDictionary actualValue) { } + public static FluentAssertions.Collections.GenericDictionaryAssertions>, TKey, TValue> Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Collections.Generic.IEnumerable> actualValue) { } + public static FluentAssertions.Collections.GenericDictionaryAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this TCollection actualValue) where TCollection : System.Collections.Generic.IEnumerable> { } } - public static class AssertionOptions - { - public static FluentAssertions.EquivalencyPlan EquivalencyPlan { get; } - public static FluentAssertions.Formatting.FormattingOptions FormattingOptions { get; } - public static void AssertEquivalencyUsing(System.Func defaultsConfigurer) { } - public static FluentAssertions.Equivalency.EquivalencyAssertionOptions CloneDefaults() { } - } public static class AsyncAssertionsExtensions { public static System.Threading.Tasks.Task, T>> WithResult(this System.Threading.Tasks.Task, T>> task, T expected, string because = "", params object[] becauseArgs) { } @@ -190,6 +192,7 @@ namespace FluentAssertions public static class CallerIdentifier { public static System.Action Logger { get; set; } + public static string[] DetermineCallerIdentities() { } public static string DetermineCallerIdentity() { } } [System.AttributeUsage(System.AttributeTargets.Method)] @@ -197,68 +200,18 @@ namespace FluentAssertions { public CustomAssertionAttribute() { } } - public static class DataColumnCollectionAssertionExtensions - { - public static FluentAssertions.AndConstraint> BeSameAs(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataColumnCollection expected, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> HaveSameCount(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataColumnCollection otherCollection, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> NotBeSameAs(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataColumnCollection unexpected, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> NotHaveSameCount(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataColumnCollection otherCollection, string because = "", params object[] becauseArgs) { } - } - public static class DataRowAssertionExtensions - { - public static FluentAssertions.Data.DataRowAssertions Should(this TDataRow actualValue) - where TDataRow : System.Data.DataRow { } - } - public static class DataRowCollectionAssertionExtensions - { - public static FluentAssertions.AndConstraint> BeSameAs(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataRowCollection expected, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> HaveSameCount(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataRowCollection otherCollection, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> NotBeSameAs(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataRowCollection unexpected, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> NotHaveSameCount(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataRowCollection otherCollection, string because = "", params object[] becauseArgs) { } - } - public static class DataSetAssertionExtensions - { - public static FluentAssertions.Data.DataSetAssertions Should(this TDataSet actualValue) - where TDataSet : System.Data.DataSet { } - } - public static class DataTableAssertionExtensions + [System.AttributeUsage(System.AttributeTargets.Assembly)] + public sealed class CustomAssertionsAssemblyAttribute : System.Attribute { - public static FluentAssertions.Data.DataTableAssertions Should(this TDataTable actualValue) - where TDataTable : System.Data.DataTable { } - } - public static class DataTableCollectionAssertionExtensions - { - public static FluentAssertions.AndConstraint> BeSameAs(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataTableCollection expected, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> HaveSameCount(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataSet otherDataSet, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> HaveSameCount(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataTableCollection otherCollection, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> NotBeSameAs(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataTableCollection unexpected, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> NotHaveSameCount(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataSet otherDataSet, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> NotHaveSameCount(this FluentAssertions.Collections.GenericCollectionAssertions assertion, System.Data.DataTableCollection otherCollection, string because = "", params object[] becauseArgs) { } + public CustomAssertionsAssemblyAttribute() { } } public static class EnumAssertionsExtensions { public static FluentAssertions.Primitives.EnumAssertions Should(this TEnum @enum) where TEnum : struct, System.Enum { } - public static FluentAssertions.Primitives.NullableEnumAssertions Should(this TEnum? @enum) + public static FluentAssertions.Primitives.NullableEnumAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this TEnum? @enum) where TEnum : struct, System.Enum { } } - public class EquivalencyPlan : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable - { - public EquivalencyPlan() { } - public void Add() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } - public void AddAfter() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } - public void Clear() { } - public System.Collections.Generic.IEnumerator GetEnumerator() { } - public void Insert() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } - public void InsertBefore() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } - public void Remove() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep { } - public void Reset() { } - } public static class EventRaisingExtensions { public static FluentAssertions.Events.IEventRecording WithArgs(this FluentAssertions.Events.IEventRecording eventRecording, System.Linq.Expressions.Expression> predicate) { } @@ -308,6 +261,10 @@ namespace FluentAssertions public static FluentAssertions.OccurrenceConstraint Times(int expected) { } public static FluentAssertions.OccurrenceConstraint Twice() { } } + public static class License + { + public static bool Accepted { get; set; } + } public static class MoreThan { public static FluentAssertions.OccurrenceConstraint Once() { } @@ -327,13 +284,17 @@ namespace FluentAssertions public static FluentAssertions.AndConstraint> BeApproximately(this FluentAssertions.Numeric.NumericAssertions parent, double expectedValue, double precision, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> BeApproximately(this FluentAssertions.Numeric.NumericAssertions parent, float expectedValue, float precision, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, byte nearbyValue, byte delta, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, short nearbyValue, ushort delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, int nearbyValue, uint delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, long nearbyValue, ulong delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, sbyte nearbyValue, byte delta, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, ushort nearbyValue, ushort delta, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, short nearbyValue, ushort delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, uint nearbyValue, uint delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, ulong nearbyValue, ulong delta, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> BeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, ushort nearbyValue, ushort delta, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> BeNaN(this FluentAssertions.Numeric.NullableNumericAssertions parent, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> BeNaN(this FluentAssertions.Numeric.NullableNumericAssertions parent, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> BeNaN(this FluentAssertions.Numeric.NumericAssertions parent, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> BeNaN(this FluentAssertions.Numeric.NumericAssertions parent, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeApproximately(this FluentAssertions.Numeric.NullableNumericAssertions parent, decimal unexpectedValue, decimal precision, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeApproximately(this FluentAssertions.Numeric.NullableNumericAssertions parent, decimal? unexpectedValue, decimal precision, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeApproximately(this FluentAssertions.Numeric.NullableNumericAssertions parent, double unexpectedValue, double precision, string because = "", params object[] becauseArgs) { } @@ -344,20 +305,22 @@ namespace FluentAssertions public static FluentAssertions.AndConstraint> NotBeApproximately(this FluentAssertions.Numeric.NumericAssertions parent, double unexpectedValue, double precision, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeApproximately(this FluentAssertions.Numeric.NumericAssertions parent, float unexpectedValue, float precision, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, byte distantValue, byte delta, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, short distantValue, ushort delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, int distantValue, uint delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, long distantValue, ulong delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, sbyte distantValue, byte delta, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, ushort distantValue, ushort delta, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, short distantValue, ushort delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, uint distantValue, uint delta, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, ulong distantValue, ulong delta, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> NotBeCloseTo(this FluentAssertions.Numeric.NumericAssertions parent, ushort distantValue, ushort delta, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> NotBeNaN(this FluentAssertions.Numeric.NullableNumericAssertions parent, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> NotBeNaN(this FluentAssertions.Numeric.NullableNumericAssertions parent, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> NotBeNaN(this FluentAssertions.Numeric.NumericAssertions parent, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint> NotBeNaN(this FluentAssertions.Numeric.NumericAssertions parent, string because = "", params object[] becauseArgs) { } } public static class ObjectAssertionsExtensions { - public static FluentAssertions.AndConstraint BeBinarySerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint BeBinarySerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> options, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint BeDataContractSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, string because = "", params object[] becauseArgs) { } - public static FluentAssertions.AndConstraint BeDataContractSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> options, string because = "", params object[] becauseArgs) { } + public static FluentAssertions.AndConstraint BeDataContractSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> options, string because = "", params object[] becauseArgs) { } public static FluentAssertions.AndConstraint BeXmlSerializable(this FluentAssertions.Primitives.ObjectAssertions assertions, string because = "", params object[] becauseArgs) { } } public abstract class OccurrenceConstraint @@ -396,33 +359,38 @@ namespace FluentAssertions public static FluentAssertions.Types.TypeSelector Types(this System.Reflection.Assembly assembly) { } public static FluentAssertions.Types.TypeSelector Types(this System.Type type) { } } + public static class Value + { + public static FluentAssertions.Equivalency.Inlining.IInlineEquivalencyAssertion ThatMatches(System.Linq.Expressions.Expression> condition) { } + public static FluentAssertions.Equivalency.Inlining.IInlineEquivalencyAssertion ThatSatisfies(System.Action assertion) { } + } public static class XmlAssertionExtensions { - public static FluentAssertions.Xml.XmlElementAssertions Should(this System.Xml.XmlElement actualValue) { } - public static FluentAssertions.Xml.XmlNodeAssertions Should(this System.Xml.XmlNode actualValue) { } + public static FluentAssertions.Xml.XmlElementAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Xml.XmlElement actualValue) { } + public static FluentAssertions.Xml.XmlNodeAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this System.Xml.XmlNode actualValue) { } } } 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) { } public FluentAssertions.AndConstraint AllBeEquivalentTo(TExpectation expectation, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint AllBeEquivalentTo(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint AllBeEquivalentTo(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint AllBeOfType(System.Type expectedType, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint> AllBeOfType(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint AllSatisfy(System.Action expected, string because = "", params object[] becauseArgs) { } @@ -431,7 +399,7 @@ namespace FluentAssertions.Collections protected void AssertSubjectEquality(System.Collections.Generic.IEnumerable expectation, System.Func equalityComparison, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEmpty(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeEquivalentTo(System.Collections.Generic.IEnumerable expectation, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeEquivalentTo(System.Collections.Generic.IEnumerable expectation, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeEquivalentTo(System.Collections.Generic.IEnumerable expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint> BeInAscendingOrder(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint> BeInAscendingOrder(System.Collections.Generic.IComparer comparer, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint> BeInAscendingOrder(System.Func comparison, string because = "", params object[] becauseArgs) { } @@ -448,7 +416,7 @@ namespace FluentAssertions.Collections public FluentAssertions.AndWhichConstraint Contain(System.Linq.Expressions.Expression> predicate, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint Contain(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint ContainEquivalentOf(TExpectation expectation, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint ContainEquivalentOf(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint ContainEquivalentOf(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint ContainInConsecutiveOrder(params T[] expected) { } public FluentAssertions.AndConstraint ContainInConsecutiveOrder(System.Collections.Generic.IEnumerable expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint ContainInOrder(params T[] expected) { } @@ -463,12 +431,10 @@ namespace FluentAssertions.Collections public FluentAssertions.AndConstraint Equal(System.Collections.Generic.IEnumerable expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Equal(System.Collections.Generic.IEnumerable expectation, System.Func equalityComparison, string because = "", params object[] becauseArgs) { } public override bool Equals(object obj) { } - public FluentAssertions.AndConstraint HaveCount(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveCount(System.Linq.Expressions.Expression> countPredicate, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint HaveCountGreaterOrEqualTo(int expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveCount(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveCountGreaterThan(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveCountGreaterThanOrEqualTo(int expected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint HaveCountLessOrEqualTo(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveCountLessThan(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveCountLessThanOrEqualTo(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint HaveElementAt(int index, T element, string because = "", params object[] becauseArgs) { } @@ -478,7 +444,7 @@ namespace FluentAssertions.Collections public FluentAssertions.AndConstraint IntersectWith(System.Collections.Generic.IEnumerable otherCollection, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEmpty(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEquivalentTo(System.Collections.Generic.IEnumerable unexpected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint NotBeEquivalentTo(System.Collections.Generic.IEnumerable unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotBeEquivalentTo(System.Collections.Generic.IEnumerable unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeInAscendingOrder(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeInAscendingOrder(System.Collections.Generic.IComparer comparer, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeInAscendingOrder(System.Func comparison, string because = "", params object[] becauseArgs) { } @@ -493,9 +459,9 @@ 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.EquivalencyAssertionOptions> config, 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) { } public FluentAssertions.AndConstraint NotContainInConsecutiveOrder(System.Collections.Generic.IEnumerable unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotContainInOrder(params T[] unexpected) { } @@ -524,16 +490,16 @@ 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.EquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeEquivalentTo(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Contain(params System.Collections.Generic.KeyValuePair[] expected) { } public FluentAssertions.AndConstraint Contain(System.Collections.Generic.IEnumerable> expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Contain(System.Collections.Generic.KeyValuePair expected, string because = "", params object[] becauseArgs) { } @@ -542,8 +508,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) { } @@ -561,42 +527,31 @@ 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.EquivalencyAssertionOptions> config, 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) { } public FluentAssertions.AndConstraint BeEquivalentTo(System.Collections.Generic.IEnumerable expectation, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeEquivalentTo(System.Collections.Generic.IEnumerable expectation, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeEquivalentTo(System.Collections.Generic.IEnumerable expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint ContainMatch(string wildcardPattern, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Equal(System.Collections.Generic.IEnumerable expected) { } public FluentAssertions.AndConstraint Equal(params string[] expected) { } public FluentAssertions.AndConstraint NotContainMatch(string wildcardPattern, string because = "", params object[] becauseArgs) { } } - public class SubsequentOrderingAssertions : FluentAssertions.Collections.SubsequentOrderingGenericCollectionAssertions, T, FluentAssertions.Collections.SubsequentOrderingAssertions> - { - public SubsequentOrderingAssertions(System.Collections.Generic.IEnumerable actualValue, System.Linq.IOrderedEnumerable previousOrderedEnumerable) { } - } - public class SubsequentOrderingGenericCollectionAssertions : FluentAssertions.Collections.SubsequentOrderingGenericCollectionAssertions> - where TCollection : System.Collections.Generic.IEnumerable + public class SubsequentOrderingAssertions : FluentAssertions.Collections.GenericCollectionAssertions, T> { - public SubsequentOrderingGenericCollectionAssertions(TCollection actualValue, System.Linq.IOrderedEnumerable previousOrderedEnumerable) { } - } - 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 SubsequentOrderingAssertions(System.Collections.Generic.IEnumerable 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) { } @@ -622,14 +577,6 @@ namespace FluentAssertions.Common InvalidForCSharp = 5, PrivateProtected = 6, } - public class Configuration - { - public Configuration(FluentAssertions.Common.IConfigurationStore store) { } - public string TestFrameworkName { get; set; } - public string ValueFormatterAssembly { get; set; } - public FluentAssertions.Common.ValueFormatterDetectionMode ValueFormatterDetectionMode { get; set; } - public static FluentAssertions.Common.Configuration Current { get; } - } public static class DateTimeExtensions { public static System.DateTimeOffset ToDateTimeOffset(this System.DateTime dateTime) { } @@ -641,31 +588,10 @@ namespace FluentAssertions.Common System.Threading.Tasks.Task DelayAsync(System.TimeSpan delay, System.Threading.CancellationToken cancellationToken); FluentAssertions.Common.ITimer StartTimer(); } - public interface ICollectionWrapper - where TCollection : System.Collections.ICollection - { - TCollection UnderlyingCollection { get; } - } - public interface IConfigurationStore - { - string GetSetting(string name); - } - public interface IReflector - { - System.Collections.Generic.IEnumerable GetAllTypesFromAppDomain(System.Func predicate); - } public interface ITimer : System.IDisposable { System.TimeSpan Elapsed { get; } } - public static class Services - { - public static FluentAssertions.Common.Configuration Configuration { get; } - public static FluentAssertions.Common.IConfigurationStore ConfigurationStore { get; set; } - public static FluentAssertions.Common.IReflector Reflector { get; set; } - public static System.Action ThrowException { get; set; } - public static void ResetToDefaults() { } - } public delegate FluentAssertions.Common.ITimer StartTimer(); public enum ValueFormatterDetectionMode { @@ -674,82 +600,36 @@ namespace FluentAssertions.Common Scan = 2, } } -namespace FluentAssertions.Data +namespace FluentAssertions.Configuration { - public class DataColumnAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions + public class GlobalConfiguration { - public DataColumnAssertions(System.Data.DataColumn dataColumn) { } - protected override string Identifier { get; } - public FluentAssertions.AndConstraint BeEquivalentTo(System.Data.DataColumn expectation, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeEquivalentTo(System.Data.DataColumn expectation, System.Func, FluentAssertions.Data.IDataEquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } + public GlobalConfiguration() { } + public FluentAssertions.Configuration.GlobalEquivalencyOptions Equivalency { get; set; } + public FluentAssertions.Configuration.GlobalFormattingOptions Formatting { get; set; } + public FluentAssertions.Configuration.TestFramework? TestFramework { get; set; } } - public class DataRowAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions> - where TDataRow : System.Data.DataRow + public class GlobalEquivalencyOptions { - public DataRowAssertions(TDataRow dataRow) { } - protected override string Identifier { get; } - public FluentAssertions.AndConstraint> BeEquivalentTo(System.Data.DataRow expectation, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint> BeEquivalentTo(System.Data.DataRow expectation, System.Func, FluentAssertions.Data.IDataEquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint, System.Data.DataColumn> HaveColumn(string expectedColumnName, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint> HaveColumns(params string[] expectedColumnNames) { } - public FluentAssertions.AndConstraint> HaveColumns(System.Collections.Generic.IEnumerable expectedColumnNames, string because = "", params object[] becauseArgs) { } + public GlobalEquivalencyOptions() { } + public FluentAssertions.Equivalency.EquivalencyPlan Plan { get; } + public FluentAssertions.Equivalency.EquivalencyOptions CloneDefaults() { } + public void Modify(System.Func configureOptions) { } } - public class DataSetAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions> - where TDataSet : System.Data.DataSet + public class GlobalFormattingOptions : FluentAssertions.Formatting.FormattingOptions { - public DataSetAssertions(TDataSet dataSet) { } - protected override string Identifier { get; } - public FluentAssertions.AndConstraint> BeEquivalentTo(System.Data.DataSet expectation, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint> BeEquivalentTo(System.Data.DataSet expectation, System.Func, FluentAssertions.Data.IDataEquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint, System.Data.DataTable> HaveTable(string expectedTableName, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint> HaveTableCount(int expected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint> HaveTables(params string[] expectedTableNames) { } - public FluentAssertions.AndConstraint> HaveTables(System.Collections.Generic.IEnumerable expectedTableNames, string because = "", params object[] becauseArgs) { } + public GlobalFormattingOptions() { } + public string ValueFormatterAssembly { get; set; } + public FluentAssertions.Common.ValueFormatterDetectionMode ValueFormatterDetectionMode { get; set; } } - public class DataTableAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions> - where TDataTable : System.Data.DataTable + public enum TestFramework { - public DataTableAssertions(TDataTable dataTable) { } - protected override string Identifier { get; } - public FluentAssertions.AndConstraint> BeEquivalentTo(System.Data.DataTable expectation, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint> BeEquivalentTo(System.Data.DataTable expectation, System.Func, FluentAssertions.Data.IDataEquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint, System.Data.DataColumn> HaveColumn(string expectedColumnName, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint> HaveColumns(params string[] expectedColumnNames) { } - public FluentAssertions.AndConstraint> HaveColumns(System.Collections.Generic.IEnumerable expectedColumnNames, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint> HaveRowCount(int expected, string because = "", params object[] becauseArgs) { } - } - public interface IDataEquivalencyAssertionOptions : FluentAssertions.Equivalency.IEquivalencyAssertionOptions - { - FluentAssertions.Data.IDataEquivalencyAssertionOptions AllowingMismatchedTypes(); - FluentAssertions.Data.IDataEquivalencyAssertionOptions Excluding(System.Linq.Expressions.Expression> predicate); - FluentAssertions.Data.IDataEquivalencyAssertionOptions Excluding(System.Linq.Expressions.Expression> expression); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingColumn(System.Data.DataColumn column); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingColumn(string tableName, string columnName); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingColumnInAllTables(string columnName); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingColumns(System.Collections.Generic.IEnumerable columns); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingColumns(params System.Data.DataColumn[] columns); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingColumns(string tableName, System.Collections.Generic.IEnumerable columnNames); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingColumns(string tableName, params string[] columnNames); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingColumnsInAllTables(System.Collections.Generic.IEnumerable columnNames); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingColumnsInAllTables(params string[] columnNames); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingOriginalData(); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingRelated(System.Linq.Expressions.Expression> expression); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingRelated(System.Linq.Expressions.Expression> expression); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingRelated(System.Linq.Expressions.Expression> expression); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingRelated(System.Linq.Expressions.Expression> expression); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingRelated(System.Linq.Expressions.Expression> expression); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingRelated(System.Linq.Expressions.Expression> expression); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingRelated(System.Linq.Expressions.Expression> expression); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingTable(string tableName); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingTables(System.Collections.Generic.IEnumerable tableNames); - FluentAssertions.Data.IDataEquivalencyAssertionOptions ExcludingTables(params string[] tableNames); - FluentAssertions.Data.IDataEquivalencyAssertionOptions IgnoringUnmatchedColumns(); - FluentAssertions.Data.IDataEquivalencyAssertionOptions UsingRowMatchMode(FluentAssertions.Data.RowMatchMode rowMatchMode); - } - public enum RowMatchMode - { - Index = 0, - PrimaryKey = 1, + XUnit2 = 0, + XUnit3 = 1, + TUnit = 2, + MsTest = 3, + NUnit = 4, + MSpec = 5, } } namespace FluentAssertions.Equivalency @@ -762,7 +642,7 @@ namespace FluentAssertions.Equivalency public object Expectation { get; set; } public System.Type RuntimeType { get; } public object Subject { get; set; } - public System.Type GetExpectedType(FluentAssertions.Equivalency.IEquivalencyAssertionOptions options) { } + public System.Type GetExpectedType(FluentAssertions.Equivalency.IEquivalencyOptions options) { } public override string ToString() { } } public class ConversionSelector @@ -792,41 +672,58 @@ namespace FluentAssertions.Equivalency ForceEquals = 2, ForceMembers = 3, } - public class EquivalencyAssertionOptions : FluentAssertions.Equivalency.SelfReferenceEquivalencyAssertionOptions + public class EquivalencyOptions : FluentAssertions.Equivalency.SelfReferenceEquivalencyOptions { - public EquivalencyAssertionOptions() { } + public EquivalencyOptions() { } } - public class EquivalencyAssertionOptions : FluentAssertions.Equivalency.SelfReferenceEquivalencyAssertionOptions> + public class EquivalencyOptions : FluentAssertions.Equivalency.SelfReferenceEquivalencyOptions> { - public EquivalencyAssertionOptions() { } - public EquivalencyAssertionOptions(FluentAssertions.Equivalency.IEquivalencyAssertionOptions defaults) { } - public FluentAssertions.Equivalency.EquivalencyAssertionOptions> AsCollection() { } - public FluentAssertions.Equivalency.EquivalencyAssertionOptions Excluding(System.Linq.Expressions.Expression> expression) { } + public EquivalencyOptions() { } + public EquivalencyOptions(FluentAssertions.Equivalency.IEquivalencyOptions defaults) { } + public FluentAssertions.Equivalency.EquivalencyOptions> AsCollection() { } + public FluentAssertions.Equivalency.EquivalencyOptions Excluding(System.Linq.Expressions.Expression> expression) { } public FluentAssertions.Equivalency.NestedExclusionOptionBuilder For(System.Linq.Expressions.Expression>> expression) { } - public FluentAssertions.Equivalency.EquivalencyAssertionOptions Including(System.Linq.Expressions.Expression> expression) { } - public FluentAssertions.Equivalency.EquivalencyAssertionOptions WithMapping(string expectationMemberPath, string subjectMemberPath) { } - public FluentAssertions.Equivalency.EquivalencyAssertionOptions WithMapping(System.Linq.Expressions.Expression> expectationMemberPath, System.Linq.Expressions.Expression> subjectMemberPath) { } - public FluentAssertions.Equivalency.EquivalencyAssertionOptions WithMapping(System.Linq.Expressions.Expression> expectationMember, System.Linq.Expressions.Expression> subjectMember) { } - public FluentAssertions.Equivalency.EquivalencyAssertionOptions WithMapping(string expectationMemberName, string subjectMemberName) { } - public FluentAssertions.Equivalency.EquivalencyAssertionOptions WithStrictOrderingFor(System.Linq.Expressions.Expression> expression) { } - public FluentAssertions.Equivalency.EquivalencyAssertionOptions WithoutStrictOrderingFor(System.Linq.Expressions.Expression> expression) { } + public FluentAssertions.Equivalency.EquivalencyOptions Including(System.Linq.Expressions.Expression> expression) { } + public FluentAssertions.Equivalency.EquivalencyOptions WithMapping(string expectationMemberPath, string subjectMemberPath) { } + public FluentAssertions.Equivalency.EquivalencyOptions WithMapping(System.Linq.Expressions.Expression> expectationMemberPath, System.Linq.Expressions.Expression> subjectMemberPath) { } + public FluentAssertions.Equivalency.EquivalencyOptions WithMapping(System.Linq.Expressions.Expression> expectationMember, System.Linq.Expressions.Expression> subjectMember) { } + public FluentAssertions.Equivalency.EquivalencyOptions WithMapping(string expectationMemberName, string subjectMemberName) { } + public FluentAssertions.Equivalency.EquivalencyOptions WithStrictOrderingFor(System.Linq.Expressions.Expression> expression) { } + public FluentAssertions.Equivalency.EquivalencyOptions WithoutStrictOrderingFor(System.Linq.Expressions.Expression> expression) { } + } + public class EquivalencyPlan : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable + { + public EquivalencyPlan() { } + public void Add() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } + public void AddAfter() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } + public void Clear() { } + public System.Collections.Generic.IEnumerator GetEnumerator() { } + public void Insert() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } + public void InsertBefore() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } + public void Remove() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep { } + public void Reset() { } } public enum EquivalencyResult { ContinueWithNext = 0, - AssertionCompleted = 1, + EquivalencyProven = 1, } public abstract class EquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { protected EquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } - protected abstract FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator); + 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 nestedValidator); } public class EquivalencyValidationContext : FluentAssertions.Equivalency.IEquivalencyValidationContext { - public EquivalencyValidationContext(FluentAssertions.Equivalency.INode root, FluentAssertions.Equivalency.IEquivalencyAssertionOptions options) { } + public EquivalencyValidationContext(FluentAssertions.Equivalency.INode root, FluentAssertions.Equivalency.IEquivalencyOptions options) { } public FluentAssertions.Equivalency.INode CurrentNode { get; } - public FluentAssertions.Equivalency.IEquivalencyAssertionOptions Options { get; } + public FluentAssertions.Equivalency.IEquivalencyOptions Options { get; } public FluentAssertions.Execution.Reason Reason { get; set; } public FluentAssertions.Equivalency.Tracing.ITraceWriter TraceWriter { get; set; } public FluentAssertions.Equivalency.Tracing.Tracer Tracer { get; } @@ -837,24 +734,6 @@ namespace FluentAssertions.Equivalency public bool IsCyclicReference(object expectation) { } public override string ToString() { } } - public class EquivalencyValidator : FluentAssertions.Equivalency.IEquivalencyValidator - { - public EquivalencyValidator() { } - public void AssertEquality(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.EquivalencyValidationContext context) { } - public void RecursivelyAssertEquality(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context) { } - } - public class Field : FluentAssertions.Equivalency.Node, FluentAssertions.Equivalency.IMember, FluentAssertions.Equivalency.INode - { - public Field(System.Reflection.FieldInfo fieldInfo, FluentAssertions.Equivalency.INode parent) { } - public Field(System.Type reflectedType, System.Reflection.FieldInfo fieldInfo, FluentAssertions.Equivalency.INode parent) { } - public System.Type DeclaringType { get; set; } - public override string Description { get; } - public FluentAssertions.Common.CSharpAccessModifier GetterAccessibility { get; } - public bool IsBrowsable { get; } - public System.Type ReflectedType { get; } - public FluentAssertions.Common.CSharpAccessModifier SetterAccessibility { get; } - public object GetValue(object obj) { } - } public delegate string GetSubjectId(); public interface IAssertionContext { @@ -864,7 +743,7 @@ namespace FluentAssertions.Equivalency FluentAssertions.Equivalency.INode SelectedNode { get; } TSubject Subject { get; } } - public interface IEquivalencyAssertionOptions + public interface IEquivalencyOptions { bool AllowInfiniteRecursion { get; } bool? CompareRecordsByValue { get; } @@ -872,7 +751,12 @@ namespace FluentAssertions.Equivalency FluentAssertions.Equivalency.CyclicReferenceHandling CyclicReferenceHandling { get; } FluentAssertions.Equivalency.EnumEquivalencyHandling EnumEquivalencyHandling { get; } bool ExcludeNonBrowsableOnExpectation { get; } + bool IgnoreCase { get; } + bool IgnoreJsonPropertyCasing { get; } + bool IgnoreLeadingWhitespace { get; } + bool IgnoreNewlineStyle { get; } bool IgnoreNonBrowsableOnSubject { get; } + bool IgnoreTrailingWhitespace { get; } FluentAssertions.Equivalency.MemberVisibility IncludedFields { get; } FluentAssertions.Equivalency.MemberVisibility IncludedProperties { get; } bool IsRecursive { get; } @@ -886,12 +770,12 @@ namespace FluentAssertions.Equivalency } public interface IEquivalencyStep { - FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator); + FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes); } public interface IEquivalencyValidationContext { FluentAssertions.Equivalency.INode CurrentNode { get; } - FluentAssertions.Equivalency.IEquivalencyAssertionOptions Options { get; } + FluentAssertions.Equivalency.IEquivalencyOptions Options { get; } FluentAssertions.Execution.Reason Reason { get; } FluentAssertions.Equivalency.Tracing.Tracer Tracer { get; } FluentAssertions.Equivalency.IEquivalencyValidationContext AsCollectionItem(string index); @@ -900,10 +784,6 @@ namespace FluentAssertions.Equivalency FluentAssertions.Equivalency.IEquivalencyValidationContext Clone(); bool IsCyclicReference(object expectation); } - public interface IEquivalencyValidator - { - void RecursivelyAssertEquality(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context); - } public interface IMember : FluentAssertions.Equivalency.INode { System.Type DeclaringType { get; } @@ -924,7 +804,7 @@ namespace FluentAssertions.Equivalency } public interface IMemberMatchingRule { - FluentAssertions.Equivalency.IMember Match(FluentAssertions.Equivalency.IMember expectedMember, object subject, FluentAssertions.Equivalency.INode parent, FluentAssertions.Equivalency.IEquivalencyAssertionOptions 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 { @@ -934,15 +814,14 @@ namespace FluentAssertions.Equivalency public interface INode { int Depth { get; } - string Description { get; } + FluentAssertions.Equivalency.Pathway Expectation { get; } FluentAssertions.Equivalency.GetSubjectId GetSubjectId { get; } bool IsRoot { get; } - string Name { get; set; } System.Type ParentType { get; } - string Path { get; } - string PathAndName { get; } bool RootIsCollection { get; } + FluentAssertions.Equivalency.Pathway Subject { get; } System.Type Type { get; } + void AdjustForRemappedSubject(FluentAssertions.Equivalency.IMember subjectMember); } public interface IObjectInfo { @@ -957,13 +836,17 @@ namespace FluentAssertions.Equivalency { FluentAssertions.Equivalency.OrderStrictness Evaluate(FluentAssertions.Equivalency.IObjectInfo objectInfo); } + public interface IValidateChildNodeEquivalency + { + void AssertEquivalencyOf(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context); + } public static class MemberFactory { public static FluentAssertions.Equivalency.IMember Create(System.Reflection.MemberInfo memberInfo, FluentAssertions.Equivalency.INode parent) { } } public class MemberSelectionContext { - public MemberSelectionContext(System.Type compileTimeType, System.Type runtimeType, FluentAssertions.Equivalency.IEquivalencyAssertionOptions options) { } + public MemberSelectionContext(System.Type compileTimeType, System.Type runtimeType, FluentAssertions.Equivalency.IEquivalencyOptions options) { } public FluentAssertions.Equivalency.MemberVisibility IncludedFields { get; } public FluentAssertions.Equivalency.MemberVisibility IncludedProperties { get; } public System.Type Type { get; } @@ -979,29 +862,9 @@ namespace FluentAssertions.Equivalency } public class NestedExclusionOptionBuilder { - public FluentAssertions.Equivalency.EquivalencyAssertionOptions Exclude(System.Linq.Expressions.Expression> expression) { } + public FluentAssertions.Equivalency.EquivalencyOptions Exclude(System.Linq.Expressions.Expression> expression) { } public FluentAssertions.Equivalency.NestedExclusionOptionBuilder For(System.Linq.Expressions.Expression>> expression) { } } - public class Node : FluentAssertions.Equivalency.INode - { - public Node() { } - public int Depth { get; } - public virtual string Description { get; } - public FluentAssertions.Equivalency.GetSubjectId GetSubjectId { get; set; } - public bool IsRoot { get; } - public string Name { get; set; } - public System.Type ParentType { get; set; } - public string Path { get; set; } - public string PathAndName { get; } - public bool RootIsCollection { get; set; } - public System.Type Type { get; set; } - public override bool Equals(object obj) { } - public override int GetHashCode() { } - public override string ToString() { } - public static FluentAssertions.Equivalency.INode From(FluentAssertions.Equivalency.GetSubjectId getSubjectId) { } - public static FluentAssertions.Equivalency.INode FromCollectionItem(string index, FluentAssertions.Equivalency.INode parent) { } - public static FluentAssertions.Equivalency.INode FromDictionaryItem(object key, FluentAssertions.Equivalency.INode parent) { } - } public enum OrderStrictness { Strict = 0, @@ -1016,24 +879,28 @@ namespace FluentAssertions.Equivalency public System.Collections.Generic.IEnumerator GetEnumerator() { } public bool IsOrderingStrictFor(FluentAssertions.Equivalency.IObjectInfo objectInfo) { } } - public class Property : FluentAssertions.Equivalency.Node, FluentAssertions.Equivalency.IMember, FluentAssertions.Equivalency.INode + public class Pathway : System.IEquatable { - public Property(System.Reflection.PropertyInfo propertyInfo, FluentAssertions.Equivalency.INode parent) { } - public Property(System.Type reflectedType, System.Reflection.PropertyInfo propertyInfo, FluentAssertions.Equivalency.INode parent) { } - public System.Type DeclaringType { get; } - public override string Description { get; } - public FluentAssertions.Common.CSharpAccessModifier GetterAccessibility { get; } - public bool IsBrowsable { get; } - public System.Type ReflectedType { get; } - public FluentAssertions.Common.CSharpAccessModifier SetterAccessibility { get; } - public object GetValue(object obj) { } + public Pathway(FluentAssertions.Equivalency.Pathway parent, string name, FluentAssertions.Equivalency.Pathway.GetDescription getDescription) { } + public Pathway(string path, string name, FluentAssertions.Equivalency.Pathway.GetDescription getDescription) { } + public string Description { get; } + public string Name { get; } + public string Path { get; } + public string PathAndName { get; } + public override string ToString() { } + public delegate string GetDescription(string pathAndName); } - public abstract class SelfReferenceEquivalencyAssertionOptions : FluentAssertions.Equivalency.IEquivalencyAssertionOptions - where TSelf : FluentAssertions.Equivalency.SelfReferenceEquivalencyAssertionOptions + public abstract class SelfReferenceEquivalencyOptions : FluentAssertions.Equivalency.IEquivalencyOptions + where TSelf : FluentAssertions.Equivalency.SelfReferenceEquivalencyOptions { - protected SelfReferenceEquivalencyAssertionOptions(FluentAssertions.Equivalency.IEquivalencyAssertionOptions defaults) { } + protected SelfReferenceEquivalencyOptions(FluentAssertions.Equivalency.IEquivalencyOptions defaults) { } public bool? CompareRecordsByValue { get; } public FluentAssertions.Equivalency.ConversionSelector ConversionSelector { get; } + public bool IgnoreCase { get; } + public bool IgnoreJsonPropertyCasing { get; set; } + public bool IgnoreLeadingWhitespace { get; } + public bool IgnoreNewlineStyle { get; } + public bool IgnoreTrailingWhitespace { get; } [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] protected FluentAssertions.Equivalency.OrderingRuleCollection OrderingRules { get; } public FluentAssertions.Equivalency.Tracing.ITraceWriter TraceWriter { get; } @@ -1049,13 +916,18 @@ namespace FluentAssertions.Equivalency public TSelf ComparingRecordsByMembers() { } public TSelf ComparingRecordsByValue() { } public TSelf Excluding(System.Linq.Expressions.Expression> predicate) { } + public TSelf ExcludingExplicitlyImplementedProperties() { } public TSelf ExcludingFields() { } + public TSelf ExcludingMembersNamed(params string[] memberNames) { } public TSelf ExcludingMissingMembers() { } - public TSelf ExcludingNestedObjects() { } public TSelf ExcludingNonBrowsableMembers() { } public TSelf ExcludingProperties() { } + public TSelf IgnoringCase() { } public TSelf IgnoringCyclicReferences() { } + public TSelf IgnoringLeadingWhitespace() { } + public TSelf IgnoringNewlineStyle() { } public TSelf IgnoringNonBrowsableMembersOnSubject() { } + public TSelf IgnoringTrailingWhitespace() { } public TSelf Including(System.Linq.Expressions.Expression> predicate) { } public TSelf IncludingAllDeclaredProperties() { } public TSelf IncludingAllRuntimeProperties() { } @@ -1064,15 +936,16 @@ namespace FluentAssertions.Equivalency public TSelf IncludingInternalProperties() { } public TSelf IncludingNestedObjects() { } public TSelf IncludingProperties() { } - public TSelf RespectingDeclaredTypes() { } - public TSelf RespectingRuntimeTypes() { } + public TSelf PreferringDeclaredMemberTypes() { } + public TSelf PreferringRuntimeMemberTypes() { } public TSelf ThrowingOnMissingMembers() { } public override string ToString() { } public TSelf Using(FluentAssertions.Equivalency.IEquivalencyStep equivalencyStep) { } public TSelf Using(FluentAssertions.Equivalency.IMemberMatchingRule matchingRule) { } public TSelf Using(FluentAssertions.Equivalency.IMemberSelectionRule selectionRule) { } public TSelf Using(FluentAssertions.Equivalency.IOrderingRule orderingRule) { } - public FluentAssertions.Equivalency.SelfReferenceEquivalencyAssertionOptions.Restriction Using(System.Action> action) { } + public TSelf Using(System.Collections.Generic.IEqualityComparer comparer) { } + public FluentAssertions.Equivalency.SelfReferenceEquivalencyOptions.Restriction Using(System.Action> action) { } public TSelf Using(System.Collections.Generic.IEqualityComparer comparer) { } public TSelf Using() where TEqualityComparer : System.Collections.Generic.IEqualityComparer, new () { } @@ -1080,12 +953,16 @@ namespace FluentAssertions.Equivalency public TSelf WithAutoConversionFor(System.Linq.Expressions.Expression> predicate) { } public TSelf WithStrictOrdering() { } public TSelf WithStrictOrderingFor(System.Linq.Expressions.Expression> predicate) { } + public TSelf WithStrictTyping() { } + public TSelf WithStrictTypingFor(System.Linq.Expressions.Expression> predicate) { } public TSelf WithTracing(FluentAssertions.Equivalency.Tracing.ITraceWriter writer = null) { } public TSelf WithoutAutoConversionFor(System.Linq.Expressions.Expression> predicate) { } - public void WithoutMatchingRules() { } - public void WithoutSelectionRules() { } + public TSelf WithoutMatchingRules() { } + public TSelf WithoutRecursing() { } + public TSelf WithoutSelectionRules() { } public TSelf WithoutStrictOrdering() { } public TSelf WithoutStrictOrderingFor(System.Linq.Expressions.Expression> predicate) { } + public TSelf WithoutStrictTyping() { } public class Restriction { public Restriction(TSelf options, System.Action> action) { } @@ -1102,135 +979,117 @@ namespace FluentAssertions.Equivalency public static bool WhichSetterHas(this FluentAssertions.Equivalency.IMemberInfo memberInfo, FluentAssertions.Common.CSharpAccessModifier accessModifier) { } } } +namespace FluentAssertions.Equivalency.Inlining +{ + public interface IInlineEquivalencyAssertion + { + void Execute(FluentAssertions.Execution.AssertionChain assertionChain, FluentAssertions.Equivalency.Comparands comparands); + } + public class InlineEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep + { + public InlineEquivalencyStep() { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } + } +} namespace FluentAssertions.Equivalency.Steps { public class AssertionRuleEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { - public AssertionRuleEquivalencyStep(System.Linq.Expressions.Expression> predicate, System.Action> assertion) { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + 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() { } } public class AutoConversionStep : FluentAssertions.Equivalency.IEquivalencyStep { public AutoConversionStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } public override string ToString() { } } - public class ConstraintCollectionEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep - { - public ConstraintCollectionEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } - } - public class ConstraintEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep - { - public ConstraintEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } - } - public class DataColumnEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep + public class DateAndTimeEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { - public DataColumnEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } - } - public class DataRelationEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep - { - public DataRelationEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } - } - public class DataRowCollectionEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep - { - public DataRowCollectionEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } - } - public class DataRowEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep - { - public DataRowEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } - } - public class DataSetEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep - { - public DataSetEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } - } - public class DataTableEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep - { - public DataTableEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public DateAndTimeEquivalencyStep() { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class DictionaryEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep { public DictionaryEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nestedValidator) { } } public class EnumEqualityStep : FluentAssertions.Equivalency.IEquivalencyStep { public EnumEqualityStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class EnumerableEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public EnumerableEquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class EqualityComparerEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public EqualityComparerEquivalencyStep(System.Collections.Generic.IEqualityComparer comparer) { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } public override string ToString() { } } public class GenericDictionaryEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public GenericDictionaryEquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class GenericEnumerableEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public GenericEnumerableEquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class ReferenceEqualityEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public ReferenceEqualityEquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class RunAllUserStepsEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public RunAllUserStepsEquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class SimpleEqualityEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public SimpleEqualityEquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class StringEqualityEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public StringEqualityEquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class StructuralEqualityEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public StructuralEqualityEquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } + } + public class TypeEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep + { + public TypeEquivalencyStep() { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class ValueTypeEquivalencyStep : FluentAssertions.Equivalency.IEquivalencyStep { public ValueTypeEquivalencyStep() { } - public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + public FluentAssertions.Equivalency.EquivalencyResult Handle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency valueChildNodes) { } } public class XAttributeEquivalencyStep : FluentAssertions.Equivalency.EquivalencyStep { public XAttributeEquivalencyStep() { } - protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IEquivalencyValidator nestedValidator) { } + 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.IEquivalencyValidator nestedValidator) { } + 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.IEquivalencyValidator nestedValidator) { } + protected override FluentAssertions.Equivalency.EquivalencyResult OnHandle(FluentAssertions.Equivalency.Comparands comparands, FluentAssertions.Equivalency.IEquivalencyValidationContext context, FluentAssertions.Equivalency.IValidateChildNodeEquivalency nestedValidator) { } } } namespace FluentAssertions.Equivalency.Tracing @@ -1260,7 +1119,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) { } @@ -1274,6 +1133,12 @@ namespace FluentAssertions.Events public string EventName { get; } public System.Type HandlerType { get; } } + public class EventMonitorOptions + { + public EventMonitorOptions() { } + public FluentAssertions.Events.EventMonitorOptions IgnoringEventAccessorExceptions() { } + public FluentAssertions.Events.EventMonitorOptions RecordingEventsWithBrokenAccessor() { } + } public interface IEventRecording : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable { System.Type EventHandlerType { get; } @@ -1300,75 +1165,60 @@ namespace FluentAssertions.Events } namespace FluentAssertions.Execution { - [System.Serializable] + 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 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() { } + } 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.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 { @@ -1378,28 +1228,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.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; } @@ -1411,6 +1246,12 @@ namespace FluentAssertions.Execution { object Clone(); } + public interface ITestFramework + { + bool IsAvailable { get; } + [System.Diagnostics.CodeAnalysis.DoesNotReturn] + void Throw(string message); + } public class Reason { public Reason(string formattedMessage, object[] arguments) { } @@ -1418,6 +1259,14 @@ namespace FluentAssertions.Execution public string FormattedMessage { get; set; } } } +namespace FluentAssertions.Extensibility +{ + [System.AttributeUsage(System.AttributeTargets.Assembly, AllowMultiple=true)] + public sealed class AssertionEngineInitializerAttribute : System.Attribute + { + public AssertionEngineInitializerAttribute(System.Type type, string methodName) { } + } +} namespace FluentAssertions.Extensions { public static class FluentDateTimeExtensions @@ -1523,7 +1372,6 @@ namespace FluentAssertions.Formatting public class DefaultValueFormatter : FluentAssertions.Formatting.IValueFormatter { public DefaultValueFormatter() { } - protected virtual int SpacesPerIndentionLevel { get; } public virtual bool CanHandle(object value) { } public void Format(object value, FluentAssertions.Formatting.FormattedObjectGraph formattedGraph, FluentAssertions.Formatting.FormattingContext context, FluentAssertions.Formatting.FormatChild formatChild) { } protected virtual System.Reflection.MemberInfo[] GetMembers(System.Type type) { } @@ -1575,7 +1423,8 @@ namespace FluentAssertions.Formatting public static int SpacesPerIndentation { get; } public void AddFragment(string fragment) { } public void AddFragmentOnNewLine(string fragment) { } - public void AddLine(string line) { } + public void AddLine(string content) { } + public void AddLineOrFragment(string fragment) { } public override string ToString() { } public System.IDisposable WithIndentation() { } } @@ -1597,6 +1446,8 @@ namespace FluentAssertions.Formatting public int MaxDepth { get; set; } public int MaxLines { get; set; } public bool UseLineBreaks { get; set; } + public void AddFormatter(FluentAssertions.Formatting.IValueFormatter formatter) { } + public void RemoveFormatter(FluentAssertions.Formatting.IValueFormatter formatter) { } } public class GuidValueFormatter : FluentAssertions.Formatting.IValueFormatter { @@ -1630,8 +1481,12 @@ namespace FluentAssertions.Formatting public class MaxLinesExceededException : System.Exception { public MaxLinesExceededException() { } - 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 { @@ -1739,21 +1594,19 @@ 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) { } - public FluentAssertions.AndConstraint BeEquivalentTo(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeGreaterOrEqualTo(T expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeEquivalentTo(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeGreaterThan(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeGreaterThanOrEqualTo(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeInRange(T minimumValue, T maximumValue, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeLessOrEqualTo(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeLessThan(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeLessThanOrEqualTo(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOneOf(params T[] validValues) { } @@ -1766,37 +1619,32 @@ 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 + public class NullableNumericAssertions : FluentAssertions.Numeric.NumericAssertionsBase where T : struct, System.IComparable where TAssertions : FluentAssertions.Numeric.NullableNumericAssertions { - public NullableNumericAssertions(T? value) { } + public NullableNumericAssertions(T? value, FluentAssertions.Execution.AssertionChain assertionChain) { } + public override T? Subject { get; } 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) { } public FluentAssertions.AndConstraint NotBeNull(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveValue(string because = "", params object[] becauseArgs) { } } - public class NumericAssertions : FluentAssertions.Numeric.NumericAssertions> - where T : struct, System.IComparable - { - public NumericAssertions(T value) { } - } - public class NumericAssertions + public abstract class NumericAssertionsBase where T : struct, System.IComparable - where TAssertions : FluentAssertions.Numeric.NumericAssertions + where TAssertions : FluentAssertions.Numeric.NumericAssertionsBase { - public NumericAssertions(T value) { } - public T? Subject { get; } + protected NumericAssertionsBase(FluentAssertions.Execution.AssertionChain assertionChain) { } + public FluentAssertions.Execution.AssertionChain CurrentAssertionChain { get; } + public abstract TSubject Subject { get; } public FluentAssertions.AndConstraint Be(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Be(T? expected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeGreaterOrEqualTo(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeGreaterThan(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeGreaterThanOrEqualTo(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeInRange(T minimumValue, T maximumValue, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeLessOrEqualTo(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeLessThan(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeLessThanOrEqualTo(T expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeNegative(string because = "", params object[] becauseArgs) { } @@ -1811,17 +1659,29 @@ namespace FluentAssertions.Numeric public FluentAssertions.AndConstraint NotBeInRange(T minimumValue, T maximumValue, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeOfType(System.Type unexpectedType, string because = "", params object[] becauseArgs) { } } + public class NumericAssertions : FluentAssertions.Numeric.NumericAssertions> + where T : struct, System.IComparable + { + public NumericAssertions(T value, FluentAssertions.Execution.AssertionChain assertionChain) { } + } + public class NumericAssertions : FluentAssertions.Numeric.NumericAssertionsBase + where T : struct, System.IComparable + where TAssertions : FluentAssertions.Numeric.NumericAssertions + { + public NumericAssertions(T value, FluentAssertions.Execution.AssertionChain assertionChain) { } + public override T Subject { get; } + } } 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) { } @@ -1832,12 +1692,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) { } @@ -1852,7 +1712,7 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint BeOnOrAfter(System.DateTime expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOnOrBefore(System.DateTime expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOneOf(params System.DateTime[] validValues) { } - public FluentAssertions.AndConstraint BeOneOf(params System.Nullable[] validValues) { } + public FluentAssertions.AndConstraint BeOneOf(params System.DateTime?[] validValues) { } public FluentAssertions.AndConstraint BeOneOf(System.Collections.Generic.IEnumerable validValues, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOneOf(System.Collections.Generic.IEnumerable validValues, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeSameDateAs(System.DateTime expected, string because = "", params object[] becauseArgs) { } @@ -1869,6 +1729,7 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint NotBeAfter(System.DateTime unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeBefore(System.DateTime unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeCloseTo(System.DateTime distantTime, System.TimeSpan precision, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotBeIn(System.DateTimeKind unexpectedKind, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeOnOrAfter(System.DateTime unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeOnOrBefore(System.DateTime unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeSameDateAs(System.DateTime unexpected, string because = "", params object[] becauseArgs) { } @@ -1881,12 +1742,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) { } @@ -1902,7 +1763,7 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint BeOnOrAfter(System.DateTimeOffset expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOnOrBefore(System.DateTimeOffset expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOneOf(params System.DateTimeOffset[] validValues) { } - public FluentAssertions.AndConstraint BeOneOf(params System.Nullable[] validValues) { } + public FluentAssertions.AndConstraint BeOneOf(params System.DateTimeOffset?[] validValues) { } public FluentAssertions.AndConstraint BeOneOf(System.Collections.Generic.IEnumerable validValues, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOneOf(System.Collections.Generic.IEnumerable validValues, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeSameDateAs(System.DateTimeOffset expected, string because = "", params object[] becauseArgs) { } @@ -1936,7 +1797,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) { } @@ -1944,7 +1805,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) { } @@ -1952,13 +1813,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) { } @@ -1985,12 +1846,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) { } @@ -2000,31 +1861,14 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint NotBe(string unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEmpty(string because = "", params object[] becauseArgs) { } } - public class HttpResponseMessageAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions - { - public HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { } - } - public class HttpResponseMessageAssertions : FluentAssertions.Primitives.ObjectAssertions - where TAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions - { - protected HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { } - protected override string Identifier { get; } - public FluentAssertions.AndConstraint BeRedirection(string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeSuccessful(string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint HaveClientError(string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint HaveError(string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint HaveServerError(string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint HaveStatusCode(System.Net.HttpStatusCode expected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint NotHaveStatusCode(System.Net.HttpStatusCode unexpected, string because = "", params object[] becauseArgs) { } - } 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) { } @@ -2036,12 +1880,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) { } @@ -2049,12 +1893,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) { } @@ -2063,13 +1907,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) { } @@ -2077,12 +1921,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) { } @@ -2091,12 +1935,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) { } @@ -2105,7 +1949,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) { } @@ -2113,12 +1957,12 @@ 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) { } public FluentAssertions.AndConstraint BeEquivalentTo(TExpectation expectation, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeEquivalentTo(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeEquivalentTo(TExpectation expectation, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOneOf(params TSubject[] validValues) { } public FluentAssertions.AndConstraint BeOneOf(System.Collections.Generic.IEnumerable validValues, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeOneOf(System.Collections.Generic.IEnumerable validValues, System.Collections.Generic.IEqualityComparer comparer, string because = "", params object[] becauseArgs) { } @@ -2126,12 +1970,13 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint NotBe(TSubject unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBe(TSubject unexpected, System.Collections.Generic.IEqualityComparer comparer, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEquivalentTo(TExpectation unexpected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint NotBeEquivalentTo(TExpectation unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyAssertionOptions> config, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotBeEquivalentTo(TExpectation unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } } 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) { } @@ -2150,22 +1995,22 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint NotBeOfType(System.Type unexpectedType, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeOfType(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeSameAs(TSubject unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint Satisfy(System.Action assertion) + where T : TSubject { } } 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) { } - public FluentAssertions.AndConstraint BeGreaterOrEqualTo(System.TimeSpan expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeGreaterThan(System.TimeSpan expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeGreaterThanOrEqualTo(System.TimeSpan expected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeLessOrEqualTo(System.TimeSpan expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeLessThan(System.TimeSpan expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeLessThanOrEqualTo(System.TimeSpan expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeNegative(string because = "", params object[] becauseArgs) { } @@ -2176,16 +2021,17 @@ 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) { } public FluentAssertions.AndConstraint BeEquivalentTo(string expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeEquivalentTo(string expected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeLowerCased(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeNullOrEmpty(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeNullOrWhiteSpace(string because = "", params object[] becauseArgs) { } @@ -2200,18 +2046,23 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint ContainAny(System.Collections.Generic.IEnumerable values, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint ContainEquivalentOf(string expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint ContainEquivalentOf(string expected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint ContainEquivalentOf(string expected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint ContainEquivalentOf(string expected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint EndWith(string expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint EndWithEquivalentOf(string expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint EndWithEquivalentOf(string expected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveLength(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint Match(string wildcardPattern, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint MatchEquivalentOf(string wildcardPattern, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint MatchRegex(string regularExpression, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint MatchEquivalentOf(string wildcardPattern, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint MatchRegex(System.Text.RegularExpressions.Regex regularExpression, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint MatchRegex(string regularExpression, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint MatchRegex(string regularExpression, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint MatchRegex(System.Text.RegularExpressions.Regex regularExpression, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint MatchRegex(string regularExpression, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBe(string unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEmpty(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEquivalentTo(string unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotBeEquivalentTo(string unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeLowerCased(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeNullOrEmpty(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeNullOrWhiteSpace(string because = "", params object[] becauseArgs) { } @@ -2222,16 +2073,21 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint NotContainAny(params string[] values) { } public FluentAssertions.AndConstraint NotContainAny(System.Collections.Generic.IEnumerable values, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotContainEquivalentOf(string unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotContainEquivalentOf(string unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotEndWith(string unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotEndWithEquivalentOf(string unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotEndWithEquivalentOf(string unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotMatch(string wildcardPattern, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotMatchEquivalentOf(string wildcardPattern, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint NotMatchRegex(string regularExpression, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotMatchEquivalentOf(string wildcardPattern, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotMatchRegex(System.Text.RegularExpressions.Regex regularExpression, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotMatchRegex(string regularExpression, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotStartWith(string unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotStartWithEquivalentOf(string unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotStartWithEquivalentOf(string unexpected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint StartWith(string expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint StartWithEquivalentOf(string expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint StartWithEquivalentOf(string expected, System.Func, FluentAssertions.Equivalency.EquivalencyOptions> config, string because = "", params object[] becauseArgs) { } } public enum TimeSpanCondition { @@ -2242,49 +2098,32 @@ namespace FluentAssertions.Primitives LessThan = 4, } } -namespace FluentAssertions.Reflection -{ - public class AssemblyAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions - { - public AssemblyAssertions(System.Reflection.Assembly assembly) { } - 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) { } - public FluentAssertions.AndWhichConstraint DefineType(string @namespace, string name, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint NotReference(System.Reflection.Assembly assembly, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint Reference(System.Reflection.Assembly assembly, string because = "", params object[] becauseArgs) { } - } -} 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) { } + public FluentAssertions.AndConstraint NotThrowAfter(System.TimeSpan waitTime, System.TimeSpan pollInterval, string because = "", params object[] becauseArgs) { } } public class AsyncFunctionAssertions : FluentAssertions.Specialized.DelegateAssertionsBase, TAssertions> where TTask : System.Threading.Tasks.Task where TAssertions : FluentAssertions.Specialized.AsyncFunctionAssertions { - [System.Obsolete("This class is intended as base class. This ctor is accidentally public and will b" + - "e removed in Version 7.")] - public AsyncFunctionAssertions(System.Func subject, FluentAssertions.Specialized.IExtractExceptions extractor) { } - [System.Obsolete("This class is intended as base class. This ctor is accidentally public and will b" + - "e made protected in Version 7.")] - public 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> CompleteWithinAsync(System.TimeSpan timeSpan, string because = "", params object[] becauseArgs) { } public System.Threading.Tasks.Task> NotCompleteWithinAsync(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 System.Threading.Tasks.Task> NotThrowAsync(string because = "", params object[] becauseArgs) where TException : System.Exception { } + public System.Threading.Tasks.Task> ThrowAsync(string because = "", params object[] becauseArgs) { } public System.Threading.Tasks.Task> ThrowAsync(string because = "", params object[] becauseArgs) where TException : System.Exception { } public System.Threading.Tasks.Task> ThrowExactlyAsync(string because = "", params object[] becauseArgs) where TException : System.Exception { } + public System.Threading.Tasks.Task> ThrowWithinAsync(System.TimeSpan timeSpan, string because = "", params object[] becauseArgs) { } public System.Threading.Tasks.Task> ThrowWithinAsync(System.TimeSpan timeSpan, string because = "", params object[] becauseArgs) where TException : System.Exception { } } @@ -2302,12 +2141,11 @@ 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) { } public FluentAssertions.AndConstraint NotThrow(string because = "", params object[] becauseArgs) where TException : System.Exception { } - public FluentAssertions.AndConstraint NotThrowAfter(System.TimeSpan waitTime, System.TimeSpan pollInterval, string because = "", params object[] becauseArgs) { } + public FluentAssertions.Specialized.ExceptionAssertions Throw(string because = "", params object[] becauseArgs) { } public FluentAssertions.Specialized.ExceptionAssertions Throw(string because = "", params object[] becauseArgs) where TException : System.Exception { } public FluentAssertions.Specialized.ExceptionAssertions ThrowExactly(string because = "", params object[] becauseArgs) @@ -2316,7 +2154,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; } @@ -2338,20 +2176,18 @@ 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 BeGreaterOrEqualTo(System.TimeSpan minDuration, 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) { } - public FluentAssertions.AndConstraint BeLessOrEqualTo(System.TimeSpan maxDuration, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeLessThan(System.TimeSpan maxDuration, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeLessThanOrEqualTo(System.TimeSpan maxDuration, string because = "", params object[] becauseArgs) { } public override bool Equals(object obj) { } } 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) { } @@ -2359,8 +2195,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) { } @@ -2376,8 +2212,11 @@ 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 TaskCompletionSourceAssertionsBase { @@ -2386,8 +2225,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) { } } @@ -2396,25 +2235,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) { } @@ -2438,16 +2277,26 @@ namespace FluentAssertions.Types { public static FluentAssertions.Types.TypeSelector From(System.Reflection.Assembly assembly) { } } + public class AssemblyAssertions : FluentAssertions.Primitives.ReferenceTypeAssertions + { + 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) { } + public FluentAssertions.AndWhichConstraint DefineType(string @namespace, string name, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotReference(System.Reflection.Assembly assembly, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint Reference(System.Reflection.Assembly assembly, string because = "", params object[] becauseArgs) { } + } 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; } } 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; } public FluentAssertions.AndWhichConstraint, TAttribute> BeDecoratedWith(string because = "", params object[] becauseArgs) where TAttribute : System.Attribute { } @@ -2462,13 +2311,13 @@ 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; } public FluentAssertions.AndConstraint BeAsync(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeVirtual(string because = "", params object[] becauseArgs) { } @@ -2512,7 +2361,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) { } @@ -2533,7 +2382,7 @@ 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; } public FluentAssertions.AndConstraint BeReadable(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint BeReadable(FluentAssertions.Common.CSharpAccessModifier accessModifier, string because = "", params object[] becauseArgs) { } @@ -2575,7 +2424,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) @@ -2590,13 +2439,13 @@ 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) { } public FluentAssertions.AndConstraint BeAbstract(string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeAssignableTo(System.Type type, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint BeAssignableTo(string because = "", params object[] becauseArgs) { } + public new FluentAssertions.AndConstraint BeAssignableTo(System.Type type, string because = "", params object[] becauseArgs) { } + public new FluentAssertions.AndConstraint BeAssignableTo(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint BeDecoratedWith(string because = "", params object[] becauseArgs) where TAttribute : System.Attribute { } public FluentAssertions.AndWhichConstraint BeDecoratedWith(System.Linq.Expressions.Expression> isMatchingAttributePredicate, string because = "", params object[] becauseArgs) @@ -2633,8 +2482,8 @@ namespace FluentAssertions.Types public FluentAssertions.AndConstraint NotBe(System.Type unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBe(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeAbstract(string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint NotBeAssignableTo(System.Type type, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint NotBeAssignableTo(string because = "", params object[] becauseArgs) { } + public new FluentAssertions.AndConstraint NotBeAssignableTo(System.Type type, string because = "", params object[] becauseArgs) { } + public new FluentAssertions.AndConstraint NotBeAssignableTo(string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeDecoratedWith(string because = "", params object[] becauseArgs) where TAttribute : System.Attribute { } public FluentAssertions.AndConstraint NotBeDecoratedWith(System.Linq.Expressions.Expression> isMatchingAttributePredicate, string because = "", params object[] becauseArgs) @@ -2708,7 +2557,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 { } @@ -2739,7 +2588,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) { } @@ -2747,38 +2596,56 @@ 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) { } - public FluentAssertions.AndWhichConstraint HaveElement(string expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint HaveElement(System.Xml.Linq.XName expected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint> HaveElement(string expected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint HaveElement(string expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint> HaveElement(System.Xml.Linq.XName expected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint HaveRoot(string expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint> HaveElement(string expected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint HaveElementWithValue(System.Xml.Linq.XName expectedElement, string expectedValue, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint HaveElementWithValue(string expectedElement, string expectedValue, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint HaveRoot(System.Xml.Linq.XName expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint HaveRoot(string expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBe(System.Xml.Linq.XDocument unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEquivalentTo(System.Xml.Linq.XDocument unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveElement(System.Xml.Linq.XName unexpectedElement, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveElement(string unexpectedElement, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveElementWithValue(System.Xml.Linq.XName unexpectedElement, string unexpectedValue, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveElementWithValue(string unexpectedElement, string unexpectedValue, string because = "", params object[] becauseArgs) { } } 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) { } - public FluentAssertions.AndConstraint HaveAttribute(string expectedName, string expectedValue, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndConstraint HaveAttribute(System.Xml.Linq.XName expectedName, string expectedValue, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint HaveElement(string expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveAttribute(System.Xml.Linq.XName expectedName, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveAttribute(string expectedName, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveAttributeWithValue(System.Xml.Linq.XName expectedName, string expectedValue, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveAttributeWithValue(string expectedName, string expectedValue, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint HaveElement(System.Xml.Linq.XName expected, string because = "", params object[] becauseArgs) { } - public FluentAssertions.AndWhichConstraint> HaveElement(string expected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint HaveElement(string expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint> HaveElement(System.Xml.Linq.XName expected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint> HaveElement(string expected, FluentAssertions.OccurrenceConstraint occurrenceConstraint, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint HaveElementWithValue(System.Xml.Linq.XName expectedElement, string expectedValue, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndWhichConstraint HaveElementWithValue(string expectedElement, string expectedValue, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveValue(string expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBe(System.Xml.Linq.XElement unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEquivalentTo(System.Xml.Linq.XElement unexpected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveAttribute(System.Xml.Linq.XName unexpectedName, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveAttribute(string unexpectedName, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveAttributeWithValue(System.Xml.Linq.XName unexpectedName, string unexpectedValue, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveAttributeWithValue(string unexpectedName, string unexpectedValue, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveElement(System.Xml.Linq.XName unexpectedElement, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveElement(string unexpectedElement, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveElementWithValue(System.Xml.Linq.XName unexpectedElement, string unexpectedValue, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveElementWithValue(string unexpectedElement, string unexpectedValue, string because = "", params object[] becauseArgs) { } } 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) { } @@ -2788,13 +2655,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/BeEquivalentToWithDeeplyNestedStructures.cs b/Tests/Benchmarks/BeEquivalentToWithDeeplyNestedStructures.cs index 3cd1072b9e..a247d830b1 100644 --- a/Tests/Benchmarks/BeEquivalentToWithDeeplyNestedStructures.cs +++ b/Tests/Benchmarks/BeEquivalentToWithDeeplyNestedStructures.cs @@ -8,7 +8,7 @@ namespace Benchmarks; [MemoryDiagnoser] [SimpleJob(RuntimeMoniker.Net472)] -[SimpleJob(RuntimeMoniker.Net60)] +[SimpleJob(RuntimeMoniker.Net80)] public class BeEquivalentToWithDeeplyNestedStructures { public class ComplexType diff --git a/Tests/Benchmarks/Benchmarks.csproj b/Tests/Benchmarks/Benchmarks.csproj index 261ff5e0d4..0846be0c90 100644 --- a/Tests/Benchmarks/Benchmarks.csproj +++ b/Tests/Benchmarks/Benchmarks.csproj @@ -1,6 +1,6 @@ - + - net6;net472 + net472;net8.0 True ..\..\Src\FluentAssertions\FluentAssertions.snk Exe @@ -9,14 +9,8 @@ - - - + - - - - - + diff --git a/Tests/Benchmarks/Benchmarks.net472.v3.ncrunchproject b/Tests/Benchmarks/Benchmarks.net472.v3.ncrunchproject new file mode 100644 index 0000000000..95a483b433 --- /dev/null +++ b/Tests/Benchmarks/Benchmarks.net472.v3.ncrunchproject @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/Tests/Benchmarks/CollectionEqual.cs b/Tests/Benchmarks/CollectionEqual.cs index 2173f235cc..38d013a57f 100644 --- a/Tests/Benchmarks/CollectionEqual.cs +++ b/Tests/Benchmarks/CollectionEqual.cs @@ -8,7 +8,7 @@ namespace Benchmarks; [MemoryDiagnoser] [SimpleJob(RuntimeMoniker.Net472)] -[SimpleJob(RuntimeMoniker.Net60)] +[SimpleJob(RuntimeMoniker.Net80)] public class CollectionEqualBenchmarks { private IEnumerable collection1; diff --git a/Tests/Benchmarks/Issue1657.cs b/Tests/Benchmarks/Issue1657.cs index 167bf97771..44f42ad421 100644 --- a/Tests/Benchmarks/Issue1657.cs +++ b/Tests/Benchmarks/Issue1657.cs @@ -9,7 +9,7 @@ namespace Benchmarks; [MemoryDiagnoser] [SimpleJob(RuntimeMoniker.Net472)] -[SimpleJob(RuntimeMoniker.Net60)] +[SimpleJob(RuntimeMoniker.Net80)] public class Issue1657 { private List list; diff --git a/Tests/Benchmarks/LargeDataTableEquivalency.cs b/Tests/Benchmarks/LargeDataTableEquivalency.cs deleted file mode 100644 index ff4b0f9455..0000000000 --- a/Tests/Benchmarks/LargeDataTableEquivalency.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System; -using System.Data; -using System.Linq; -using BenchmarkDotNet.Attributes; -using Bogus; -using FluentAssertions; -using FluentAssertions.Data; - -namespace Benchmarks; - -[MemoryDiagnoser] -public class LargeDataTableEquivalencyBenchmarks -{ - private DataTable dataTable1; - private DataTable dataTable2; - - [Params(10, 100, 1_000, 10_000, 100_000)] - public int RowCount { get; set; } - - [GlobalSetup] - public void GlobalSetup() - { - dataTable1 = CreateDataTable(); - dataTable2 = CreateDataTable(); - - object[] rowData = new object[dataTable1.Columns.Count]; - - var faker = new Faker - { - Random = new Randomizer(localSeed: 1) - }; - - for (int i = 0; i < RowCount; i++) - { - for (int j = 0; j < dataTable1.Columns.Count; j++) - { - Type columnType = dataTable1.Columns[j].DataType; - rowData[j] = GetData(faker, columnType); - } - - dataTable1.Rows.Add(rowData); - dataTable2.Rows.Add(rowData); - } - } - - private static object GetData(Faker faker, Type columnType) - { - return Type.GetTypeCode(columnType) switch - { - TypeCode.Empty or TypeCode.DBNull => null, - TypeCode.Boolean => faker.Random.Bool(), - TypeCode.Char => faker.Lorem.Letter().Single(), - TypeCode.SByte => faker.Random.SByte(), - TypeCode.Byte => faker.Random.Byte(), - TypeCode.Int16 => faker.Random.Short(), - TypeCode.UInt16 => faker.Random.UShort(), - TypeCode.Int32 => faker.Random.Int(), - TypeCode.UInt32 => faker.Random.UInt(), - TypeCode.Int64 => faker.Random.Long(), - TypeCode.UInt64 => faker.Random.ULong(), - TypeCode.Single => faker.Random.Float(), - TypeCode.Double => faker.Random.Double(), - TypeCode.Decimal => faker.Random.Decimal(), - TypeCode.DateTime => faker.Date.Between(DateTime.UtcNow.AddDays(-30), DateTime.UtcNow.AddDays(+30)), - TypeCode.String => faker.Lorem.Lines(1), - _ => GetDefault(faker, columnType), - }; - } - - private static object GetDefault(Faker faker, Type columnType) - { - if (columnType == typeof(TimeSpan)) - { - return faker.Date.Future() - faker.Date.Future(); - } - else if (columnType == typeof(Guid)) - { - return faker.Random.Guid(); - } - - throw new Exception("Unable to populate column of type " + columnType); - } - - private static DataTable CreateDataTable() - { - var table = new DataTable("Test"); - - table.Columns.Add("A", typeof(bool)); - table.Columns.Add("B", typeof(char)); - table.Columns.Add("C", typeof(sbyte)); - table.Columns.Add("D", typeof(byte)); - table.Columns.Add("E", typeof(short)); - table.Columns.Add("F", typeof(ushort)); - table.Columns.Add("G", typeof(int)); - table.Columns.Add("H", typeof(uint)); - table.Columns.Add("I", typeof(long)); - table.Columns.Add("J", typeof(ulong)); - table.Columns.Add("K", typeof(float)); - table.Columns.Add("L", typeof(double)); - table.Columns.Add("M", typeof(decimal)); - table.Columns.Add("N", typeof(DateTime)); - table.Columns.Add("O", typeof(string)); - table.Columns.Add("P", typeof(TimeSpan)); - table.Columns.Add("Q", typeof(Guid)); - - return table; - } - - [Benchmark] - public AndConstraint> BeEquivalentTo() => dataTable1.Should().BeEquivalentTo(dataTable2); -} diff --git a/Tests/Benchmarks/UsersOfGetClosedGenericInterfaces.cs b/Tests/Benchmarks/UsersOfGetClosedGenericInterfaces.cs deleted file mode 100644 index b8167ca9a7..0000000000 --- a/Tests/Benchmarks/UsersOfGetClosedGenericInterfaces.cs +++ /dev/null @@ -1,167 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Engines; -using Bogus; -using FluentAssertions.Equivalency; -using FluentAssertions.Equivalency.Steps; -using FluentAssertions.Equivalency.Tracing; -using FluentAssertions.Execution; - -namespace Benchmarks; - -[SimpleJob(RunStrategy.Throughput, warmupCount: 3, iterationCount: 20)] -public class UsersOfGetClosedGenericInterfaces -{ - private const int ValueCount = 100_000; - - private object[] values; - - private GenericDictionaryEquivalencyStep dictionaryStep; - private GenericEnumerableEquivalencyStep enumerableStep; - - private IEquivalencyValidationContext context; - - private class Context : IEquivalencyValidationContext - { - public INode CurrentNode { get; } - public Reason Reason { get; } - public Tracer Tracer { get; } - public IEquivalencyAssertionOptions Options { get; internal set; } - public bool IsCyclicReference(object expectation) => throw new NotImplementedException(); - - public IEquivalencyValidationContext AsNestedMember(IMember expectationMember) => throw new NotImplementedException(); - - public IEquivalencyValidationContext AsCollectionItem(string index) => throw new NotImplementedException(); - - public IEquivalencyValidationContext AsDictionaryItem(TKey key) => - throw new NotImplementedException(); - - public IEquivalencyValidationContext Clone() => throw new NotImplementedException(); - } - - private class Config : IEquivalencyAssertionOptions - { - public IEnumerable SelectionRules => throw new NotImplementedException(); - - public IEnumerable MatchingRules => throw new NotImplementedException(); - - public bool IsRecursive => throw new NotImplementedException(); - - public bool AllowInfiniteRecursion => throw new NotImplementedException(); - - public CyclicReferenceHandling CyclicReferenceHandling => throw new NotImplementedException(); - - public OrderingRuleCollection OrderingRules => throw new NotImplementedException(); - - public ConversionSelector ConversionSelector => throw new NotImplementedException(); - - public EnumEquivalencyHandling EnumEquivalencyHandling => throw new NotImplementedException(); - - public IEnumerable UserEquivalencySteps => throw new NotImplementedException(); - - public bool UseRuntimeTyping => false; - - public MemberVisibility IncludedProperties => throw new NotImplementedException(); - - public MemberVisibility IncludedFields => throw new NotImplementedException(); - - public bool IgnoreNonBrowsableOnSubject => throw new NotImplementedException(); - - public bool ExcludeNonBrowsableOnExpectation => throw new NotImplementedException(); - - public bool? CompareRecordsByValue => throw new NotImplementedException(); - - public ITraceWriter TraceWriter => throw new NotImplementedException(); - - public EqualityStrategy GetEqualityStrategy(Type type) => throw new NotImplementedException(); - } - - [Params(typeof(DBNull), typeof(bool), typeof(char), typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), - typeof(int), typeof(long), typeof(ulong), typeof(float), typeof(double), typeof(decimal), typeof(DateTime), - typeof(string), typeof(TimeSpan), typeof(Guid), typeof(Dictionary), typeof(IEnumerable))] - public Type DataType { get; set; } - - [GlobalSetup] - [SuppressMessage("Style", "IDE0055:Fix formatting", Justification = "Big long list of one-liners")] - public void GlobalSetup() - { - dictionaryStep = new GenericDictionaryEquivalencyStep(); - enumerableStep = new GenericEnumerableEquivalencyStep(); - - var faker = new Faker - { - Random = new Randomizer(localSeed: 1) - }; - - values = Enumerable.Range(0, ValueCount).Select(_ => CreateValue(faker)).ToArray(); - - context = new Context - { - Options = new Config() - }; - } - - private object CreateValue(Faker faker) => Type.GetTypeCode(DataType) switch - { - TypeCode.DBNull => DBNull.Value, - TypeCode.Boolean => faker.Random.Bool(), - TypeCode.Char => faker.Lorem.Letter().Single(), - TypeCode.SByte => faker.Random.SByte(), - TypeCode.Byte => faker.Random.Byte(), - TypeCode.Int16 => faker.Random.Short(), - TypeCode.UInt16 => faker.Random.UShort(), - TypeCode.Int32 => faker.Random.Int(), - TypeCode.UInt32 => faker.Random.UInt(), - TypeCode.Int64 => faker.Random.Long(), - TypeCode.UInt64 => faker.Random.ULong(), - TypeCode.Single => faker.Random.Float(), - TypeCode.Double => faker.Random.Double(), - TypeCode.Decimal => faker.Random.Decimal(), - TypeCode.DateTime => faker.Date.Between(DateTime.UtcNow.AddDays(-30), DateTime.UtcNow.AddDays(+30)), - TypeCode.String => faker.Lorem.Lines(1), - _ => CustomValue(faker), - }; - - private object CustomValue(Faker faker) - { - if (DataType == typeof(TimeSpan)) - { - return faker.Date.Future() - faker.Date.Future(); - } - else if (DataType == typeof(Guid)) - { - return faker.Random.Guid(); - } - else if (DataType == typeof(Dictionary)) - { - return new Dictionary { { faker.Random.Int(), faker.Random.Int() } }; - } - else if (DataType == typeof(IEnumerable)) - { - return new[] { faker.Random.Int(), faker.Random.Int() }; - } - - throw new Exception("Unable to populate data of type " + DataType); - } - - [Benchmark] - public void GenericDictionaryEquivalencyStep_CanHandle() - { - for (int i = 0; i < values.Length; i++) - { - dictionaryStep.Handle(new Comparands(values[i], values[0], typeof(object)), context, null); - } - } - - [Benchmark] - public void GenericEnumerableEquivalencyStep_CanHandle() - { - for (int i = 0; i < values.Length; i++) - { - enumerableStep.Handle(new Comparands(values[i], values[0], typeof(object)), context, null); - } - } -} diff --git a/Tests/ExampleExtensions/AssemblyInfo.cs b/Tests/ExampleExtensions/AssemblyInfo.cs new file mode 100644 index 0000000000..520561d30e --- /dev/null +++ b/Tests/ExampleExtensions/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using FluentAssertions; + +[assembly: CustomAssertionsAssembly] diff --git a/Tests/ExampleExtensions/ExampleExtensions.csproj b/Tests/ExampleExtensions/ExampleExtensions.csproj new file mode 100644 index 0000000000..c8eedf388c --- /dev/null +++ b/Tests/ExampleExtensions/ExampleExtensions.csproj @@ -0,0 +1,16 @@ + + + + netstandard2.0;net6.0 + enable + disable + false + True + ..\..\Src\FluentAssertions\FluentAssertions.snk + + + + + + + diff --git a/Tests/ExampleExtensions/StringAssertionExtensions.cs b/Tests/ExampleExtensions/StringAssertionExtensions.cs new file mode 100644 index 0000000000..4ed34ab39f --- /dev/null +++ b/Tests/ExampleExtensions/StringAssertionExtensions.cs @@ -0,0 +1,16 @@ +using FluentAssertions; +using FluentAssertions.Primitives; + +namespace ExampleExtensions; + +public static class StringAssertionExtensions +{ + public static void BePalindromic(this StringAssertions assertions) + { + char[] charArray = assertions.Subject.ToCharArray(); + Array.Reverse(charArray); + string reversedSubject = new string(charArray); + + assertions.Subject.Should().Be(reversedSubject); + } +} diff --git a/Tests/FSharp.Specs/FSharp.Specs.fsproj b/Tests/FSharp.Specs/FSharp.Specs.fsproj index dbd0ceceeb..529597fa17 100644 --- a/Tests/FSharp.Specs/FSharp.Specs.fsproj +++ b/Tests/FSharp.Specs/FSharp.Specs.fsproj @@ -1,8 +1,9 @@ - + net6.0 6.0 + true @@ -10,13 +11,14 @@ - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/Tests/FluentAssertions.Equivalency.Specs/AssertionRuleSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/AssertionRuleSpecs.cs index 3a17770ceb..4207daa471 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/AssertionRuleSpecs.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/AssertionRuleSpecs.cs @@ -238,7 +238,7 @@ public void When_the_runtime_type_does_match_the_equality_comparer_type_it_shoul // Act Action act = () => subject.Should().BeEquivalentTo(expectation, opt => opt - .RespectingRuntimeTypes() + .PreferringRuntimeMemberTypes() .Using()); // Assert @@ -289,7 +289,7 @@ public void An_equality_comparer_of_non_nullable_type_is_invoked_on_non_nullable // Act subject.Should().BeEquivalentTo(expectation, opt => opt - .RespectingRuntimeTypes() + .PreferringRuntimeMemberTypes() .Using(new DateTimeByYearComparer())); } @@ -303,7 +303,7 @@ public void An_equality_comparer_of_nullable_type_is_not_invoked_on_non_nullable // Act Action act = () => subject.Should().BeEquivalentTo(expectation, opt => opt - .RespectingRuntimeTypes() + .PreferringRuntimeMemberTypes() .Using(new NullableDateTimeByYearComparer())); // Assert diff --git a/Tests/FluentAssertions.Equivalency.Specs/BasicSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/BasicSpecs.cs index 85b8fd12a2..d643cdcb77 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/BasicSpecs.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/BasicSpecs.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Net; using FluentAssertions.Extensions; using Xunit; @@ -57,9 +58,9 @@ public void When_expectation_is_null_it_should_throw() public void When_comparing_nested_collection_with_a_null_value_it_should_fail_with_the_correct_message() { // Arrange - var subject = new[] { new MyClass { Items = new[] { "a" } } }; + MyClass[] subject = [new MyClass { Items = ["a"] }]; - var expectation = new[] { new MyClass() }; + MyClass[] expectation = [new MyClass()]; // Act Action act = () => subject.Should().BeEquivalentTo(expectation); @@ -194,6 +195,7 @@ public class VirtualClass { public string Property { get; set; } + [SuppressMessage("Class Design", "AV1010:Member hides inherited member")] public new virtual bool Equals(object obj) { return obj is VirtualClass other && other.Property == Property; @@ -209,19 +211,15 @@ public class VirtualClassOverride : VirtualClass public void When_treating_a_value_type_in_a_collection_as_a_complex_type_it_should_compare_them_by_members() { // Arrange - var subject = new[] { new ClassWithValueSemanticsOnSingleProperty { Key = "SameKey", NestedProperty = "SomeValue" } }; - - var expected = new[] - { - new ClassWithValueSemanticsOnSingleProperty { Key = "SameKey", NestedProperty = "OtherValue" } - }; + ClassWithValueSemanticsOnSingleProperty[] subject = [new() { Key = "SameKey", NestedProperty = "SomeValue" }]; + ClassWithValueSemanticsOnSingleProperty[] expected = [new() { Key = "SameKey", NestedProperty = "OtherValue" }]; // Act Action act = () => subject.Should().BeEquivalentTo(expected, options => options.ComparingByMembers()); // Assert - act.Should().Throw().WithMessage("*NestedProperty*OtherValue*SomeValue*"); + act.Should().Throw().WithMessage("*NestedProperty*SomeValue*OtherValue*"); } [Fact] @@ -237,7 +235,7 @@ public void When_treating_a_value_type_as_a_complex_type_it_should_compare_them_ options => options.ComparingByMembers()); // Assert - act.Should().Throw().WithMessage("*NestedProperty*OtherValue*SomeValue*"); + act.Should().Throw().WithMessage("*NestedProperty*SomeValue*OtherValue*"); } [Fact] @@ -344,8 +342,8 @@ public void When_treating_a_null_type_as_reference_type_it_should_throw() public void When_comparing_an_open_type_by_members_it_should_succeed() { // Arrange - var subject = new Option(new[] { 1, 3, 2 }); - var expected = new Option(new[] { 1, 2, 3 }); + var subject = new Option([1, 3, 2]); + var expected = new Option([1, 2, 3]); // Act Action act = () => subject.Should().BeEquivalentTo(expected, opt => opt @@ -359,8 +357,8 @@ public void When_comparing_an_open_type_by_members_it_should_succeed() public void When_threating_open_type_as_reference_type_and_a_closed_type_as_value_type_it_should_compare_by_value() { // Arrange - var subject = new Option(new[] { 1, 3, 2 }); - var expected = new Option(new[] { 1, 2, 3 }); + var subject = new Option([1, 3, 2]); + var expected = new Option([1, 2, 3]); // Act Action act = () => subject.Should().BeEquivalentTo(expected, opt => opt @@ -375,8 +373,8 @@ public void When_threating_open_type_as_reference_type_and_a_closed_type_as_valu public void When_threating_open_type_as_value_type_and_a_closed_type_as_reference_type_it_should_compare_by_members() { // Arrange - var subject = new Option(new[] { 1, 3, 2 }); - var expected = new Option(new[] { 1, 2, 3 }); + var subject = new Option([1, 3, 2]); + var expected = new Option([1, 2, 3]); // Act Action act = () => subject.Should().BeEquivalentTo(expected, opt => opt @@ -569,7 +567,7 @@ public void When_a_field_on_the_subject_matches_a_property_the_members_should_ma Action act = () => onlyAField.Should().BeEquivalentTo(onlyAProperty); // Assert - act.Should().Throw().WithMessage("Expected property onlyAField.Value*to be 101, but found 1.*"); + act.Should().Throw().WithMessage("Expected field onlyAField.Value*to be 101, but found 1.*"); } [Fact] @@ -584,7 +582,7 @@ public void When_asserting_equivalence_including_only_fields_it_should_not_match // Assert act.Should().Throw() - .WithMessage("Expectation has field onlyAProperty.Value that the other object does not have.*"); + .WithMessage("Expectation has field Value that the other object does not have.*"); } [Fact] @@ -599,12 +597,11 @@ public void When_asserting_equivalence_including_only_properties_it_should_not_m // Assert act.Should().Throw() - .WithMessage("Expectation has property onlyAField.Value that the other object does not have*"); + .WithMessage("Expectation has property Value that the other object does not have*"); } [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 128514b678..15e8bd9b0d 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/CollectionSpecs.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/CollectionSpecs.cs @@ -146,7 +146,7 @@ private class SelectNoMembersSelectionRule : IMemberSelectionRule public IEnumerable SelectMembers(INode currentNode, IEnumerable selectedMembers, MemberSelectionContext context) { - return Enumerable.Empty(); + return []; } bool IMemberSelectionRule.IncludesMembers => OverridesStandardIncludeRules; @@ -154,12 +154,10 @@ public IEnumerable SelectMembers(INode currentNode, IEnumerable> innerRoles = new(); + private readonly Dictionary> innerRoles = []; public virtual Dictionary> Roles - { - get { return innerRoles.ToDictionary(x => x.Key, y => y.Value.Select(z => z)); } - } + => innerRoles.ToDictionary(x => x.Key, y => y.Value.Select(z => z)); public void Add(Guid userId, params string[] roles) { @@ -202,7 +200,7 @@ public void When_the_expectation_has_fewer_dimensions_than_a_multi_dimensional_s object objectA = new(); object objectB = new(); - var actual = new[] { new[] { objectA, objectB } }; + object[][] actual = [[objectA, objectB]]; var expected = actual[0]; // Act @@ -281,9 +279,9 @@ public void public void When_a_collection_does_not_match_it_should_include_items_in_message() { // Arrange - var subject = new[] { 1, 2 }; + int[] subject = [1, 2]; - var expectation = new[] { 3, 2, 1 }; + int[] expectation = [3, 2, 1]; // Act Action action = () => subject.Should().BeEquivalentTo(expectation); @@ -297,11 +295,9 @@ public void When_a_collection_does_not_match_it_should_include_items_in_message( public void When_collection_of_same_count_does_not_match_it_should_include_at_most_10_items_in_message() { // Arrange - const int commonLength = 11; - // Subjects contains different values, because we want to distinguish them in the assertion message - var subject = new int[commonLength] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; - var expectation = Enumerable.Repeat(20, commonLength).ToArray(); + var subject = new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + var expectation = Enumerable.Repeat(20, subject.Length).ToArray(); // Act Action action = () => subject.Should().BeEquivalentTo(expectation); @@ -315,7 +311,10 @@ public void When_collection_of_same_count_does_not_match_it_should_include_at_mo public void When_a_nullable_collection_does_not_match_it_should_throw() { // Arrange - var subject = new { Values = (ImmutableArray?)ImmutableArray.Create(1, 2, 3) }; + var subject = new + { + Values = (ImmutableArray?)ImmutableArray.Create(1, 2, 3) + }; // Act Action act = () => subject.Should().BeEquivalentTo(new @@ -337,13 +336,13 @@ public void var logbookEntry = new LogbookEntryProjection { Logbook = logbook, - LogbookRelations = new[] { new LogbookRelation { Logbook = logbook } } + LogbookRelations = [new LogbookRelation { Logbook = logbook }] }; var equivalentLogbookEntry = new LogbookEntryProjection { Logbook = logbook, - LogbookRelations = new[] { new LogbookRelation { Logbook = logbook } } + LogbookRelations = [new LogbookRelation { Logbook = logbook }] }; // Act @@ -439,12 +438,11 @@ public void When_a_collection_property_contains_objects_with_matching_properties public void When_two_deeply_nested_collections_are_equivalent_while_ignoring_the_order_it_should_not_throw() { // Arrange - var items = new[] { new int[0], new[] { 42 } }; + int[][] subject = [[], [42]]; + int[][] expectation = [[42], []]; // Act / Assert - items.Should().BeEquivalentTo( - new[] { new[] { 42 }, new int[0] } - ); + subject.Should().BeEquivalentTo(expectation); } [Fact] @@ -471,7 +469,7 @@ public void When_the_subject_is_a_non_generic_collection_it_should_still_work() { // Arrange object item = new(); - object[] array = { item }; + object[] array = [item]; IList readOnlyList = ArrayList.ReadOnly(array); // Act / Assert @@ -642,7 +640,7 @@ public void When_property_in_collection_is_excluded_it_should_not_throw_if_root_ }; // Act / Assert - new[] { subject }.Should().BeEquivalentTo(new[] { expected }, + new[] { subject }.Should().BeEquivalentTo([expected], options => options .For(x => x.Level.Collection) .Exclude(x => x.Number)); @@ -1101,7 +1099,7 @@ public void // Act Action act = - () => collection1.Should().BeEquivalentTo(collection2, opts => opts.RespectingRuntimeTypes()); + () => collection1.Should().BeEquivalentTo(collection2, opts => opts.PreferringRuntimeMemberTypes()); // Assert act.Should().Throw("the runtime type is assignable to two IEnumerable interfaces") @@ -1117,7 +1115,7 @@ public void When_a_specific_property_is_included_it_should_ignore_the_rest_of_th var expected = new { A = "aaa", B = "ccc" }; // Act - Action act = () => result.Should().BeEquivalentTo(new[] { expected }, options => options.Including(x => x.A)); + Action act = () => result.Should().BeEquivalentTo([expected], options => options.Including(x => x.A)); // Assert act.Should().NotThrow(); @@ -1133,7 +1131,7 @@ public void // Act Action act = - () => collection1.Should().BeEquivalentTo(collection2, opts => opts.RespectingRuntimeTypes()); + () => collection1.Should().BeEquivalentTo(collection2, opts => opts.PreferringRuntimeMemberTypes()); // Assert act.Should().Throw("the items have different runtime types"); @@ -1169,7 +1167,7 @@ public void When_all_strings_in_the_collection_are_equal_to_the_expected_string_ public void When_some_string_in_the_collection_is_not_equal_to_the_expected_string_it_should_throw() { // Arrange - var subject = new[] { "one", "two", "six" }; + string[] subject = ["one", "two", "six"]; // Act Action action = () => subject.Should().AllBe("one"); @@ -1184,7 +1182,7 @@ public void When_some_string_in_the_collection_is_not_equal_to_the_expected_stri public void When_some_string_in_the_collection_is_in_different_case_than_expected_string_it_should_throw() { // Arrange - var subject = new[] { "one", "One", "ONE" }; + string[] subject = ["one", "One", "ONE"]; // Act Action action = () => subject.Should().AllBe("one"); @@ -1295,7 +1293,7 @@ public void When_all_subject_items_are_equivalent_to_expectation_object_it_shoul public void When_some_subject_items_are_not_equivalent_to_expectation_object_it_should_throw() { // Arrange - var subject = new[] { 1, 2, 3 }; + int[] subject = [1, 2, 3]; // Act Action action = () => subject.Should().AllBeEquivalentTo(1); @@ -1469,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] @@ -1605,19 +1602,33 @@ public void When_an_unordered_collection_must_not_be_strict_using_an_expression_ .WithMessage("*not strict*"); } + [Fact] + public void Can_request_strict_ordering_using_an_expression_that_points_to_the_collection_items() + { + // Arrange + var subject = new List { "first", "second" }; + + var expectation = new List { "second", "first" }; + + var act = () => subject.Should().BeEquivalentTo(expectation, + options => options.WithStrictOrderingFor(v => v)); + + act.Should().Throw().WithMessage("Expected*second*but*first*"); + } + [Fact] public void When_asserting_equivalence_of_collections_and_configured_to_use_runtime_properties_it_should_respect_the_runtime_type() { // Arrange - ICollection collection1 = new NonGenericCollection(new[] { new Customer() }); - ICollection collection2 = new NonGenericCollection(new[] { new Car() }); + ICollection collection1 = new NonGenericCollection([new Customer()]); + ICollection collection2 = new NonGenericCollection([new Car()]); // Act Action act = () => collection1.Should().BeEquivalentTo(collection2, - opts => opts.RespectingRuntimeTypes()); + opts => opts.PreferringRuntimeMemberTypes()); // Assert act.Should().Throw("the types have different properties"); @@ -1641,8 +1652,8 @@ public void When_asserting_equivalence_of_generic_collections_it_should_respect_ public void When_asserting_equivalence_of_non_generic_collections_it_should_respect_the_runtime_type() { // Arrange - ICollection subject = new NonGenericCollection(new[] { new Customer() }); - ICollection expectation = new NonGenericCollection(new[] { new Car() }); + ICollection subject = new NonGenericCollection([new Customer()]); + ICollection expectation = new NonGenericCollection([new Car()]); // Act Action act = () => subject.Should().BeEquivalentTo(expectation); @@ -1682,7 +1693,7 @@ public void When_custom_assertion_rules_are_utilized_the_rules_should_be_respect public void When_expectation_is_null_enumerable_it_should_throw() { // Arrange - var subject = Enumerable.Empty(); + IEnumerable subject = []; // Act Action act = () => subject.Should().BeEquivalentTo((IEnumerable)null); @@ -1712,7 +1723,7 @@ public void When_nested_objects_are_excluded_from_collections_it_should_use_simp IList expectationList = new List { expectation }; // Act - Action act = () => actualList.Should().BeEquivalentTo(expectationList, opt => opt.ExcludingNestedObjects()); + Action act = () => actualList.Should().BeEquivalentTo(expectationList, opt => opt.WithoutRecursing()); // Assert act.Should().NotThrow(); @@ -1970,7 +1981,7 @@ public void When_subject_is_null_and_expectation_is_enumerable_it_should_throw() public void When_the_expectation_is_null_it_should_throw() { // Arrange - var actual = new[,] + int[,] actual = { { 1, 2, 3 }, { 4, 5, 6 } @@ -1990,7 +2001,7 @@ public void When_a_multi_dimensional_array_is_compared_to_null_it_should_throw() // Arrange Array actual = null; - var expectation = new[,] + int[,] expectation = { { 1, 2, 3 }, { 4, 5, 6 } @@ -2010,7 +2021,7 @@ public void When_a_multi_dimensional_array_is_compared_to_a_non_array_it_should_ // Arrange var actual = new object(); - var expectation = new[,] + int[,] expectation = { { 1, 2, 3 }, { 4, 5, 6 } @@ -2028,13 +2039,13 @@ public void When_a_multi_dimensional_array_is_compared_to_a_non_array_it_should_ public void When_the_length_of_the_2nd_dimension_differs_between_the_arrays_it_should_throw() { // Arrange - var actual = new[,] + int[,] actual = { { 1, 2, 3 }, { 4, 5, 6 } }; - var expectation = new[,] { { 1, 2, 3 } }; + int[,] expectation = { { 1, 2, 3 } }; // Act Action act = () => actual.Should().BeEquivalentTo(expectation); @@ -2048,13 +2059,13 @@ public void When_the_length_of_the_2nd_dimension_differs_between_the_arrays_it_s public void When_the_length_of_the_first_dimension_differs_between_the_arrays_it_should_throw() { // Arrange - var actual = new[,] + int[,] actual = { { 1, 2, 3 }, { 4, 5, 6 } }; - var expectation = new[,] + int[,] expectation = { { 1, 2 }, { 4, 5 } @@ -2073,7 +2084,7 @@ public void When_the_number_of_dimensions_of_the_arrays_are_not_the_same_it_shou { // Arrange #pragma warning disable format // VS and Rider disagree on how to format a multidimensional array initializer - var actual = new[,,] + int[,,] actual = { { { 1 }, @@ -2088,7 +2099,7 @@ public void When_the_number_of_dimensions_of_the_arrays_are_not_the_same_it_shou }; #pragma warning restore format - var expectation = new[,] + int[,] expectation = { { 1, 2, 3 }, { 4, 5, 6 } @@ -2141,13 +2152,13 @@ public void // Arrange var company1 = new MyCompany { Name = "Company" }; var user1 = new MyUser { Name = "User", Company = company1 }; - company1.Users = new List { user1 }; + company1.Users = [user1]; var logo1 = new MyCompanyLogo { Url = "blank", Company = company1, CreatedBy = user1 }; company1.Logo = logo1; var company2 = new MyCompany { Name = "Company" }; var user2 = new MyUser { Name = "User", Company = company2 }; - company2.Users = new List { user2 }; + company2.Users = [user2]; var logo2 = new MyCompanyLogo { Url = "blank", Company = company2, CreatedBy = user2 }; company2.Logo = logo2; @@ -2307,8 +2318,8 @@ public void When_two_collections_have_properties_of_the_contained_items_excluded_but_still_differ_it_should_throw() { // Arrange - var list1 = new[] { new KeyValuePair(1, 123) }; - var list2 = new[] { new KeyValuePair(2, 321) }; + KeyValuePair[] list1 = [new(1, 123)]; + KeyValuePair[] list2 = [new(2, 321)]; // Act Action act = () => list1.Should().BeEquivalentTo(list2, config => config @@ -2408,13 +2419,13 @@ public void When_two_lists_only_differ_in_excluded_properties_it_should_not_thro public void When_two_multi_dimensional_arrays_are_equivalent_it_should_not_throw() { // Arrange - var subject = new[,] + int[,] subject = { { 1, 2, 3 }, { 4, 5, 6 } }; - var expectation = new[,] + int[,] expectation = { { 1, 2, 3 }, { 4, 5, 6 } @@ -2431,13 +2442,13 @@ public void When_two_multi_dimensional_arrays_are_equivalent_it_should_not_throw public void When_two_multi_dimensional_arrays_are_not_equivalent_it_should_throw() { // Arrange - var actual = new[,] + int[,] actual = { { 1, 2, 3 }, { 4, 5, 6 } }; - var expectation = new[,] + int[,] expectation = { { 1, 2, 4 }, { 4, -5, 6 } @@ -2560,10 +2571,13 @@ public void When_two_ordered_lists_are_structurally_equivalent_it_should_succeed public void When_two_unordered_lists_are_structurally_equivalent_and_order_is_strict_it_should_fail() { // Arrange - var subject = new[] - { - new Customer { Name = "John", Age = 27, Id = 1 }, new Customer { Name = "Jane", Age = 24, Id = 2 } - }; + Customer[] subject = + [ + new() + { Name = "John", Age = 27, Id = 1 }, + new() + { Name = "Jane", Age = 24, Id = 2 } + ]; var expectation = new Collection { @@ -2584,10 +2598,13 @@ public void When_two_unordered_lists_are_structurally_equivalent_and_order_is_st public void When_two_unordered_lists_are_structurally_equivalent_and_order_was_reset_to_strict_it_should_fail() { // Arrange - var subject = new[] - { - new Customer { Name = "John", Age = 27, Id = 1 }, new Customer { Name = "Jane", Age = 24, Id = 2 } - }; + Customer[] subject = + [ + new() + { Name = "John", Age = 27, Id = 1 }, + new() + { Name = "Jane", Age = 24, Id = 2 } + ]; var expectation = new Collection { @@ -2613,10 +2630,13 @@ public void When_two_unordered_lists_are_structurally_equivalent_and_order_was_r public void When_two_unordered_lists_are_structurally_equivalent_and_order_was_reset_to_not_strict_it_should_succeed() { // Arrange - var subject = new[] - { - new Customer { Name = "John", Age = 27, Id = 1 }, new Customer { Name = "Jane", Age = 24, Id = 2 } - }; + Customer[] subject = + [ + new() + { Name = "John", Age = 27, Id = 1 }, + new() + { Name = "Jane", Age = 24, Id = 2 } + ]; var expectation = new Collection { @@ -2636,10 +2656,13 @@ public void When_two_unordered_lists_are_structurally_equivalent_and_order_was_r public void When_two_unordered_lists_are_structurally_equivalent_it_should_succeed() { // Arrange - var subject = new[] - { - new Customer { Name = "John", Age = 27, Id = 1 }, new Customer { Name = "Jane", Age = 24, Id = 2 } - }; + Customer[] subject = + [ + new() + { Name = "John", Age = 27, Id = 1 }, + new() + { Name = "Jane", Age = 24, Id = 2 } + ]; var expectation = new Collection { @@ -2700,7 +2723,9 @@ public void When_two_unordered_lists_contain_null_in_expectation_it_should_throw [Theory] [MemberData(nameof(ArrayTestData))] public void When_two_unordered_lists_contain_empty_objects_they_should_still_be_structurally_equivalent(TActual[] actual, TExpected[] expected) +#pragma warning restore xUnit1039 { // Act Action act = () => actual.Should().BeEquivalentTo(expected); @@ -2718,7 +2743,7 @@ public void When_an_exception_is_thrown_during_data_access_the_stack_trace_conta var genericCollectionB = new List { new() }; var expectedTargetSite = typeof(ExceptionThrowingClass) - .GetProperty(nameof(ExceptionThrowingClass.ExceptionThrowingProperty)).GetMethod; + .GetProperty(nameof(ExceptionThrowingClass.ExceptionThrowingProperty))!.GetMethod; // Act Action act = () => genericCollectionA.Should().BeEquivalentTo(genericCollectionB); @@ -2727,17 +2752,26 @@ public void When_an_exception_is_thrown_during_data_access_the_stack_trace_conta act.Should().Throw().And.TargetSite.Should().Be(expectedTargetSite); } - public static IEnumerable ArrayTestData() + public static TheoryData ArrayTestData() { var arrays = new object[] { new int?[] { null, 1 }, new int?[] { 1, null }, new object[] { null, 1 }, new object[] { 1, null } }; - return + var pairs = from x in arrays from y in arrays - select new[] { x, y }; + select (x, y); + + var data = new TheoryData(); + + foreach (var (x, y) in pairs) + { + data.Add(x, y); + } + + return data; } [Fact] diff --git a/Tests/FluentAssertions.Equivalency.Specs/ConfigurationSpecsDefinition.cs b/Tests/FluentAssertions.Equivalency.Specs/ConfigurationSpecsDefinition.cs new file mode 100644 index 0000000000..c169880e82 --- /dev/null +++ b/Tests/FluentAssertions.Equivalency.Specs/ConfigurationSpecsDefinition.cs @@ -0,0 +1,7 @@ +using Xunit; + +namespace FluentAssertions.Equivalency.Specs; + +// Due to tests that call the static AssertionConfiguration or AssertionEngine, we need to disable parallelization +[CollectionDefinition("ConfigurationSpecs", DisableParallelization = true)] +public class ConfigurationSpecsDefinition; diff --git a/Tests/FluentAssertions.Equivalency.Specs/CyclicReferencesSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/CyclicReferencesSpecs.cs index b57ed1a148..e4e3ee97cd 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/CyclicReferencesSpecs.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/CyclicReferencesSpecs.cs @@ -305,7 +305,7 @@ public void Can_detect_cyclic_references_in_enumerables() // Assert Action act = () => actual.Should().BeEquivalentTo( - new[] { new SelfReturningEnumerable(), new SelfReturningEnumerable() }, + [new SelfReturningEnumerable(), new SelfReturningEnumerable()], "we want to test the failure {0}", "message"); // Assert @@ -411,7 +411,7 @@ public void Allow_ignoring_cyclic_references_in_value_types_compared_by_members( // Assert act.Should().Throw() - .WithMessage("*subject.Next.Title*Second*SecondDifferent*") + .WithMessage("*subject.Next.Title*SecondDifferent*Second*") .Which.Message.Should().NotContain("maximum recursion depth was reached"); } diff --git a/Tests/FluentAssertions.Equivalency.Specs/DataColumnSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/DataColumnSpecs.cs deleted file mode 100644 index bc6481daeb..0000000000 --- a/Tests/FluentAssertions.Equivalency.Specs/DataColumnSpecs.cs +++ /dev/null @@ -1,785 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using Xunit; -using Xunit.Sdk; - -namespace FluentAssertions.Equivalency.Specs; - -public class DataColumnSpecs : DataSpecs -{ - [Fact] - public void When_DataColumns_are_identical_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable1 = dataSet1.TypedDataTable1; - var dataTable2 = dataSet2.TypedDataTable1; - - // Act & Assert - dataTable1.RowIDColumn.Should().BeEquivalentTo(dataTable2.RowIDColumn); - } - - [Fact] - public void When_DataColumns_are_both_null_it_should_succeed() - { - // Act & Assert - ((DataColumn)null).Should().BeEquivalentTo(null); - } - - [Fact] - public void When_DataColumn_is_null_and_isnt_expected_to_be_it_should_fail() - { - // Arrange - var dataSet = CreateDummyDataSet(); - - var dataTable = dataSet.TypedDataTable1; - - // Act - Action action = () => ((DataColumn)null).Should().BeEquivalentTo(dataTable.RowIDColumn); - - // Assert - action.Should().Throw().WithMessage( - "Expected *to be non-null, but found null*"); - } - - [Fact] - public void When_DataColumn_is_expected_to_be_null_and_isnt_it_should_fail() - { - // Arrange - var dataSet = CreateDummyDataSet(); - - var dataTable = dataSet.TypedDataTable1; - - // Act - Action action = () => dataTable.RowIDColumn.Should().BeEquivalentTo(null); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_DataColumn_has_changes_but_is_excluded_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataColumn1 = dataSet1.TypedDataTable1.DecimalColumn; - var dataColumn2 = dataSet2.TypedDataTable1.DecimalColumn; - - dataColumn2.Unique = true; - dataColumn2.Caption = "Test"; - - // Act & Assert - dataColumn1.Should().BeEquivalentTo(dataColumn2, options => options - .ExcludingColumn(dataColumn2)); - } - - [Fact] - public void Can_exclude_a_column_from_all_tables() - { - // Arrange - var subjectDataSet = CreateDummyDataSet(); - var expectationDataSet = new TypedDataSetSubclass(subjectDataSet); - - var subject = subjectDataSet.TypedDataTable1.DecimalColumn; - var expectation = expectationDataSet.TypedDataTable1.DecimalColumn; - - expectation.Unique = true; - expectation.Caption = "Test"; - - // Act & Assert - subject.Should().BeEquivalentTo(expectation, options => options - .ExcludingColumnInAllTables(expectation.ColumnName)); - } - - [Fact] - public void When_DataColumn_has_changes_but_is_excluded_as_params_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataColumn1 = dataSet1.TypedDataTable1.DecimalColumn; - var dataColumn2 = dataSet2.TypedDataTable1.DecimalColumn; - - dataColumn2.Unique = true; - dataColumn2.Caption = "Test"; - - // Act & Assert - dataColumn1.Should().BeEquivalentTo(dataColumn2, options => options - .ExcludingColumns(dataColumn2)); - } - - [Fact] - public void When_DataColumn_has_changes_but_is_excluded_as_enumerable_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataColumn1 = dataSet1.TypedDataTable1.DecimalColumn; - var dataColumn2 = dataSet2.TypedDataTable1.DecimalColumn; - - dataColumn2.Unique = true; - dataColumn2.Caption = "Test"; - - // Act & Assert - IEnumerable excludedColumns = new[] { dataColumn2 }; - - dataColumn1.Should().BeEquivalentTo(dataColumn2, options => options - .ExcludingColumns(excludedColumns)); - } - - [Fact] - public void When_DataColumn_has_changes_but_is_excluded_it_should_succeed_when_compared_via_DataTable() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable1 = dataSet1.TypedDataTable1; - var dataTable2 = dataSet2.TypedDataTable1; - - dataTable2.DecimalColumn.Unique = true; - dataTable2.DecimalColumn.Caption = "Test"; - - // Act & Assert - dataTable1.Should().BeEquivalentTo(dataTable2, options => options - .ExcludingColumn(dataTable2.DecimalColumn) - .ExcludingRelated(dataTable => dataTable.Constraints)); - } - - [Fact] - public void When_DataColumn_has_changes_but_is_excluded_it_should_succeed_when_compared_via_DataSet() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable2 = dataSet2.TypedDataTable1; - - dataTable2.DecimalColumn.Unique = true; - dataTable2.DecimalColumn.Caption = "Test"; - - // Act & Assert - dataSet1.Should().BeEquivalentTo(dataSet2, options => options - .ExcludingColumn(dataTable2.DecimalColumn) - .ExcludingRelated(dataTable => dataTable.Constraints)); - } - - [Fact] - public void When_ColumnName_does_not_match_and_property_is_not_excluded_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataColumn1 = dataSet1.TypedDataTable1.DecimalColumn; - var dataColumn2 = dataSet2.TypedDataTable1.DecimalColumn; - - dataColumn2.ColumnName += "different"; - - // Act - Action action = () => dataColumn1.Should().BeEquivalentTo(dataColumn2); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_ColumnName_does_not_match_and_property_is_excluded_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataColumn1 = dataSet1.TypedDataTable1.DecimalColumn; - var dataColumn2 = dataSet2.TypedDataTable1.DecimalColumn; - - dataColumn2.ColumnName += "different"; - - // Act & Assert - dataColumn1.Should().BeEquivalentTo(dataColumn2, options => options - .Excluding(dataColumn => dataColumn.ColumnName) - .Excluding(dataColumn => dataColumn.Caption)); - } - - [Fact] - public void When_AllowDBNull_does_not_match_and_property_is_not_excluded_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataColumn1 = dataSet1.TypedDataTable1.DecimalColumn; - var dataColumn2 = dataSet2.TypedDataTable1.DecimalColumn; - - dataColumn2.AllowDBNull = !dataColumn2.AllowDBNull; - - // Act - Action action = () => dataColumn1.Should().BeEquivalentTo(dataColumn2); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_AllowDBNull_does_not_match_and_property_is_excluded_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataColumn1 = dataSet1.TypedDataTable1.DecimalColumn; - var dataColumn2 = dataSet2.TypedDataTable1.DecimalColumn; - - dataColumn2.AllowDBNull = !dataColumn2.AllowDBNull; - - // Act & Assert - dataColumn1.Should().BeEquivalentTo(dataColumn2, options => options - .Excluding(dataColumn => dataColumn.AllowDBNull)); - } - - [Fact] - public void When_AutoIncrement_does_not_match_and_property_is_not_excluded_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataColumn1 = dataSet1.TypedDataTable1.DecimalColumn; - var dataColumn2 = dataSet2.TypedDataTable1.DecimalColumn; - - dataColumn2.AutoIncrement = !dataColumn2.AutoIncrement; - - // Act - Action action = () => dataColumn1.Should().BeEquivalentTo(dataColumn2); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_AutoIncrement_does_not_match_and_property_is_excluded_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataColumn1 = dataSet1.TypedDataTable1.DecimalColumn; - var dataColumn2 = dataSet2.TypedDataTable1.DecimalColumn; - - dataColumn2.AutoIncrement = !dataColumn2.AutoIncrement; - - // Act & Assert - dataColumn1.Should().BeEquivalentTo(dataColumn2, options => options - .Excluding(dataColumn => dataColumn.AutoIncrement)); - } - - [Fact] - public void When_AutoIncrementSeed_does_not_match_and_property_is_not_excluded_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataColumn1 = dataSet1.TypedDataTable1.DecimalColumn; - var dataColumn2 = dataSet2.TypedDataTable1.DecimalColumn; - - dataColumn2.AutoIncrementSeed++; - - // Act - Action action = () => dataColumn1.Should().BeEquivalentTo(dataColumn2); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_AutoIncrementSeed_does_not_match_and_property_is_excluded_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataColumn1 = dataSet1.TypedDataTable1.DecimalColumn; - var dataColumn2 = dataSet2.TypedDataTable1.DecimalColumn; - - dataColumn2.AutoIncrementSeed++; - - // Act & Assert - dataColumn1.Should().BeEquivalentTo(dataColumn2, options => options - .Excluding(dataColumn => dataColumn.AutoIncrementSeed)); - } - - [Fact] - public void When_AutoIncrementStep_does_not_match_and_property_is_not_excluded_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataColumn1 = dataSet1.TypedDataTable1.DecimalColumn; - var dataColumn2 = dataSet2.TypedDataTable1.DecimalColumn; - - dataColumn2.AutoIncrementStep++; - - // Act - Action action = () => dataColumn1.Should().BeEquivalentTo(dataColumn2); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_AutoIncrementStep_does_not_match_and_property_is_excluded_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataColumn1 = dataSet1.TypedDataTable1.DecimalColumn; - var dataColumn2 = dataSet2.TypedDataTable1.DecimalColumn; - - dataColumn2.AutoIncrementStep++; - - // Act & Assert - dataColumn1.Should().BeEquivalentTo(dataColumn2, options => options - .Excluding(dataColumn => dataColumn.AutoIncrementStep)); - } - - [Fact] - public void When_Caption_does_not_match_and_property_is_not_excluded_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataColumn1 = dataSet1.TypedDataTable1.DecimalColumn; - var dataColumn2 = dataSet2.TypedDataTable1.DecimalColumn; - - dataColumn2.Caption += "different"; - - // Act - Action action = () => dataColumn1.Should().BeEquivalentTo(dataColumn2); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_Caption_does_not_match_and_property_is_excluded_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataColumn1 = dataSet1.TypedDataTable1.DecimalColumn; - var dataColumn2 = dataSet2.TypedDataTable1.DecimalColumn; - - dataColumn2.Caption += "different"; - - // Act & Assert - dataColumn1.Should().BeEquivalentTo(dataColumn2, options => options - .Excluding(dataColumn => dataColumn.Caption)); - } - - [Fact] - public void When_DataType_does_not_match_and_property_is_not_excluded_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(includeDummyData: false); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataColumn1 = dataSet1.TypedDataTable2.DecimalColumn; - var dataColumn2 = dataSet2.TypedDataTable2.DecimalColumn; - - dataColumn2.DataType = typeof(double); - - // Act - Action action = () => dataColumn1.Should().BeEquivalentTo(dataColumn2); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_DataType_does_not_match_and_property_is_excluded_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(includeDummyData: false); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataColumn1 = dataSet1.TypedDataTable2.DecimalColumn; - var dataColumn2 = dataSet2.TypedDataTable2.DecimalColumn; - - dataColumn2.DataType = typeof(double); - - // Act & Assert - dataColumn1.Should().BeEquivalentTo(dataColumn2, options => options - .Excluding(dataColumn => dataColumn.DataType)); - } - - [Fact] - public void When_DateTimeMode_does_not_match_and_property_is_not_excluded_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(includeDummyData: false); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataColumn1 = dataSet1.TypedDataTable2.DateTimeColumn; - var dataColumn2 = dataSet2.TypedDataTable2.DateTimeColumn; - - dataColumn2.DateTimeMode = - dataColumn2.DateTimeMode == DataSetDateTime.Local - ? DataSetDateTime.Utc - : DataSetDateTime.Local; - - // Act - Action action = () => dataColumn1.Should().BeEquivalentTo(dataColumn2); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_DateTimeMode_does_not_match_and_property_is_excluded_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(includeDummyData: false); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataColumn1 = dataSet1.TypedDataTable2.DateTimeColumn; - var dataColumn2 = dataSet2.TypedDataTable2.DateTimeColumn; - - dataColumn2.DateTimeMode = - dataColumn2.DateTimeMode == DataSetDateTime.Local - ? DataSetDateTime.Utc - : DataSetDateTime.Local; - - // Act & Assert - dataColumn1.Should().BeEquivalentTo(dataColumn2, options => options - .Excluding(dataColumn => dataColumn.DateTimeMode)); - } - - [Fact] - public void When_DefaultValue_does_not_match_and_property_is_not_excluded_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataColumn1 = dataSet1.TypedDataTable1.DecimalColumn; - var dataColumn2 = dataSet2.TypedDataTable1.DecimalColumn; - - dataColumn2.DefaultValue = 10M; - - // Act - Action action = () => dataColumn1.Should().BeEquivalentTo(dataColumn2); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_DefaultValue_does_not_match_and_property_is_excluded_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataColumn1 = dataSet1.TypedDataTable1.DecimalColumn; - var dataColumn2 = dataSet2.TypedDataTable1.DecimalColumn; - - dataColumn2.DefaultValue = 10M; - - // Act & Assert - dataColumn1.Should().BeEquivalentTo(dataColumn2, options => options - .Excluding(dataColumn => dataColumn.DefaultValue)); - } - - [Fact] - public void When_Expression_does_not_match_and_property_is_not_excluded_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataColumn1 = dataSet1.TypedDataTable1.DecimalColumn; - var dataColumn2 = dataSet2.TypedDataTable1.DecimalColumn; - - dataColumn2.Expression = "RowID"; - - // Act - Action action = () => - dataColumn1.Should().BeEquivalentTo(dataColumn2, options => options - .Excluding(dataColumn => dataColumn.ReadOnly)); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_Expression_does_not_match_and_property_is_excluded_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataColumn1 = dataSet1.TypedDataTable1.DecimalColumn; - var dataColumn2 = dataSet2.TypedDataTable1.DecimalColumn; - - dataColumn2.Expression = "RowID"; - - // Act & Assert - dataColumn1.Should().BeEquivalentTo(dataColumn2, options => options - .Excluding(dataColumn => dataColumn.Expression) - .Excluding(dataColumn => dataColumn.ReadOnly)); - } - - [Fact] - public void When_MaxLength_does_not_match_and_property_is_not_excluded_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataColumn1 = dataSet1.TypedDataTable1.StringColumn; - var dataColumn2 = dataSet2.TypedDataTable1.StringColumn; - - dataColumn2.MaxLength = 250; - - // Act - Action action = () => dataColumn1.Should().BeEquivalentTo(dataColumn2); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_MaxLength_does_not_match_and_property_is_excluded_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataColumn1 = dataSet1.TypedDataTable1.StringColumn; - var dataColumn2 = dataSet2.TypedDataTable1.StringColumn; - - dataColumn2.MaxLength = 250; - - // Act & Assert - dataColumn1.Should().BeEquivalentTo(dataColumn2, options => options - .Excluding(dataColumn => dataColumn.MaxLength)); - } - - [Fact] - public void When_Namespace_does_not_match_and_property_is_not_excluded_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataColumn1 = dataSet1.TypedDataTable1.DecimalColumn; - var dataColumn2 = dataSet2.TypedDataTable1.DecimalColumn; - - dataColumn2.Namespace += "different"; - - // Act - Action action = () => dataColumn1.Should().BeEquivalentTo(dataColumn2); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_Namespace_does_not_match_and_property_is_excluded_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataColumn1 = dataSet1.TypedDataTable1.DecimalColumn; - var dataColumn2 = dataSet2.TypedDataTable1.DecimalColumn; - - dataColumn2.Namespace += "different"; - - // Act & Assert - dataColumn1.Should().BeEquivalentTo(dataColumn2, options => options - .Excluding(dataColumn => dataColumn.Namespace)); - } - - [Fact] - public void When_Prefix_does_not_match_and_property_is_not_excluded_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataColumn1 = dataSet1.TypedDataTable1.DecimalColumn; - var dataColumn2 = dataSet2.TypedDataTable1.DecimalColumn; - - dataColumn2.Prefix += "different"; - - // Act - Action action = () => dataColumn1.Should().BeEquivalentTo(dataColumn2); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_Prefix_does_not_match_and_property_is_excluded_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataColumn1 = dataSet1.TypedDataTable1.DecimalColumn; - var dataColumn2 = dataSet2.TypedDataTable1.DecimalColumn; - - dataColumn2.Prefix += "different"; - - // Act & Assert - dataColumn1.Should().BeEquivalentTo(dataColumn2, options => options - .Excluding(dataColumn => dataColumn.Prefix)); - } - - [Fact] - public void When_ReadOnly_does_not_match_and_property_is_not_excluded_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataColumn1 = dataSet1.TypedDataTable1.DecimalColumn; - var dataColumn2 = dataSet2.TypedDataTable1.DecimalColumn; - - dataColumn2.ReadOnly = !dataColumn2.ReadOnly; - - // Act - Action action = () => dataColumn1.Should().BeEquivalentTo(dataColumn2); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_ReadOnly_does_not_match_and_property_is_excluded_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataColumn1 = dataSet1.TypedDataTable1.DecimalColumn; - var dataColumn2 = dataSet2.TypedDataTable1.DecimalColumn; - - dataColumn2.ReadOnly = !dataColumn2.ReadOnly; - - // Act & Assert - dataColumn1.Should().BeEquivalentTo(dataColumn2, options => options - .Excluding(dataColumn => dataColumn.ReadOnly)); - } - - [Fact] - public void When_Unique_does_not_match_and_property_is_not_excluded_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataColumn1 = dataSet1.TypedDataTable1.DecimalColumn; - var dataColumn2 = dataSet2.TypedDataTable1.DecimalColumn; - - dataColumn2.Unique = !dataColumn2.Unique; - - // Act - Action action = () => dataColumn1.Should().BeEquivalentTo(dataColumn2); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_Unique_does_not_match_and_property_is_excluded_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataColumn1 = dataSet1.TypedDataTable1.DecimalColumn; - var dataColumn2 = dataSet2.TypedDataTable1.DecimalColumn; - - dataColumn2.Unique = !dataColumn2.Unique; - - // Act & Assert - dataColumn1.Should().BeEquivalentTo(dataColumn2, options => options - .Excluding(dataColumn => dataColumn.Unique)); - } - - [Theory] - [MemberData(nameof(AllChangeTypes))] - public void When_ExtendedProperties_do_not_match_and_property_is_not_excluded_it_should_fail(ChangeType changeType) - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - var dataColumn1 = typedDataSet1.TypedDataTable1.DecimalColumn; - var dataColumn2 = typedDataSet2.TypedDataTable1.DecimalColumn; - - ApplyChange(dataColumn2.ExtendedProperties, changeType); - - // Act - Action action = () => dataColumn1.Should().BeEquivalentTo(dataColumn2); - - // Assert - action.Should().Throw(); - } - - [Theory] - [MemberData(nameof(AllChangeTypes))] - public void When_ExtendedProperties_do_not_match_and_property_is_excluded_it_should_succeed(ChangeType changeType) - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - var dataColumn1 = typedDataSet1.TypedDataTable1.DecimalColumn; - var dataColumn2 = typedDataSet2.TypedDataTable1.DecimalColumn; - - ApplyChange(dataColumn2.ExtendedProperties, changeType); - - // Act & Assert - dataColumn1.Should().BeEquivalentTo(dataColumn2, - options => options.Excluding(dataColumn => dataColumn.ExtendedProperties)); - } - - [Fact] - public void Data_column_is_not_equivalent_to_another_type() - { - // Arrange - var subject = new - { - DataColumn = "foobar" - }; - - var expected = new - { - DataColumn = new DataColumn() - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expected); - - // Assert - act.Should().Throw() - .WithMessage("Expected*System.Data.DataColumn*found System.String*"); - } -} diff --git a/Tests/FluentAssertions.Equivalency.Specs/DataRelationSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/DataRelationSpecs.cs deleted file mode 100644 index 62ee3276dd..0000000000 --- a/Tests/FluentAssertions.Equivalency.Specs/DataRelationSpecs.cs +++ /dev/null @@ -1,353 +0,0 @@ -using System; -using System.Data; -using Xunit; -using Xunit.Sdk; - -namespace FluentAssertions.Equivalency.Specs; - -public class DataRelationSpecs : DataSpecs -{ - [Fact] - public void When_RelationName_does_not_match_and_property_is_not_excluded_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable1 = dataSet1.TypedDataTable1; - var dataTable2 = dataSet2.TypedDataTable1; - - dataSet2.Relations[0].RelationName += "different"; - - // Act - Action action = () => dataTable1.Should().BeEquivalentTo(dataTable2); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_RelationName_does_not_match_and_property_is_excluded_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable1 = dataSet1.TypedDataTable1; - var dataTable2 = dataSet2.TypedDataTable1; - - dataSet2.Relations[0].RelationName += "different"; - - // Act & Assert - dataTable1.Should().BeEquivalentTo( - dataTable2, - options => options - .ExcludingRelated(dataRelation => dataRelation.RelationName)); - } - - [Fact] - public void When_Nested_does_not_match_and_property_is_not_excluded_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable1 = dataSet1.TypedDataTable1; - var dataTable2 = dataSet2.TypedDataTable1; - - dataSet2.Relations[0].Nested = !dataSet2.Relations[0].Nested; - - // Act - Action action = () => dataTable1.Should().BeEquivalentTo(dataTable2); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_Nested_does_not_match_and_property_is_excluded_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable1 = dataSet1.TypedDataTable1; - var dataTable2 = dataSet2.TypedDataTable1; - - dataSet2.Relations[0].Nested = !dataSet2.Relations[0].Nested; - - // Act & Assert - dataTable1.Should().BeEquivalentTo( - dataTable2, - options => options - .ExcludingRelated(dataRelation => dataRelation.Nested)); - } - - [Fact] - public void When_DataSet_does_not_match_and_property_is_not_excluded_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable1 = dataSet1.TypedDataTable1; - var dataTable2 = dataSet2.TypedDataTable1; - - dataSet2.DataSetName += "different"; - - // Act - Action action = () => - dataTable1.Should().BeEquivalentTo(dataTable2, options => options.Excluding(dataTable => dataTable.DataSet)); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_DataSet_does_not_match_and_property_is_excluded_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(identicalTables: true); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable1 = dataSet1.TypedDataTable1; - var dataTable2 = dataSet2.TypedDataTable1; - - dataSet2.DataSetName += "different"; - - // Act & Assert - dataTable1.Should().BeEquivalentTo( - dataTable2, - options => options - .Excluding(dataTable => dataTable.DataSet) - .ExcludingRelated((DataRelation dataRelation) => dataRelation.DataSet)); - } - - [Theory] - [MemberData(nameof(AllChangeTypes))] - public void When_ExtendedProperties_do_not_match_and_property_is_not_excluded_it_should_fail(ChangeType changeType) - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable1 = dataSet1.TypedDataTable1; - var dataTable2 = dataSet2.TypedDataTable1; - - ApplyChange(dataTable2.ChildRelations[0].ExtendedProperties, changeType); - - // Act - Action action = () => dataTable1.Should().BeEquivalentTo(dataTable2); - - // Assert - action.Should().Throw(); - } - - [Theory] - [MemberData(nameof(AllChangeTypes))] - public void When_ExtendedProperties_do_not_match_and_property_is_excluded_it_should_succeed(ChangeType changeType) - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable1 = dataSet1.TypedDataTable1; - var dataTable2 = dataSet2.TypedDataTable1; - - ApplyChange(dataTable2.ChildRelations[0].ExtendedProperties, changeType); - - // Act & Assert - dataTable1.Should().BeEquivalentTo(dataTable2, options => options - .ExcludingRelated((DataRelation dataRelation) => dataRelation.ExtendedProperties)); - } - - [Fact] - public void When_ParentColumns_do_not_match_and_property_is_not_excluded_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable1 = dataSet1.TypedDataTable2; - var dataTable2 = dataSet2.TypedDataTable2; - - dataSet2.TypedDataTable1.RowIDColumn.ColumnName += "different"; - - // Act - Action action = () => - dataTable1.Should().BeEquivalentTo(dataTable2, options => options - .Excluding(dataTable => dataTable.ChildRelations) - .Excluding(dataTable => dataTable.Constraints)); - - // Assert - action.Should().Throw().Which.Message.Should() - .Contain(dataSet2.TypedDataTable1.Columns[0].ColumnName); - } - - [Fact] - public void When_ParentColumns_do_not_match_and_property_is_excluded_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable1 = dataSet1.TypedDataTable2; - var dataTable2 = dataSet2.TypedDataTable2; - - dataSet2.TypedDataTable1.RowIDColumn.ColumnName += "different"; - - // Act & Assert - dataTable1.Should().BeEquivalentTo(dataTable2, options => options - .Excluding(dataTable => dataTable.ChildRelations) - .Excluding(dataTable => dataTable.Constraints) - .ExcludingRelated(dataRelation => dataRelation.ParentColumns) - .ExcludingRelated(dataRelation => dataRelation.ParentKeyConstraint) - .ExcludingRelated(dataRelation => dataRelation.ChildKeyConstraint)); - } - - [Fact] - public void When_ChildColumns_do_not_match_and_property_is_not_excluded_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable1 = dataSet1.TypedDataTable1; - var dataTable2 = dataSet2.TypedDataTable1; - - dataSet2.TypedDataTable2.ForeignRowIDColumn.ColumnName += "different"; - - // Act - Action action = () => - dataTable1.Should().BeEquivalentTo(dataTable2, options => options - .Excluding(dataTable => dataTable.ParentRelations) - .Excluding(dataTable => dataTable.Constraints)); - - // Assert - action.Should().Throw().Which.Message.Should() - .Contain(dataSet2.TypedDataTable1.Columns[0].ColumnName); - } - - [Fact] - public void When_ChildColumns_do_not_match_and_property_is_excluded_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable1 = dataSet1.TypedDataTable1; - var dataTable2 = dataSet2.TypedDataTable1; - - dataSet2.TypedDataTable2.ForeignRowIDColumn.ColumnName += "different"; - - // Act & Assert - dataTable1.Should().BeEquivalentTo(dataTable2, options => options - .Excluding(dataTable => dataTable.ParentRelations) - .Excluding(dataTable => dataTable.Constraints) - .ExcludingRelated(dataRelation => dataRelation.ChildColumns) - .ExcludingRelated(dataRelation => dataRelation.ParentKeyConstraint) - .ExcludingRelated(dataRelation => dataRelation.ChildKeyConstraint)); - } - - [Fact] - public void Data_relation_is_not_equivalent_to_another_type() - { - // Arrange - var dataSet = new DataSet(); - var table = new DataTable(); - var col1 = new DataColumn(); - var col2 = new DataColumn(); - - table.Columns.Add(col1); - table.Columns.Add(col2); - - dataSet.Tables.Add(table); - - var subject = new - { - DataRelation = "foobar" - }; - - var expected = new - { - DataRelation = new DataRelation("bar", col1, col2) - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expected); - - // Assert - act.Should().Throw() - .WithMessage("Expected*System.Data.DataRelation*found System.String*"); - } - - [Fact] - public void Null_is_not_equivalent_to_a_data_relation() - { - // Arrange - var dataSet = new DataSet(); - var table = new DataTable(); - var col1 = new DataColumn(); - var col2 = new DataColumn(); - - table.Columns.Add(col1); - table.Columns.Add(col2); - - dataSet.Tables.Add(table); - - var subject = new - { - DataRelation = (DataRelation)null - }; - - var expected = new - { - DataRelation = new DataRelation("bar", col1, col2) - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expected); - - // Assert - act.Should().Throw() - .WithMessage("Expected *value to be non-null, but found*"); - } - - [Fact] - public void Data_relation_is_not_equivalent_to_null() - { - // Arrange - var dataSet = new DataSet(); - var table = new DataTable(); - var col1 = new DataColumn(); - var col2 = new DataColumn(); - - table.Columns.Add(col1); - table.Columns.Add(col2); - - dataSet.Tables.Add(table); - - var subject = new - { - DataRelation = new DataRelation("bar", col1, col2) - }; - - var expected = new - { - DataRelation = (DataRelation)null - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expected); - - // Assert - act.Should().Throw() - .WithMessage("Expected *to be null, but found*"); - } -} diff --git a/Tests/FluentAssertions.Equivalency.Specs/DataRowSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/DataRowSpecs.cs deleted file mode 100644 index 6e561ded69..0000000000 --- a/Tests/FluentAssertions.Equivalency.Specs/DataRowSpecs.cs +++ /dev/null @@ -1,530 +0,0 @@ -using System; -using System.Data; -using System.Linq; -using FluentAssertions.Execution; -using Xunit; -using Xunit.Sdk; - -namespace FluentAssertions.Equivalency.Specs; - -public class DataRowSpecs : DataSpecs -{ - [Fact] - public void When_data_rows_are_identical_equivalency_test_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable1 = dataSet1.TypedDataTable1; - var dataTable2 = dataSet2.TypedDataTable1; - - // Act & Assert - dataTable1[0].Should().BeEquivalentTo(dataTable2[0]); - } - - [Fact] - public void When_data_rows_are_both_null_equivalency_test_should_succeed() - { - // Act & Assert - ((DataRow)null).Should().BeEquivalentTo(null); - } - - [Fact] - public void When_data_row_is_null_and_isnt_expected_to_be_then_equivalency_test_should_fail() - { - // Arrange - var dataSet = CreateDummyDataSet(); - - var dataTable = dataSet.TypedDataTable1; - - // Act - Action action = () => ((DataRow)null).Should().BeEquivalentTo(dataTable[0]); - - // Assert - action.Should().Throw().WithMessage( - "Expected *to be non-null, but found null*"); - } - - [Fact] - public void When_data_row_is_expected_to_be_null_and_isnt_then_equivalency_test_should_fail() - { - // Arrange - var dataSet = CreateDummyDataSet(); - - var dataTable = dataSet.TypedDataTable1; - - // Act - Action action = () => dataTable[0].Should().BeEquivalentTo(null); - - // Assert - action.Should().Throw().WithMessage("Expected dataTable[0] value to be null, but found *"); - } - - [Fact] - public void - When_data_row_subject_is_deleted_and_expectation_is_not_but_the_row_state_is_excluded_equivalency_test_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable1 = dataSet1.TypedDataTable2; - var dataTable2 = dataSet2.TypedDataTable2; - - var dataRow1 = dataTable1[0]; - var dataRow2 = dataTable2[0]; - - dataRow1.Delete(); - - // Act & Assert - dataRow1.Should().BeEquivalentTo(dataRow2, config => config - .Excluding(row => row.RowState)); - } - - [Fact] - public void - When_data_row_expectation_is_deleted_and_subject_is_not_but_the_row_state_is_excluded_equivalency_test_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable1 = dataSet1.TypedDataTable2; - var dataTable2 = dataSet2.TypedDataTable2; - - var dataRow1 = dataTable1[0]; - var dataRow2 = dataTable2[0]; - - dataRow2.Delete(); - - // Act & Assert - dataRow1.Should().BeEquivalentTo(dataRow2, config => config - .Excluding(row => row.RowState)); - } - - [Fact] - public void When_data_row_is_deleted_equivalency_test_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable1 = dataSet1.TypedDataTable2; - var dataTable2 = dataSet2.TypedDataTable2; - - var dataRow1 = dataTable1[0]; - var dataRow2 = dataTable2[0]; - - dataRow1.Delete(); - dataRow2.Delete(); - - // Act & Assert - dataRow1.Should().BeEquivalentTo(dataRow2); - } - - [Fact] - public void When_data_row_is_modified_and_original_data_differs_equivalency_test_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable1 = dataSet1.TypedDataTable2; - var dataTable2 = dataSet2.TypedDataTable2; - - var dataRow1 = dataTable1[0]; - var dataRow2 = dataTable2[0]; - - dataRow1.Decimal++; - dataTable1.AcceptChanges(); - dataRow1.Decimal--; - - dataRow2.Decimal--; - dataTable2.AcceptChanges(); - dataRow2.Decimal++; - - // Act - Action action = () => dataRow1.Should().BeEquivalentTo(dataRow2); - - // Assert - action.Should().Throw() - .WithMessage("Expected dataRow1[Decimal, DataRowVersion.Original] to be *, but found *"); - } - - [Fact] - public void - When_data_row_is_modified_and_original_data_differs_but_original_data_is_excluded_then_equivalency_test_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable1 = dataSet1.TypedDataTable2; - var dataTable2 = dataSet2.TypedDataTable2; - - var dataRow1 = dataTable1[0]; - var dataRow2 = dataTable2[0]; - - // Set the Decimal property to be the same for both rows, but by doing ++ and -- in - // different orders, the AcceptChanges call will load different values into the - // original data for each row. - dataRow1.Decimal++; - dataTable1.AcceptChanges(); - dataRow1.Decimal--; - - dataRow2.Decimal--; - dataTable2.AcceptChanges(); - dataRow2.Decimal++; - - // Act & Assert - dataRow1.Should().BeEquivalentTo(dataRow2, config => config - .ExcludingOriginalData()); - } - - [Fact] - public void When_data_row_type_does_not_match_and_mismatched_types_are_not_allowed_then_equivalency_test_should_fail() - { - // Arrange - var dataSet = CreateDummyDataSet(identicalTables: true); - var dataSet2 = new TypedDataSetSubclass(dataSet); - - var dataTable = dataSet.TypedDataTable1; - var dataTableOfMismatchedType = dataSet2.TypedDataTable2; - - dataSet2.Tables.Remove(dataTable.TableName); - dataTableOfMismatchedType.TableName = dataTable.TableName; - - // Act - Action action = () => dataTable[0].Should().BeEquivalentTo(dataTableOfMismatchedType[0]); - - // Assert - action.Should().Throw() - .WithMessage("Expected dataTable[0] to be of type *TypedDataRow2, but found *TypedDataRow1*"); - } - - [Fact] - public void When_data_row_type_does_not_match_but_mismatched_types_are_allowed_then_equivalency_test_should_succeed() - { - // Arrange - var dataSet = CreateDummyDataSet(identicalTables: true); - var dataSet2 = new TypedDataSetSubclass(dataSet); - - var dataTable = dataSet.TypedDataTable1; - var dataTableOfMismatchedType = dataSet2.TypedDataTable2; - - dataSet2.Tables.Remove(dataTable.TableName); - dataTableOfMismatchedType.TableName = dataTable.TableName; - - // Act & Assert - dataTable[0].Should().BeEquivalentTo(dataTableOfMismatchedType[0], options => options.AllowingMismatchedTypes()); - } - - [Fact] - public void - When_one_data_row_has_errors_and_the_other_does_not_and_the_corresponding_property_is_not_excluded_then_equivalency_test_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataRow1 = dataSet1.TypedDataTable1[0]; - var dataRow2 = dataSet2.TypedDataTable1[0]; - - dataRow2.RowError = "Manually added error"; - - // Act - Action action = () => dataRow1.Should().BeEquivalentTo(dataRow2); - - // Assert - action.Should().Throw().Which.Message.Should().Contain("HasErrors"); - } - - [Fact] - public void - When_one_data_row_has_errors_and_the_other_does_not_but_the_corresponding_property_is_excluded_then_equivalency_test_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataRow1 = dataSet1.TypedDataTable1[0]; - var dataRow2 = dataSet2.TypedDataTable1[0]; - - dataRow2.RowError = "Manually added error"; - - // Act & Assert - dataRow1.Should().BeEquivalentTo(dataRow2, config => config.Excluding(dataRow => dataRow.HasErrors)); - } - - [Fact] - public void - When_the_data_row_state_does_not_match_and_the_corresponding_property_is_not_excluded_equivalency_test_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataRow1 = dataSet1.TypedDataTable1[0]; - var dataRow2 = dataSet2.TypedDataTable1[0]; - - dataRow1.DateTime = DateTime.UtcNow; - dataRow2.DateTime = dataRow1.DateTime; - - dataSet2.AcceptChanges(); - - // Act - Action action = () => dataRow1.Should().BeEquivalentTo(dataRow2); - - // Assert - action.Should().Throw().Which.Message.Should().Contain("RowState"); - } - - [Fact] - public void - When_the_data_row_state_does_not_match_but_the_corresponding_property_is_excluded_equivalency_test_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataRow1 = dataSet1.TypedDataTable1[0]; - var dataRow2 = dataSet2.TypedDataTable1[0]; - - dataRow1.DateTime = DateTime.UtcNow; - dataRow2.DateTime = dataRow1.DateTime; - - dataSet2.AcceptChanges(); - - // Act & Assert - dataRow1.Should().BeEquivalentTo(dataRow2, config => config.Excluding(dataRow => dataRow.RowState)); - } - - [Fact] - public void When_data_row_data_does_not_match_and_the_column_in_question_is_not_excluded_then_equivalency_test_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataRow1 = dataSet1.TypedDataTable1[0]; - var dataRow2 = dataSet2.TypedDataTable1[0]; - - dataRow2.String = Guid.NewGuid().ToString(); - dataSet2.AcceptChanges(); - - // Act - Action action = () => dataRow1.Should().BeEquivalentTo(dataRow2); - - // Assert - action.Should().Throw().Which.Message.Should().Contain(dataRow2.String); - } - - [Fact] - public void When_data_row_data_does_not_match_but_the_column_is_excluded_then_equivalency_test_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataRow1 = dataSet1.TypedDataTable1[0]; - var dataRow2 = dataSet2.TypedDataTable1[0]; - - dataRow2.DateTime = DateTime.UtcNow; - dataSet2.AcceptChanges(); - - // Act & Assert - dataRow1.Should().BeEquivalentTo(dataRow2, config => config.ExcludingColumn(dataSet2.TypedDataTable1.DateTimeColumn)); - } - - [Fact] - public void Data_row_is_not_equivalent_to_another_type() - { - // Arrange - var table = new DataTable(); - var dataRow = table.NewRow(); - - var subject = new - { - DataRow = "foobar" - }; - - var expected = new - { - DataRow = dataRow - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expected); - - // Assert - act.Should().Throw() - .WithMessage("Expected*System.Data.DataRow*found System.String*"); - } - - [Fact] - public void Any_type_is_not_equivalent_to_data_row_colletion() - { - // Arrange - var o = new object(); - - // Act - Action act = () => o.Should().BeEquivalentTo((DataRowCollection)null); - - // Assert - act.Should().Throw() - .WithMessage("Expected* to be of type DataRowCollection, but found*"); - } - - [Fact] - public void When_data_row_has_column_then_asserting_that_it_has_that_column_should_succeed() - { - // Arrange - var dataSet = CreateDummyDataSet(); - - var dataRow = dataSet.TypedDataTable1[0]; - - string expectedColumnName = dataSet.TypedDataTable1.Columns.Cast().Last().ColumnName; - - // Act & Assert - dataRow.Should().HaveColumn(expectedColumnName); - } - - [Fact] - public void When_data_row_does_not_have_column_then_asserting_that_it_has_that_column_should_fail() - { - // Arrange - var dataSet = CreateDummyDataSet(); - - var dataRow = dataSet.TypedDataTable1[0]; - - // Act - Action action = - () => dataRow.Should().HaveColumn("Unicorn"); - - // Assert - action.Should().Throw().WithMessage("Expected dataRow to contain a column named *Unicorn*"); - } - - [Fact] - public void Null_data_row_does_not_have_column() - { - // Arrange - var dataRow = (DataRow)null; - - // Act - Action action = - () => dataRow.Should().HaveColumn("Does not matter"); - - // Assert - action.Should().Throw() - .WithMessage("Expected dataRow to contain a column named *Does not matter*, but found *"); - } - - [Fact] - public void When_data_row_data_has_all_columns_being_asserted_then_it_should_succeed() - { - // Arrange - var dataSet = CreateDummyDataSet(); - - var dataRow = dataSet.TypedDataTable1[0]; - - var subsetOfColumnNames = dataRow.Table.Columns.Cast() - .Take(dataRow.Table.Columns.Count - 2) - .Select(column => column.ColumnName); - - // Act & Assert - dataRow.Should().HaveColumns(subsetOfColumnNames); - } - - [Fact] - public void Data_row_with_all_colums_asserted_and_using_the_array_overload_passes() - { - // Arrange - var dataSet = CreateDummyDataSet(); - - var dataRow = dataSet.TypedDataTable1[0]; - - string[] subsetOfColumnNames = dataRow.Table.Columns.Cast() - .Take(dataRow.Table.Columns.Count - 2) - .Select(column => column.ColumnName) - .ToArray(); - - // Act & Assert - dataRow.Should().HaveColumns(subsetOfColumnNames); - } - - [Fact] - public void Null_data_row_and_using_the_array_overload_fails() - { - // Arrange - var dataSet = CreateDummyDataSet(); - - var dataRow = dataSet.TypedDataTable1[0]; - - var actual = (DataRow)null; - - string[] subsetOfColumnNames = dataRow.Table.Columns.Cast() - .Take(dataRow.Table.Columns.Count - 2) - .Select(column => column.ColumnName) - .ToArray(); - - // Act - Action act = () => - { - using var _ = new AssertionScope(); - actual.Should().HaveColumns(subsetOfColumnNames); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expected actual to be in a table containing *column*, but found *"); - } - - [Fact] - public void When_data_row_data_has_only_some_of_the_columns_being_asserted_then_it_should_fail() - { - // Arrange - var dataSet = CreateDummyDataSet(); - - var dataRow = dataSet.TypedDataTable1[0]; - - var subsetOfColumnNamesWithUnicorn = dataRow.Table.Columns.Cast() - .Take(dataRow.Table.Columns.Count - 2) - .Select(column => column.ColumnName) - .Concat(new[] { "Unicorn" }); - - // Act - Action action = - () => dataRow.Should().HaveColumns(subsetOfColumnNamesWithUnicorn); - - // Assert - action.Should().Throw() - .WithMessage("Expected table containing dataRow to contain a column named *Unicorn*"); - } - - [Fact] - public void When_data_row_data_has_none_of_the_columns_being_asserted_then_it_should_fail() - { - // Arrange - var dataSet = CreateDummyDataSet(); - - var dataRow = dataSet.TypedDataTable1[0]; - - var columnNames = new[] { "Unicorn", "Dragon" }; - - // Act - Action action = - () => dataRow.Should().HaveColumns(columnNames); - - // Assert - action.Should().Throw() - .WithMessage("Expected table containing dataRow to contain a column named *Unicorn*"); - } -} diff --git a/Tests/FluentAssertions.Equivalency.Specs/DataSetSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/DataSetSpecs.cs deleted file mode 100644 index 91e1ebaecd..0000000000 --- a/Tests/FluentAssertions.Equivalency.Specs/DataSetSpecs.cs +++ /dev/null @@ -1,835 +0,0 @@ -using System; -using System.Data; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using System.Linq; -using FluentAssertions.Execution; -using Xunit; -using Xunit.Sdk; - -namespace FluentAssertions.Equivalency.Specs; - -public class DataSetSpecs : DataSpecs -{ - [Fact] - public void When_data_sets_are_identical_equivalence_test_should_succeed() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - var dataSet1 = typedDataSet1.ToUntypedDataSet(); - var dataSet2 = typedDataSet2.ToUntypedDataSet(); - - // Act & Assert - dataSet1.Should().BeEquivalentTo(dataSet2); - } - - [Fact] - public void When_data_sets_are_both_null_equivalence_test_should_succeed() - { - // Act & Assert - ((DataSet)null).Should().BeEquivalentTo(null); - } - - [Fact] - public void When_data_set_is_null_and_isnt_expected_to_be_equivalence_test_should_fail() - { - // Arrange - var typedDataSet = CreateDummyDataSet(); - - var dataSet = typedDataSet.ToUntypedDataSet(); - - // Act - Action action = () => ((DataSet)null).Should().BeEquivalentTo(dataSet); - - // Assert - action.Should().Throw().WithMessage( - "Expected *to be non-null, but found null*"); - } - - [Fact] - public void When_data_set_is_expected_to_be_null_and_isnt_equivalence_test_should_fail() - { - // Arrange - var typedDataSet = CreateDummyDataSet(); - - var dataSet = typedDataSet.ToUntypedDataSet(); - - // Act - Action action = () => dataSet.Should().BeEquivalentTo(null); - - // Assert - action.Should().Throw().WithMessage("Expected dataSet value to be null, but found *"); - } - - [Fact] - public void When_data_set_type_does_not_match_and_not_allowing_msimatched_types_equivalence_test_should_fail() - { - // Arrange - var typedDataSet = CreateDummyDataSet(); - - var dataSet = typedDataSet.ToUntypedDataSet(); - - var dataSetOfMismatchedType = new TypedDataSetSubclass(typedDataSet); - - // Act - Action action = () => dataSet.Should().BeEquivalentTo(dataSetOfMismatchedType); - - // Assert - action.Should().Throw() - .WithMessage("Expected dataSet to be of type *TypedDataSetSubclass, but found System.Data.DataSet*"); - } - - [Fact] - public void When_data_set_type_does_not_match_but_mismatched_types_are_allowed_equivalence_test_should_succeed() - { - // Arrange - var typedDataSet = CreateDummyDataSet(); - - var dataSet = typedDataSet.ToUntypedDataSet(); - - var dataSetOfMismatchedType = new TypedDataSetSubclass(typedDataSet); - - // Act & Assert - dataSet.Should().BeEquivalentTo(dataSetOfMismatchedType, options => options - .AllowingMismatchedTypes() - .Excluding(dataSet => dataSet.SchemaSerializationMode)); - } - - [Fact] - public void When_data_set_name_does_not_match_and_the_corresponding_property_is_not_excluded_equivalence_test_should_fail() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - typedDataSet2.DataSetName += "different"; - - var dataSet1 = typedDataSet1.ToUntypedDataSet(); - var dataSet2 = typedDataSet2.ToUntypedDataSet(); - - // Act - Action action = () => dataSet1.Should().BeEquivalentTo(dataSet2); - - // Assert - action.Should().Throw().WithMessage("Expected dataSet1 to have DataSetName *different*"); - } - - [Fact] - public void When_data_set_name_does_not_match_but_the_corresponding_property_is_excluded_equivalence_test_should_succeed() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - typedDataSet2.DataSetName += "different"; - - var dataSet1 = typedDataSet1.ToUntypedDataSet(); - var dataSet2 = typedDataSet2.ToUntypedDataSet(); - - // Act & Assert - dataSet1.Should().BeEquivalentTo(dataSet2, options => options - .Excluding(dataSet => dataSet.DataSetName) - .ExcludingRelated((DataRelation dataRelation) => dataRelation.DataSet)); - } - - [Fact] - public void - When_one_data_set_is_configured_to_be_case_sensitive_and_the_other_is_not_and_the_corresponding_property_is_not_excluded_equivalence_test_should_fail() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - typedDataSet2.CaseSensitive = !typedDataSet2.CaseSensitive; - - var dataSet1 = typedDataSet1.ToUntypedDataSet(); - var dataSet2 = typedDataSet2.ToUntypedDataSet(); - - // Act - Action action = () => dataSet1.Should().BeEquivalentTo(dataSet2); - - // Assert - action.Should().Throw() - .WithMessage("Expected dataSet1 to have CaseSensitive value of True, but found False instead*"); - } - - [Fact] - public void - When_one_data_set_is_configured_to_be_case_sensitive_and_the_other_is_not_but_the_corresponding_property_is_excluded_equivalence_test_should_succeed() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - typedDataSet2.CaseSensitive = !typedDataSet2.CaseSensitive; - - var dataSet1 = typedDataSet1.ToUntypedDataSet(); - var dataSet2 = typedDataSet2.ToUntypedDataSet(); - - // Act & Assert - dataSet1.Should().BeEquivalentTo(dataSet2, options => options.Excluding(dataSet => dataSet.CaseSensitive)); - } - - [Fact] - public void - When_one_data_set_is_configured_to_enforce_constraints_and_the_other_is_not_and_the_corresponding_property_is_not_excluded_equivalence_test_should_fail() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - typedDataSet2.EnforceConstraints = !typedDataSet2.EnforceConstraints; - - var dataSet1 = typedDataSet1.ToUntypedDataSet(); - var dataSet2 = typedDataSet2.ToUntypedDataSet(); - - // Act - Action action = () => dataSet1.Should().BeEquivalentTo(dataSet2); - - // Assert - action.Should().Throw() - .WithMessage("Expected dataSet1 to have EnforceConstraints value of False, but found True instead*"); - } - - [Fact] - public void - When_one_data_set_is_configured_to_enforce_constraints_and_the_other_is_not_but_the_corresponding_property_is_excluded_equivalence_test_should_succeed() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - typedDataSet2.EnforceConstraints = !typedDataSet2.EnforceConstraints; - - var dataSet1 = typedDataSet1.ToUntypedDataSet(); - var dataSet2 = typedDataSet2.ToUntypedDataSet(); - - // Act & Assert - dataSet1.Should().BeEquivalentTo(dataSet2, options => options.Excluding(dataSet => dataSet.EnforceConstraints)); - } - - [Fact] - public void - When_one_data_set_has_errors_and_the_other_does_not_and_the_corresponding_property_is_not_excluded_equivalence_test_should_fail() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - typedDataSet2.TypedDataTable1.Rows[0].RowError = "Manually added error"; - - var dataSet1 = typedDataSet1.ToUntypedDataSet(); - var dataSet2 = typedDataSet2.ToUntypedDataSet(); - - // Act - Action action = () => dataSet1.Should().BeEquivalentTo(dataSet2, config => config.ExcludingTables("TypedDataTable1")); - - // Assert - action.Should().Throw().Which.Message.Should().Contain("HasErrors"); - } - - [Fact] - public void - When_one_data_set_has_errors_and_the_other_does_not_but_the_corresponding_property_is_excluded_equivalence_test_should_succeed() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - typedDataSet2.TypedDataTable1.Rows[0].RowError = "Manually added error"; - - var dataSet1 = typedDataSet1.ToUntypedDataSet(); - var dataSet2 = typedDataSet2.ToUntypedDataSet(); - - // Act & Assert - dataSet1.Should().BeEquivalentTo(dataSet2, - config => config.Excluding(dataSet => dataSet.HasErrors).ExcludingTables("TypedDataTable1")); - } - - [Fact] - public void - When_data_sets_have_mismatched_locale_and_the_corresponding_property_is_not_excluded_equivalence_test_should_fail() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - typedDataSet1.Locale = new CultureInfo("en-US"); - typedDataSet2.Locale = new CultureInfo("fr-CA"); - - var dataSet1 = typedDataSet1.ToUntypedDataSet(); - var dataSet2 = typedDataSet2.ToUntypedDataSet(); - - // Act - Action action = () => dataSet1.Should().BeEquivalentTo(dataSet2); - - // Assert - action.Should().Throw() - .WithMessage("Expected dataSet1 to have Locale value of fr-CA, but found en-US instead*"); - } - - [Fact] - public void When_data_set_locale_does_not_match_but_the_corresponding_property_is_excluded_equivalence_test_should_succeed() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - typedDataSet1.Locale = new CultureInfo("en-US"); - typedDataSet2.Locale = new CultureInfo("fr-CA"); - - var dataSet1 = typedDataSet1.ToUntypedDataSet(); - var dataSet2 = typedDataSet2.ToUntypedDataSet(); - - // Act & Assert - dataSet1.Should().BeEquivalentTo(dataSet2, options => options.Excluding(dataSet => dataSet.Locale)); - } - - [Fact] - public void - When_data_set_namespace_does_not_match_and_the_corresponding_property_is_not_excluded_equivalence_test_should_fail() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - typedDataSet2.Namespace += "different"; - - var dataSet1 = typedDataSet1.ToUntypedDataSet(); - var dataSet2 = typedDataSet2.ToUntypedDataSet(); - - // Act - Action action = () => dataSet1.Should().BeEquivalentTo(dataSet2); - - // Assert - action.Should().Throw().WithMessage("Expected dataSet1 to have Namespace value of *different*"); - } - - [Fact] - public void - When_data_set_namespace_does_not_match_but_the_corresponding_property_is_excluded_equivalence_test_should_succeed() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - typedDataSet2.Namespace += "different"; - - var dataSet1 = typedDataSet1.ToUntypedDataSet(); - var dataSet2 = typedDataSet2.ToUntypedDataSet(); - - // Act & Assert - dataSet1.Should().BeEquivalentTo(dataSet2, options => options - .Excluding(dataSet => dataSet.Namespace) - .ExcludingRelated((DataTable dataTable) => dataTable.Namespace) - .ExcludingRelated((DataColumn dataColumn) => dataColumn.Namespace)); - } - - [Fact] - public void When_data_set_prefix_does_not_match_and_the_corresponding_property_is_not_excluded_equivalence_test_should_fail() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - typedDataSet2.Prefix += "different"; - - var dataSet1 = typedDataSet1.ToUntypedDataSet(); - var dataSet2 = typedDataSet2.ToUntypedDataSet(); - - // Act - Action action = () => dataSet1.Should().BeEquivalentTo(dataSet2); - - // Assert - action.Should().Throw().WithMessage("Expected dataSet1 to have Prefix value of *different*"); - } - - [Fact] - public void When_data_set_prefix_does_not_match_but_the_corresponding_property_is_excluded_equivalence_test_should_succeed() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - typedDataSet2.Prefix += "different"; - - var dataSet1 = typedDataSet1.ToUntypedDataSet(); - var dataSet2 = typedDataSet2.ToUntypedDataSet(); - - // Act & Assert - dataSet1.Should().BeEquivalentTo(dataSet2, options => options.Excluding(dataSet => dataSet.Prefix)); - } - -#if !NET8_0_OR_GREATER - - [Fact] - public void - When_data_set_remoting_format_does_not_match_and_the_corresponding_property_is_not_excluded_equivalence_test_should_fail() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - typedDataSet2.RemotingFormat = - typedDataSet2.RemotingFormat == SerializationFormat.Binary - ? SerializationFormat.Xml - : SerializationFormat.Binary; - - var dataSet1 = typedDataSet1.ToUntypedDataSet(); - var dataSet2 = typedDataSet2.ToUntypedDataSet(); - - // Act - Action action = () => dataSet1.Should().BeEquivalentTo(dataSet2); - - // Assert - action.Should().Throw() - .WithMessage( - "Expected dataSet1 to have RemotingFormat value of SerializationFormat.Binary*, but found *Xml* instead*"); - } - - [Fact] - public void - When_data_set_remoting_format_does_not_match_but_the_corresponding_property_is_excluded_equivalence_test_should_succeed() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - typedDataSet2.RemotingFormat = - typedDataSet2.RemotingFormat == SerializationFormat.Binary - ? SerializationFormat.Xml - : SerializationFormat.Binary; - - var dataSet1 = typedDataSet1.ToUntypedDataSet(); - var dataSet2 = typedDataSet2.ToUntypedDataSet(); - - // Act & Assert - dataSet1.Should().BeEquivalentTo(dataSet2, options => options - .Excluding(dataSet => dataSet.RemotingFormat) - .ExcludingRelated(dataTable => dataTable.RemotingFormat)); - } - - [Theory] - [MemberData(nameof(AllChangeTypes))] - public void - When_data_set_extended_properties_do_not_match_and_the_corresponding_property_is_not_excluded_equivalence_test_should_fail( - ChangeType changeType) - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - ApplyChange(typedDataSet2.ExtendedProperties, changeType); - - var dataSet1 = typedDataSet1.ToUntypedDataSet(); - var dataSet2 = typedDataSet2.ToUntypedDataSet(); - - // Act - Action action = () => dataSet1.Should().BeEquivalentTo(dataSet2); - - // Assert - action.Should().Throw().WithMessage("Expected *dataSet1.ExtendedProperties* to be *, but *"); - } - - [Theory] - [MemberData(nameof(AllChangeTypes))] - public void - When_data_set_extended_properties_do_not_match_but_the_corresponding_property_is_excluded_equivalence_test_should_succeed( - ChangeType changeType) - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - ApplyChange(typedDataSet2.ExtendedProperties, changeType); - - var dataSet1 = typedDataSet1.ToUntypedDataSet(); - var dataSet2 = typedDataSet2.ToUntypedDataSet(); - - // Act & Assert - dataSet1.Should().BeEquivalentTo(dataSet2, options => options.Excluding(dataSet => dataSet.ExtendedProperties)); - } - -#endif - - [Theory] - [MemberData(nameof(AllChangeTypes))] - [SuppressMessage("Style", "IDE0010:Add missing cases", Justification = "All enum values are accounted for.")] - public void When_data_set_relations_do_not_match_and_the_corresponding_property_is_not_excluded_equivalence_test_should_fail( - ChangeType changeType) - { - // Arrange - TypedDataSetSubclass typedDataSet1; - TypedDataSetSubclass typedDataSet2; - - if (changeType == ChangeType.Changed) - { - typedDataSet1 = CreateDummyDataSet(); - typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - typedDataSet2.Relations[0].RelationName += "different"; - } - else - { - var doesNotHaveRelation = CreateDummyDataSet(includeRelation: false); - var hasRelation = new TypedDataSetSubclass(doesNotHaveRelation); - - AddRelation(hasRelation); - - if (changeType == ChangeType.Added) - { - typedDataSet1 = doesNotHaveRelation; - typedDataSet2 = hasRelation; - } - else - { - typedDataSet1 = hasRelation; - typedDataSet2 = doesNotHaveRelation; - } - } - - var dataSet1 = typedDataSet1.ToUntypedDataSet(); - var dataSet2 = typedDataSet2.ToUntypedDataSet(); - - // Act - Action action = () => dataSet1.Should().BeEquivalentTo(dataSet2); - - // Assert - action.Should().Throw().WithMessage("Expected *dataSet1.Relations* to *, but *"); - } - - [Theory] - [MemberData(nameof(AllChangeTypes))] - [SuppressMessage("Style", "IDE0010:Add missing cases", Justification = "All enum values are accounted for.")] - public void When_data_set_relations_do_not_match_but_the_corresponding_property_is_excluded_equivalence_test_should_succeed( - ChangeType changeType) - { - // Arrange - TypedDataSetSubclass typedDataSet1; - TypedDataSetSubclass typedDataSet2; - - if (changeType == ChangeType.Changed) - { - typedDataSet1 = CreateDummyDataSet(); - typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - typedDataSet2.Relations[0].RelationName += "different"; - } - else - { - var doesNotHaveRelation = CreateDummyDataSet(includeRelation: false); - var hasRelation = new TypedDataSetSubclass(doesNotHaveRelation); - - AddRelation(hasRelation); - - if (changeType == ChangeType.Added) - { - typedDataSet1 = doesNotHaveRelation; - typedDataSet2 = hasRelation; - } - else - { - typedDataSet1 = hasRelation; - typedDataSet2 = doesNotHaveRelation; - } - } - - var dataSet1 = typedDataSet1.ToUntypedDataSet(); - var dataSet2 = typedDataSet2.ToUntypedDataSet(); - - // Act & Assert - dataSet1.Should().BeEquivalentTo(dataSet2, options => options - .Excluding(dataSet => dataSet.Relations) - .ExcludingRelated(dataTable => dataTable.Constraints) - .ExcludingRelated(dataTable => dataTable.ParentRelations) - .ExcludingRelated(dataTable => dataTable.ChildRelations) - .ExcludingRelated(dataColumn => dataColumn.Unique)); - } - - [Fact] - public void When_data_set_tables_are_the_same_but_in_a_different_order_equivalence_test_should_succeed() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1, swapTableOrder: true); - - var dataSet1 = typedDataSet1.ToUntypedDataSet(); - var dataSet2 = typedDataSet2.ToUntypedDataSet(); - - // Act & Assert - dataSet1.Should().BeEquivalentTo(dataSet2); - } - - [Fact] - public void When_data_set_table_count_does_not_match_equivalence_test_should_fail() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1, swapTableOrder: true); - - typedDataSet2.Tables.Add(new DataTable("ThirdWheel")); - - var dataSet1 = typedDataSet1.ToUntypedDataSet(); - var dataSet2 = typedDataSet2.ToUntypedDataSet(); - - // Act - Action action = () => dataSet1.Should().BeEquivalentTo(dataSet2); - - // Assert - action.Should().Throw().Which.Message.Should().Contain("to contain " + dataSet2.Tables.Count); - } - - [Fact] - public void When_data_set_table_count_matches_but_tables_are_different_equivalence_test_should_fail() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1, swapTableOrder: true); - - typedDataSet2.TypedDataTable2.TableName = "DifferentTableName"; - - var dataSet1 = typedDataSet1.ToUntypedDataSet(); - var dataSet2 = typedDataSet2.ToUntypedDataSet(); - - // Act - Action action = () => dataSet1.Should().BeEquivalentTo(dataSet2); - - // Assert - action.Should().Throw() - .WithMessage( - "Expected dataSet1.Relations[0].ExtendedProperties* to reference column *ForeignRowID* in table *Different*, but found a reference to *ForeignRowID* in table *TypedDataTable2* instead*"); - } - - [Fact] - public void When_data_set_tables_contain_different_data_equivalence_test_should_fail() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1, swapTableOrder: true); - - typedDataSet2.TypedDataTable2[0].Guid = Guid.NewGuid(); - - var dataSet1 = typedDataSet1.ToUntypedDataSet(); - var dataSet2 = typedDataSet2.ToUntypedDataSet(); - - // Act - Action action = () => dataSet1.Should().BeEquivalentTo(dataSet2); - - // Assert - action.Should().Throw() - .WithMessage( - "Expected dataSet1[TypedDataTable2].Rows[0] to have RowState value of *Modified*, but found *Unchanged* instead*"); - } - - [Fact] - public void Data_set_is_not_equivalent_to_another_type() - { - // Arrange - var subject = new - { - DataSet = "foobar" - }; - - var expected = new - { - DataSet = new DataSet() - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expected); - - // Assert - act.Should().Throw() - .WithMessage("Expected*System.Data.DataSet*found System.String*"); - } - - [Fact] - public void When_data_set_table_count_has_expected_value_equivalence_test_should_succeed() - { - // Arrange - var dataSet = CreateDummyDataSet(); - - var correctTableCount = dataSet.Tables.Count; - - // Act & Assert - dataSet.Should().HaveTableCount(correctTableCount); - } - - [Fact] - public void Null_data_set_fails() - { - // Arrange - var dataSet = (DataSet)null; - - var correctTableCount = -1; - - // Act - Action act = () => - { - using var _ = new AssertionScope(); - dataSet.Should().HaveTableCount(correctTableCount); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expected dataSet to contain exactly*, but found *"); - } - - [Fact] - public void When_data_set_table_count_has_unexpected_value_equivalence_test_should_fail() - { - // Arrange - var dataSet = CreateDummyDataSet(); - - var correctTableCount = dataSet.Tables.Count; - - var incorrectTableCount = correctTableCount + 1; - - // Act - Action action = - () => dataSet.Should().HaveTableCount(incorrectTableCount); - - // Assert - action.Should().Throw().WithMessage("Expected dataSet to contain exactly 3 table(s), but found 2."); - } - - [Fact] - public void When_data_set_contains_expected_table_and_asserting_that_it_has_that_table_it_should_succeed() - { - // Arrange - var dataSet = CreateDummyDataSet(); - - var existingTableName = dataSet.Tables[0].TableName; - - // Act & Assert - dataSet.Should().HaveTable(existingTableName); - } - - [Fact] - public void Null_data_set_does_not_contain_expected_table() - { - // Arrange - var dataSet = (DataSet)null; - - var existingTableName = "Does not matter"; - - // Act - Action act = () => dataSet.Should().HaveTable(existingTableName); - - // Assert - act.Should().Throw() - .WithMessage("Expected dataSet to contain a table named*, but found *"); - } - - [Fact] - public void When_data_set_does_not_contain_expected_table_asserting_that_it_has_that_table_should_fail() - { - // Arrange - var dataSet = CreateDummyDataSet(); - - string nonExistingTableName = "Unicorn"; - - // Act - Action action = - () => dataSet.Should().HaveTable(nonExistingTableName); - - // Assert - action.Should().Throw().WithMessage("Expected dataSet to contain a table named *Unicorn*"); - } - - [Fact] - public void When_data_set_has_all_expected_tables_asserting_that_it_has_them_should_succeed() - { - // Arrange - var dataSet = CreateDummyDataSet(); - - var existingTableNames = dataSet.Tables.Cast() - .Select(table => table.TableName); - - // Act & Assert - dataSet.Should().HaveTables(existingTableNames); - } - - [Fact] - public void Null_data_set_has_no_tables_and_fails() - { - // Arrange - var dataSet = CreateDummyDataSet(); - var actual = (DataSet)null; - - var existingTableNames = dataSet.Tables.Cast() - .Select(table => table.TableName); - - // Act - Action act = () => - { - using var _ = new AssertionScope(); - actual.Should().HaveTables(existingTableNames); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expected actual to contain*table*with specific name*, but found *"); - } - - [Fact] - public void When_data_set_has_some_of_the_expected_tables_but_not_all_then_asserting_that_it_has_them_should_fail() - { - // Arrange - var dataSet = CreateDummyDataSet(); - - var tableNames = dataSet.Tables.Cast() - .Select(table => table.TableName) - .Concat(new[] { "Unicorn" }); - - // Act - Action action = - () => dataSet.Should().HaveTables(tableNames); - - // Assert - action.Should().Throw() - .WithMessage("Expected dataSet to contain a table named *Unicorn*, but it does not."); - } - - [Fact] - public void When_data_set_has_none_of_the_expected_tables_then_asserting_that_it_has_them_should_fail() - { - // Arrange - var dataSet = CreateDummyDataSet(); - - var nonExistentTableNames = new[] { "Unicorn", "Dragon" }; - - // Act - Action action = - () => dataSet.Should().HaveTables(nonExistentTableNames); - - // Assert - action.Should().Throw() - .WithMessage("Expected dataSet to contain a table named *Unicorn*, but it does not."); - } -} diff --git a/Tests/FluentAssertions.Equivalency.Specs/DataSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/DataSpecs.cs deleted file mode 100644 index a2d6b77e35..0000000000 --- a/Tests/FluentAssertions.Equivalency.Specs/DataSpecs.cs +++ /dev/null @@ -1,676 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Data; -using System.Diagnostics.CodeAnalysis; -using System.Linq; - -namespace FluentAssertions.Equivalency.Specs; - -/// -/// Class containing specs related to System.Data, including many common declarations and methods. -/// -public class DataSpecs -{ - // Non-static member to avoid getting flagged as a "static holder type". This - // type is inherited by the specialized specs classes it contains. - public int Dummy { get; set; } - - #region Subject Types - - /* - - NB: TypedDataTable1 and TypedDataTable2 intentionally have the same schema. This allows testing of - Should().BeEquivalentTo(options => options.AllowMismatchedTypes). - - */ - - public class TypedDataRow1 : DataRow - { - private readonly TypedDataTable1 ownerTable; - - public TypedDataRow1(DataRowBuilder rowBuilder) - : base(rowBuilder) - { - ownerTable = (TypedDataTable1)Table; - } - - public int RowID - { - get => (int)this[ownerTable.RowIDColumn]; - set => this[ownerTable.RowIDColumn] = value; - } - - public decimal Decimal - { - get => (decimal)this[ownerTable.DecimalColumn]; - set => this[ownerTable.DecimalColumn] = value; - } - - public string String - { - get => (string)this[ownerTable.StringColumn]; - set => this[ownerTable.StringColumn] = value; - } - - public Guid Guid - { - get => (Guid)this[ownerTable.GuidColumn]; - set => this[ownerTable.GuidColumn] = value; - } - - public DateTime DateTime - { - get => (DateTime)this[ownerTable.DateTimeColumn]; - set => this[ownerTable.DateTimeColumn] = value; - } - - public int? ForeignRowID - { - get => IsNull(ownerTable.ForeignRowIDColumn) ? null : (int?)this[ownerTable.ForeignRowIDColumn]; - set => this[ownerTable.ForeignRowIDColumn] = value; - } - } - - [SuppressMessage("Microsoft.StyleCop.CSharp.Naming", "SA1306", - Justification = "DataColumn accessors are named after the columns.")] - [SuppressMessage("Microsoft.StyleCop.CSharp.Naming", "SA1516", - Justification = "DataColumn accessors are grouped in a block of lines.")] - public class TypedDataTable1 : TypedTableBase - { - public TypedDataTable1() - { - TableName = "TypedDataTable1"; - - Columns.Add(new DataColumn("RowID", typeof(int))); - Columns.Add(new DataColumn("Decimal", typeof(decimal))); - Columns.Add(new DataColumn("String", typeof(string))); - Columns.Add(new DataColumn("Guid", typeof(Guid))); - Columns.Add(new DataColumn("DateTime", typeof(DateTime))); - Columns.Add(new DataColumn("ForeignRowID", typeof(int))); - - PrimaryKey = new[] { RowIDColumn }; - - Constraints.Add(new UniqueConstraint(GuidColumn)); - } - - protected override DataRow NewRowFromBuilder(DataRowBuilder builder) - { - return new TypedDataRow1(builder); - } - - public new TypedDataRow1 NewRow() - { - return (TypedDataRow1)base.NewRow(); - } - - public TypedDataRow1 this[int index] - { - get => (TypedDataRow1)Rows[index]; - } - - public DataColumn RowIDColumn => Columns["RowID"]; - public DataColumn DecimalColumn => Columns["Decimal"]; - public DataColumn StringColumn => Columns["String"]; - public DataColumn GuidColumn => Columns["Guid"]; - public DataColumn DateTimeColumn => Columns["DateTime"]; - public DataColumn ForeignRowIDColumn => Columns["ForeignRowID"]; - } - - public class TypedDataRow2 : DataRow - { - private readonly TypedDataTable2 ownerTable; - - public TypedDataRow2(DataRowBuilder rowBuilder) - : base(rowBuilder) - { - ownerTable = (TypedDataTable2)Table; - } - - public int RowID - { - get => (int)this[ownerTable.RowIDColumn]; - set => this[ownerTable.RowIDColumn] = value; - } - - public decimal Decimal - { - get => (decimal)this[ownerTable.DecimalColumn]; - set => this[ownerTable.DecimalColumn] = value; - } - - public string String - { - get => (string)this[ownerTable.StringColumn]; - set => this[ownerTable.StringColumn] = value; - } - - public Guid Guid - { - get => (Guid)this[ownerTable.GuidColumn]; - set => this[ownerTable.GuidColumn] = value; - } - - public DateTime DateTime - { - get => (DateTime)this[ownerTable.DateTimeColumn]; - set => this[ownerTable.DateTimeColumn] = value; - } - - public int? ForeignRowID - { - get => IsNull(ownerTable.ForeignRowIDColumn) ? null : (int?)this[ownerTable.ForeignRowIDColumn]; - set => this[ownerTable.ForeignRowIDColumn] = value; - } - } - - [SuppressMessage("Microsoft.StyleCop.CSharp.Naming", "SA1306", - Justification = "DataColumn accessors are named after the columns.")] - [SuppressMessage("Microsoft.StyleCop.CSharp.Naming", "SA1516", - Justification = "DataColumn accessors are grouped in a block of lines.")] - public class TypedDataTable2 : TypedTableBase - { - public TypedDataTable2() - { - TableName = "TypedDataTable2"; - - Columns.Add(new DataColumn("RowID", typeof(int))); - Columns.Add(new DataColumn("Decimal", typeof(decimal))); - Columns.Add(new DataColumn("String", typeof(string))); - Columns.Add(new DataColumn("Guid", typeof(Guid))); - Columns.Add(new DataColumn("DateTime", typeof(DateTime))); - Columns.Add(new DataColumn("ForeignRowID", typeof(int))); - - PrimaryKey = new[] { RowIDColumn }; - - Constraints.Add(new UniqueConstraint(GuidColumn)); - } - - protected override DataRow NewRowFromBuilder(DataRowBuilder builder) - { - return new TypedDataRow2(builder); - } - - public new TypedDataRow2 NewRow() - { - return (TypedDataRow2)base.NewRow(); - } - - public TypedDataRow2 this[int index] - { - get => (TypedDataRow2)Rows[index]; - } - - public DataColumn RowIDColumn => Columns["RowID"]; - public DataColumn DecimalColumn => Columns["Decimal"]; - public DataColumn StringColumn => Columns["String"]; - public DataColumn GuidColumn => Columns["Guid"]; - public DataColumn DateTimeColumn => Columns["DateTime"]; - public DataColumn ForeignRowIDColumn => Columns["ForeignRowID"]; - } - - [SuppressMessage("Microsoft.StyleCop.CSharp.Naming", "SA1516", - Justification = "DataTable accessors are grouped in a block of lines.")] - public class TypedDataSet : DataSet - { - public override SchemaSerializationMode SchemaSerializationMode { get; set; } - - public TypedDataSet() - : this(swapTableOrder: false) - { - } - - public TypedDataSet(bool swapTableOrder) - { - if (swapTableOrder) - { - Tables.Add(new TypedDataTable2()); - Tables.Add(new TypedDataTable1()); - } - else - { - Tables.Add(new TypedDataTable1()); - Tables.Add(new TypedDataTable2()); - } - } - - public TypedDataTable1 TypedDataTable1 => (TypedDataTable1)Tables["TypedDataTable1"]; - public TypedDataTable2 TypedDataTable2 => (TypedDataTable2)Tables["TypedDataTable2"]; - - public DataSet ToUntypedDataSet() - { - var dataSet = new DataSet(); - - dataSet.DataSetName = DataSetName; - dataSet.CaseSensitive = CaseSensitive; - dataSet.EnforceConstraints = EnforceConstraints; - dataSet.Locale = Locale; - dataSet.Namespace = Namespace; - dataSet.Prefix = Prefix; - dataSet.RemotingFormat = RemotingFormat; - - foreach (var typedTable in Tables.Cast()) - { - var dataTable = new DataTable(); - - dataTable.TableName = typedTable.TableName; - dataTable.DisplayExpression = typedTable.DisplayExpression; - dataTable.MinimumCapacity = typedTable.MinimumCapacity; - dataTable.Namespace = typedTable.Namespace; - dataTable.Prefix = typedTable.Prefix; - dataTable.RemotingFormat = typedTable.RemotingFormat; - - foreach (var column in typedTable.Columns.Cast()) - { - var dataColumn = new DataColumn(); - - dataColumn.ColumnName = column.ColumnName; - dataColumn.AllowDBNull = column.AllowDBNull; - dataColumn.AutoIncrement = column.AutoIncrement; - dataColumn.AutoIncrementSeed = column.AutoIncrementSeed; - dataColumn.AutoIncrementStep = column.AutoIncrementStep; - dataColumn.Caption = column.Caption; - dataColumn.ColumnMapping = column.ColumnMapping; - dataColumn.DataType = column.DataType; - dataColumn.DateTimeMode = column.DateTimeMode; - dataColumn.DefaultValue = column.DefaultValue; - dataColumn.Expression = column.Expression; - dataColumn.MaxLength = column.MaxLength; - dataColumn.Namespace = column.Namespace; - dataColumn.Prefix = column.Prefix; - dataColumn.ReadOnly = column.ReadOnly; - - foreach (var property in column.ExtendedProperties.Cast()) - { - dataColumn.ExtendedProperties.Add(property.Key, property.Value); - } - - dataTable.Columns.Add(dataColumn); - } - - foreach (var row in typedTable.Rows.Cast()) - { - dataTable.ImportRow(row); - } - - dataTable.PrimaryKey = typedTable.PrimaryKey - .Select(col => dataTable.Columns[col.ColumnName]) - .ToArray(); - - foreach (var property in typedTable.ExtendedProperties.Cast()) - { - dataTable.ExtendedProperties.Add(property.Key, property.Value); - } - - foreach (var constraint in typedTable.Constraints.Cast()) - { - if (!Relations.Cast().Any(rel => - rel.ChildKeyConstraint == constraint || - rel.ParentKeyConstraint == constraint)) - { - if (constraint is UniqueConstraint uniqueConstraint) - { - if (!uniqueConstraint.IsPrimaryKey) - { - dataTable.Constraints.Add(new UniqueConstraint( - name: uniqueConstraint.ConstraintName, - column: dataTable.Columns[uniqueConstraint.Columns.Single().ColumnName])); - } - } - else - { - throw new Exception($"Don't know how to clone a constraint of type {constraint.GetType()}"); - } - } - } - - dataSet.Tables.Add(dataTable); - } - - foreach (var property in ExtendedProperties.Cast()) - { - dataSet.ExtendedProperties.Add(property.Key, property.Value); - } - - foreach (var relation in Relations.Cast()) - { - // NB: In the context of the unit test, this is assuming that there is only one column in a relation. - var dataRelation = new DataRelation( - relation.RelationName, - parentColumn: dataSet.Tables[relation.ParentTable.TableName] - .Columns[relation.ParentColumns.Single().ColumnName], - childColumn: dataSet.Tables[relation.ChildTable.TableName] - .Columns[relation.ChildColumns.Single().ColumnName]); - - foreach (var property in relation.ExtendedProperties.Cast()) - { - dataRelation.ExtendedProperties.Add(property.Key, property.Value); - } - - dataSet.Relations.Add(dataRelation); - } - - return dataSet; - } - } - - public class TypedDataSetSubclass : TypedDataSet - { - public TypedDataSetSubclass() - { - } - - public TypedDataSetSubclass(TypedDataSet copyFrom, bool swapTableOrder = false, bool randomizeRowOrder = false) - : base(swapTableOrder) - { - DataSetName = copyFrom.DataSetName; - CaseSensitive = copyFrom.CaseSensitive; - EnforceConstraints = copyFrom.EnforceConstraints; - Locale = copyFrom.Locale; - Namespace = copyFrom.Namespace; - Prefix = copyFrom.Prefix; - RemotingFormat = copyFrom.RemotingFormat; -#pragma warning disable MA0056 - SchemaSerializationMode = copyFrom.SchemaSerializationMode; -#pragma warning restore MA0056 - - CopyTable( - from: copyFrom.TypedDataTable1, - to: TypedDataTable1, - randomizeRowOrder); - - CopyTable( - from: copyFrom.TypedDataTable2, - to: TypedDataTable2, - randomizeRowOrder); - - foreach (var property in copyFrom.ExtendedProperties.Cast()) - { - ExtendedProperties.Add(property.Key, property.Value); - } - - foreach (var copyFromRelation in copyFrom.Relations.Cast()) - { - // NB: In the context of the unit test, this is assuming that there is only one column in a relation. - var relation = new DataRelation( - copyFromRelation.RelationName, - parentColumn: Tables[copyFromRelation.ParentTable.TableName] - .Columns[copyFromRelation.ParentColumns.Single().ColumnName], - childColumn: Tables[copyFromRelation.ChildTable.TableName] - .Columns[copyFromRelation.ChildColumns.Single().ColumnName]); - - foreach (var property in copyFromRelation.ExtendedProperties.Cast()) - { - relation.ExtendedProperties.Add(property.Key, property.Value); - } - - Relations.Add(relation); - } - } - - private void CopyTable(TDataTable from, TDataTable to, bool randomizeRowOrder) - where TDataTable : DataTable, IEnumerable - where TDataRow : DataRow - { - if (randomizeRowOrder) - { - foreach (var row in from.OrderBy(_ => Guid.NewGuid())) - { - to.ImportRow(row); - } - } - else - { - foreach (var row in from) - { - to.ImportRow(row); - } - } - - foreach (var property in from.ExtendedProperties.Cast()) - { - to.ExtendedProperties.Add(property.Key, property.Value); - } - - foreach (var column in to.Columns.Cast()) - { - foreach (var property in from.Columns[column.ColumnName].ExtendedProperties.Cast()) - { - column.ExtendedProperties.Add(property.Key, property.Value); - } - } - } - } - - #endregion - - private static readonly Random Random = new(0); - - internal static TDataSet CreateDummyDataSet(bool identicalTables = false, bool includeDummyData = true, - bool includeRelation = true) - where TDataSet : TypedDataSet, new() - { - var ret = new TDataSet(); - - foreach (var dataColumn in ret.TypedDataTable1.Columns.Cast()) - { - AddExtendedProperties(dataColumn.ExtendedProperties); - } - - if (includeDummyData) - { - InsertDummyRows(ret.TypedDataTable1); - } - - AddExtendedProperties(ret.TypedDataTable1.ExtendedProperties); - - if (identicalTables) - { - foreach (var dataColumn in ret.TypedDataTable2.Columns.Cast()) - { - foreach (var property in ret.TypedDataTable1.ExtendedProperties.Cast()) - { - dataColumn.ExtendedProperties.Add(property.Key, property.Value); - } - } - - foreach (var row in ret.TypedDataTable1.Cast()) - { - ret.TypedDataTable2.ImportRow(row); - } - - foreach (var property in ret.TypedDataTable1.ExtendedProperties.Cast()) - { - ret.TypedDataTable2.ExtendedProperties.Add(property.Key, property.Value); - } - } - else - { - foreach (var dataColumn in ret.TypedDataTable2.Columns.Cast()) - { - AddExtendedProperties(dataColumn.ExtendedProperties); - } - - if (includeDummyData) - { - InsertDummyRows(ret.TypedDataTable2, ret.TypedDataTable1); - } - - if (includeRelation) - { - AddRelation(ret); - } - - AddExtendedProperties(ret.TypedDataTable2.ExtendedProperties); - } - - AddExtendedProperties(ret.ExtendedProperties); - - return ret; - } - - protected static void AddRelation(TypedDataSet dataSet) - { - var relation = new DataRelation("TestRelation", dataSet.TypedDataTable1.RowIDColumn, - dataSet.TypedDataTable2.ForeignRowIDColumn); - - AddExtendedProperties(relation.ExtendedProperties); - - dataSet.Relations.Add(relation); - } - - private static void AddExtendedProperties(PropertyCollection extendedProperties) - { - extendedProperties.Add(Guid.NewGuid(), Guid.NewGuid()); - extendedProperties.Add(Guid.NewGuid(), Guid.NewGuid().ToString()); - extendedProperties.Add(Guid.NewGuid().ToString(), Guid.NewGuid()); - } - - private static void InsertDummyRows(DataTable table, DataTable foreignRows = null) - { - for (int i = 0; i < 10; i++) - { - InsertDummyRow(table, foreignRows?.Rows[i]); - } - - table.AcceptChanges(); - } - - private static void InsertDummyRow(DataTable table, DataRow foreignRow = null) - { - var row = table.NewRow(); - - foreach (var column in table.Columns.Cast()) - { - if (column.ColumnName.StartsWith("Foreign", StringComparison.Ordinal)) - { - row[column] = foreignRow?[column.ColumnName.Substring(7)] ?? DBNull.Value; - } - else - { - row[column] = GetDummyValueOfType(column.DataType); - } - } - - table.Rows.Add(row); - } - - [SuppressMessage("Style", "IDE0010:Add missing cases", - Justification = "TypedDataTable test types have a limited set of columns.")] - private static object GetDummyValueOfType(Type dataType) - { - switch (Type.GetTypeCode(dataType)) - { - case TypeCode.Int32: - return Random.Next(); - case TypeCode.DateTime: - return DateTime.MinValue.AddTicks((Random.Next() & 0x7FFFFFFF) * 1469337835L) - .AddTicks(((Random.Next() & 0x7FFFFFFF) * 1469337835L) / 2147483648); - case TypeCode.Decimal: - return new decimal(Random.NextDouble()); - case TypeCode.String: - return Guid.NewGuid().ToString(); - - default: - if (dataType == typeof(Guid)) - { - return Guid.NewGuid(); - } - - throw new Exception($"Don't know how to generate dummy value of type {dataType}"); - } - } - - public enum ChangeType - { - Added, - Changed, - Removed, - } - - public static IEnumerable AllChangeTypes => - Enum.GetValues(typeof(ChangeType)).Cast().Select(t => new object[] { t }); - - public static IEnumerable AllChangeTypesWithAcceptChangesValues - { - get - { - return - from changeType in Enum.GetValues(typeof(ChangeType)).Cast() - from acceptChanges in new[] { true, false } - select new object[] { changeType, acceptChanges }; - } - } - - [SuppressMessage("Style", "IDE0010:Add missing cases", Justification = "Every enum value is covered")] - protected static void ApplyChange(DataColumnCollection columns, ChangeType changeType) - { - switch (changeType) - { - case ChangeType.Added: - columns.Add("Test", typeof(int)); - break; - case ChangeType.Changed: - columns[1].ColumnName += "different"; - break; - case ChangeType.Removed: - columns.RemoveAt(1); - break; - } - } - - [SuppressMessage("Style", "IDE0010:Add missing cases", Justification = "Every enum value is covered")] - protected static void ApplyChange(PropertyCollection extendedProperties, ChangeType changeType) - { - switch (changeType) - { - case ChangeType.Added: - extendedProperties[Guid.NewGuid()] = Guid.NewGuid(); - break; - case ChangeType.Changed: - extendedProperties[extendedProperties.Keys.Cast().First()] = Guid.NewGuid(); - break; - case ChangeType.Removed: - extendedProperties.Remove(extendedProperties.Keys.Cast().First()); - break; - } - } - - [SuppressMessage("Style", "IDE0010:Add missing cases", Justification = "Every enum value is covered")] - protected static void ApplyChange(ConstraintCollection constraints, DataColumn columnForNewConstraint, ChangeType changeType) - { - switch (changeType) - { - case ChangeType.Added: - constraints.Add( - new UniqueConstraint( - "Test", - columnForNewConstraint)); - - break; - case ChangeType.Changed: - constraints[1].ConstraintName += "different"; - break; - case ChangeType.Removed: - constraints.RemoveAt(1); - break; - } - } - - [SuppressMessage("Style", "IDE0010:Add missing cases", Justification = "Every enum value is covered")] - protected static void ApplyChange(DataRowCollection rows, DataTable dataTable, ChangeType changeType) - { - switch (changeType) - { - case ChangeType.Added: - InsertDummyRow(dataTable); - break; - case ChangeType.Changed: - rows[1]["String"] += "different"; - break; - case ChangeType.Removed: - rows.RemoveAt(0); - break; - } - } -} diff --git a/Tests/FluentAssertions.Equivalency.Specs/DataTableSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/DataTableSpecs.cs deleted file mode 100644 index 6c2077c4b2..0000000000 --- a/Tests/FluentAssertions.Equivalency.Specs/DataTableSpecs.cs +++ /dev/null @@ -1,1118 +0,0 @@ -using System; -using System.Data; -using System.Globalization; -using System.Linq; -using FluentAssertions.Data; -using FluentAssertions.Execution; -using Xunit; -using Xunit.Sdk; - -namespace FluentAssertions.Equivalency.Specs; - -public class DataTableSpecs : DataSpecs -{ - [Fact] - public void When_data_tables_are_identical_equivalence_test_should_succeed() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - var dataTable1 = typedDataSet1.ToUntypedDataSet().Tables["TypedDataTable1"]; - var dataTable2 = typedDataSet2.ToUntypedDataSet().Tables["TypedDataTable1"]; - - // Act & Assert - dataTable1.Should().BeEquivalentTo(dataTable2); - } - - [Fact] - public void When_data_tables_are_both_null_equivalence_test_should_succeed() - { - // Act & Assert - ((DataTable)null).Should().BeEquivalentTo(null); - } - - [Fact] - public void When_row_match_mode_is_invalid_it_should_fail() - { - // Arrange - var typedDataSet = CreateDummyDataSet(); - - var subject = typedDataSet.ToUntypedDataSet().Tables["TypedDataTable1"]; - var expectation = typedDataSet.ToUntypedDataSet().Tables["TypedDataTable1"]; - - // Act - Action action = () => subject.Should().BeEquivalentTo(expectation, options => options.UsingRowMatchMode((RowMatchMode)2)); - - // Assert - action.Should().Throw().WithMessage( - "Unknown RowMatchMode *when trying to compare *"); - } - - [Theory] - [MemberData(nameof(EmptyPrimaryKeys))] - public void When_row_match_mode_is_primary_key_without_primary_key_it_should_fail(DataColumn[] emptyPrimaryKey) - { - // Arrange - var typedDataSet = CreateDummyDataSet(includeRelation: false); - - var subject = typedDataSet.ToUntypedDataSet().Tables["TypedDataTable1"]; - var expectation = typedDataSet.ToUntypedDataSet().Tables["TypedDataTable1"]; - - subject.PrimaryKey = emptyPrimaryKey; - - // Act - Action action = () => - subject.Should().BeEquivalentTo(expectation, options => options.UsingRowMatchMode(RowMatchMode.PrimaryKey)); - - // Assert - action.Should().Throw().WithMessage( - "*Table *containing *does not have a primary key. RowMatchMode.PrimaryKey cannot be applied.*"); - } - - public static TheoryData EmptyPrimaryKeys => new() - { - null, - new DataColumn[] { } - }; - - [Fact] - public void When_primary_key_types_do_not_match_it_should_throw() - { - // Arrange - var typedDataSetSubject = CreateDummyDataSet(includeDummyData: false, includeRelation: false); - var typedDataSetExpectation = new TypedDataSetSubclass(typedDataSetSubject); - - var subject = typedDataSetSubject.ToUntypedDataSet().Tables["TypedDataTable1"]; - var expectation = typedDataSetExpectation.ToUntypedDataSet().Tables["TypedDataTable1"]; - - subject.PrimaryKey[0].DataType = typeof(long); - subject.Rows.Add(1L); - subject.AcceptChanges(); - expectation.Rows.Add(1); - expectation.AcceptChanges(); - - // Act - Action action = () => - subject.Should().BeEquivalentTo(expectation, options => options.UsingRowMatchMode(RowMatchMode.PrimaryKey)); - - // Assert - action.Should().Throw().WithMessage( - "*Subject and expectation primary keys of table containing *do not have the same schema and cannot be compared. " + - "RowMatchMode.PrimaryKey cannot be applied.*"); - } - - [Fact] - public void When_primary_key_of_one_rows_differ_it_should_fail() - { - // Arrange - var typedDataSetSubject = CreateDummyDataSet(); - var typedDataSetExpectation = new TypedDataSetSubclass(typedDataSetSubject); - - var subject = typedDataSetSubject.ToUntypedDataSet().Tables["TypedDataTable1"]; - var expectation = typedDataSetExpectation.ToUntypedDataSet().Tables["TypedDataTable1"]; - - expectation.Rows[0].SetField(expectation.PrimaryKey[0], 0); - - expectation.AcceptChanges(); - - // Act - Action action = () => - subject.Should().BeEquivalentTo(expectation, options => options.UsingRowMatchMode(RowMatchMode.PrimaryKey)); - - // Assert - action.Should().Throw().WithMessage( - "Found unexpected row in *with key *Expected to find a row with key *in *, but no such row was found*"); - } - - [Fact] - public void When_primary_key_of_multiple_rows_differ_it_should_fail() - { - // Arrange - var typedDataSetSubject = CreateDummyDataSet(); - var typedDataSetExpectation = new TypedDataSetSubclass(typedDataSetSubject); - - var subject = typedDataSetSubject.ToUntypedDataSet().Tables["TypedDataTable1"]; - var expectation = typedDataSetExpectation.ToUntypedDataSet().Tables["TypedDataTable1"]; - - for (int i = 0; i < 3; i++) - { - expectation.Rows[i].SetField(expectation.PrimaryKey[0], i); - } - - expectation.AcceptChanges(); - - // Act - Action action = () => - subject.Should().BeEquivalentTo(expectation, options => options.UsingRowMatchMode(RowMatchMode.PrimaryKey)); - - // Assert - action.Should().Throw().WithMessage( - "Found unexpected row in *with key * rows were expected in *and not found*"); - } - - [Fact] - public void When_data_table_is_null_and_isnt_expected_to_be_equivalence_test_should_fail() - { - // Arrange - var typedDataSet = CreateDummyDataSet(); - - var dataTable = typedDataSet.ToUntypedDataSet().Tables["TypedDataTable1"]; - - // Act - Action action = () => ((DataTable)null).Should().BeEquivalentTo(dataTable); - - // Assert - action.Should().Throw().WithMessage( - "Expected *to be non-null, but found null*"); - } - - [Fact] - public void When_data_table_is_expected_to_be_null_and_isnt_equivalence_test_should_fail() - { - // Arrange - var typedDataSet = CreateDummyDataSet(); - - var dataTable = typedDataSet.ToUntypedDataSet().Tables["TypedDataTable1"]; - - // Act - Action action = () => dataTable.Should().BeEquivalentTo(null); - - // Assert - action.Should().Throw().WithMessage("Expected dataTable value to be null, but found TypedDataTable1*"); - } - - [Fact] - public void - When_data_table_type_does_not_match_and_assertion_is_not_configured_to_allow_mismatched_types_equivalence_test_should_fail() - { - // Arrange - var typedDataSet = CreateDummyDataSet(identicalTables: true); - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet); - - var dataTable = typedDataSet.ToUntypedDataSet().Tables["TypedDataTable1"]; - var dataTableOfMismatchedType = typedDataSet2.TypedDataTable1; - - // Act - Action action = () => dataTable.Should().BeEquivalentTo(dataTableOfMismatchedType); - - // Assert - action.Should().Throw() - .WithMessage("Expected dataTable to be of type *TypedDataTable1*, but found *System.Data.DataTable*"); - } - - [Fact] - public void - When_data_table_type_does_not_match_but_assertion_is_configured_to_allow_mismatched_types_equivalence_test_should_succeed() - { - // Arrange - var typedDataSet = CreateDummyDataSet(identicalTables: true); - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet); - - var dataTable = typedDataSet.ToUntypedDataSet().Tables["TypedDataTable1"]; - var dataTableOfMismatchedType = typedDataSet2.TypedDataTable1; - - // Act & Assert - dataTable.Should().BeEquivalentTo(dataTableOfMismatchedType, options => options.AllowingMismatchedTypes()); - } - - [Fact] - public void When_data_table_name_does_not_match_and_the_corresponding_property_is_not_excluded_equivalence_test_should_fail() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - var dataTable1 = typedDataSet1.ToUntypedDataSet().Tables["TypedDataTable1"]; - var dataTable2 = typedDataSet2.ToUntypedDataSet().Tables["TypedDataTable1"]; - - dataTable2.TableName += "different"; - - // Act - Action action = () => dataTable1.Should().BeEquivalentTo(dataTable2); - - // Assert - action.Should().Throw() - .WithMessage("Expected dataTable1 to have TableName *different*, but found *TypedDataTable1* instead*"); - } - - [Fact] - public void When_excluding_invalid_constraint_it_should_throw() - { - // Arrange - var typedDataSet = CreateDummyDataSet(); - - var subject = typedDataSet.ToUntypedDataSet().Tables["TypedDataTable1"]; - var expectation = typedDataSet.ToUntypedDataSet().Tables["TypedDataTable1"]; - - // Act - Action action = () => subject.Should().BeEquivalentTo(expectation, options => options - .ExcludingRelated((Constraint constraint) => new object())); - - // Assert - action.Should().Throw().WithMessage( - "*Expression must be a simple member access*"); - } - - [Fact] - public void When_data_table_name_does_not_match_but_the_corresponding_property_is_excluded_equivalence_test_should_succeed() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(identicalTables: true); - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - var dataTable1 = typedDataSet1.ToUntypedDataSet().Tables["TypedDataTable1"]; - var dataTable2 = typedDataSet2.ToUntypedDataSet().Tables["TypedDataTable1"]; - - dataTable2.TableName += "different"; - - // Act & Assert - dataTable1.Should().BeEquivalentTo( - dataTable2, - options => options - .Excluding(dataTable => dataTable.TableName) - .ExcludingRelated((DataColumn dataColumn) => dataColumn.Table) - .ExcludingRelated((Constraint constraint) => constraint.Table)); - } - - [Fact] - public void - When_data_table_case_sensitivity_does_not_match_and_the_corresponding_property_is_not_excluded_equivalence_test_should_fail() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - typedDataSet2.CaseSensitive = !typedDataSet2.CaseSensitive; - - var dataTable1 = typedDataSet1.ToUntypedDataSet().Tables["TypedDataTable1"]; - var dataTable2 = typedDataSet2.ToUntypedDataSet().Tables["TypedDataTable1"]; - - // Act - Action action = () => dataTable1.Should().BeEquivalentTo(dataTable2); - - // Assert - action.Should().Throw() - .WithMessage("Expected dataTable1 to have CaseSensitive value of True, but found False instead*"); - } - - [Fact] - public void - When_data_table_case_sensitivity_does_not_match_but_the_corresponding_property_is_excluded_equivalence_test_should_succeed() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - typedDataSet2.CaseSensitive = !typedDataSet2.CaseSensitive; - - var dataTable1 = typedDataSet1.ToUntypedDataSet().Tables["TypedDataTable1"]; - var dataTable2 = typedDataSet2.ToUntypedDataSet().Tables["TypedDataTable1"]; - - // Act & Assert - dataTable1.Should().BeEquivalentTo(dataTable2, options => options.Excluding(dataTable => dataTable.CaseSensitive)); - } - - [Fact] - public void - When_data_table_display_expression_does_not_match_and_the_corresponding_property_is_not_excluded_equivalence_test_should_fail() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - var dataTable1 = typedDataSet1.ToUntypedDataSet().Tables["TypedDataTable1"]; - var dataTable2 = typedDataSet2.ToUntypedDataSet().Tables["TypedDataTable1"]; - - dataTable2.DisplayExpression = typedDataSet2.TypedDataTable1.StringColumn.ColumnName; - - // Act - Action action = () => dataTable1.Should().BeEquivalentTo(dataTable2); - - // Assert - action.Should().Throw().WithMessage("Expected dataTable1 to have DisplayExpression value of *String*"); - } - - [Fact] - public void - When_data_table_display_expression_does_not_match_but_the_corresponding_property_is_excluded_equivalence_test_should_succeed() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - var dataTable1 = typedDataSet1.ToUntypedDataSet().Tables["TypedDataTable1"]; - var dataTable2 = typedDataSet2.ToUntypedDataSet().Tables["TypedDataTable1"]; - - dataTable2.DisplayExpression = typedDataSet2.TypedDataTable1.StringColumn.ColumnName; - - // Act & Assert - dataTable1.Should().BeEquivalentTo(dataTable2, options => options.Excluding(dataTable => dataTable.DisplayExpression)); - } - - [Fact] - public void - When_one_data_table_has_errors_and_the_other_does_not_and_the_property_that_indicates_the_presence_of_errors_is_not_excluded_equivalence_test_should_fail() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - var dataTable1 = typedDataSet1.ToUntypedDataSet().Tables["TypedDataTable1"]; - var dataTable2 = typedDataSet2.ToUntypedDataSet().Tables["TypedDataTable1"]; - - dataTable2.Rows[0].RowError = "Manually added error"; - - // Act - Action action = () => dataTable1.Should().BeEquivalentTo(dataTable2); - - // Assert - action.Should().Throw().Which.Message.Should().Contain("HasErrors"); - } - - [Fact] - public void - When_one_data_table_has_errors_and_the_other_does_not_but_the_property_that_indicates_the_presence_of_errors_is_excluded_equivalence_test_should_succeed() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - var dataTable1 = typedDataSet1.ToUntypedDataSet().Tables["TypedDataTable1"]; - var dataTable2 = typedDataSet2.ToUntypedDataSet().Tables["TypedDataTable1"]; - - dataTable2.Rows[0].RowError = "Manually added error"; - - // Act & Assert - dataTable1.Should().BeEquivalentTo(dataTable2, config => config - .Excluding(dataTable => dataTable.HasErrors) - .ExcludingRelated((DataRow dataRow) => dataRow.HasErrors)); - } - - [Fact] - public void - When_data_table_locale_does_not_match_and_the_corresponding_property_is_not_excluded_equivalence_test_should_fail() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - typedDataSet1.Locale = new CultureInfo("en-US"); - typedDataSet2.Locale = new CultureInfo("fr-CA"); - - var dataTable1 = typedDataSet1.ToUntypedDataSet().Tables["TypedDataTable1"]; - var dataTable2 = typedDataSet2.ToUntypedDataSet().Tables["TypedDataTable1"]; - - // Act - Action action = () => dataTable1.Should().BeEquivalentTo(dataTable2); - - // Assert - action.Should().Throw() - .WithMessage("Expected dataTable1 to have Locale value of *fr-CA*, but found *en-US* instead*"); - } - - [Fact] - public void When_data_table_locale_does_not_match_but_the_corresponding_property_is_excluded_equivalence_test_should_succeed() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - typedDataSet1.Locale = new CultureInfo("en-US"); - typedDataSet2.Locale = new CultureInfo("fr-CA"); - - var dataTable1 = typedDataSet1.ToUntypedDataSet().Tables["TypedDataTable1"]; - var dataTable2 = typedDataSet2.ToUntypedDataSet().Tables["TypedDataTable1"]; - - // Act & Assert - dataTable1.Should().BeEquivalentTo(dataTable2, options => options.Excluding(dataTable => dataTable.Locale)); - } - - [Fact] - public void - When_data_table_namespace_does_not_match_and_the_corresponding_property_is_not_excluded_equivalence_test_should_fail() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - typedDataSet2.Namespace += "different"; - - var dataTable1 = typedDataSet1.ToUntypedDataSet().Tables["TypedDataTable1"]; - var dataTable2 = typedDataSet2.ToUntypedDataSet().Tables["TypedDataTable1"]; - - // Act - Action action = () => dataTable1.Should().BeEquivalentTo(dataTable2); - - // Assert - action.Should().Throw() - .WithMessage("Expected dataTable1 to have Namespace value of *different*, but found *"); - } - - [Fact] - public void - When_data_table_namespace_does_not_match_but_the_corresponding_property_is_excluded_equivalence_test_should_succeed() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - typedDataSet2.Namespace += "different"; - - var dataTable1 = typedDataSet1.ToUntypedDataSet().Tables["TypedDataTable1"]; - var dataTable2 = typedDataSet2.ToUntypedDataSet().Tables["TypedDataTable1"]; - - // Act & Assert - dataTable1.Should().BeEquivalentTo(dataTable2, options => options - .Excluding(dataTable => dataTable.Namespace) - .ExcludingRelated((DataColumn dataColumn) => dataColumn.Namespace)); - } - - [Fact] - public void - When_data_table_prefix_does_not_match_and_the_corresponding_property_is_not_excluded_equivalence_test_should_fail() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - var dataTable1 = typedDataSet1.ToUntypedDataSet().Tables["TypedDataTable1"]; - var dataTable2 = typedDataSet2.ToUntypedDataSet().Tables["TypedDataTable1"]; - - dataTable2.Prefix += "different"; - - // Act - Action action = () => dataTable1.Should().BeEquivalentTo(dataTable2); - - // Assert - action.Should().Throw() - .WithMessage("Expected dataTable1 to have Prefix value of *different*, but found * instead*"); - } - - [Fact] - public void When_data_table_prefix_does_not_match_but_the_corresponding_property_is_excluded_equivalence_test_should_succeed() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - var dataTable1 = typedDataSet1.ToUntypedDataSet().Tables["TypedDataTable1"]; - var dataTable2 = typedDataSet2.ToUntypedDataSet().Tables["TypedDataTable1"]; - - dataTable2.Prefix += "different"; - - // Act & Assert - dataTable1.Should().BeEquivalentTo(dataTable2, options => options.Excluding(dataTable => dataTable.Prefix)); - } - -#if !NET8_0_OR_GREATER - - [Fact] - public void - When_data_table_remoting_format_does_not_match_and_the_corresponding_property_is_not_excluded_equivalence_test_should_fail() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - typedDataSet2.RemotingFormat = - typedDataSet2.RemotingFormat == SerializationFormat.Binary - ? SerializationFormat.Xml - : SerializationFormat.Binary; - - var dataTable1 = typedDataSet1.ToUntypedDataSet().Tables["TypedDataTable1"]; - var dataTable2 = typedDataSet2.ToUntypedDataSet().Tables["TypedDataTable1"]; - - // Act - Action action = () => dataTable1.Should().BeEquivalentTo(dataTable2); - - // Assert - action.Should().Throw() - .WithMessage("Expected dataTable1 to have RemotingFormat value of *Binary*, but found *Xml* instead*"); - } - - [Fact] - public void - When_data_table_remoting_format_does_not_match_but_the_corresponding_property_is_excluded_equivalence_test_should_succeed() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - typedDataSet2.RemotingFormat = - typedDataSet2.RemotingFormat == SerializationFormat.Binary - ? SerializationFormat.Xml - : SerializationFormat.Binary; - - var dataTable1 = typedDataSet1.ToUntypedDataSet().Tables["TypedDataTable1"]; - var dataTable2 = typedDataSet2.ToUntypedDataSet().Tables["TypedDataTable1"]; - - // LAST ONE - - // Act & Assert - dataTable1.Should().BeEquivalentTo(dataTable2, options => options.Excluding(dataTable => dataTable.RemotingFormat)); - } - -#endif - - [Theory] - [MemberData(nameof(AllChangeTypes))] - public void When_data_table_columns_do_not_match_and_the_corresponding_property_is_not_excluded_equivalence_test_should_fail( - ChangeType changeType) - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - var dataTable1 = typedDataSet1.ToUntypedDataSet().Tables["TypedDataTable1"]; - var dataTable2 = typedDataSet2.ToUntypedDataSet().Tables["TypedDataTable1"]; - - ApplyChange(dataTable2.Columns, changeType); - - // Act - Action action = () => dataTable1.Should().BeEquivalentTo(dataTable2); - - // Assert - action.Should().Throw().WithMessage("Expected property dataTable1.Column* to be *, but *"); - } - - [Theory] - [MemberData(nameof(AllChangeTypes))] - public void When_data_table_columns_do_not_match_but_columns_and_rows_are_excluded_equivalence_test_should_succeed( - ChangeType changeType) - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - var dataTable1 = typedDataSet1.ToUntypedDataSet().Tables["TypedDataTable1"]; - var dataTable2 = typedDataSet2.ToUntypedDataSet().Tables["TypedDataTable1"]; - - ApplyChange(dataTable2.Columns, changeType); - - // Act & Assert - dataTable1.Should().BeEquivalentTo(dataTable2, options => options - .Excluding(dataTable => dataTable.Columns) - .Excluding(dataTable => dataTable.Rows)); - } - - [Theory] - [MemberData(nameof(AllChangeTypes))] - public void - When_data_table_extended_properties_do_not_match_and_the_corresponding_property_is_not_excluded_equivalence_test_should_fail( - ChangeType changeType) - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - var dataTable1 = typedDataSet1.ToUntypedDataSet().Tables["TypedDataTable1"]; - var dataTable2 = typedDataSet2.ToUntypedDataSet().Tables["TypedDataTable1"]; - - ApplyChange(dataTable2.ExtendedProperties, changeType); - - // Act - Action action = () => dataTable1.Should().BeEquivalentTo(dataTable2); - - // Assert - action.Should().Throw().WithMessage("Expected *dataTable1.ExtendedProperties* to be *, but *"); - } - - [Theory] - [MemberData(nameof(AllChangeTypes))] - public void - When_data_table_extended_properties_do_not_match_but_the_corresponding_property_is_excluded_equivalence_test_should_succeed( - ChangeType changeType) - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - var dataTable1 = typedDataSet1.ToUntypedDataSet().Tables["TypedDataTable1"]; - var dataTable2 = typedDataSet2.ToUntypedDataSet().Tables["TypedDataTable1"]; - - ApplyChange(dataTable2.ExtendedProperties, changeType); - - // Act & Assert - dataTable1.Should().BeEquivalentTo(dataTable2, options => options.Excluding(dataTable => dataTable.ExtendedProperties)); - } - - [Fact] - public void - When_data_table_primary_key_does_not_match_and_the_corresponding_property_is_not_excluded_equivalence_test_should_fail() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - var dataTable1 = typedDataSet1.ToUntypedDataSet().Tables["TypedDataTable2"]; - var dataTable2 = typedDataSet2.ToUntypedDataSet().Tables["TypedDataTable2"]; - - dataTable1.Columns.Cast().Skip(2).ToList() - .ForEach(col => col.AllowDBNull = false); - - dataTable2.PrimaryKey = dataTable2.Columns.Cast().Skip(2).ToArray(); - dataTable2.Columns[0].Unique = true; - - // Act - Action action = () => - dataTable1.Should().BeEquivalentTo(dataTable2, options => options - .Excluding(dataTable => dataTable.Constraints)); - - // Assert - action.Should().Throw() - .WithMessage( - "Expected property dataTable1.PrimaryKey to be a collection with * item(s), but *contains * item(s) less than*"); - } - - [Fact] - public void - When_data_table_primary_key_does_not_match_but_the_corresponding_property_is_excluded_equivalence_test_should_succeed() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - for (int i = 2; i < typedDataSet1.TypedDataTable2.Columns.Count; i++) - { - typedDataSet1.TypedDataTable2.Columns[i].AllowDBNull = false; - } - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - var dataTable1 = typedDataSet1.ToUntypedDataSet().Tables["TypedDataTable2"]; - var dataTable2 = typedDataSet2.ToUntypedDataSet().Tables["TypedDataTable2"]; - - dataTable2.PrimaryKey = dataTable2.Columns.Cast().Skip(2).ToArray(); - dataTable2.Columns[0].Unique = true; - - // Act & Assert - dataTable1.Should().BeEquivalentTo(dataTable2, options => options - .Excluding(dataTable => dataTable.PrimaryKey) - .Excluding(dataTable => dataTable.Constraints)); - } - - public enum NumberOfColumnsInConstraintDifference - { - SingleColumn, - MultipleColumns, - } - - [Theory] - [InlineData(NumberOfColumnsInConstraintDifference.SingleColumn)] - [InlineData(NumberOfColumnsInConstraintDifference.MultipleColumns)] - public void When_columns_for_constraint_in_data_table_do_not_match_message_should_list_all_columns_involved( - NumberOfColumnsInConstraintDifference difference) - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - var dataTable1 = typedDataSet1.ToUntypedDataSet().Tables["TypedDataTable2"]; - var dataTable2 = typedDataSet2.ToUntypedDataSet().Tables["TypedDataTable2"]; - - int differenceCount = - difference switch - { - NumberOfColumnsInConstraintDifference.SingleColumn => 1, - NumberOfColumnsInConstraintDifference.MultipleColumns => 2, - - _ => throw new Exception("Sanity failure") - }; - - var dataTable1ColumnsForConstraint = dataTable1.Columns.Cast() - .Take(dataTable1.Columns.Count - differenceCount) - .ToArray(); - - var dataTable2ColumnsForConstraint = dataTable2.Columns.Cast() - .Skip(differenceCount) - .ToArray(); - - const string ConstraintName = "TestSubjectConstraint"; - - dataTable1.Constraints.Add(new UniqueConstraint(ConstraintName, dataTable1ColumnsForConstraint)); - dataTable2.Constraints.Add(new UniqueConstraint(ConstraintName, dataTable2ColumnsForConstraint)); - - var missingColumnNames = dataTable2ColumnsForConstraint.Select(col => col.ColumnName) - .Except(dataTable1ColumnsForConstraint.Select(col => col.ColumnName)); - - var extraColumnNames = dataTable1ColumnsForConstraint.Select(col => col.ColumnName) - .Except(dataTable2ColumnsForConstraint.Select(col => col.ColumnName)); - - string columnsNoun = differenceCount == 1 - ? "column" - : "columns"; - - // Act - Action action = () => dataTable1.Should().BeEquivalentTo(dataTable2); - - // Assert - action.Should().Throw().WithMessage( - $"Expected *{ConstraintName}* to include {columnsNoun} {string.Join("*", missingColumnNames)}*" + - $"Did not expect *{ConstraintName}* to include {columnsNoun} {string.Join("*", extraColumnNames)}*"); - } - - [Theory] - [MemberData(nameof(AllChangeTypes))] - public void - When_data_table_constraints_do_not_match_and_the_corresponding_property_is_not_excluded_equivalence_test_should_fail( - ChangeType changeType) - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - var dataTable1 = typedDataSet1.ToUntypedDataSet().Tables["TypedDataTable2"]; - var dataTable2 = typedDataSet2.ToUntypedDataSet().Tables["TypedDataTable2"]; - - ApplyChange(dataTable2.Constraints, dataTable2.Columns["Decimal"], changeType); - - string expectedExceptionPattern = - changeType == ChangeType.Changed - ? "Found unexpected constraint named *Constraint2* in property dataTable1.Constraints*" - : "Expected property dataTable1.Columns[*].Unique to be *, but found *"; - - // Act - Action action = () => dataTable1.Should().BeEquivalentTo(dataTable2); - - // Assert - action.Should().Throw().WithMessage(expectedExceptionPattern); - } - - [Theory] - [MemberData(nameof(AllChangeTypes))] - public void - When_data_table_constraints_do_not_match_but_the_corresponding_property_is_excluded_equivalence_test_should_succeed( - ChangeType changeType) - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - var dataTable1 = typedDataSet1.ToUntypedDataSet().Tables["TypedDataTable2"]; - var dataTable2 = typedDataSet2.ToUntypedDataSet().Tables["TypedDataTable2"]; - - ApplyChange(dataTable2.Constraints, dataTable2.Columns["Decimal"], changeType); - - // Act & Assert - dataTable1.Should().BeEquivalentTo(dataTable2, options => options - .Excluding(dataTable => dataTable.Constraints) - .ExcludingRelated(dataColumn => dataColumn.Unique)); - } - - [Theory] - [MemberData(nameof(AllChangeTypesWithAcceptChangesValues))] - public void When_data_table_rows_do_not_match_and_the_corresponding_property_is_not_excluded_equivalence_test_should_fail( - ChangeType changeType, bool acceptChanges) - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - var dataTable1 = typedDataSet1.ToUntypedDataSet().Tables["TypedDataTable1"]; - var dataTable2 = typedDataSet2.ToUntypedDataSet().Tables["TypedDataTable1"]; - - ApplyChange(dataTable2.Rows, dataTable2, changeType); - - if (acceptChanges) - { - dataTable2.AcceptChanges(); - } - - string exceptionPattern; - - if (changeType == ChangeType.Changed) - { - exceptionPattern = - acceptChanges - ? "Expected dataTable1.Rows[1][String] to be *different* with a length of *, but * has a length of *, differs near *" - : "Expected dataTable1.Rows[1] to have RowState value of *Modified*, but found *Unchanged* instead*"; - } - else - { - exceptionPattern = - "Expected property dataTable1.Rows to contain * row(s), but found 10*"; - } - - // Act - Action action = () => dataTable1.Should().BeEquivalentTo(dataTable2); - - // Assert - action.Should().Throw().WithMessage(exceptionPattern); - } - - [Theory] - [MemberData(nameof(AllChangeTypesWithAcceptChangesValues))] - public void When_data_table_rows_do_not_match_but_the_corresponding_property_is_excluded_equivalence_test_should_succeed( - ChangeType changeType, bool acceptChanges) - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1); - - var dataTable1 = typedDataSet1.ToUntypedDataSet().Tables["TypedDataTable1"]; - var dataTable2 = typedDataSet2.ToUntypedDataSet().Tables["TypedDataTable1"]; - - ApplyChange(dataTable2.Rows, dataTable2, changeType); - - if (acceptChanges) - { - dataTable2.AcceptChanges(); - } - - // Act & Assert - dataTable1.Should().BeEquivalentTo(dataTable2, options => options.Excluding(dataTable => dataTable.Rows)); - } - - [Fact] - public void - When_data_table_data_matches_in_different_order_and_the_row_match_mode_is_by_primary_key_equivalence_test_should_succeed() - { - // Arrange - var typedDataSet1 = CreateDummyDataSet(); - - var typedDataSet2 = new TypedDataSetSubclass(typedDataSet1, randomizeRowOrder: true); - - var dataTable1 = typedDataSet1.ToUntypedDataSet().Tables["TypedDataTable1"]; - var dataTable2 = typedDataSet2.ToUntypedDataSet().Tables["TypedDataTable1"]; - - // Act & Assert - dataTable1.Should().BeEquivalentTo(dataTable2, options => options.UsingRowMatchMode(RowMatchMode.PrimaryKey)); - } - - [Fact] - public void Data_table_is_not_equivalent_to_another_type() - { - // Arrange - var subject = new - { - DataTable = "foobar" - }; - - var expected = new - { - DataTable = new DataTable() - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expected); - - // Assert - act.Should().Throw() - .WithMessage("Expected*System.Data.DataTable*found System.String*"); - } - - [Fact] - public void When_data_table_has_expected_row_count_it_should_succeed() - { - // Arrange - var dataSet = CreateDummyDataSet(); - - var dataTable = dataSet.TypedDataTable1; - - int correctRowCount = dataTable.Rows.Count; - - // Act & Assert - dataTable.Should().HaveRowCount(correctRowCount); - } - - [Fact] - public void Null_data_table_no_rows_and_fails_test() - { - // Arrange - var dataTable = (DataTable)null; - - int correctRowCount = -1; - - // Act - Action act = () => - { - using var _ = new AssertionScope(); - dataTable.Should().HaveRowCount(correctRowCount); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expected dataTable to contain exactly*row*, but found *"); - } - - [Fact] - public void When_empty_data_table_has_expected_row_count_of_zero_it_should_succeed() - { - // Arrange - var dataSet = CreateDummyDataSet(); - - var dataTable = dataSet.TypedDataTable2; - - dataTable.Rows.Clear(); - dataTable.AcceptChanges(); - - // Act & Assert - dataTable.Should().HaveRowCount(0); - } - - [Fact] - public void When_data_table_does_not_have_expected_row_count_does_not_match_it_should_fail() - { - // Arrange - var dataSet = CreateDummyDataSet(); - - var dataTable = dataSet.TypedDataTable1; - - int correctRowCount = dataTable.Rows.Count; - - int incorrectRowCount = correctRowCount * 2; - - // Act - Action action = - () => dataTable.Should().HaveRowCount(incorrectRowCount); - - // Assert - action.Should().Throw().WithMessage("Expected dataTable to contain exactly * row(s), but found *"); - } - - [Fact] - public void When_data_table_has_expected_column_it_should_succeed() - { - // Arrange - var dataSet = CreateDummyDataSet(); - - var dataTable = dataSet.TypedDataTable1; - - var expectedColumnName = dataTable.Columns[0].ColumnName; - - // Act & Assert - dataTable.Should().HaveColumn(expectedColumnName); - } - - [Fact] - public void Null_data_table_has_no_columns_and_fail_the_test() - { - // Arrange - var dataTable = (DataTable)null; - - var expectedColumnName = "Does not matter"; - - // Act - Action act = () => dataTable.Should().HaveColumn(expectedColumnName); - - // Assert - act.Should().Throw() - .WithMessage("Expected dataTable to contain a column named*, but found *"); - } - - [Fact] - public void When_data_table_does_not_have_expected_column_it_should_fail() - { - // Arrange - var dataSet = CreateDummyDataSet(); - - var dataTable = dataSet.TypedDataTable1; - - // Act - Action action = - () => dataTable.Should().HaveColumn("Unicorn"); - - // Assert - action.Should().Throw() - .WithMessage("Expected dataTable to contain a column named *Unicorn*, but it does not."); - } - - [Fact] - public void When_data_table_has_all_expected_columns_it_should_succeed() - { - // Arrange - var dataSet = CreateDummyDataSet(); - - var dataTable = dataSet.TypedDataTable1; - - var existingColumnNames = dataTable.Columns.Cast() - .Select(column => column.ColumnName); - - // Act & Assert - dataTable.Should().HaveColumns(existingColumnNames); - } - - [Fact] - public void Null_data_table_has_no_columns_and_fails_the_test() - { - // Arrange - var actual = (DataTable)null; - - var existingColumnName = "Does not matter"; - - // Act - Action act = () => - { - using var _ = new AssertionScope(); - actual.Should().HaveColumns(existingColumnName); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expected actual to contain*column*with specific name*, but found *"); - } - - [Fact] - public void When_data_table_has_only_some_expected_columns_then_asserting_that_it_has_all_of_them_should_fail() - { - // Arrange - var dataSet = CreateDummyDataSet(); - - var dataTable = dataSet.TypedDataTable1; - - var columnNames = dataTable.Columns.Cast() - .Select(column => column.ColumnName) - .Concat(new[] { "Unicorn" }); - - // Act - Action action = - () => dataTable.Should().HaveColumns(columnNames); - - // Assert - action.Should().Throw() - .WithMessage("Expected dataTable to contain a column named *Unicorn*, but it does not."); - } - - [Fact] - public void When_data_table_has_none_of_the_expected_columns_then_asserting_that_it_has_all_of_them_should_fail() - { - // Arrange - var dataSet = CreateDummyDataSet(); - - var dataTable = dataSet.TypedDataTable1; - - var nonExistingColumnNames = new[] { "Unicorn", "Dragon" }; - - // Act - Action action = - () => dataTable.Should().HaveColumns(nonExistingColumnNames); - - // Assert - action.Should().Throw() - .WithMessage("Expected dataTable to contain a column named *Unicorn*, but it does not."); - } -} diff --git a/Tests/FluentAssertions.Equivalency.Specs/DateTimePropertiesSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/DateTimePropertiesSpecs.cs index d0c8a799e1..e6fe32b7c8 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/DateTimePropertiesSpecs.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/DateTimePropertiesSpecs.cs @@ -1,4 +1,5 @@ using System; +using FluentAssertions.Extensions; using Xunit; using Xunit.Sdk; @@ -150,4 +151,159 @@ public void act.Should().Throw().WithMessage( "Expected*Time*to be <2013-12-09 15:58:00>, but found .*"); } + +#if NET6_0_OR_GREATER + [Fact] + public void Clarifies_that_a_date_time_is_compared_with_a_date_only() + { + // Arrange + var subject = new + { + SomeDate = new DateOnly(2020, 1, 2) + }; + + // Act + var act = () => subject.Should().BeEquivalentTo(new + { + SomeDate = 2.January(2020) + }); + + // Assert + act.Should().Throw().WithMessage("*SomeDate*DateTime*found*DateOnly*"); + } + + [Fact] + public void Clarifies_that_a_date_only_is_compared_with_a_date_time() + { + // Arrange + var subject = new + { + SomeDate = 2.January(2020) + }; + + // Act + var act = () => subject.Should().BeEquivalentTo(new + { + SomeDate = new DateOnly(2020, 1, 2) + }); + + // Assert + act.Should().Throw().WithMessage("*SomeDate*DateOnly*found*DateTime*"); + } + + [Fact] + public void Clarifies_that_a_time_only_is_compared_with_a_date_time() + { + // Arrange + var subject = new + { + SomeDate = new DateTime(2020, 1, 2).At(9, 30) + }; + + // Act + var act = () => subject.Should().BeEquivalentTo(new + { + SomeDate = new TimeOnly(9, 30) + }); + + // Assert + act.Should().Throw().WithMessage("*SomeDate*TimeOnly*found*DateTime*"); + } + + [Fact] + public void Clarifies_that_a_date_time_is_compared_with_a_time_only() + { + // Arrange + var subject = new + { + SomeDate = new TimeOnly(9, 30) + }; + + // Act + var act = () => subject.Should().BeEquivalentTo(new + { + SomeDate = new DateTime(2020, 1, 2).At(9, 30) + }); + + // Assert + act.Should().Throw().WithMessage("*SomeDate*DateTime*found*TimeOnly*"); + } + + [Fact] + public void Clarifies_that_a_time_only_is_compared_with_a_time_span() + { + // Arrange + var subject = new + { + SomeTime = new TimeOnly(9, 30) + }; + + // Act + var act = () => subject.Should().BeEquivalentTo(new + { + SomeTime = new TimeSpan(9, 30, 0) + }); + + // Assert + act.Should().Throw().WithMessage("*SomeTime*TimeSpan*found*TimeOnly*"); + } + + [Fact] + public void Clarifies_that_a_time_span_is_compared_with_a_time_only() + { + // Arrange + var subject = new + { + SomeTime = new TimeSpan(9, 30, 0) + }; + + // Act + var act = () => subject.Should().BeEquivalentTo(new + { + SomeTime = new TimeOnly(9, 30) + }); + + // Assert + act.Should().Throw().WithMessage("*SomeTime*TimeOnly*found*TimeSpan*"); + } + +#endif + + [Fact] + public void Clarifies_that_a_date_time_is_compared_to_a_date_time_offset() + { + // Arrange + var subject = new + { + SomeDate = 2.January(2020) + }; + + // Act + var act = () => subject.Should().BeEquivalentTo(new + { + SomeDate = new DateTimeOffset(2.January(2020), TimeSpan.Zero) + }); + + // Assert + act.Should().Throw().WithMessage("*SomeDate*DateTimeOffset*found*DateTime*"); + } + + [Fact] + public void Clarifies_that_a_date_time_offset_is_compared_to_a_date_time() + { + // Arrange + var subject = new + { + SomeDate = new DateTimeOffset(2.January(2020), TimeSpan.Zero) + }; + + // Act + var act = () => subject.Should().BeEquivalentTo(new + { + SomeDate = 2.January(2020) + }); + + // Assert + act.Should().Throw().WithMessage("*SomeDate*DateTime*found*DateTimeOffset*"); + } } diff --git a/Tests/FluentAssertions.Equivalency.Specs/DictionarySpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/DictionarySpecs.cs index 6afa5407d1..ba15c61d19 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/DictionarySpecs.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/DictionarySpecs.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using FluentAssertions.Equivalency.Tracing; @@ -76,7 +77,7 @@ public object this[object key] private class GenericDictionaryNotImplementingIDictionary : IDictionary { - private readonly Dictionary dictionary = new(); + private readonly Dictionary dictionary = []; IEnumerator IEnumerable.GetEnumerator() { @@ -220,10 +221,8 @@ public object this[string key] set => this[Parse(key)] = value; } - ICollection IDictionary.Keys - { - get { return Keys.Select(_ => _.ToString(CultureInfo.InvariantCulture)).ToList(); } - } + ICollection IDictionary.Keys => + Keys.Select(key => key.ToString(CultureInfo.InvariantCulture)).ToList(); ICollection IDictionary.Values => Values; @@ -235,12 +234,10 @@ private int Parse(string key) public class UserRolesLookupElement { - private readonly Dictionary> innerRoles = new(); + private readonly Dictionary> innerRoles = []; - public virtual Dictionary> Roles - { - get { return innerRoles.ToDictionary(x => x.Key, y => y.Value.Select(z => z)); } - } + public virtual Dictionary> Roles => + innerRoles.ToDictionary(x => x.Key, y => y.Value.Select(z => z)); public void Add(Guid userId, params string[] roles) { @@ -323,15 +320,15 @@ public void When_a_read_only_dictionary_matches_the_expectation_it_should_succee new ReadOnlyDictionary>( new Dictionary> { - ["Key2"] = new[] { "Value2" }, - ["Key1"] = new[] { "Value1" } + ["Key2"] = ["Value2"], + ["Key1"] = ["Value1"] }); // Act Action act = () => dictionary.Should().BeEquivalentTo(new Dictionary> { - ["Key1"] = new[] { "Value1" }, - ["Key2"] = new[] { "Value2" } + ["Key1"] = ["Value1"], + ["Key2"] = ["Value2"] }); // Assert @@ -346,15 +343,15 @@ public void When_a_read_only_dictionary_does_not_match_the_expectation_it_should new ReadOnlyDictionary>( new Dictionary> { - ["Key2"] = new[] { "Value2" }, - ["Key1"] = new[] { "Value1" } + ["Key2"] = ["Value2"], + ["Key1"] = ["Value1"] }); // Act Action act = () => dictionary.Should().BeEquivalentTo(new Dictionary> { - ["Key2"] = new[] { "Value3" }, - ["Key1"] = new[] { "Value1" } + ["Key2"] = ["Value3"], + ["Key1"] = ["Value1"] }); // Assert @@ -366,7 +363,7 @@ public void When_a_dictionary_is_compared_to_null_it_should_not_throw_a_NullRefe { // Arrange Dictionary subject = null; - Dictionary expectation = new(); + Dictionary expectation = []; // Act Action act = () => subject.Should().BeEquivalentTo(expectation, "because we do expect a valid dictionary"); @@ -475,7 +472,7 @@ public void object object2 = new Dictionary { ["greeting"] = "hello" }; // Act - Action act = () => object1.Should().BeEquivalentTo(object2, opts => opts.RespectingRuntimeTypes()); + Action act = () => object1.Should().BeEquivalentTo(object2, opts => opts.PreferringRuntimeMemberTypes()); // Assert act.Should().NotThrow("the runtime type is a dictionary and the dictionaries are equivalent"); @@ -504,7 +501,7 @@ public void object object2 = new NonGenericDictionary { ["greeting"] = "hello" }; // Act - Action act = () => object1.Should().BeEquivalentTo(object2, opts => opts.RespectingRuntimeTypes()); + Action act = () => object1.Should().BeEquivalentTo(object2, opts => opts.PreferringRuntimeMemberTypes()); // Assert act.Should().NotThrow("the runtime type is a dictionary and the dictionaries are equivalent"); @@ -520,7 +517,7 @@ public void var traceWriter = new StringBuilderTraceWriter(); // Act - object1.Should().BeEquivalentTo(object2, opts => opts.RespectingRuntimeTypes().WithTracing(traceWriter)); + object1.Should().BeEquivalentTo(object2, opts => opts.PreferringRuntimeMemberTypes().WithTracing(traceWriter)); // Assert string trace = traceWriter.ToString(); @@ -568,12 +565,35 @@ public void Action act = () => dictionary1.Should().BeEquivalentTo(dictionary2, - opts => opts.RespectingRuntimeTypes()); + opts => opts.PreferringRuntimeMemberTypes()); // Assert act.Should().Throw("the types have different properties"); } + [Fact] + public void Can_compare_non_generic_dictionaries_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.WithoutRecursing()); + + // 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() { @@ -615,7 +635,7 @@ public void Action act = () => actual.Should().BeEquivalentTo(expectation, opts => opts - .RespectingRuntimeTypes() + .PreferringRuntimeMemberTypes() .ComparingByMembers() ); @@ -1121,14 +1141,15 @@ public void When_a_dictionary_is_missing_a_key_it_should_report_the_specific_key public void When_a_nested_dictionary_value_doesnt_match_it_should_throw() { // Arrange - const string json = """ - { - "NestedDictionary": { - "StringProperty": "string", - "IntProperty": 123 - } - } - """; + const string json = + """ + { + "NestedDictionary": { + "StringProperty": "string", + "IntProperty": 123 + } + } + """; var expectedResult = new Dictionary { @@ -1242,7 +1263,7 @@ public void Excluding_nested_objects_when_dictionary_is_equivalent() ["CustomerId"] = 33 }; - subject.Should().BeEquivalentTo(expected, opt => opt.ExcludingNestedObjects()); + subject.Should().BeEquivalentTo(expected, opt => opt.WithoutRecursing()); } [Fact] @@ -1284,6 +1305,7 @@ public void Custom_types_which_implementing_dictionaries_pass_with_swapped_subje internal class NonGenericChildDictionary : Dictionary { + [SuppressMessage("Class Design", "AV1010:Member hides inherited member")] public new void Add(string key, int value) { base.Add(key, value); @@ -1292,7 +1314,7 @@ internal class NonGenericChildDictionary : Dictionary internal class NonGenericDictionary : IDictionary { - private readonly Dictionary innerDictionary = new(); + private readonly Dictionary innerDictionary = []; public int this[string key] { diff --git a/Tests/FluentAssertions.Equivalency.Specs/EnumSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/EnumSpecs.cs index 93321bdc57..b30e8ff7b7 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/EnumSpecs.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/EnumSpecs.cs @@ -71,8 +71,8 @@ public void When_asserting_different_enum_members_are_equivalent_it_should_fail( public void Comparing_collections_of_enums_by_value_includes_custom_message() { // Arrange - var subject = new[] { EnumOne.One }; - var expectation = new[] { EnumOne.Two }; + EnumOne[] subject = [EnumOne.One]; + EnumOne[] expectation = [EnumOne.Two]; // Act Action act = () => subject.Should().BeEquivalentTo(expectation, "some {0}", "reason"); @@ -86,8 +86,8 @@ public void Comparing_collections_of_enums_by_value_includes_custom_message() public void Comparing_collections_of_enums_by_name_includes_custom_message() { // Arrange - var subject = new[] { EnumOne.Two }; - var expectation = new[] { EnumFour.Three }; + EnumOne[] subject = [EnumOne.Two]; + EnumFour[] expectation = [EnumFour.Three]; // Act Action act = () => subject.Should().BeEquivalentTo(expectation, config => config.ComparingEnumsByName(), @@ -102,9 +102,9 @@ public void Comparing_collections_of_enums_by_name_includes_custom_message() public void Comparing_collections_of_numerics_with_collections_of_enums_includes_custom_message() { // Arrange - var actual = new[] { 1 }; + int[] actual = [1]; - var expected = new[] { TestEnum.First }; + TestEnum[] expected = [TestEnum.First]; // Act Action act = () => actual.Should().BeEquivalentTo(expected, options => options.ComparingEnumsByValue(), diff --git a/Tests/FluentAssertions.Equivalency.Specs/ExtensibilitySpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/ExtensibilitySpecs.cs index 3dc3649b8a..f586ebb26f 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; @@ -75,7 +76,7 @@ public bool OverridesStandardIncludeRules public IEnumerable SelectMembers(INode currentNode, IEnumerable selectedMembers, MemberSelectionContext context) { - return selectedMembers.Where(pi => !pi.Name.EndsWith("Id", StringComparison.Ordinal)).ToArray(); + return selectedMembers.Where(pi => !pi.Subject.Name.EndsWith("Id", StringComparison.Ordinal)).ToArray(); } bool IMemberSelectionRule.IncludesMembers @@ -141,9 +142,10 @@ 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, IEquivalencyAssertionOptions options) + public IMember Match(IMember expectedMember, object subject, INode parent, IEquivalencyOptions options, + AssertionChain assertionChain) { - string name = expectedMember.Name; + string name = expectedMember.Subject.Name; if (name.EndsWith("Id", StringComparison.Ordinal)) { @@ -163,8 +165,8 @@ public IMember Match(IMember expectedMember, object subject, INode parent, IEqui public void When_an_ordering_rule_is_added_it_should_be_evaluated_after_all_existing_rules() { // Arrange - var subject = new[] { "First", "Second" }; - var expected = new[] { "First", "Second" }; + string[] subject = ["First", "Second"]; + string[] expected = ["First", "Second"]; // Act Action act = () => subject.Should().BeEquivalentTo( @@ -179,8 +181,8 @@ public void When_an_ordering_rule_is_added_it_should_be_evaluated_after_all_exis public void When_an_ordering_rule_is_added_it_should_appear_in_the_exception_message() { // Arrange - var subject = new[] { "First", "Second" }; - var expected = new[] { "Second", "First" }; + string[] subject = ["First", "Second"]; + string[] expected = ["Second", "First"]; // Act Action act = () => subject.Should().BeEquivalentTo( @@ -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")); @@ -627,7 +629,7 @@ public void When_multiple_steps_are_added_they_should_be_evaluated_first_to_last private class AlwaysFailOnDateTimesEquivalencyStep : IEquivalencyStep { public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, - IEquivalencyValidator nestedValidator) + IValidateChildNodeEquivalency valueChildNodes) { if (comparands.Expectation is DateTime) { @@ -641,13 +643,13 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon private class RelaxingDateTimeEquivalencyStep : IEquivalencyStep { public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, - IEquivalencyValidator nestedValidator) + IValidateChildNodeEquivalency valueChildNodes) { if (comparands.Expectation is DateTime time) { ((DateTime)comparands.Subject).Should().BeCloseTo(time, 1.Minutes()); - return EquivalencyResult.AssertionCompleted; + return EquivalencyResult.EquivalencyProven; } return EquivalencyResult.ContinueWithNext; @@ -812,7 +814,7 @@ private class ThrowExceptionEquivalencyStep : IEquivalencyStep where TException : Exception, new() { public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, - IEquivalencyValidator nestedValidator) + IValidateChildNodeEquivalency valueChildNodes) { throw new TException(); } @@ -821,16 +823,16 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon private class AlwaysHandleEquivalencyStep : IEquivalencyStep { public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, - IEquivalencyValidator nestedValidator) + IValidateChildNodeEquivalency valueChildNodes) { - return EquivalencyResult.AssertionCompleted; + return EquivalencyResult.EquivalencyProven; } } private class NeverHandleEquivalencyStep : IEquivalencyStep { public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, - IEquivalencyValidator nestedValidator) + IValidateChildNodeEquivalency valueChildNodes) { return EquivalencyResult.ContinueWithNext; } @@ -839,10 +841,10 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon private class EqualityEquivalencyStep : IEquivalencyStep { public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, - IEquivalencyValidator nestedValidator) + IValidateChildNodeEquivalency valueChildNodes) { comparands.Subject.Should().Be(comparands.Expectation, context.Reason.FormattedMessage, context.Reason.Arguments); - return EquivalencyResult.AssertionCompleted; + return EquivalencyResult.EquivalencyProven; } } @@ -856,10 +858,10 @@ public DoEquivalencyStep(Action doAction) } public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, - IEquivalencyValidator nestedValidator) + IValidateChildNodeEquivalency valueChildNodes) { doAction(); - return EquivalencyResult.AssertionCompleted; + return EquivalencyResult.EquivalencyProven; } } diff --git a/Tests/FluentAssertions.Equivalency.Specs/FluentAssertions.Equivalency.Specs.csproj b/Tests/FluentAssertions.Equivalency.Specs/FluentAssertions.Equivalency.Specs.csproj index c48906bb6a..77c4b66723 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/FluentAssertions.Equivalency.Specs.csproj +++ b/Tests/FluentAssertions.Equivalency.Specs/FluentAssertions.Equivalency.Specs.csproj @@ -7,7 +7,6 @@ false $(NoWarn),IDE0052,1573,1591,1712 full - 12 @@ -19,36 +18,22 @@ Upgrading to 1.6.0 gives "Could not load file or assembly 'System.Runtime.CompilerServices.Unsafe, Version=4.0.4.0" --> - - all - runtime; build; native; contentfiles; analyzers - - - - - - - all - runtime; build; native; contentfiles; analyzers - - - - - all - runtime; build; native; contentfiles; analyzers - + + - - - - - + + + all + runtime; build; native; contentfiles; analyzers + + + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Tests/FluentAssertions.Equivalency.Specs/InlineAssertionsSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/InlineAssertionsSpecs.cs new file mode 100644 index 0000000000..31148ce28b --- /dev/null +++ b/Tests/FluentAssertions.Equivalency.Specs/InlineAssertionsSpecs.cs @@ -0,0 +1,201 @@ +using System; +using FluentAssertions.Execution; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Equivalency.Specs; + +public class InlineAssertionsSpecs +{ + [Fact] + public void The_inline_condition_must_be_met() + { + // Arrange + var actual = new + { + Name = "John", + Age = 30 + }; + + var expectation = new + { + Name = "John", + Age = Value.ThatMatches(age => age < 30) + }; + + // Act + var act = () => actual.Should().BeEquivalentTo(expectation); + + // Assert + act.Should().Throw().WithMessage("*actual.Age*meet condition*(age < 30)*"); + } + + [Fact] + public void The_type_of_the_condition_must_be_met_too() + { + // Arrange + var actual = new + { + Name = "John", + Age = 30 + }; + + var expectation = new + { + Name = "John", + Age = Value.ThatMatches(s => s.Length > 0) + }; + + // Act + var act = () => + { + using var _ = new AssertionScope(); + actual.Should().BeEquivalentTo(expectation); + }; + + // Assert + act.Should().Throw().WithMessage("*actual.Age*type*String*found*Int32*"); + } + + [Fact] + public void Succeeds_for_a_matching_condition() + { + // Arrange + var actual = new + { + Name = "John", + Age = 30 + }; + + var expectation = new + { + Name = "John", + Age = Value.ThatMatches(age => age < 40) + }; + + // Act / Assert + actual.Should().BeEquivalentTo(expectation); + } + + [Fact] + public void A_condition_expression_is_required() + { + // Arrange + var actual = new + { + Name = "John", + Age = 30 + }; + + // Act + var act = () => + { + var expectation = new + { + Name = "John", + Age = Value.ThatMatches(null) + }; + + actual.Should().BeEquivalentTo(expectation); + }; + + // Assert + act.Should().Throw().WithParameterName("condition"); + } + + [Fact] + public void The_inline_assertion_must_be_met() + { + // Arrange + var actual = new + { + Name = "John", + Age = 30 + }; + + var expectation = new + { + Name = "John", + Age = Value.ThatSatisfies(age => age.Should().BeLessThan(30)) + }; + + // Act + var act = () => actual.Should().BeEquivalentTo(expectation); + + // Assert + act.Should().Throw().WithMessage("*age to be less than 30*found 30*"); + } + + [Fact] + public void Succeeds_for_a_matching_assertion() + { + // Arrange + var actual = new + { + Name = "John", + Age = 30 + }; + + var expectation = new + { + Name = "John", + Age = Value.ThatSatisfies(age => age.Should().BeGreaterThan(20)) + }; + + // Act / Assert + actual.Should().BeEquivalentTo(expectation); + } + + [Fact] + public void An_assertion_action_is_required() + { + // Arrange + var actual = new + { + Name = "John", + Age = 30 + }; + + // Act + var act = () => + { + var expectation = new + { + Name = "John", + Age = Value.ThatSatisfies(null) + }; + + actual.Should().BeEquivalentTo(expectation); + }; + + // Assert + act.Should().Throw().WithParameterName("assertion"); + } + + [Fact] + public void The_type_expected_by_the_assertion_must_be_met_too() + { + // Arrange + var actual = new + { + Name = "John", + Age = 30 + }; + + var expectation = new + { + Name = "John", + Age = Value.ThatSatisfies(s => s.Should().BeNullOrEmpty()) + }; + + // Act + var act = () => + { + using var _ = new AssertionScope(); + actual.Should().BeEquivalentTo(expectation); + }; + + // Assert + act.Should().Throw().WithMessage("*actual.Age*type*String*found*Int32*"); + } +} diff --git a/Tests/FluentAssertions.Equivalency.Specs/JsonNodeSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/JsonNodeSpecs.cs new file mode 100644 index 0000000000..f7dd1d3b27 --- /dev/null +++ b/Tests/FluentAssertions.Equivalency.Specs/JsonNodeSpecs.cs @@ -0,0 +1,591 @@ +#if NET6_0_OR_GREATER + +using System; +using System.Text.Json.Nodes; +using FluentAssertions.Extensions; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Equivalency.Specs; + +public class JsonNodeSpecs +{ + [Fact] + public void Supports_numeric_properties() + { + // Arrange + var node = JsonNode.Parse( + """ + { + "signedInt" : -2147483647, + "unsignedInt": 4294967295, + "signedLong" : -9223372036854775808, + "unsignedLong" : 18446744073709551615, + "floatingPoint": 1.0, + "largeDouble": 1.0E+20, + "money": 2.0 + } + """); + + // Act + var act = () => node.Should().BeEquivalentTo(new + { + signedInt = int.MinValue, + unsignedInt = uint.MaxValue, + signedLong = long.MinValue, + unsignedLong = ulong.MaxValue, + floatingPoint = 1.0F, + largeDouble = 1E+20F, + money = 2.0M + }); + + // Assert + act.Should().Throw().WithMessage("*$.signedInt to be -2147483648, but found -2147483647*"); + } + + [Fact] + public void Supports_string_properties() + { + // Arrange + var node = JsonNode.Parse( + """ + { + "name": "John", + "description": "A person" + } + """); + + // Act + var act = () => node.Should().BeEquivalentTo(new + { + name = "Jane", + description = "Another person" + }); + + // Assert + act.Should().Throw().WithMessage("*JSON property $.name*Jane*John*"); + } + + [Fact] + public void Supports_boolean_properties() + { + // Arrange + var node = JsonNode.Parse( + """ + { + "isActive": true, + "isDeleted": false + } + """); + + // Act + var act = () => node.Should().BeEquivalentTo(new + { + isActive = false, + isDeleted = true + }); + + // Assert + act.Should().Throw().WithMessage("*$.isActive*False*True*$.isDeleted*True*False*"); + } + + [Fact] + public void Supports_null_properties() + { + // Arrange + var node = JsonNode.Parse( + """ + { + "nullValue": null, + "notNull": "value" + } + """); + + // Act + var act = () => node.Should().BeEquivalentTo(new + { + nullValue = "not null", + notNull = (string)null + }); + + // Assert + act.Should().Throw() + .WithMessage("Expected JSON property $.nullValue to be*not null*but found *$.notNull to be *value*"); + } + + [Fact] + public void Supports_array_properties() + { + // Arrange + var node = JsonNode.Parse( + """ + { + "numbers": [1, 2, 3], + "strings": ["a", "b", "c"] + } + """); + + // Act + var act = () => node.Should().BeEquivalentTo(new + { + numbers = new[] + { + 1, + 2, + 4 + }, + strings = new[] + { + "a", + "b", + "d" + } + }); + + // Assert + act.Should().Throw().WithMessage("*$.numbers[2]*4*3*$.strings[2]*d*c*"); + } + + [Fact] + public void Supports_nested_object_properties() + { + // Arrange + var node = JsonNode.Parse( + """ + { + "person": { + "name": "John", + "age": 25 + }, + "address": { + "street": "Main St", + "city": "New York" + } + } + """); + + // Act + var act = () => node.Should().BeEquivalentTo(new + { + person = new + { + name = "Jane", + age = 30 + }, + address = new + { + street = "Second St", + city = "Boston" + } + }); + + // Assert + act.Should().Throw().WithMessage("*property $.person.name*Jane*John*"); + } + + [Fact] + public void Supports_empty_objects() + { + // Arrange + var node = JsonNode.Parse("{}"); + + // Act + var act = () => node.Should().BeEquivalentTo(new + { + }); + + // Assert + act.Should().Throw().WithMessage("*No members were found for comparison*"); + } + + [Fact] + public void Supports_empty_arrays() + { + // Arrange + var node = JsonNode.Parse( + """ + { + "emptyArray": [] + } + """); + + // Act & Assert + node.Should().BeEquivalentTo(new + { + emptyArray = new int[0] + }); + } + + [Fact] + public void Supports_mixed_type_arrays() + { + // Arrange + var node = JsonNode.Parse( + """ + { + "mixedArray": [1, "text", true, null] + } + """); + + // Act & Assert + node.Should().BeEquivalentTo(new + { + mixedArray = new object[] + { + 1, + "text", + true, + null + } + }); + } + + [Fact] + public void Supports_deeply_nested_structures() + { + // Arrange + var node = JsonNode.Parse( + """ + { + "level1": { + "level2": { + "level3": { + "value": "deep" + } + } + } + } + """); + + // Act + var act = () => node.Should().BeEquivalentTo(new + { + level1 = new + { + level2 = new + { + level3 = new + { + value = "shallow" + } + } + } + }); + + // Assert + act.Should().Throw().WithMessage("*$.level1.level2.level3.value*shallow*deep*"); + } + + [Fact] + public void Can_detect_a_missing_property() + { + // Arrange + var node = JsonNode.Parse( + """ + { + "existingProperty": "value" + } + """); + + // Act + var act = () => node.Should().BeEquivalentTo(new + { + existingProperty = "value", + missingProperty = "missing" + }); + + // Assert + act.Should().Throw().WithMessage("*missingProperty*"); + } + + [Fact] + public void Can_ignore_a_missing_property() + { + // Arrange + var node = JsonNode.Parse( + """ + { + "existingProperty": "value" + } + """); + + // Act + var act = () => node.Should().BeEquivalentTo(new + { + existingProperty = "wrongvalue", + missingProperty = "missing" + }, options => options.ExcludingMissingMembers()); + + // Assert + act.Should().Throw().WithMessage("*existingProperty*wrongvalue*") + .Which.Message.Should().NotContain("missingProperty"); + } + + [Fact] + public void Can_interpret_an_iso_date_as_a_local_datetime() + { + // Arrange + var node = JsonNode.Parse( + """ + { + "date": "2025-09-11T21:17:00" + } + """); + + // Act / Assert + node.Should().BeEquivalentTo(new + { + date = 11.September(2025).At(21, 17), + }); + } + + [Fact] + public void Can_interpret_an_iso_date_as_an_utc_datetime() + { + // Arrange + var node = JsonNode.Parse( + """ + { + "date": "2025-09-11T21:17:00Z" + } + """); + + // Act / Assert + node.Should().BeEquivalentTo(new + { + date = 11.September(2025).At(21, 17).AsUtc() + }); + } + + [Fact] + public void Casing_of_properties_must_match_by_default() + { + // Arrange + var node = JsonNode.Parse( + """ + { + "existingProperty": "value" + } + """); + + // Act / Assert + node.Should().BeEquivalentTo(new + { + existingProperty = "value", + }); + } + + [Fact] + public void Can_match_properties_even_if_the_casing_is_different() + { + // Arrange + var node = JsonNode.Parse( + """ + { + "existingProperty": "value" + } + """); + + // Act / Assert + node.Should().BeEquivalentTo(new + { + ExistingProperty = "value", + }, options => options.IgnoringJsonPropertyCasing()); + } + + [Fact] + public void Supports_extra_properties_in_json() + { + // Arrange + var node = JsonNode.Parse( + """ + { + "expectedProperty": "value", + "extraProperty": "extra" + } + """); + + // Act & Assert + // This should pass as extra properties are typically ignored in equivalency + node.Should().BeEquivalentTo(new + { + expectedProperty = "value" + }, options => options.IgnoringJsonPropertyCasing()); + } + + [Fact] + public void Supports_complex_mixed_scenario() + { + // Arrange + var node = JsonNode.Parse( + """ + { + "id": 123, + "name": "Product", + "price": 99.99, + "isAvailable": true, + "tags": ["electronics", "gadget"], + "metadata": { + "createdBy": "admin", + "createdAt": null, + "settings": { + "visible": true, + "priority": 1 + } + }, + "variants": [] + } + """); + + // Act & Assert + node.Should().BeEquivalentTo(new + { + id = 123, + name = "Product", + price = 99.99, + isAvailable = true, + tags = new[] + { + "electronics", + "gadget" + }, + metadata = new + { + createdBy = "admin", + createdAt = (string)null, + settings = new + { + visible = true, + priority = 1 + } + }, + variants = new object[0] + }); + } + + [Fact] + public void Can_assert_the_inequivalence_of_complex_mixed_scenario() + { + // Arrange + var node = JsonNode.Parse( + """ + { + "id": 123, + "name": "Product", + "price": 99.99, + "isAvailable": true, + "tags": ["electronics", "gadget"], + "metadata": { + "createdBy": "admin", + "createdAt": null, + "settings": { + "visible": true, + "priority": 1 + } + }, + "variants": [] + } + """); + + // Act + var act = () => node.Should().NotBeEquivalentTo(new + { + id = 123, + name = "Product", + price = 99.99, + isAvailable = true, + tags = new[] + { + "electronics", + "gadget" + }, + metadata = new + { + createdBy = "admin", + createdAt = (string)null, + settings = new + { + visible = true, + priority = 1 + } + }, + variants = new object[0] + }); + + // Assert + act.Should().Throw().WithMessage("Did not expect*metadata*but they are*"); + } + + [Fact] + public void An_inequivalency_assertion_can_be_case_insensitive() + { + // Arrange + var node = JsonNode.Parse( + """ + { + "existingProperty": "value" + } + """); + + // Act + var act = () => node.Should().NotBeEquivalentTo(new + { + ExistingProperty = "value", + }, options => options.IgnoringJsonPropertyCasing()); + + // Assert + act.Should().Throw().WithMessage("Did not expect*existingProperty*"); + } + + [Fact] + public void Can_treat_a_json_array_as_a_collection() + { + // Arrange + JsonArray array = JsonNode.Parse("[1, 2, 3]")!.AsArray(); + + // Act & Assert + array.Should().NotBeEmpty(); + } + + [Fact] + public void Can_treat_a_null_json_array_as_a_collection() + { + // Arrange + JsonArray array = null; + + // Act & Assert + array.Should().BeNull(); + } + + [Fact] + public void Can_assert_json_node_instances() + { + // Arrange + JsonNode jsonNode = null; + + // Act & Assert + jsonNode.Should().BeNull(); + } + + [Fact] + public void Can_assert_json_value_instances() + { + // Arrange + JsonValue jsonValue = null; + + // Act & Assert + jsonValue.Should().BeNull(); + } + + [Fact] + public void Can_assert_json_object_instances() + { + // Arrange + JsonObject jsonObject = null; + + // Act & Assert + jsonObject.Should().BeNull(); + } +} + +#endif diff --git a/Tests/FluentAssertions.Equivalency.Specs/MemberLessObjectsSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/MemberLessObjectsSpecs.cs index 688bfa5884..d1768baad8 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/MemberLessObjectsSpecs.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/MemberLessObjectsSpecs.cs @@ -139,7 +139,7 @@ public void When_throwing_on_missing_members_and_there_is_a_missing_member_shoul // Assert act.Should().Throw() - .WithMessage("Expectation has property subject.Age that the other object does not have*"); + .WithMessage("Expectation has property Age that the other object does not have*"); } [Fact] diff --git a/Tests/FluentAssertions.Equivalency.Specs/MemberMatchingSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/MemberMatchingSpecs.cs index 231c68de0a..a4b032705c 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/MemberMatchingSpecs.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/MemberMatchingSpecs.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.Diagnostics.CodeAnalysis; +using JetBrains.Annotations; using Xunit; using Xunit.Sdk; @@ -51,12 +52,12 @@ public void When_a_property_shared_by_anonymous_types_doesnt_match_it_should_thr public void Nested_properties_can_be_mapped_using_a_nested_expression() { // Arrange - var subject = new ParentOfSubjectWithProperty1(new[] { new SubjectWithProperty1 { Property1 = "Hello" } }); + var subject = new ParentOfSubjectWithProperty1([new SubjectWithProperty1 { Property1 = "Hello" }]); - var expectation = new ParentOfExpectationWithProperty2(new[] - { + var expectation = new ParentOfExpectationWithProperty2( + [ new ExpectationWithProperty2 { Property2 = "Hello" } - }); + ]); // Act / Assert subject.Should() @@ -70,12 +71,12 @@ public void Nested_properties_can_be_mapped_using_a_nested_expression() public void Nested_properties_can_be_mapped_using_a_nested_type_and_property_names() { // Arrange - var subject = new ParentOfSubjectWithProperty1(new[] { new SubjectWithProperty1 { Property1 = "Hello" } }); + var subject = new ParentOfSubjectWithProperty1([new SubjectWithProperty1 { Property1 = "Hello" }]); - var expectation = new ParentOfExpectationWithProperty2(new[] - { + var expectation = new ParentOfExpectationWithProperty2( + [ new ExpectationWithProperty2 { Property2 = "Hello" } - }); + ]); // Act / Assert subject.Should() @@ -87,14 +88,15 @@ public void Nested_properties_can_be_mapped_using_a_nested_type_and_property_nam public void Nested_explicitly_implemented_properties_can_be_mapped_using_a_nested_type_and_property_names() { // Arrange - var subject = new ParentOfSubjectWithExplicitlyImplementedProperty(new[] { new SubjectWithExplicitImplementedProperty() }); + var subject = + new ParentOfSubjectWithExplicitlyImplementedProperty(new[] { new SubjectWithExplicitImplementedProperty() }); ((IProperty)subject.Children[0]).Property = "Hello"; - var expectation = new ParentOfExpectationWithProperty2(new[] - { + var expectation = new ParentOfExpectationWithProperty2( + [ new ExpectationWithProperty2 { Property2 = "Hello" } - }); + ]); // Act / Assert subject.Should() @@ -121,12 +123,12 @@ public void Nested_fields_can_be_mapped_using_a_nested_type_and_field_names() public void Nested_properties_can_be_mapped_using_a_nested_type_and_a_property_expression() { // Arrange - var subject = new ParentOfSubjectWithProperty1(new[] { new SubjectWithProperty1 { Property1 = "Hello" } }); + var subject = new ParentOfSubjectWithProperty1([new SubjectWithProperty1 { Property1 = "Hello" }]); - var expectation = new ParentOfExpectationWithProperty2(new[] - { + var expectation = new ParentOfExpectationWithProperty2( + [ new ExpectationWithProperty2 { Property2 = "Hello" } - }); + ]); // Act / Assert subject.Should() @@ -405,12 +407,12 @@ public void A_null_subject_should_result_in_a_normal_assertion_failure() public void Nested_types_and_dotted_expectation_member_paths_cannot_be_combined() { // Arrange - var subject = new ParentOfSubjectWithProperty1(new[] { new SubjectWithProperty1 { Property1 = "Hello" } }); + var subject = new ParentOfSubjectWithProperty1([new SubjectWithProperty1 { Property1 = "Hello" }]); - var expectation = new ParentOfExpectationWithProperty2(new[] - { + var expectation = new ParentOfExpectationWithProperty2( + [ new ExpectationWithProperty2 { Property2 = "Hello" } - }); + ]); // Act Action act = () => subject.Should() @@ -427,12 +429,12 @@ public void Nested_types_and_dotted_expectation_member_paths_cannot_be_combined( public void Nested_types_and_dotted_subject_member_paths_cannot_be_combined() { // Arrange - var subject = new ParentOfSubjectWithProperty1(new[] { new SubjectWithProperty1 { Property1 = "Hello" } }); + var subject = new ParentOfSubjectWithProperty1([new SubjectWithProperty1 { Property1 = "Hello" }]); - var expectation = new ParentOfExpectationWithProperty2(new[] - { + var expectation = new ParentOfExpectationWithProperty2( + [ new ExpectationWithProperty2 { Property2 = "Hello" } - }); + ]); // Act Action act = () => subject.Should() @@ -449,12 +451,12 @@ public void Nested_types_and_dotted_subject_member_paths_cannot_be_combined() public void The_member_name_on_a_nested_type_mapping_must_be_a_valid_member() { // Arrange - var subject = new ParentOfSubjectWithProperty1(new[] { new SubjectWithProperty1 { Property1 = "Hello" } }); + var subject = new ParentOfSubjectWithProperty1([new SubjectWithProperty1 { Property1 = "Hello" }]); - var expectation = new ParentOfExpectationWithProperty2(new[] - { + var expectation = new ParentOfExpectationWithProperty2( + [ new ExpectationWithProperty2 { Property2 = "Hello" } - }); + ]); // Act Action act = () => subject.Should() @@ -529,18 +531,174 @@ public void Can_map_members_of_a_root_collection() Name = "Test" }; - var entityCol = new[] { entity }; - var dtoCol = new[] { dto }; + Entity[] entityCol = [entity]; + EntityDto[] dtoCol = [dto]; // Act / Assert dtoCol.Should().BeEquivalentTo(entityCol, c => c.WithMapping(s => s.EntityId, d => d.Id)); } + [Fact] + public void Can_explicitly_include_a_property_on_a_mapped_type() + { + // Arrange + var expectation = new CustomerWithPropertiesDto + { + AddressInformation = new CustomerWithPropertiesDto.ResidenceDto + { + Address = "123 Main St", + IsValidated = true, + }, + }; + + var subject = new CustomerWithProperty + { + Address = new CustomerWithProperty.Residence + { + Address = "123 Main St", + }, + }; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, o => o + .Including(r => r.AddressInformation.Address) + .WithMapping(s => s.AddressInformation, d => d.Address)); + } + + [Fact] + public void Can_exclude_a_property_on_a_mapped_type() + { + // Arrange + var expectation = new CustomerWithPropertiesDto + { + AddressInformation = new CustomerWithPropertiesDto.ResidenceDto + { + Address = "123 Main St", + IsValidated = true, + }, + }; + + var subject = new CustomerWithProperty + { + Address = new CustomerWithProperty.Residence + { + Address = "123 Main St", + }, + }; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, o => o + .Excluding(r => r.AddressInformation.IsValidated) + .WithMapping(s => s.AddressInformation, d => d.Address)); + } + + [Fact] + public void Can_explicitly_include_a_field_on_a_mapped_type() + { + // Arrange + var expectation = new CustomerWithFieldDto + { + AddressInformation = new CustomerWithFieldDto.ResidenceDto + { + Address = "123 Main St", + IsValidated = true, + }, + }; + + var subject = new CustomerWithField + { + Address = new CustomerWithField.Residence + { + Address = "123 Main St", + }, + }; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, o => o + .Including(r => r.AddressInformation.Address) + .WithMapping(s => s.AddressInformation, d => d.Address)); + } + + [Fact] + public void Can_exclude_a_field_on_a_mapped_type() + { + // Arrange + var expectation = new CustomerWithFieldDto + { + AddressInformation = new CustomerWithFieldDto.ResidenceDto + { + Address = "123 Main St", + IsValidated = true, + }, + }; + + var subject = new CustomerWithField + { + Address = new CustomerWithField.Residence + { + Address = "123 Main St", + }, + }; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, o => o + .Excluding(r => r.AddressInformation.IsValidated) + .WithMapping(s => s.AddressInformation, d => d.Address)); + } + + private class CustomerWithProperty + { + public Residence Address { get; set; } + + public class Residence + { + [UsedImplicitly] + public string Address { get; set; } + } + } + + private class CustomerWithPropertiesDto + { + public ResidenceDto AddressInformation { get; set; } + + public class ResidenceDto + { + public string Address { get; set; } + + public bool IsValidated { get; set; } + } + } + + private class CustomerWithField + { + public Residence Address; + + public class Residence + { + [UsedImplicitly] + public string Address; + } + } + + private class CustomerWithFieldDto + { + public ResidenceDto AddressInformation; + + public class ResidenceDto + { + public string Address; + + [UsedImplicitly] + public bool IsValidated; + } + } + private class Entity { public int EntityId { get; init; } + [UsedImplicitly] public string Name { get; init; } } @@ -548,6 +706,7 @@ private class EntityDto { public int Id { get; init; } + [UsedImplicitly] public string Name { get; init; } } diff --git a/Tests/FluentAssertions.Equivalency.Specs/NestedPropertiesSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/NestedPropertiesSpecs.cs index 2c61962b6e..c4ca546adb 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/NestedPropertiesSpecs.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/NestedPropertiesSpecs.cs @@ -71,7 +71,7 @@ public void // Act Action act = () => subject.Should().BeEquivalentTo(expected, - options => options.ExcludingNestedObjects()); + options => options.WithoutRecursing()); // Assert act.Should().NotThrow(); @@ -84,7 +84,7 @@ public void When_nested_objects_should_be_excluded_it_should_do_a_simple_equalit var item = new Item { Child = new Item() }; // Act - Action act = () => item.Should().BeEquivalentTo(new Item(), options => options.ExcludingNestedObjects()); + Action act = () => item.Should().BeEquivalentTo(new Item(), options => options.WithoutRecursing()); // Assert act.Should().Throw() @@ -118,12 +118,12 @@ public void When_not_all_the_properties_of_the_nested_objects_are_equal_it_shoul .Should().Match( @"Expected property subject.Level.Text to be ""Level2"", but ""Level1"" differs near ""1"" (index 5).*" + "With configuration:*" + - "- Use declared types and members*" + + "- Prefer the declared type of the members*" + "- Compare enums by value*" + "- Compare tuples by their properties*" + "- Compare anonymous types by their properties*" + "- Compare records by their members*" + - "- Match member by name (or throw)*" + + "- Match (JSON) member by name (or throw)*" + "- Be strict about the order of items in byte arrays*" + "- Without automatic conversion.*"); } @@ -155,7 +155,7 @@ public class StringContainer public StringContainer(string mainValue, string subValue = null) { MainValue = mainValue; - SubValues = new[] { new StringSubContainer { SubValue = subValue } }; + SubValues = [new StringSubContainer { SubValue = subValue }]; } public string MainValue { get; set; } @@ -174,20 +174,20 @@ public class MyClass2 public void When_deeply_nested_strings_dont_match_it_should_properly_report_the_mismatches() { // Arrange - var expected = new[] - { + MyClass2[] expected = + [ new MyClass2 { One = new StringContainer("EXPECTED", "EXPECTED"), Two = new StringContainer("CORRECT") }, new MyClass2() - }; + ]; - var actual = new[] - { + MyClass2[] actual = + [ new MyClass2 { One = new StringContainer("INCORRECT", "INCORRECT"), Two = new StringContainer("CORRECT") }, new MyClass2() - }; + ]; // Act Action act = () => actual.Should().BeEquivalentTo(expected); @@ -229,7 +229,7 @@ public void When_not_all_the_properties_of_the_nested_object_exist_on_the_expect // Assert act .Should().Throw() - .WithMessage("Expectation has property subject.Level.OtherProperty that the other object does not have*"); + .WithMessage("Expectation has property Level.OtherProperty that the other object does not have*"); } [Fact] @@ -270,7 +270,7 @@ public void When_deeply_nested_properties_do_not_have_all_equal_values_it_should act .Should().Throw() .WithMessage( - "Expected*Level.Level.Text*to be *A wrong text value*but*\"Level2\"*length*"); + "Expected*Level.Level.Text*to be *\"Level2\"*A wrong text value*"); } [Fact] @@ -307,9 +307,9 @@ public void When_a_property_of_a_nested_object_doesnt_match_it_should_clearly_in public void Should_support_nested_collections_containing_empty_objects() { // Arrange - var orig = new[] { new OuterWithObject { MyProperty = new[] { new Inner() } } }; + OuterWithObject[] orig = [new OuterWithObject { MyProperty = [new Inner()] }]; - var expectation = new[] { new OuterWithObject { MyProperty = new[] { new Inner() } } }; + OuterWithObject[] expectation = [new OuterWithObject { MyProperty = [new Inner()] }]; // Act / Assert orig.Should().BeEquivalentTo(expectation); diff --git a/Tests/FluentAssertions.Equivalency.Specs/RecordSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/RecordSpecs.cs index 9ce6473ae1..8158da2f73 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/RecordSpecs.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/RecordSpecs.cs @@ -9,9 +9,9 @@ public class RecordSpecs [Fact] public void When_the_subject_is_a_record_it_should_compare_it_by_its_members() { - var actual = new MyRecord { StringField = "foo", CollectionProperty = new[] { "bar", "zip", "foo" } }; + var actual = new MyRecord { StringField = "foo", CollectionProperty = ["bar", "zip", "foo"] }; - var expected = new MyRecord { StringField = "foo", CollectionProperty = new[] { "foo", "bar", "zip" } }; + var expected = new MyRecord { StringField = "foo", CollectionProperty = ["foo", "bar", "zip"] }; actual.Should().BeEquivalentTo(expected); } @@ -19,9 +19,9 @@ public void When_the_subject_is_a_record_it_should_compare_it_by_its_members() [Fact] public void When_the_subject_is_a_record_struct_it_should_compare_it_by_its_members() { - var actual = new MyRecordStruct("foo", new[] { "bar", "zip", "foo" }); + var actual = new MyRecordStruct("foo", ["bar", "zip", "foo"]); - var expected = new MyRecordStruct("foo", new[] { "bar", "zip", "foo" }); + var expected = new MyRecordStruct("foo", ["bar", "zip", "foo"]); actual.Should().BeEquivalentTo(expected); } @@ -42,9 +42,9 @@ public void When_the_subject_is_a_record_it_should_mention_that_in_the_configura [Fact] public void When_a_record_should_be_treated_as_a_value_type_it_should_use_its_equality_for_comparing() { - var actual = new MyRecord { StringField = "foo", CollectionProperty = new[] { "bar", "zip", "foo" } }; + var actual = new MyRecord { StringField = "foo", CollectionProperty = ["bar", "zip", "foo"] }; - var expected = new MyRecord { StringField = "foo", CollectionProperty = new[] { "foo", "bar", "zip" } }; + var expected = new MyRecord { StringField = "foo", CollectionProperty = ["foo", "bar", "zip"] }; Action act = () => actual.Should().BeEquivalentTo(expected, o => o .ComparingByValue()); @@ -57,9 +57,9 @@ public void When_a_record_should_be_treated_as_a_value_type_it_should_use_its_eq [Fact] public void When_all_records_should_be_treated_as_value_types_it_should_use_equality_for_comparing() { - var actual = new MyRecord { StringField = "foo", CollectionProperty = new[] { "bar", "zip", "foo" } }; + var actual = new MyRecord { StringField = "foo", CollectionProperty = ["bar", "zip", "foo"] }; - var expected = new MyRecord { StringField = "foo", CollectionProperty = new[] { "foo", "bar", "zip" } }; + var expected = new MyRecord { StringField = "foo", CollectionProperty = ["foo", "bar", "zip"] }; Action act = () => actual.Should().BeEquivalentTo(expected, o => o .ComparingRecordsByValue()); @@ -73,9 +73,9 @@ public void When_all_records_should_be_treated_as_value_types_it_should_use_equa public void When_all_records_except_a_specific_type_should_be_treated_as_value_types_it_should_compare_that_specific_type_by_its_members() { - var actual = new MyRecord { StringField = "foo", CollectionProperty = new[] { "bar", "zip", "foo" } }; + var actual = new MyRecord { StringField = "foo", CollectionProperty = ["bar", "zip", "foo"] }; - var expected = new MyRecord { StringField = "foo", CollectionProperty = new[] { "foo", "bar", "zip" } }; + var expected = new MyRecord { StringField = "foo", CollectionProperty = ["foo", "bar", "zip"] }; actual.Should().BeEquivalentTo(expected, o => o .ComparingRecordsByValue() @@ -85,9 +85,9 @@ public void [Fact] public void When_global_record_comparing_options_are_chained_it_should_ensure_the_last_one_wins() { - var actual = new MyRecord { CollectionProperty = new[] { "bar", "zip", "foo" } }; + var actual = new MyRecord { CollectionProperty = ["bar", "zip", "foo"] }; - var expected = new MyRecord { CollectionProperty = new[] { "foo", "bar", "zip" } }; + var expected = new MyRecord { CollectionProperty = ["foo", "bar", "zip"] }; actual.Should().BeEquivalentTo(expected, o => o .ComparingRecordsByValue() diff --git a/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.Accessibility.cs b/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.Accessibility.cs new file mode 100644 index 0000000000..090df003ab --- /dev/null +++ b/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.Accessibility.cs @@ -0,0 +1,308 @@ +using System; +using JetBrains.Annotations; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Equivalency.Specs; + +public partial class SelectionRulesSpecs +{ + public class Accessibility + { + [Fact] + public void When_a_property_is_write_only_it_should_be_ignored() + { + // Arrange + var expected = new ClassWithWriteOnlyProperty + { + WriteOnlyProperty = 123, + SomeOtherProperty = "whatever" + }; + + var subject = new + { + SomeOtherProperty = "whatever" + }; + + // Act + Action action = () => subject.Should().BeEquivalentTo(expected); + + // Assert + action.Should().NotThrow(); + } + + [Fact] + public void When_a_property_is_private_it_should_be_ignored() + { + // Arrange + var subject = new Customer("MyPassword") + { + Age = 36, + Birthdate = new DateTime(1973, 9, 20), + Name = "John" + }; + + var other = new Customer("SomeOtherPassword") + { + Age = 36, + Birthdate = new DateTime(1973, 9, 20), + Name = "John" + }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(other); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_a_field_is_private_it_should_be_ignored() + { + // Arrange + var subject = new ClassWithAPrivateField(1234) + { + Value = 1 + }; + + var other = new ClassWithAPrivateField(54321) + { + Value = 1 + }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(other); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_a_property_is_protected_it_should_be_ignored() + { + // Arrange + var subject = new Customer + { + Age = 36, + Birthdate = new DateTime(1973, 9, 20), + Name = "John" + }; + + subject.SetProtected("ActualValue"); + + var expected = new Customer + { + Age = 36, + Birthdate = new DateTime(1973, 9, 20), + Name = "John" + }; + + expected.SetProtected("ExpectedValue"); + + // Act + Action act = () => subject.Should().BeEquivalentTo(expected); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_a_property_is_internal_and_it_should_be_included_it_should_fail_the_assertion() + { + // Arrange + var actual = new ClassWithInternalProperty + { + PublicProperty = "public", + InternalProperty = "internal", + ProtectedInternalProperty = "internal" + }; + + var expected = new ClassWithInternalProperty + { + PublicProperty = "public", + InternalProperty = "also internal", + ProtectedInternalProperty = "also internal" + }; + + // Act + Action act = () => actual.Should().BeEquivalentTo(expected, options => options.IncludingInternalProperties()); + + // Assert + act.Should().Throw() + .WithMessage("*InternalProperty*internal*also internal*ProtectedInternalProperty*"); + } + + private class ClassWithInternalProperty + { + [UsedImplicitly] + public string PublicProperty { get; set; } + + [UsedImplicitly] + internal string InternalProperty { get; set; } + + [UsedImplicitly] + protected internal string ProtectedInternalProperty { get; set; } + } + + [Fact] + public void When_a_field_is_internal_it_should_be_excluded_from_the_comparison() + { + // Arrange + var actual = new ClassWithInternalField + { + PublicField = "public", + InternalField = "internal", + ProtectedInternalField = "internal" + }; + + var expected = new ClassWithInternalField + { + PublicField = "public", + InternalField = "also internal", + ProtectedInternalField = "also internal" + }; + + // Act / Assert + actual.Should().BeEquivalentTo(expected); + } + + [Fact] + public void When_a_field_is_internal_and_it_should_be_included_it_should_fail_the_assertion() + { + // Arrange + var actual = new ClassWithInternalField + { + PublicField = "public", + InternalField = "internal", + ProtectedInternalField = "internal" + }; + + var expected = new ClassWithInternalField + { + PublicField = "public", + InternalField = "also internal", + ProtectedInternalField = "also internal" + }; + + // Act + Action act = () => actual.Should().BeEquivalentTo(expected, options => options.IncludingInternalFields()); + + // Assert + act.Should().Throw().WithMessage("*InternalField*internal*also internal*ProtectedInternalField*"); + } + + private class ClassWithInternalField + { + [UsedImplicitly] + public string PublicField; + + [UsedImplicitly] + internal string InternalField; + + [UsedImplicitly] + protected internal string ProtectedInternalField; + } + + [Fact] + public void When_a_property_is_internal_it_should_be_excluded_from_the_comparison() + { + // Arrange + var actual = new ClassWithInternalProperty + { + PublicProperty = "public", + InternalProperty = "internal", + ProtectedInternalProperty = "internal" + }; + + var expected = new ClassWithInternalProperty + { + PublicProperty = "public", + InternalProperty = "also internal", + ProtectedInternalProperty = "also internal" + }; + + // Act / Assert + actual.Should().BeEquivalentTo(expected); + } + + [Fact] + public void Private_protected_properties_are_ignored() + { + // Arrange + var subject = new ClassWithPrivateProtectedProperty("Name", 13); + var other = new ClassWithPrivateProtectedProperty("Name", 37); + + // Act/Assert + subject.Should().BeEquivalentTo(other); + } + + private class ClassWithPrivateProtectedProperty + { + public ClassWithPrivateProtectedProperty(string name, int value) + { + Name = name; + Value = value; + } + + [UsedImplicitly] + public string Name { get; } + + [UsedImplicitly] + private protected int Value { get; } + } + + [Fact] + public void Private_protected_fields_are_ignored() + { + // Arrange + var subject = new ClassWithPrivateProtectedField("Name", 13); + var other = new ClassWithPrivateProtectedField("Name", 37); + + // Act/Assert + subject.Should().BeEquivalentTo(other); + } + + private class ClassWithPrivateProtectedField + { + public ClassWithPrivateProtectedField(string name, int value) + { + Name = name; + this.value = value; + } + + [UsedImplicitly] + public string Name; + + [UsedImplicitly] + private protected int value; + } + + [Fact] + public void Normal_properties_have_priority_over_explicitly_implemented_properties() + { + var instance = new MyClass + { + MyError = 42, + }; + + var other = new MyClass + { + MyError = 42, + }; + + instance.Should().BeEquivalentTo(other); + } + + private class MyClass : Exception, IMyInterface + { + public int MyError { get; set; } + + int IMyInterface.Message => MyError; + } + + private interface IMyInterface + { + int Message { get; } + } + } +} diff --git a/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.Basic.cs b/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.Basic.cs new file mode 100644 index 0000000000..a86d39c2ea --- /dev/null +++ b/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.Basic.cs @@ -0,0 +1,331 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using JetBrains.Annotations; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Equivalency.Specs; + +public partial class SelectionRulesSpecs +{ + public class Basic + { + [Fact] + public void Property_names_are_case_sensitive() + { + // Arrange + var subject = new + { + Name = "John" + }; + + var other = new + { + name = "John" + }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(other); + + // Assert + act.Should().Throw().WithMessage( + "Expectation*name**other*not have*"); + } + + [Fact] + public void Field_names_are_case_sensitive() + { + // Arrange + var subject = new ClassWithFieldInUpperCase + { + Name = "John" + }; + + var other = new ClassWithFieldInLowerCase + { + name = "John" + }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(other); + + // Assert + act.Should().Throw().WithMessage( + "Expectation*name**other*not have*"); + } + + private class ClassWithFieldInLowerCase + { + [UsedImplicitly] +#pragma warning disable SA1307 + public string name; +#pragma warning restore SA1307 + } + + private class ClassWithFieldInUpperCase + { + [UsedImplicitly] + public string Name; + } + + [Fact] + public void When_a_property_is_an_indexer_it_should_be_ignored() + { + // Arrange + var expected = new ClassWithIndexer + { + Foo = "test" + }; + + var result = new ClassWithIndexer + { + Foo = "test" + }; + + // Act + Action act = () => result.Should().BeEquivalentTo(expected); + + // Assert + act.Should().NotThrow(); + } + + public class ClassWithIndexer + { + [UsedImplicitly] + public object Foo { get; set; } + + public string this[int n] => + n.ToString( + CultureInfo.InvariantCulture); + } + + [Fact] + public void When_the_expected_object_has_a_property_not_available_on_the_subject_it_should_throw() + { + // Arrange + var subject = new + { + }; + + var other = new + { + // ReSharper disable once StringLiteralTypo + City = "Rijswijk" + }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(other); + + // Assert + act.Should().Throw().WithMessage( + "Expectation has property City that the other object does not have*"); + } + + [Fact] + public void When_equally_named_properties_are_type_incompatible_it_should_throw() + { + // Arrange + var subject = new + { + Type = "A" + }; + + var other = new + { + Type = 36 + }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(other); + + // Assert + act + .Should().Throw() + .WithMessage("Expected property subject.Type to be 36, but found*\"A\"*"); + } + + [Fact] + public void When_multiple_properties_mismatch_it_should_report_all_of_them() + { + // Arrange + var subject = new + { + Property1 = "A", + Property2 = "B", + SubType1 = new + { + SubProperty1 = "C", + SubProperty2 = "D" + } + }; + + var other = new + { + Property1 = "1", + Property2 = "2", + SubType1 = new + { + SubProperty1 = "3", + SubProperty2 = "D" + } + }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(other); + + // Assert + act + .Should().Throw() + .WithMessage("*property subject.Property1*to be \"1\", but \"A\" differs near \"A\"*") + .WithMessage("*property subject.Property2*to be \"2\", but \"B\" differs near \"B\"*") + .WithMessage("*property subject.SubType1.SubProperty1*to be \"3\", but \"C\" differs near \"C\"*"); + } + + [Fact] + [SuppressMessage("ReSharper", "StringLiteralTypo")] + public void Including_all_declared_properties_excludes_all_fields() + { + // Arrange + var class1 = new ClassWithSomeFieldsAndProperties + { + Field1 = "Lorem", + Field2 = "ipsum", + Field3 = "dolor", + Property1 = "sit", + Property2 = "amet", + Property3 = "consectetur" + }; + + var class2 = new ClassWithSomeFieldsAndProperties + { + Field1 = "foo", + Property1 = "sit", + Property2 = "amet", + Property3 = "consectetur" + }; + + // Act + Action act = + () => class1.Should().BeEquivalentTo(class2, opts => opts.IncludingAllDeclaredProperties()); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + [SuppressMessage("ReSharper", "StringLiteralTypo")] + public void Including_all_runtime_properties_excludes_all_fields() + { + // Arrange + object class1 = new ClassWithSomeFieldsAndProperties + { + Field1 = "Lorem", + Field2 = "ipsum", + Field3 = "dolor", + Property1 = "sit", + Property2 = "amet", + Property3 = "consectetur" + }; + + object class2 = new ClassWithSomeFieldsAndProperties + { + Property1 = "sit", + Property2 = "amet", + Property3 = "consectetur" + }; + + // Act + Action act = + () => class1.Should().BeEquivalentTo(class2, opts => opts.IncludingAllRuntimeProperties()); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + [SuppressMessage("ReSharper", "StringLiteralTypo")] + public void Respecting_the_runtime_type_includes_both_properties_and_fields_included() + { + // Arrange + object class1 = new ClassWithSomeFieldsAndProperties + { + Field1 = "Lorem", + Property1 = "sit" + }; + + object class2 = new ClassWithSomeFieldsAndProperties(); + + // Act + Action act = + () => class1.Should().BeEquivalentTo(class2, opts => opts.PreferringRuntimeMemberTypes()); + + // Assert + act.Should().Throw().Which.Message.Should().Contain("Field1").And.Contain("Property1"); + } + + [Fact] + public void A_nested_class_without_properties_inside_a_collection_is_fine() + { + // Arrange + var sut = new List + { + new() + { + Name = "theName" + } + }; + + // Act / Assert + sut.Should().BeEquivalentTo( + [ + new BaseClassPointingToClassWithoutProperties + { + Name = "theName" + } + ]); + } + +#if NETCOREAPP3_0_OR_GREATER + [Fact] + public void Will_include_default_interface_properties_in_the_comparison() + { + var lista = new List + { + new Test { Name = "Test" } + }; + + List listb = new() + { + new Test { Name = "Test" } + }; + + lista.Should().BeEquivalentTo(listb); + } + + private class Test : ITest + { + public string Name { get; set; } + } + + private interface ITest + { + string Name { get; } + + int NameLength => Name.Length; + } +#endif + + internal class BaseClassPointingToClassWithoutProperties + { + [UsedImplicitly] + public string Name { get; set; } + + [UsedImplicitly] + public ClassWithoutProperty ClassWithoutProperty { get; } = new(); + } + + internal class ClassWithoutProperty; + } +} diff --git a/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.Browsability.cs b/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.Browsability.cs new file mode 100644 index 0000000000..dd93169c9c --- /dev/null +++ b/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.Browsability.cs @@ -0,0 +1,604 @@ +using System; +using System.ComponentModel; +using JetBrains.Annotations; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Equivalency.Specs; + +public partial class SelectionRulesSpecs +{ + public class Browsability + { + [Fact] + public void When_browsable_field_differs_excluding_non_browsable_members_should_not_affect_result() + { + // Arrange + var subject = new ClassWithNonBrowsableMembers + { + BrowsableField = 0 + }; + + var expectation = new ClassWithNonBrowsableMembers + { + BrowsableField = 1 + }; + + // Act + Action action = + () => subject.Should().BeEquivalentTo(expectation, config => config.ExcludingNonBrowsableMembers()); + + // Assert + action.Should().Throw(); + } + + [Fact] + public void When_browsable_property_differs_excluding_non_browsable_members_should_not_affect_result() + { + // Arrange + var subject = new ClassWithNonBrowsableMembers + { + BrowsableProperty = 0 + }; + + var expectation = new ClassWithNonBrowsableMembers + { + BrowsableProperty = 1 + }; + + // Act + Action action = + () => subject.Should().BeEquivalentTo(expectation, config => config.ExcludingNonBrowsableMembers()); + + // Assert + action.Should().Throw(); + } + + [Fact] + public void When_advanced_browsable_field_differs_excluding_non_browsable_members_should_not_affect_result() + { + // Arrange + var subject = new ClassWithNonBrowsableMembers + { + AdvancedBrowsableField = 0 + }; + + var expectation = new ClassWithNonBrowsableMembers + { + AdvancedBrowsableField = 1 + }; + + // Act + Action action = + () => subject.Should().BeEquivalentTo(expectation, config => config.ExcludingNonBrowsableMembers()); + + // Assert + action.Should().Throw(); + } + + [Fact] + public void When_advanced_browsable_property_differs_excluding_non_browsable_members_should_not_affect_result() + { + // Arrange + var subject = new ClassWithNonBrowsableMembers + { + AdvancedBrowsableProperty = 0 + }; + + var expectation = new ClassWithNonBrowsableMembers + { + AdvancedBrowsableProperty = 1 + }; + + // Act + Action action = + () => subject.Should().BeEquivalentTo(expectation, config => config.ExcludingNonBrowsableMembers()); + + // Assert + action.Should().Throw(); + } + + [Fact] + public void When_explicitly_browsable_field_differs_excluding_non_browsable_members_should_not_affect_result() + { + // Arrange + var subject = new ClassWithNonBrowsableMembers + { + ExplicitlyBrowsableField = 0 + }; + + var expectation = new ClassWithNonBrowsableMembers + { + ExplicitlyBrowsableField = 1 + }; + + // Act + Action action = + () => subject.Should().BeEquivalentTo(expectation, config => config.ExcludingNonBrowsableMembers()); + + // Assert + action.Should().Throw(); + } + + [Fact] + public void When_explicitly_browsable_property_differs_excluding_non_browsable_members_should_not_affect_result() + { + // Arrange + var subject = new ClassWithNonBrowsableMembers + { + ExplicitlyBrowsableProperty = 0 + }; + + var expectation = new ClassWithNonBrowsableMembers + { + ExplicitlyBrowsableProperty = 1 + }; + + // Act + Action action = + () => subject.Should().BeEquivalentTo(expectation, config => config.ExcludingNonBrowsableMembers()); + + // Assert + action.Should().Throw(); + } + + [Fact] + public void When_non_browsable_field_differs_excluding_non_browsable_members_should_make_it_succeed() + { + // Arrange + var subject = new ClassWithNonBrowsableMembers + { + NonBrowsableField = 0 + }; + + var expectation = new ClassWithNonBrowsableMembers + { + NonBrowsableField = 1 + }; + + // Act & Assert + subject.Should().BeEquivalentTo(expectation, config => config.ExcludingNonBrowsableMembers()); + } + + [Fact] + public void When_non_browsable_property_differs_excluding_non_browsable_members_should_make_it_succeed() + { + // Arrange + var subject = new ClassWithNonBrowsableMembers + { + NonBrowsableProperty = 0 + }; + + var expectation = new ClassWithNonBrowsableMembers + { + NonBrowsableProperty = 1 + }; + + // Act & Assert + subject.Should().BeEquivalentTo(expectation, config => config.ExcludingNonBrowsableMembers()); + } + + [Fact] + public void When_property_is_non_browsable_only_in_subject_excluding_non_browsable_members_should_not_make_it_succeed() + { + // Arrange + var subject = + new ClassWhereMemberThatCouldBeNonBrowsableIsNonBrowsable + { + PropertyThatMightBeNonBrowsable = 0 + }; + + var expectation = + new ClassWhereMemberThatCouldBeNonBrowsableIsBrowsable + { + PropertyThatMightBeNonBrowsable = 1 + }; + + // Act + Action action = + () => subject.Should().BeEquivalentTo(expectation, config => config.ExcludingNonBrowsableMembers()); + + // Assert + action.Should().Throw() + .WithMessage("Expected property subject.PropertyThatMightBeNonBrowsable to be 1, but found 0.*"); + } + + [Fact] + public void + When_property_is_non_browsable_only_in_subject_ignoring_non_browsable_members_on_subject_should_make_it_succeed() + { + // Arrange + var subject = + new ClassWhereMemberThatCouldBeNonBrowsableIsNonBrowsable + { + PropertyThatMightBeNonBrowsable = 0 + }; + + var expectation = + new ClassWhereMemberThatCouldBeNonBrowsableIsBrowsable + { + PropertyThatMightBeNonBrowsable = 1 + }; + + // Act & Assert + subject.Should().BeEquivalentTo( + expectation, + config => config.IgnoringNonBrowsableMembersOnSubject().ExcludingMissingMembers()); + } + + [Fact] + public void When_non_browsable_property_on_subject_is_ignored_but_is_present_on_expectation_it_should_fail() + { + // Arrange + var subject = + new ClassWhereMemberThatCouldBeNonBrowsableIsNonBrowsable + { + PropertyThatMightBeNonBrowsable = 0 + }; + + var expectation = + new ClassWhereMemberThatCouldBeNonBrowsableIsBrowsable + { + PropertyThatMightBeNonBrowsable = 1 + }; + + // Act + Action action = + () => subject.Should().BeEquivalentTo(expectation, config => config.IgnoringNonBrowsableMembersOnSubject()); + + // Assert + action.Should().Throw().WithMessage( + "Expectation has*ThatMightBeNonBrowsable that is non-browsable in the other object, and non-browsable " + + "members on the subject are ignored with the current configuration*"); + } + + [Fact] + public void Only_ignore_non_browsable_matching_members() + { + // Arrange + var subject = new + { + NonExisting = 0 + }; + + var expectation = new + { + Existing = 1 + }; + + // Act + Action action = () => subject.Should().BeEquivalentTo(expectation, config => config.IgnoringNonBrowsableMembersOnSubject()); + + // Assert + action.Should().Throw(); + } + + [Fact] + public void When_property_is_non_browsable_only_in_expectation_excluding_non_browsable_members_should_make_it_succeed() + { + // Arrange + var subject = new ClassWhereMemberThatCouldBeNonBrowsableIsBrowsable + { + PropertyThatMightBeNonBrowsable = 0 + }; + + var expectation = + new ClassWhereMemberThatCouldBeNonBrowsableIsNonBrowsable + { + PropertyThatMightBeNonBrowsable = 1 + }; + + // Act & Assert + subject.Should().BeEquivalentTo(expectation, config => config.ExcludingNonBrowsableMembers()); + } + + [Fact] + public void When_field_is_non_browsable_only_in_subject_excluding_non_browsable_members_should_not_make_it_succeed() + { + // Arrange + var subject = + new ClassWhereMemberThatCouldBeNonBrowsableIsNonBrowsable + { + FieldThatMightBeNonBrowsable = 0 + }; + + var expectation = + new ClassWhereMemberThatCouldBeNonBrowsableIsBrowsable + { + FieldThatMightBeNonBrowsable = 1 + }; + + // Act + Action action = + () => subject.Should().BeEquivalentTo(expectation, config => config.ExcludingNonBrowsableMembers()); + + // Assert + action.Should().Throw() + .WithMessage("Expected field subject.FieldThatMightBeNonBrowsable to be 1, but found 0.*"); + } + + [Fact] + public void When_field_is_non_browsable_only_in_subject_ignoring_non_browsable_members_on_subject_should_make_it_succeed() + { + // Arrange + var subject = + new ClassWhereMemberThatCouldBeNonBrowsableIsNonBrowsable + { + FieldThatMightBeNonBrowsable = 0 + }; + + var expectation = + new ClassWhereMemberThatCouldBeNonBrowsableIsBrowsable + { + FieldThatMightBeNonBrowsable = 1 + }; + + // Act & Assert + subject.Should().BeEquivalentTo( + expectation, + config => config.IgnoringNonBrowsableMembersOnSubject().ExcludingMissingMembers()); + } + + [Fact] + public void When_field_is_non_browsable_only_in_expectation_excluding_non_browsable_members_should_make_it_succeed() + { + // Arrange + var subject = new ClassWhereMemberThatCouldBeNonBrowsableIsBrowsable + { + FieldThatMightBeNonBrowsable = 0 + }; + + var expectation = + new ClassWhereMemberThatCouldBeNonBrowsableIsNonBrowsable + { + FieldThatMightBeNonBrowsable = 1 + }; + + // Act & Assert + subject.Should().BeEquivalentTo(expectation, config => config.ExcludingNonBrowsableMembers()); + } + + [Fact] + public void When_property_is_missing_from_subject_excluding_non_browsable_members_should_make_it_succeed() + { + // Arrange + var subject = + new + { + BrowsableField = 1, + BrowsableProperty = 1, + ExplicitlyBrowsableField = 1, + ExplicitlyBrowsableProperty = 1, + AdvancedBrowsableField = 1, + AdvancedBrowsableProperty = 1, + NonBrowsableField = 2 + /* NonBrowsableProperty missing */ + }; + + var expected = new ClassWithNonBrowsableMembers + { + BrowsableField = 1, + BrowsableProperty = 1, + ExplicitlyBrowsableField = 1, + ExplicitlyBrowsableProperty = 1, + AdvancedBrowsableField = 1, + AdvancedBrowsableProperty = 1, + NonBrowsableField = 2, + NonBrowsableProperty = 2 + }; + + // Act & Assert + subject.Should().BeEquivalentTo(expected, opt => opt.ExcludingNonBrowsableMembers()); + } + + [Fact] + public void When_field_is_missing_from_subject_excluding_non_browsable_members_should_make_it_succeed() + { + // Arrange + var subject = + new + { + BrowsableField = 1, + BrowsableProperty = 1, + ExplicitlyBrowsableField = 1, + ExplicitlyBrowsableProperty = 1, + AdvancedBrowsableField = 1, + AdvancedBrowsableProperty = 1, + /* NonBrowsableField missing */ + NonBrowsableProperty = 2 + }; + + var expected = new ClassWithNonBrowsableMembers + { + BrowsableField = 1, + BrowsableProperty = 1, + ExplicitlyBrowsableField = 1, + ExplicitlyBrowsableProperty = 1, + AdvancedBrowsableField = 1, + AdvancedBrowsableProperty = 1, + NonBrowsableField = 2, + NonBrowsableProperty = 2 + }; + + // Act & Assert + subject.Should().BeEquivalentTo(expected, opt => opt.ExcludingNonBrowsableMembers()); + } + + [Fact] + public void When_property_is_missing_from_expectation_excluding_non_browsable_members_should_make_it_succeed() + { + // Arrange + var subject = new ClassWithNonBrowsableMembers + { + BrowsableField = 1, + BrowsableProperty = 1, + ExplicitlyBrowsableField = 1, + ExplicitlyBrowsableProperty = 1, + AdvancedBrowsableField = 1, + AdvancedBrowsableProperty = 1, + NonBrowsableField = 2, + NonBrowsableProperty = 2 + }; + + var expected = + new + { + BrowsableField = 1, + BrowsableProperty = 1, + ExplicitlyBrowsableField = 1, + ExplicitlyBrowsableProperty = 1, + AdvancedBrowsableField = 1, + AdvancedBrowsableProperty = 1, + NonBrowsableField = 2 + /* NonBrowsableProperty missing */ + }; + + // Act & Assert + subject.Should().BeEquivalentTo(expected, opt => opt.ExcludingNonBrowsableMembers()); + } + + [Fact] + public void When_field_is_missing_from_expectation_excluding_non_browsable_members_should_make_it_succeed() + { + // Arrange + var subject = new ClassWithNonBrowsableMembers + { + BrowsableField = 1, + BrowsableProperty = 1, + ExplicitlyBrowsableField = 1, + ExplicitlyBrowsableProperty = 1, + AdvancedBrowsableField = 1, + AdvancedBrowsableProperty = 1, + NonBrowsableField = 2, + NonBrowsableProperty = 2 + }; + + var expected = + new + { + BrowsableField = 1, + BrowsableProperty = 1, + ExplicitlyBrowsableField = 1, + ExplicitlyBrowsableProperty = 1, + AdvancedBrowsableField = 1, + AdvancedBrowsableProperty = 1, + /* NonBrowsableField missing */ + NonBrowsableProperty = 2 + }; + + // Act & Assert + subject.Should().BeEquivalentTo(expected, opt => opt.ExcludingNonBrowsableMembers()); + } + + [Fact] + public void + When_non_browsable_members_are_excluded_it_should_still_be_possible_to_explicitly_include_non_browsable_field() + { + // Arrange + var subject = new ClassWithNonBrowsableMembers + { + NonBrowsableField = 1 + }; + + var expectation = new ClassWithNonBrowsableMembers + { + NonBrowsableField = 2 + }; + + // Act + Action action = + () => subject.Should().BeEquivalentTo( + expectation, + opt => opt.IncludingFields().ExcludingNonBrowsableMembers().Including(e => e.NonBrowsableField)); + + // Assert + action.Should().Throw() + .WithMessage("Expected field subject.NonBrowsableField to be 2, but found 1.*"); + } + + [Fact] + public void + When_non_browsable_members_are_excluded_it_should_still_be_possible_to_explicitly_include_non_browsable_property() + { + // Arrange + var subject = new ClassWithNonBrowsableMembers + { + NonBrowsableProperty = 1 + }; + + var expectation = new ClassWithNonBrowsableMembers + { + NonBrowsableProperty = 2 + }; + + // Act + Action action = + () => subject.Should().BeEquivalentTo( + expectation, + opt => opt.IncludingProperties().ExcludingNonBrowsableMembers().Including(e => e.NonBrowsableProperty)); + + // Assert + action.Should().Throw() + .WithMessage("Expected property subject.NonBrowsableProperty to be 2, but found 1.*"); + } + + private class ClassWithNonBrowsableMembers + { + [UsedImplicitly] + public int BrowsableField = -1; + + [UsedImplicitly] + public int BrowsableProperty { get; set; } + + [UsedImplicitly] + [EditorBrowsable(EditorBrowsableState.Always)] + public int ExplicitlyBrowsableField = -1; + + [UsedImplicitly] + [EditorBrowsable(EditorBrowsableState.Always)] + public int ExplicitlyBrowsableProperty { get; set; } + + [UsedImplicitly] + [EditorBrowsable(EditorBrowsableState.Advanced)] + public int AdvancedBrowsableField = -1; + + [UsedImplicitly] + [EditorBrowsable(EditorBrowsableState.Advanced)] + public int AdvancedBrowsableProperty { get; set; } + + [UsedImplicitly] + [EditorBrowsable(EditorBrowsableState.Never)] + public int NonBrowsableField = -1; + + [UsedImplicitly] + [EditorBrowsable(EditorBrowsableState.Never)] + public int NonBrowsableProperty { get; set; } + } + + private class ClassWhereMemberThatCouldBeNonBrowsableIsBrowsable + { + [UsedImplicitly] + public int BrowsableProperty { get; set; } + + [UsedImplicitly] + public int FieldThatMightBeNonBrowsable = -1; + + [UsedImplicitly] + public int PropertyThatMightBeNonBrowsable { get; set; } + } + + private class ClassWhereMemberThatCouldBeNonBrowsableIsNonBrowsable + { + [UsedImplicitly] + public int BrowsableProperty { get; set; } + + [EditorBrowsable(EditorBrowsableState.Never)] + [UsedImplicitly] + public int FieldThatMightBeNonBrowsable = -1; + + [EditorBrowsable(EditorBrowsableState.Never)] + [UsedImplicitly] + public int PropertyThatMightBeNonBrowsable { get; set; } + } + } +} diff --git a/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.Covariance.cs b/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.Covariance.cs new file mode 100644 index 0000000000..c776012494 --- /dev/null +++ b/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.Covariance.cs @@ -0,0 +1,104 @@ +#if NET5_0_OR_GREATER + +using System; +using JetBrains.Annotations; +using Xunit; + +namespace FluentAssertions.Equivalency.Specs; + +public partial class SelectionRulesSpecs +{ + public class Covariance + { + [Fact] + public void Excluding_a_covariant_property_should_work() + { + // Arrange + var actual = new DerivedWithCovariantOverride(new DerivedWithProperty + { + DerivedProperty = "a", + BaseProperty = "a_base" + }) + { + OtherProp = "other" + }; + + var expectation = new DerivedWithCovariantOverride(new DerivedWithProperty + { + DerivedProperty = "b", + BaseProperty = + "b_base" + }) + { + OtherProp = "other" + }; + + // Act / Assert + actual.Should().BeEquivalentTo(expectation, opts => opts + .Excluding(d => d.Property)); + } + + [Fact] + public void Excluding_a_covariant_property_through_the_base_class_excludes_the_base_class_property() + { + // Arrange + var actual = new DerivedWithCovariantOverride(new DerivedWithProperty + { + DerivedProperty = "a", + BaseProperty = "a_base" + }) + { + OtherProp = "other" + }; + + BaseWithAbstractProperty expectation = new DerivedWithCovariantOverride(new DerivedWithProperty + { + DerivedProperty = + "b", + BaseProperty = "b_base" + }) + { + OtherProp = "other" + }; + + // Act + Action act = () => actual.Should().BeEquivalentTo(expectation, opts => opts + .Excluding(d => d.Property)); + + // Assert + act.Should().Throw().WithMessage("No members*"); + } + + private class BaseWithProperty + { + [UsedImplicitly] + public string BaseProperty { get; set; } + } + + private class DerivedWithProperty : BaseWithProperty + { + [UsedImplicitly] + public string DerivedProperty { get; set; } + } + + private abstract class BaseWithAbstractProperty + { + public abstract BaseWithProperty Property { get; } + } + + private sealed class DerivedWithCovariantOverride : BaseWithAbstractProperty + { + public override DerivedWithProperty Property { get; } + + [UsedImplicitly] + public string OtherProp { get; set; } + + public DerivedWithCovariantOverride(DerivedWithProperty prop) + { + Property = prop; + } + } + } +} + +#endif diff --git a/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.Excluding.cs b/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.Excluding.cs new file mode 100644 index 0000000000..44b8686a82 --- /dev/null +++ b/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.Excluding.cs @@ -0,0 +1,1263 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using FluentAssertions.Common; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Equivalency.Specs; + +public partial class SelectionRulesSpecs +{ + public class Excluding + { + [Fact] + public void A_member_excluded_by_path_is_described_in_the_failure_message() + { + // Arrange + var subject = new + { + Name = "John", + Age = 13 + }; + + var customer = new + { + Name = "Jack", + Age = 37 + }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(customer, options => options + .Excluding(d => d.Age)); + + // Assert + act.Should().Throw() + .WithMessage("*Exclude*Age*"); + } + + [Fact] + public void A_member_excluded_by_predicate_is_described_in_the_failure_message() + { + // Arrange + var subject = new + { + Name = "John", + Age = 13 + }; + + var customer = new + { + Name = "Jack", + Age = 37 + }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(customer, options => options + .Excluding(ctx => ctx.Path == "Age")); + + // Assert + act.Should().Throw() + .WithMessage("*Exclude member when*Age*"); + } + + [Fact] + public void When_only_the_excluded_property_doesnt_match_it_should_not_throw() + { + // Arrange + var dto = new CustomerDto + { + Age = 36, + Birthdate = new DateTime(1973, 9, 20), + Name = "John" + }; + + var customer = new Customer + { + Age = 36, + Birthdate = new DateTime(1973, 9, 20), + Name = "Dennis" + }; + + // Act / Assert + dto.Should().BeEquivalentTo(customer, options => options + .Excluding(d => d.Name) + .Excluding(d => d.Id)); + } + + [Fact] + public void When_only_the_excluded_property_doesnt_match_it_should_not_throw_if_root_is_a_collection() + { + // Arrange + var dto = new Customer + { + Age = 36, + Birthdate = new DateTime(1973, 9, 20), + Name = "John" + }; + + var customer = new Customer + { + Age = 36, + Birthdate = new DateTime(1973, 9, 20), + Name = "Dennis" + }; + + // Act / Assert + new[] { dto }.Should().BeEquivalentTo(new[] { customer }, options => options + .Excluding(d => d.Name) + .Excluding(d => d.Id)); + } + + [Fact] + [SuppressMessage("ReSharper", "StringLiteralTypo")] + public void When_excluding_members_it_should_pass_if_only_the_excluded_members_are_different() + { + // Arrange + var class1 = new ClassWithSomeFieldsAndProperties + { + Field1 = "Lorem", + Field2 = "ipsum", + Field3 = "dolor", + Property1 = "sit" + }; + + var class2 = new ClassWithSomeFieldsAndProperties + { + Field1 = "Lorem", + Field2 = "ipsum" + }; + + // Act + Action act = + () => + class1.Should().BeEquivalentTo(class2, + opts => opts.Excluding(o => o.Field3).Excluding(o => o.Property1)); + + // Assert + act.Should().NotThrow("the non-excluded fields have the same value"); + } + + [Fact] + [SuppressMessage("ReSharper", "StringLiteralTypo")] + public void When_excluding_members_it_should_fail_if_any_non_excluded_members_are_different() + { + // Arrange + var class1 = new ClassWithSomeFieldsAndProperties + { + Field1 = "Lorem", + Field2 = "ipsum", + Field3 = "dolor", + Property1 = "sit" + }; + + var class2 = new ClassWithSomeFieldsAndProperties + { + Field1 = "Lorem", + Field2 = "ipsum" + }; + + // Act + Action act = + () => class1.Should().BeEquivalentTo(class2, opts => opts.Excluding(o => o.Property1)); + + // Assert + act.Should().Throw().WithMessage("Expected*Field3*"); + } + + [Fact] + public void When_all_shared_properties_match_it_should_not_throw() + { + // Arrange + var dto = new CustomerDto + { + Age = 36, + Birthdate = new DateTime(1973, 9, 20), + Name = "John" + }; + + var customer = new Customer + { + Id = 1, + Age = 36, + Birthdate = new DateTime(1973, 9, 20), + Name = "John" + }; + + // Act + Action act = () => dto.Should().BeEquivalentTo(customer, options => options.ExcludingMissingMembers()); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_a_deeply_nested_property_with_a_value_mismatch_is_excluded_it_should_not_throw() + { + // Arrange + var subject = new Root + { + Text = "Root", + Level = new Level1 + { + Text = "Level1", + Level = new Level2 + { + Text = "Mismatch" + } + } + }; + + var expected = new RootDto + { + Text = "Root", + Level = new Level1Dto + { + Text = "Level1", + Level = new Level2Dto + { + Text = "Level2" + } + } + }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expected, + options => options.Excluding(r => r.Level.Level.Text)); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_a_deeply_nested_property_with_a_value_mismatch_is_excluded_it_should_not_throw_if_root_is_a_collection() + { + // Arrange + var subject = new Root + { + Text = "Root", + Level = new Level1 + { + Text = "Level1", + Level = new Level2 + { + Text = "Mismatch" + } + } + }; + + var expected = new RootDto + { + Text = "Root", + Level = new Level1Dto + { + Text = "Level1", + Level = new Level2Dto + { + Text = "Level2" + } + } + }; + + // Act / Assert + new[] { subject }.Should().BeEquivalentTo(new[] { expected }, + options => options.Excluding(r => r.Level.Level.Text)); + } + + [Fact] + public void When_a_property_with_a_value_mismatch_is_excluded_using_a_predicate_it_should_not_throw() + { + // Arrange + var subject = new Root + { + Text = "Root", + Level = new Level1 + { + Text = "Level1", + Level = new Level2 + { + Text = "Mismatch" + } + } + }; + + var expected = new RootDto + { + Text = "Root", + Level = new Level1Dto + { + Text = "Level1", + Level = new Level2Dto + { + Text = "Level2" + } + } + }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expected, config => + config.Excluding(ctx => ctx.Path == "Level.Level.Text")); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_members_are_excluded_by_the_access_modifier_of_the_getter_using_a_predicate_they_should_be_ignored() + { + // Arrange + var subject = new ClassWithAllAccessModifiersForMembers("public", "protected", + "internal", "protected-internal", "private", "private-protected"); + + var expected = new ClassWithAllAccessModifiersForMembers("public", "protected", + "ignored-internal", "ignored-protected-internal", "private", "private-protected"); + + // Act + Action act = () => subject.Should().BeEquivalentTo(expected, config => config + .IncludingInternalFields() + .Excluding(ctx => + ctx.WhichGetterHas(CSharpAccessModifier.Internal) || + ctx.WhichGetterHas(CSharpAccessModifier.ProtectedInternal))); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_members_are_excluded_by_the_access_modifier_of_the_setter_using_a_predicate_they_should_be_ignored() + { + // Arrange + var subject = new ClassWithAllAccessModifiersForMembers("public", "protected", + "internal", "protected-internal", "private", "private-protected"); + + var expected = new ClassWithAllAccessModifiersForMembers("public", "protected", + "ignored-internal", "ignored-protected-internal", "ignored-private", "private-protected"); + + // Act + Action act = () => subject.Should().BeEquivalentTo(expected, config => config + .IncludingInternalFields() + .Excluding(ctx => + ctx.WhichSetterHas(CSharpAccessModifier.Internal) || + ctx.WhichSetterHas(CSharpAccessModifier.ProtectedInternal) || + ctx.WhichSetterHas(CSharpAccessModifier.Private))); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + [SuppressMessage("ReSharper", "StringLiteralTypo")] + public void When_excluding_properties_it_should_still_compare_fields() + { + // Arrange + var class1 = new ClassWithSomeFieldsAndProperties + { + Field1 = "Lorem", + Field2 = "ipsum", + Field3 = "dolor", + Property1 = "sit", + Property2 = "amet", + Property3 = "consectetur" + }; + + var class2 = new ClassWithSomeFieldsAndProperties + { + Field1 = "Lorem", + Field2 = "ipsum", + Field3 = "color" + }; + + // Act + Action act = + () => class1.Should().BeEquivalentTo(class2, opts => opts.ExcludingProperties()); + + // Assert + act.Should().Throw().WithMessage("*color*dolor*"); + } + + [Fact] + public void When_excluding_fields_it_should_still_compare_properties() + { + // Arrange + var class1 = new ClassWithSomeFieldsAndProperties + { + Field1 = "Lorem", + Field2 = "ipsum", + Field3 = "dolor", + Property1 = "sit", + Property2 = "amet", + Property3 = "consectetur" + }; + + var class2 = new ClassWithSomeFieldsAndProperties + { + Property1 = "sit", + Property2 = "amet", + Property3 = "different" + }; + + // Act + Action act = + () => class1.Should().BeEquivalentTo(class2, opts => opts.ExcludingFields()); + + // Assert + act.Should().Throw().WithMessage("*Property3*consectetur*"); + } + + [Fact] + public void When_excluding_properties_via_non_array_indexers_it_should_exclude_the_specified_paths() + { + // Arrange + var subject = new + { + List = new[] + { + new + { + Foo = 1, + Bar = 2 + }, + new + { + Foo = 3, + Bar = 4 + } + }.ToList(), + Dictionary = new Dictionary + { + ["Foo"] = new() + { + Value = 1 + }, + ["Bar"] = new() + { + Value = 2 + } + } + }; + + var expected = new + { + List = new[] + { + new + { + Foo = 1, + Bar = 2 + }, + new + { + Foo = 2, + Bar = 4 + } + }.ToList(), + Dictionary = new Dictionary + { + ["Foo"] = new() + { + Value = 1 + }, + ["Bar"] = new() + { + Value = 3 + } + } + }; + + // Act + Action act = () => + subject.Should().BeEquivalentTo(expected, + options => options + .Excluding(x => x.List[1].Foo) + .Excluding(x => x.Dictionary["Bar"].Value)); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void + When_excluding_properties_via_non_array_indexers_it_should_exclude_the_specified_paths_if_root_is_a_collection() + { + // Arrange + var subject = new + { + List = new[] + { + new + { + Foo = 1, + Bar = 2 + }, + new + { + Foo = 3, + Bar = 4 + } + }.ToList(), + Dictionary = new Dictionary + { + ["Foo"] = new() + { + Value = 1 + }, + ["Bar"] = new() + { + Value = 2 + } + } + }; + + var expected = new + { + List = new[] + { + new + { + Foo = 1, + Bar = 2 + }, + new + { + Foo = 2, + Bar = 4 + } + }.ToList(), + Dictionary = new Dictionary + { + ["Foo"] = new() + { + Value = 1 + }, + ["Bar"] = new() + { + Value = 3 + } + } + }; + + // Act / Assert + new[] { subject }.Should().BeEquivalentTo(new[] { expected }, + options => options + .Excluding(x => x.List[1].Foo) + .Excluding(x => x.Dictionary["Bar"].Value)); + } + + [Fact] + public void When_excluding_properties_via_non_array_indexers_it_should_not_exclude_paths_with_different_indexes() + { + // Arrange + var subject = new + { + List = new[] + { + new + { + Foo = 1, + Bar = 2 + }, + new + { + Foo = 3, + Bar = 4 + } + }.ToList(), + Dictionary = new Dictionary + { + ["Foo"] = new() + { + Value = 1 + }, + ["Bar"] = new() + { + Value = 2 + } + } + }; + + var expected = new + { + List = new[] + { + new + { + Foo = 5, + Bar = 2 + }, + new + { + Foo = 2, + Bar = 4 + } + }.ToList(), + Dictionary = new Dictionary + { + ["Foo"] = new() + { + Value = 6 + }, + ["Bar"] = new() + { + Value = 3 + } + } + }; + + // Act + Action act = () => + subject.Should().BeEquivalentTo(expected, + options => options + .Excluding(x => x.List[1].Foo) + .Excluding(x => x.Dictionary["Bar"].Value)); + + // Assert + act.Should().Throw(); + } + + [Fact] + [SuppressMessage("ReSharper", "StringLiteralTypo")] + public void + When_configured_for_runtime_typing_and_properties_are_excluded_the_runtime_type_should_be_used_and_properties_should_be_ignored() + { + // Arrange + object class1 = new ClassWithSomeFieldsAndProperties + { + Field1 = "Lorem", + Field2 = "ipsum", + Field3 = "dolor", + Property1 = "sit", + Property2 = "amet", + Property3 = "consectetur" + }; + + object class2 = new ClassWithSomeFieldsAndProperties + { + Field1 = "Lorem", + Field2 = "ipsum", + Field3 = "dolor" + }; + + // Act + Action act = + () => class1.Should().BeEquivalentTo(class2, opts => opts.ExcludingProperties().PreferringRuntimeMemberTypes()); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_excluding_virtual_or_abstract_property_exclusion_works_properly() + { + var obj1 = new Derived + { + DerivedProperty1 = "Something", + DerivedProperty2 = "A" + }; + + var obj2 = new Derived + { + DerivedProperty1 = "Something", + DerivedProperty2 = "B" + }; + + obj1.Should().BeEquivalentTo(obj2, opt => opt + .Excluding(o => o.AbstractProperty) + .Excluding(o => o.VirtualProperty) + .Excluding(o => o.DerivedProperty2)); + } + + [Fact] + public void Abstract_properties_cannot_be_excluded() + { + var obj1 = new Derived + { + DerivedProperty1 = "Something", + DerivedProperty2 = "A" + }; + + var obj2 = new Derived + { + DerivedProperty1 = "Something", + DerivedProperty2 = "B" + }; + + Action act = () => obj1.Should().BeEquivalentTo(obj2, opt => opt + .Excluding(o => o.AbstractProperty + "B")); + + act.Should().Throw() + .WithMessage("*(o.AbstractProperty + \"B\")*cannot be used to select a member*"); + } + +#if NETCOREAPP3_0_OR_GREATER + [Fact] + public void Can_exclude_a_default_interface_property_using_an_expression() + { + // Arrange + IHaveDefaultProperty subject = new ClassReceivedDefaultInterfaceProperty + { + NormalProperty = "Value" + }; + + IHaveDefaultProperty expectation = new ClassReceivedDefaultInterfaceProperty + { + NormalProperty = "Another Value" + }; + + // Act + var act = () => subject.Should().BeEquivalentTo(expectation, + x => x.Excluding(p => p.DefaultProperty)); + + // Assert + act.Should().Throw().Which.Message.Should().NotContain("subject.DefaultProperty"); + } + + [Fact] + public void Can_exclude_a_default_interface_property_using_a_name() + { + // Arrange + IHaveDefaultProperty subject = new ClassReceivedDefaultInterfaceProperty + { + NormalProperty = "Value" + }; + + IHaveDefaultProperty expectation = new ClassReceivedDefaultInterfaceProperty + { + NormalProperty = "Another Value" + }; + + // Act + var act = () => subject.Should().BeEquivalentTo(expectation, + x => x.Excluding(info => info.Name.Contains("DefaultProperty"))); + + // Assert + act.Should().Throw().Which.Message.Should().NotContain("subject.DefaultProperty"); + } + + private class ClassReceivedDefaultInterfaceProperty : IHaveDefaultProperty + { + public string NormalProperty { get; set; } + } + + private interface IHaveDefaultProperty + { + string NormalProperty { get; set; } + + int DefaultProperty => NormalProperty.Length; + } +#endif + + [Fact] + public void An_anonymous_object_with_multiple_fields_excludes_correctly() + { + // Arrange + var subject = new + { + FirstName = "John", + MiddleName = "X", + LastName = "Doe", + Age = 34 + }; + + var expectation = new + { + FirstName = "John", + MiddleName = "W.", + LastName = "Smith", + Age = 29 + }; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, opts => opts + .Excluding(p => new { p.MiddleName, p.LastName, p.Age })); + } + + [Fact] + public void An_empty_anonymous_object_excludes_nothing() + { + // Arrange + var subject = new + { + FirstName = "John", + MiddleName = "X", + LastName = "Doe", + Age = 34 + }; + + var expectation = new + { + FirstName = "John", + MiddleName = "W.", + LastName = "Smith", + Age = 29 + }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, opts => opts + .Excluding(p => new { })); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void An_anonymous_object_can_exclude_collections() + { + // Arrange + var subject = new + { + Names = new[] + { + "John", + "X.", + "Doe" + }, + Age = 34 + }; + + var expectation = new + { + Names = new[] + { + "John", + "W.", + "Smith" + }, + Age = 34 + }; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, opts => opts + .Excluding(p => new { p.Names })); + } + + [Fact] + public void An_anonymous_object_can_exclude_nested_objects() + { + // Arrange + var subject = new + { + Names = new + { + FirstName = "John", + MiddleName = "X", + LastName = "Doe", + }, + Age = 34 + }; + + var expectation = new + { + Names = new + { + FirstName = "John", + MiddleName = "W.", + LastName = "Smith", + }, + Age = 34 + }; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, opts => opts + .Excluding(p => new { p.Names.MiddleName, p.Names.LastName })); + } + + [Fact] + public void An_anonymous_object_can_exclude_nested_objects_inside_collections() + { + // Arrange + var subject = new + { + Names = new + { + FirstName = "John", + MiddleName = "X", + LastName = "Doe", + }, + Pets = new[] + { + new + { + Name = "Dog", + Age = 1, + Color = "Black" + }, + new + { + Name = "Cat", + Age = 1, + Color = "Black" + }, + new + { + Name = "Bird", + Age = 1, + Color = "Black" + }, + } + }; + + var expectation = new + { + Names = new + { + FirstName = "John", + MiddleName = "W.", + LastName = "Smith", + }, + Pets = new[] + { + new + { + Name = "Dog", + Age = 1, + Color = "Black" + }, + new + { + Name = "Dog", + Age = 2, + Color = "Gray" + }, + new + { + Name = "Bird", + Age = 3, + Color = "Black" + }, + } + }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, opts => opts + .Excluding(p => new { p.Names.MiddleName, p.Names.LastName }) + .For(p => p.Pets) + .Exclude(p => new { p.Age, p.Name })); + + // Assert + act.Should().Throw().Which.Message.Should() + .NotMatch("*Pets[1].Age*").And + .NotMatch("*Pets[1].Name*").And + .Match("*Pets[1].Color*"); + } + + [Fact] + public void An_anonymous_object_can_exclude_nested_objects_inside_nested_collections() + { + // Arrange + var subject = new + { + Names = new + { + FirstName = "John", + MiddleName = "W.", + LastName = "Smith", + }, + Pets = new[] + { + new + { + Name = "Dog", + Fleas = new[] + { + new + { + Name = "Flea 1", + Age = 1, + }, + new + { + Name = "Flea 2", + Age = 2, + }, + }, + }, + new + { + Name = "Dog", + Fleas = new[] + { + new + { + Name = "Flea 10", + Age = 1, + }, + new + { + Name = "Flea 21", + Age = 3, + }, + }, + }, + new + { + Name = "Dog", + Fleas = new[] + { + new + { + Name = "Flea 1", + Age = 1, + }, + new + { + Name = "Flea 2", + Age = 2, + }, + }, + }, + }, + }; + + var expectation = new + { + Names = new + { + FirstName = "John", + MiddleName = "W.", + LastName = "Smith", + }, + Pets = new[] + { + new + { + Name = "Dog", + Fleas = new[] + { + new + { + Name = "Flea 1", + Age = 1, + }, + new + { + Name = "Flea 2", + Age = 2, + }, + }, + }, + new + { + Name = "Dog", + Fleas = new[] + { + new + { + Name = "Flea 1", + Age = 1, + }, + new + { + Name = "Flea 2", + Age = 1, + }, + }, + }, + new + { + Name = "Bird", + Fleas = new[] + { + new + { + Name = "Flea 1", + Age = 1, + }, + new + { + Name = "Flea 2", + Age = 2, + }, + }, + }, + }, + }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, opts => opts + .Excluding(p => new { p.Names.MiddleName, p.Names.LastName }) + .For(person => person.Pets) + .For(pet => pet.Fleas) + .Exclude(flea => new { flea.Name, flea.Age })); + + // Assert + act.Should().Throw().Which.Message.Should() + .NotMatch("*Pets[*].Fleas[*].Age*").And + .NotMatch("*Pets[*].Fleas[*].Name*").And + .Match("*- Exclude*Pets[]Fleas[]Age*").And + .Match("*- Exclude*Pets[]Fleas[]Name*"); + } + + [Fact] + public void An_empty_anonymous_object_excludes_nothing_inside_collections() + { + // Arrange + var subject = new + { + Names = new + { + FirstName = "John", + MiddleName = "X", + LastName = "Doe", + }, + Pets = new[] + { + new + { + Name = "Dog", + Age = 1 + }, + new + { + Name = "Cat", + Age = 1 + }, + new + { + Name = "Bird", + Age = 1 + }, + } + }; + + var expectation = new + { + Names = new + { + FirstName = "John", + MiddleName = "W.", + LastName = "Smith", + }, + Pets = new[] + { + new + { + Name = "Dog", + Age = 1 + }, + new + { + Name = "Dog", + Age = 2 + }, + new + { + Name = "Bird", + Age = 1 + }, + } + }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, opts => opts + .Excluding(p => new { p.Names.MiddleName, p.Names.LastName }) + .For(p => p.Pets) + .Exclude(p => new { })); + + // Assert + act.Should().Throw().WithMessage("*Pets[1].Name*Pets[1].Age*"); + } + + [Fact] + public void Can_exclude_root_properties_by_name() + { + // Arrange + var subject = new + { + FirstName = "John", + LastName = "Doe", + Age = 30 + }; + + var expectation = new + { + FirstName = "John", + LastName = "Smith", + Age = 35 + }; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, options => options + .ExcludingMembersNamed("LastName", "Age")); + } + + [Fact] + public void Can_exclude_properties_deeper_in_the_graph_by_name() + { + // Arrange + var subject = new + { + Person = new + { + FirstName = "John", + LastName = "Doe", + Age = 30 + }, + Address = new + { + Street = "123 Main St", + City = "Anytown", + ZipCode = "12345" + } + }; + + var expectation = new + { + Person = new + { + FirstName = "John", + LastName = "Smith", + Age = 35 + }, + Address = new + { + Street = "123 Main St", + City = "Othertown", + ZipCode = "54321" + } + }; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, options => options + .ExcludingMembersNamed("LastName", "Age", "City", "ZipCode")); + } + + [Fact] + public void Must_provide_property_names_when_excluding_by_name() + { + // Arrange + var subject = new + { + FirstName = "John", + LastName = "Doe" + }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(subject, options => options + .ExcludingMembersNamed()); + + // Assert + act.Should().Throw() + .WithMessage("*least one*name*"); + } + + [Fact] + public void Cannot_provide_null_as_a_property_name() + { + // Arrange + var subject = new + { + FirstName = "John", + LastName = "Doe" + }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(subject, options => options + .ExcludingMembersNamed(null)); + + // Assert + act.Should().Throw() + .WithMessage("*Member names cannot be null*"); + } + } +} diff --git a/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.Including.cs b/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.Including.cs new file mode 100644 index 0000000000..c43e529ec3 --- /dev/null +++ b/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.Including.cs @@ -0,0 +1,510 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using JetBrains.Annotations; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Equivalency.Specs; + +public partial class SelectionRulesSpecs +{ + public class Including + { + [Fact] + public void When_specific_properties_have_been_specified_it_should_ignore_the_other_properties() + { + // Arrange + var subject = new + { + Age = 36, + Birthdate = new DateTime(1973, 9, 20), + Name = "John" + }; + + var customer = new + { + Age = 36, + Birthdate = new DateTime(1973, 9, 20), + Name = "Dennis" + }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(customer, options => options + .Including(d => d.Age) + .Including(d => d.Birthdate)); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void A_member_included_by_path_is_described_in_the_failure_message() + { + // Arrange + var subject = new + { + Name = "John" + }; + + var customer = new + { + Name = "Jack" + }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(customer, options => options + .Including(d => d.Name)); + + // Assert + act.Should().Throw() + .WithMessage("*Include*Name*"); + } + + [Fact] + public void A_member_included_by_predicate_is_described_in_the_failure_message() + { + // Arrange + var subject = new + { + Name = "John" + }; + + var customer = new + { + Name = "Jack" + }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(customer, options => options + .Including(ctx => ctx.Path == "Name")); + + // Assert + act.Should().Throw() + .WithMessage("*Include member when*Name*"); + } + + [Fact] + public void When_a_predicate_for_properties_to_include_has_been_specified_it_should_ignore_the_other_properties() + { + // Arrange + var subject = new + { + Age = 36, + Birthdate = new DateTime(1973, 9, 20), + Name = "John" + }; + + var customer = new + { + Age = 36, + Birthdate = new DateTime(1973, 9, 20), + Name = "Dennis" + }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(customer, options => options + .Including(info => info.Path.EndsWith("Age", StringComparison.Ordinal)) + .Including(info => info.Path.EndsWith("Birthdate", StringComparison.Ordinal))); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_a_non_property_expression_is_provided_it_should_throw() + { + // Arrange + var dto = new CustomerDto(); + + // Act + Action act = () => dto.Should().BeEquivalentTo(dto, options => options.Including(d => d.GetType())); + + // Assert + act.Should().Throw() + .WithMessage("Expression cannot be used to select a member.*") + .WithParameterName("expression"); + } + + [Fact] + public void When_including_a_property_it_should_exactly_match_the_property() + { + // Arrange + var actual = new + { + DeclaredType = LocalOtherType.NonDefault, + Type = LocalType.NonDefault + }; + + var expectation = new + { + DeclaredType = LocalOtherType.NonDefault + }; + + // Act + Action act = () => actual.Should().BeEquivalentTo(expectation, + config => config.Including(o => o.DeclaredType)); + + // Assert + act.Should().NotThrow(); + } + + private enum LocalOtherType : byte + { + Default, + NonDefault + } + + private enum LocalType : byte + { + Default, + NonDefault + } + + public class CustomType + { + [UsedImplicitly] + public string Name { get; set; } + } + + [Fact] + public void When_including_a_property_using_an_expression_it_should_evaluate_it_from_the_root() + { + // Arrange + var list1 = new List + { + new() + { + Name = "A" + }, + new() + { + Name = "B" + } + }; + + var list2 = new List + { + new() + { + Name = "C" + }, + new() + { + Name = "D" + } + }; + + var objectA = new ClassA + { + ListOfCustomTypes = list1 + }; + + var objectB = new ClassA + { + ListOfCustomTypes = list2 + }; + + // Act + Action act = () => objectA.Should().BeEquivalentTo(objectB, options => options.Including(x => x.ListOfCustomTypes)); + + // Assert + act.Should().Throw().WithMessage("*C*but*A*D*but*B*"); + } + + private class ClassA + { + public List ListOfCustomTypes { get; set; } + } + + [Fact] + public void When_null_is_provided_as_property_expression_it_should_throw() + { + // Arrange + var dto = new CustomerDto(); + + // Act + Action act = + () => dto.Should().BeEquivalentTo(dto, options => options.Including(null)); + + // Assert + act.Should().Throw().WithMessage( + "Expected an expression, but found .*"); + } + + [Fact] + [SuppressMessage("ReSharper", "StringLiteralTypo")] + public void When_including_fields_it_should_succeed_if_just_the_included_field_match() + { + // Arrange + var class1 = new ClassWithSomeFieldsAndProperties + { + Field1 = "Lorem", + Field2 = "ipsum", + Field3 = "dolor", + Property1 = "sit", + Property2 = "amet", + Property3 = "consectetur" + }; + + var class2 = new ClassWithSomeFieldsAndProperties + { + Field1 = "Lorem", + Field2 = "ipsum" + }; + + // Act + Action act = + () => + class1.Should().BeEquivalentTo(class2, opts => opts.Including(o => o.Field1).Including(o => o.Field2)); + + // Assert + act.Should().NotThrow("the only selected fields have the same value"); + } + + [Fact] + [SuppressMessage("ReSharper", "StringLiteralTypo")] + public void When_including_fields_it_should_fail_if_any_included_field_do_not_match() + { + // Arrange + var class1 = new ClassWithSomeFieldsAndProperties + { + Field1 = "Lorem", + Field2 = "ipsum", + Field3 = "dolor", + Property1 = "sit", + Property2 = "amet", + Property3 = "consectetur" + }; + + var class2 = new ClassWithSomeFieldsAndProperties + { + Field1 = "Lorem", + Field2 = "ipsum" + }; + + // Act + Action act = + () => + class1.Should().BeEquivalentTo(class2, + opts => opts.Including(o => o.Field1).Including(o => o.Field2).Including(o => o.Field3)); + + // Assert + act.Should().Throw().WithMessage("Expected field class1.Field3*"); + } + + [Fact] + [SuppressMessage("ReSharper", "StringLiteralTypo")] + public void When_both_field_and_properties_are_configured_for_inclusion_both_should_be_included() + { + // Arrange + var class1 = new ClassWithSomeFieldsAndProperties + { + Field1 = "Lorem", + Property1 = "sit" + }; + + var class2 = new ClassWithSomeFieldsAndProperties(); + + // Act + Action act = + () => class1.Should().BeEquivalentTo(class2, opts => opts.IncludingFields().IncludingProperties()); + + // Assert + act.Should().Throw().Which.Message.Should().Contain("Field1").And.Contain("Property1"); + } + + [Fact] + public void Including_nested_objects_restores_the_default_behavior() + { + // Arrange + var subject = new Root + { + Text = "Root", + Level = new Level1 + { + Text = "Level1", + Level = new Level2 + { + Text = "Mismatch" + } + } + }; + + var expected = new RootDto + { + Text = "Root", + Level = new Level1Dto + { + Text = "Level1", + Level = new Level2Dto + { + Text = "Level2" + } + } + }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expected, + options => options.WithoutRecursing().IncludingNestedObjects()); + + // Assert + act.Should().Throw().WithMessage("*Level.Level.Text*Level2*Mismatch*"); + } + +#if NETCOREAPP3_0_OR_GREATER + [Fact] + public void Can_include_a_default_interface_property_using_an_expression() + { + // Arrange + IHaveDefaultProperty subject = new ClassReceivedDefaultInterfaceProperty + { + NormalProperty = "Value" + }; + + IHaveDefaultProperty expectation = new ClassReceivedDefaultInterfaceProperty + { + NormalProperty = "Another Value" + }; + + // Act + var act = () => subject.Should().BeEquivalentTo(expectation, x => x.Including(p => p.DefaultProperty)); + + // Assert + act.Should().Throw().WithMessage("Expected property subject.DefaultProperty to be 13, but found 5.*"); + } + + [Fact] + public void Can_include_a_default_interface_property_using_a_name() + { + // Arrange + IHaveDefaultProperty subject = new ClassReceivedDefaultInterfaceProperty + { + NormalProperty = "Value" + }; + + IHaveDefaultProperty expectation = new ClassReceivedDefaultInterfaceProperty + { + NormalProperty = "Another Value" + }; + + // Act + var act = () => subject.Should().BeEquivalentTo(expectation, + x => x.Including(p => p.Name.Contains("DefaultProperty"))); + + // Assert + act.Should().Throw().WithMessage("Expected property subject.DefaultProperty to be 13, but found 5.*"); + } + + private class ClassReceivedDefaultInterfaceProperty : IHaveDefaultProperty + { + public string NormalProperty { get; set; } + } + + private interface IHaveDefaultProperty + { + string NormalProperty { get; set; } + + int DefaultProperty => NormalProperty.Length; + } +#endif + + [Fact] + public void An_anonymous_object_selects_correctly() + { + // Arrange + var subject = new ClassWithSomeFieldsAndProperties + { + Field1 = "Lorem", + Field2 = "ipsum", + Field3 = "dolor", + Property1 = "sit", + Property2 = "amet", + Property3 = "consectetur" + }; + + var expectation = new ClassWithSomeFieldsAndProperties + { + Field1 = "Lorem", + Field2 = "ipsum" + }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, + opts => opts.Including(o => new { o.Field1, o.Field2, o.Field3 })); + + // Assert + act.Should().Throw().WithMessage("Expected field subject.Field3*"); + } + + [Fact] + public void An_anonymous_object_selects_nested_fields_correctly() + { + // Arrange + var subject = new + { + Field = "Lorem", + NestedField = new + { + FieldB = "ipsum" + } + }; + + var expectation = new + { + FieldA = "Lorem", + NestedField = new + { + FieldB = "no ipsum" + } + }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, + opts => opts.Including(o => new { o.NestedField.FieldB })); + + // Assert + act.Should().Throw().WithMessage("Expected*subject.NestedField.FieldB*"); + } + + [Fact] + public void An_anonymous_object_in_combination_with_exclude_selects_nested_fields_correctly() + { + // Arrange + var subject = new + { + FieldA = "Lorem", + NestedField = new + { + FieldB = "ipsum", + FieldC = "ipsum2", + FieldD = "ipsum3", + FieldE = "ipsum4", + } + }; + + var expectation = new + { + FieldA = "Lorem", + NestedField = new + { + FieldB = "no ipsum", + FieldC = "no ipsum2", + FieldD = "no ipsum3", + FieldE = "no ipsum4", + } + }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, opts => opts + .Excluding(o => new { o.NestedField }) + .Including(o => new { o.NestedField.FieldB })); + + // Assert + act.Should().Throw() + .Which.Message.Should() + .Match("*Expected*subject.NestedField.FieldB*").And + .NotMatch("*Expected*FieldC*FieldD*FieldE*"); + } + } +} diff --git a/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.Interfaces.cs b/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.Interfaces.cs new file mode 100644 index 0000000000..93e294f79b --- /dev/null +++ b/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.Interfaces.cs @@ -0,0 +1,402 @@ +using System; +using JetBrains.Annotations; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Equivalency.Specs; + +public partial class SelectionRulesSpecs +{ + public class Interfaces + { + [Fact] + public void When_an_interface_hierarchy_is_used_it_should_include_all_inherited_properties() + { + // Arrange + ICar subject = new Car + { + VehicleId = 1, + Wheels = 4 + }; + + ICar expected = new Car + { + VehicleId = 99999, + Wheels = 4 + }; + + // Act + Action action = () => subject.Should().BeEquivalentTo(expected); + + // Assert + action + .Should().Throw() + .WithMessage("Expected*VehicleId*99999*but*1*"); + } + + [Fact] + public void When_a_reference_to_an_interface_is_provided_it_should_only_include_those_properties() + { + // Arrange + IVehicle expected = new Car + { + VehicleId = 1, + Wheels = 4 + }; + + IVehicle subject = new Car + { + VehicleId = 1, + Wheels = 99999 + }; + + // Act + Action action = () => subject.Should().BeEquivalentTo(expected); + + // Assert + action.Should().NotThrow(); + } + + [Fact] + public void When_a_reference_to_an_explicit_interface_impl_is_provided_it_should_only_include_those_properties() + { + // Arrange + IVehicle expected = new ExplicitCar + { + Wheels = 4 + }; + + IVehicle subject = new ExplicitCar + { + Wheels = 99999 + }; + + // Act + Action action = () => subject.Should().BeEquivalentTo(expected); + + // Assert + action.Should().NotThrow(); + } + + [Fact] + public void Explicitly_implemented_subject_properties_are_ignored_if_a_normal_property_exists_with_the_same_name() + { + // Arrange + IVehicle expected = new Vehicle + { + VehicleId = 1 + }; + + IVehicle subject = new ExplicitVehicle + { + VehicleId = 2 // normal property + }; + + subject.VehicleId = 1; // explicitly implemented property + + // Act + Action action = () => subject.Should().BeEquivalentTo(expected); + + // Assert + action.Should().Throw(); + } + + [Fact] + public void Explicitly_implemented_read_only_subject_properties_are_ignored_if_a_normal_property_exists_with_the_same_name() + { + // Arrange + IReadOnlyVehicle subject = new ExplicitReadOnlyVehicle(explicitValue: 1) + { + VehicleId = 2 // normal property + }; + + var expected = new Vehicle + { + VehicleId = 1 + }; + + // Act + Action action = () => subject.Should().BeEquivalentTo(expected); + + // Assert + action.Should().Throw(); + } + + [Fact] + public void Explicitly_implemented_subject_properties_are_ignored_if_only_fields_are_included() + { + // Arrange + var expected = new VehicleWithField + { + VehicleId = 1 // A field named like a property + }; + + var subject = new ExplicitVehicle + { + VehicleId = 2 // A real property + }; + + // Act + Action action = () => subject.Should().BeEquivalentTo(expected, opt => opt + .IncludingFields() + .ExcludingProperties()); + + // Assert + action.Should().Throw().WithMessage("*field*VehicleId*other*"); + } + + [Fact] + public void Explicitly_implemented_subject_properties_are_ignored_if_only_fields_are_included_and_they_may_be_missing() + { + // Arrange + var expected = new VehicleWithField + { + VehicleId = 1 // A field named like a property + }; + + var subject = new ExplicitVehicle + { + VehicleId = 2 // A real property + }; + + // Act / Assert + subject.Should().BeEquivalentTo(expected, opt => opt + .IncludingFields() + .ExcludingProperties() + .ExcludingMissingMembers()); + } + + [Fact] + public void Excluding_missing_members_does_not_affect_how_explicitly_implemented_subject_properties_are_dealt_with() + { + // Arrange + IVehicle expected = new Vehicle + { + VehicleId = 1 + }; + + IVehicle subject = new ExplicitVehicle + { + VehicleId = 2 // instance member + }; + + subject.VehicleId = 1; // interface member + + // Act + Action action = () => subject.Should().BeEquivalentTo(expected, opt => opt.ExcludingMissingMembers()); + + // Assert + action.Should().Throw(); + } + + [Fact] + public void When_respecting_declared_types_explicit_interface_member_on_interfaced_expectation_should_be_used() + { + // Arrange + IVehicle expected = new ExplicitVehicle + { + VehicleId = 2 // instance member + }; + + expected.VehicleId = 1; // interface member + + IVehicle subject = new Vehicle + { + VehicleId = 1 + }; + + // Act + Action action = () => subject.Should().BeEquivalentTo(expected, opt => opt.PreferringDeclaredMemberTypes()); + + // Assert + action.Should().NotThrow(); + } + + [Fact] + public void When_respecting_runtime_types_explicit_interface_member_on_interfaced_subject_should_not_be_used() + { + // Arrange + IVehicle expected = new Vehicle + { + VehicleId = 1 + }; + + IVehicle subject = new ExplicitVehicle + { + VehicleId = 2 // instance member + }; + + subject.VehicleId = 1; // interface member + + // Act + Action action = () => subject.Should().BeEquivalentTo(expected, opt => opt.PreferringRuntimeMemberTypes()); + + // Assert + action.Should().Throw(); + } + + [Fact] + public void When_respecting_runtime_types_explicit_interface_member_on_interfaced_expectation_should_not_be_used() + { + // Arrange + IVehicle expected = new ExplicitVehicle + { + VehicleId = 2 // instance member + }; + + expected.VehicleId = 1; // interface member + + IVehicle subject = new Vehicle + { + VehicleId = 1 + }; + + // Act + Action action = () => subject.Should().BeEquivalentTo(expected, opt => opt.PreferringRuntimeMemberTypes()); + + // Assert + action.Should().Throw(); + } + + [Fact] + public void When_respecting_declared_types_explicit_interface_member_on_expectation_should_not_be_used() + { + // Arrange + var expected = new ExplicitVehicle + { + VehicleId = 2 + }; + + ((IVehicle)expected).VehicleId = 1; + + var subject = new Vehicle + { + VehicleId = 1 + }; + + // Act + Action action = () => subject.Should().BeEquivalentTo(expected); + + // Assert + action.Should().Throw(); + } + + [Fact] + public void Can_find_explicitly_implemented_property_on_the_subject() + { + // Arrange + IPerson person = new Person(); + person.Name = "Bob"; + + // Act / Assert + person.Should().BeEquivalentTo(new { Name = "Bob" }); + } + + [Fact] + public void Can_exclude_explicitly_implemented_properties() + { + // Arrange + var subject = new Person + { + NormalProperty = "Normal", + }; + + ((IPerson)subject).Name = "Bob"; + + var expectation = new Person + { + NormalProperty = "Normal", + }; + + ((IPerson)expectation).Name = "Jim"; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, options => options.ExcludingExplicitlyImplementedProperties()); + } + + private interface IPerson + { + string Name { get; set; } + } + + private class Person : IPerson + { + [UsedImplicitly] + public string NormalProperty { get; set; } + + string IPerson.Name { get; set; } + } + + [Fact] + public void Excluding_an_interface_property_through_inheritance_should_work() + { + // Arrange + IInterfaceWithTwoProperties[] actual = + [ + new DerivedClassImplementingInterface + { + Value1 = 1, + Value2 = 2 + } + ]; + + IInterfaceWithTwoProperties[] expected = + [ + new DerivedClassImplementingInterface + { + Value1 = 999, + Value2 = 2 + } + ]; + + // Act / Assert + actual.Should().BeEquivalentTo(expected, options => options + .Excluding(a => a.Value1) + .PreferringRuntimeMemberTypes()); + } + + [Fact] + public void Including_an_interface_property_through_inheritance_should_work() + { + // Arrange + IInterfaceWithTwoProperties[] actual = + [ + new DerivedClassImplementingInterface + { + Value1 = 1, + Value2 = 2 + } + ]; + + IInterfaceWithTwoProperties[] expected = + [ + new DerivedClassImplementingInterface + { + Value1 = 999, + Value2 = 2 + } + ]; + + // Act / Assert + actual.Should().BeEquivalentTo(expected, options => options + .Including(a => a.Value2) + .PreferringRuntimeMemberTypes()); + } + + public interface IInterfaceWithTwoProperties + { + int Value1 { get; set; } + + int Value2 { get; set; } + } + + public class BaseProvidingSamePropertiesAsInterface + { + public int Value1 { get; set; } + + public int Value2 { get; set; } + } + + public class DerivedClassImplementingInterface : BaseProvidingSamePropertiesAsInterface, IInterfaceWithTwoProperties; + } +} diff --git a/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.MemberHiding.cs b/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.MemberHiding.cs new file mode 100644 index 0000000000..8ef535eea8 --- /dev/null +++ b/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.MemberHiding.cs @@ -0,0 +1,324 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using JetBrains.Annotations; +using Xunit; + +namespace FluentAssertions.Equivalency.Specs; + +public partial class SelectionRulesSpecs +{ + [SuppressMessage("Class Design", "AV1010:Member hides inherited member")] + public class MemberHiding + { + [Fact] + public void Ignores_properties_hidden_by_the_derived_class() + { + // Arrange + var subject = new SubclassAHidingProperty + { + Property = "DerivedValue" + }; + + ((BaseWithProperty)subject).Property = "ActualBaseValue"; + + var expectation = new SubclassBHidingProperty + { + Property = "DerivedValue" + }; + + ((AnotherBaseWithProperty)expectation).Property = "ExpectedBaseValue"; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation); + } + + [Fact] + public void Ignores_properties_of_the_same_runtime_types_hidden_by_the_derived_class() + { + // Arrange + var subject = new SubclassHidingStringProperty + { + Property = "DerivedValue" + }; + + ((BaseWithStringProperty)subject).Property = "ActualBaseValue"; + + var expectation = new SubclassHidingStringProperty + { + Property = "DerivedValue" + }; + + ((BaseWithStringProperty)expectation).Property = "ExpectedBaseValue"; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation); + } + + [Fact] + public void Includes_hidden_property_of_the_base_when_using_a_reference_to_the_base() + { + // Arrange + BaseWithProperty subject = new SubclassAHidingProperty + { + Property = "ActualDerivedValue" + }; + + // FA doesn't know the compile-time type of the subject, so even though we pass a reference to the base-class, + // at run-time, it'll start finding the property on the subject starting from the run-time type, and thus ignore the + // hidden base-class field + ((SubclassAHidingProperty)subject).Property = "BaseValue"; + + AnotherBaseWithProperty expectation = new SubclassBHidingProperty + { + Property = "ExpectedDerivedValue" + }; + + expectation.Property = "BaseValue"; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation); + } + + [Fact] + public void Run_type_typing_ignores_hidden_properties_even_when_using_a_reference_to_the_base_class() + { + // Arrange + var subject = new SubclassAHidingProperty + { + Property = "DerivedValue" + }; + + ((BaseWithProperty)subject).Property = "ActualBaseValue"; + + AnotherBaseWithProperty expectation = new SubclassBHidingProperty + { + Property = "DerivedValue" + }; + + expectation.Property = "ExpectedBaseValue"; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, o => o.PreferringRuntimeMemberTypes()); + } + + [Fact] + public void Including_the_derived_property_excludes_the_hidden_property() + { + // Arrange + var subject = new SubclassAHidingProperty + { + Property = "DerivedValue" + }; + + ((BaseWithProperty)subject).Property = "ActualBaseValue"; + + var expectation = new SubclassBHidingProperty + { + Property = "DerivedValue" + }; + + ((AnotherBaseWithProperty)expectation).Property = "ExpectedBaseValue"; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, opt => opt + .Including(o => o.Property)); + } + + [Fact] + public void Excluding_the_property_hiding_the_base_class_one_does_not_reveal_the_latter() + { + // Arrange + var subject = new SubclassAHidingProperty(); + + ((BaseWithProperty)subject).Property = "ActualBaseValue"; + + var expectation = new SubclassBHidingProperty(); + + ((AnotherBaseWithProperty)expectation).Property = "ExpectedBaseValue"; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, o => o + .Excluding(b => b.Property)); + + // Assert + act.Should().Throw().WithMessage("*No members were found *"); + } + + private class BaseWithProperty + { + [UsedImplicitly] + public object Property { get; set; } + } + + private class SubclassAHidingProperty : BaseWithProperty + { + [UsedImplicitly] + public new T Property { get; set; } + } + + private class BaseWithStringProperty + { + [UsedImplicitly] + public string Property { get; set; } + } + + private class SubclassHidingStringProperty : BaseWithStringProperty + { + [UsedImplicitly] + public new string Property { get; set; } + } + + private class AnotherBaseWithProperty + { + [UsedImplicitly] + public object Property { get; set; } + } + + private class SubclassBHidingProperty : AnotherBaseWithProperty + { + public new T Property + { + get; + set; + } + } + + [Fact] + public void Ignores_fields_hidden_by_the_derived_class() + { + // Arrange + var subject = new SubclassAHidingField + { + Field = "DerivedValue" + }; + + ((BaseWithField)subject).Field = "ActualBaseValue"; + + var expectation = new SubclassBHidingField + { + Field = "DerivedValue" + }; + + ((AnotherBaseWithField)expectation).Field = "ExpectedBaseValue"; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, options => options.IncludingFields()); + } + + [Fact] + public void Includes_hidden_field_of_the_base_when_using_a_reference_to_the_base() + { + // Arrange + BaseWithField subject = new SubclassAHidingField + { + Field = "BaseValueFromSubject" + }; + + // FA doesn't know the compile-time type of the subject, so even though we pass a reference to the base-class, + // at run-time, it'll start finding the field on the subject starting from the run-time type, and thus ignore the + // hidden base-class field + ((SubclassAHidingField)subject).Field = "BaseValueFromExpectation"; + + AnotherBaseWithField expectation = new SubclassBHidingField + { + Field = "ExpectedDerivedValue" + }; + + expectation.Field = "BaseValueFromExpectation"; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, options => options.IncludingFields()); + } + + [Fact] + public void Run_type_typing_ignores_hidden_fields_even_when_using_a_reference_to_the_base_class() + { + // Arrange + var subject = new SubclassAHidingField + { + Field = "DerivedValue" + }; + + ((BaseWithField)subject).Field = "ActualBaseValue"; + + AnotherBaseWithField expectation = new SubclassBHidingField + { + Field = "DerivedValue" + }; + + expectation.Field = "ExpectedBaseValue"; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, options => options.IncludingFields().PreferringRuntimeMemberTypes()); + } + + [Fact] + public void Including_the_derived_field_excludes_the_hidden_field() + { + // Arrange + var subject = new SubclassAHidingField + { + Field = "DerivedValue" + }; + + ((BaseWithField)subject).Field = "ActualBaseValue"; + + var expectation = new SubclassBHidingField + { + Field = "DerivedValue" + }; + + ((AnotherBaseWithField)expectation).Field = "ExpectedBaseValue"; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, options => options + .IncludingFields() + .Including(o => o.Field)); + } + + [Fact] + public void Excluding_the_field_hiding_the_base_class_one_does_not_reveal_the_latter() + { + // Arrange + var subject = new SubclassAHidingField(); + + ((BaseWithField)subject).Field = "ActualBaseValue"; + + var expectation = new SubclassBHidingField(); + + ((AnotherBaseWithField)expectation).Field = "ExpectedBaseValue"; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, options => options + .IncludingFields() + .Excluding(b => b.Field)); + + // Assert + act.Should().Throw().WithMessage("*No members were found *"); + } + + private class BaseWithField + { + [UsedImplicitly] + public string Field; + } + + private class SubclassAHidingField : BaseWithField + { + [UsedImplicitly] + public new string Field; + } + + private class AnotherBaseWithField + { + [UsedImplicitly] + public string Field; + } + + private class SubclassBHidingField : AnotherBaseWithField + { + [UsedImplicitly] + public new string Field; + } + } +} diff --git a/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.cs index 0ce864c318..0b332ebbc3 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.cs @@ -1,3056 +1,66 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using System.Linq; -using FluentAssertions.Common; -using JetBrains.Annotations; +using System; +using FluentAssertions.Equivalency.Matching; +using FluentAssertions.Equivalency.Ordering; +using FluentAssertions.Equivalency.Selection; using Xunit; -using Xunit.Sdk; namespace FluentAssertions.Equivalency.Specs; -public class SelectionRulesSpecs +public partial class SelectionRulesSpecs { - public class Basic + [Fact] + public void Public_methods_follow_fluent_syntax() { - [Fact] - public void Property_names_are_case_sensitive() - { - // Arrange - var subject = new - { - Name = "John" - }; - - var other = new - { - name = "John" - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(other); - - // Assert - act.Should().Throw().WithMessage( - "Expectation*subject.name**other*not have*"); - } - - [Fact] - public void Field_names_are_case_sensitive() - { - // Arrange - var subject = new ClassWithFieldInUpperCase - { - Name = "John" - }; - - var other = new ClassWithFieldInLowerCase - { - name = "John" - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(other); - - // Assert - act.Should().Throw().WithMessage( - "Expectation*subject.name**other*not have*"); - } - - private class ClassWithFieldInLowerCase - { - [UsedImplicitly] -#pragma warning disable SA1307 - public string name; -#pragma warning restore SA1307 - } - - private class ClassWithFieldInUpperCase - { - [UsedImplicitly] - public string Name; - } - - [Fact] - public void When_a_property_is_an_indexer_it_should_be_ignored() - { - // Arrange - var expected = new ClassWithIndexer - { - Foo = "test" - }; - - var result = new ClassWithIndexer - { - Foo = "test" - }; - - // Act - Action act = () => result.Should().BeEquivalentTo(expected); - - // Assert - act.Should().NotThrow(); - } - - public class ClassWithIndexer - { - [UsedImplicitly] - public object Foo { get; set; } - - public string this[int n] => - n.ToString( - CultureInfo.InvariantCulture); - } - - [Fact] - public void When_the_expected_object_has_a_property_not_available_on_the_subject_it_should_throw() - { - // Arrange - var subject = new - { - }; - - var other = new - { - // ReSharper disable once StringLiteralTypo - City = "Rijswijk" - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(other); - - // Assert - act.Should().Throw().WithMessage( - "Expectation has property subject.City that the other object does not have*"); - } - - [Fact] - public void When_equally_named_properties_are_type_incompatible_it_should_throw() - { - // Arrange - var subject = new - { - Type = "A" - }; - - var other = new - { - Type = 36 - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(other); - - // Assert - act - .Should().Throw() - .WithMessage("Expected property subject.Type to be 36, but found*\"A\"*"); - } - - [Fact] - public void When_multiple_properties_mismatch_it_should_report_all_of_them() - { - // Arrange - var subject = new - { - Property1 = "A", - Property2 = "B", - SubType1 = new - { - SubProperty1 = "C", - SubProperty2 = "D" - } - }; - - var other = new - { - Property1 = "1", - Property2 = "2", - SubType1 = new - { - SubProperty1 = "3", - SubProperty2 = "D" - } - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(other); - - // Assert - act - .Should().Throw() - .WithMessage("*property subject.Property1*to be \"1\", but \"A\" differs near \"A\"*") - .WithMessage("*property subject.Property2*to be \"2\", but \"B\" differs near \"B\"*") - .WithMessage("*property subject.SubType1.SubProperty1*to be \"3\", but \"C\" differs near \"C\"*"); - } - - [Fact] - [SuppressMessage("ReSharper", "StringLiteralTypo")] - public void Including_all_declared_properties_excludes_all_fields() - { - // Arrange - var class1 = new ClassWithSomeFieldsAndProperties - { - Field1 = "Lorem", - Field2 = "ipsum", - Field3 = "dolor", - Property1 = "sit", - Property2 = "amet", - Property3 = "consectetur" - }; - - var class2 = new ClassWithSomeFieldsAndProperties - { - Property1 = "sit", - Property2 = "amet", - Property3 = "consectetur" - }; - - // Act - Action act = - () => class1.Should().BeEquivalentTo(class2, opts => opts.IncludingAllDeclaredProperties()); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - [SuppressMessage("ReSharper", "StringLiteralTypo")] - public void Including_all_runtime_properties_excludes_all_fields() - { - // Arrange - object class1 = new ClassWithSomeFieldsAndProperties - { - Field1 = "Lorem", - Field2 = "ipsum", - Field3 = "dolor", - Property1 = "sit", - Property2 = "amet", - Property3 = "consectetur" - }; - - object class2 = new ClassWithSomeFieldsAndProperties - { - Property1 = "sit", - Property2 = "amet", - Property3 = "consectetur" - }; - - // Act - Action act = - () => class1.Should().BeEquivalentTo(class2, opts => opts.IncludingAllRuntimeProperties()); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - [SuppressMessage("ReSharper", "StringLiteralTypo")] - public void Respecting_the_runtime_type_includes_both_properties_and_fields_included() - { - // Arrange - object class1 = new ClassWithSomeFieldsAndProperties - { - Field1 = "Lorem", - Property1 = "sit" - }; - - object class2 = new ClassWithSomeFieldsAndProperties(); - - // Act - Action act = - () => class1.Should().BeEquivalentTo(class2, opts => opts.RespectingRuntimeTypes()); - - // Assert - act.Should().Throw().Which.Message.Should().Contain("Field1").And.Contain("Property1"); - } - - [Fact] - public void A_nested_class_without_properties_inside_a_collection_is_fine() - { - // Arrange - var sut = new List - { - new() - { - Name = "theName" - } - }; - - // Act / Assert - sut.Should().BeEquivalentTo(new[] - { - new BaseClassPointingToClassWithoutProperties - { - Name = "theName" - } - }); - } - - internal class BaseClassPointingToClassWithoutProperties - { - [UsedImplicitly] - public string Name { get; set; } - - [UsedImplicitly] - public ClassWithoutProperty ClassWithoutProperty { get; } = new(); - } - - internal class ClassWithoutProperty; - -#if NET6_0_OR_GREATER - [Fact] - public void Will_include_default_interface_properties_in_the_comparison() - { - var lista = new List - { - new Test { Name = "Test" } - }; - - List listb = new() - { - new Test { Name = "Test" } - }; - - lista.Should().BeEquivalentTo(listb); - } - - private class Test : ITest - { - public string Name { get; set; } - } - - private interface ITest - { - public string Name { get; } - - public int NameLength => Name.Length; - } -#endif - } - - public class Including - { - [Fact] - public void When_specific_properties_have_been_specified_it_should_ignore_the_other_properties() - { - // Arrange - var subject = new - { - Age = 36, - Birthdate = new DateTime(1973, 9, 20), - Name = "John" - }; - - var customer = new - { - Age = 36, - Birthdate = new DateTime(1973, 9, 20), - Name = "Dennis" - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(customer, options => options - .Including(d => d.Age) - .Including(d => d.Birthdate)); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void A_member_included_by_path_is_described_in_the_failure_message() - { - // Arrange - var subject = new - { - Name = "John" - }; - - var customer = new - { - Name = "Jack" - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(customer, options => options - .Including(d => d.Name)); - - // Assert - act.Should().Throw() - .WithMessage("*Include*Name*"); - } - - [Fact] - public void A_member_included_by_predicate_is_described_in_the_failure_message() - { - // Arrange - var subject = new - { - Name = "John" - }; - - var customer = new - { - Name = "Jack" - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(customer, options => options - .Including(ctx => ctx.Path == "Name")); - - // Assert - act.Should().Throw() - .WithMessage("*Include member when*Name*"); - } - - [Fact] - public void When_a_predicate_for_properties_to_include_has_been_specified_it_should_ignore_the_other_properties() - { - // Arrange - var subject = new - { - Age = 36, - Birthdate = new DateTime(1973, 9, 20), - Name = "John" - }; - - var customer = new - { - Age = 36, - Birthdate = new DateTime(1973, 9, 20), - Name = "Dennis" - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(customer, options => options - .Including(info => info.Path.EndsWith("Age", StringComparison.Ordinal)) - .Including(info => info.Path.EndsWith("Birthdate", StringComparison.Ordinal))); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_a_non_property_expression_is_provided_it_should_throw() - { - // Arrange - var dto = new CustomerDto(); - - // Act - Action act = () => dto.Should().BeEquivalentTo(dto, options => options.Including(d => d.GetType())); - - // Assert - act.Should().Throw() - .WithMessage("Expression cannot be used to select a member.*") - .WithParameterName("expression"); - } - - [Fact] - public void When_including_a_property_it_should_exactly_match_the_property() - { - // Arrange - var actual = new - { - DeclaredType = LocalOtherType.NonDefault, - Type = LocalType.NonDefault - }; - - var expectation = new - { - DeclaredType = LocalOtherType.NonDefault - }; - - // Act - Action act = () => actual.Should().BeEquivalentTo(expectation, - config => config.Including(o => o.DeclaredType)); - - // Assert - act.Should().NotThrow(); - } - - private enum LocalOtherType : byte - { - Default, - NonDefault - } - - private enum LocalType : byte - { - Default, - NonDefault - } - - public class CustomType - { - [UsedImplicitly] - public string Name { get; set; } - } - - [Fact] - public void When_including_a_property_using_an_expression_it_should_evaluate_it_from_the_root() - { - // Arrange - var list1 = new List - { - new() - { - Name = "A" - }, - new() - { - Name = "B" - } - }; - - var list2 = new List - { - new() - { - Name = "C" - }, - new() - { - Name = "D" - } - }; - - var objectA = new ClassA - { - ListOfCustomTypes = list1 - }; - - var objectB = new ClassA - { - ListOfCustomTypes = list2 - }; - - // Act - Action act = () => objectA.Should().BeEquivalentTo(objectB, options => options.Including(x => x.ListOfCustomTypes)); - - // Assert - act.Should().Throw().WithMessage("*C*but*A*D*but*B*"); - } - - private class ClassA - { - public List ListOfCustomTypes { get; set; } - } - - [Fact] - public void When_null_is_provided_as_property_expression_it_should_throw() - { - // Arrange - var dto = new CustomerDto(); - - // Act - Action act = - () => dto.Should().BeEquivalentTo(dto, options => options.Including(null)); - - // Assert - act.Should().Throw().WithMessage( - "Expected an expression, but found .*"); - } - - [Fact] - [SuppressMessage("ReSharper", "StringLiteralTypo")] - public void When_including_fields_it_should_succeed_if_just_the_included_field_match() - { - // Arrange - var class1 = new ClassWithSomeFieldsAndProperties - { - Field1 = "Lorem", - Field2 = "ipsum", - Field3 = "dolor", - Property1 = "sit", - Property2 = "amet", - Property3 = "consectetur" - }; - - var class2 = new ClassWithSomeFieldsAndProperties - { - Field1 = "Lorem", - Field2 = "ipsum" - }; - - // Act - Action act = - () => - class1.Should().BeEquivalentTo(class2, opts => opts.Including(o => o.Field1).Including(o => o.Field2)); - - // Assert - act.Should().NotThrow("the only selected fields have the same value"); - } - - [Fact] - [SuppressMessage("ReSharper", "StringLiteralTypo")] - public void When_including_fields_it_should_fail_if_any_included_field_do_not_match() - { - // Arrange - var class1 = new ClassWithSomeFieldsAndProperties - { - Field1 = "Lorem", - Field2 = "ipsum", - Field3 = "dolor", - Property1 = "sit", - Property2 = "amet", - Property3 = "consectetur" - }; - - var class2 = new ClassWithSomeFieldsAndProperties - { - Field1 = "Lorem", - Field2 = "ipsum" - }; - - // Act - Action act = - () => - class1.Should().BeEquivalentTo(class2, - opts => opts.Including(o => o.Field1).Including(o => o.Field2).Including(o => o.Field3)); - - // Assert - act.Should().Throw().WithMessage("Expected field class1.Field3*"); - } - - [Fact] - [SuppressMessage("ReSharper", "StringLiteralTypo")] - public void When_both_field_and_properties_are_configured_for_inclusion_both_should_be_included() - { - // Arrange - var class1 = new ClassWithSomeFieldsAndProperties - { - Field1 = "Lorem", - Property1 = "sit" - }; - - var class2 = new ClassWithSomeFieldsAndProperties(); - - // Act - Action act = - () => class1.Should().BeEquivalentTo(class2, opts => opts.IncludingFields().IncludingProperties()); - - // Assert - act.Should().Throw().Which.Message.Should().Contain("Field1").And.Contain("Property1"); - } - -#if NET6_0_OR_GREATER - [Fact] - public void Can_include_a_default_interface_property_using_an_expression() - { - // Arrange - IHaveDefaultProperty subject = new ClassReceivedDefaultInterfaceProperty - { - NormalProperty = "Value" - }; - - IHaveDefaultProperty expectation = new ClassReceivedDefaultInterfaceProperty - { - NormalProperty = "Another Value" - }; - - // Act - var act = () => subject.Should().BeEquivalentTo(expectation, x => x.Including(p => p.DefaultProperty)); - - // Assert - act.Should().Throw().WithMessage("Expected property subject.DefaultProperty to be 13, but found 5.*"); - } - - [Fact] - public void Can_include_a_default_interface_property_using_a_name() - { - // Arrange - IHaveDefaultProperty subject = new ClassReceivedDefaultInterfaceProperty - { - NormalProperty = "Value" - }; - - IHaveDefaultProperty expectation = new ClassReceivedDefaultInterfaceProperty - { - NormalProperty = "Another Value" - }; - - // Act - var act = () => subject.Should().BeEquivalentTo(expectation, - x => x.Including(p => p.Name.Contains("DefaultProperty"))); - - // Assert - act.Should().Throw().WithMessage("Expected property subject.DefaultProperty to be 13, but found 5.*"); - } - - private class ClassReceivedDefaultInterfaceProperty : IHaveDefaultProperty - { - public string NormalProperty { get; set; } - } - - private interface IHaveDefaultProperty - { - string NormalProperty { get; set; } - - int DefaultProperty => NormalProperty.Length; - } -#endif - } - - public class Excluding - { - [Fact] - public void A_member_excluded_by_path_is_described_in_the_failure_message() - { - // Arrange - var subject = new - { - Name = "John", - Age = 13 - }; - - var customer = new - { - Name = "Jack", - Age = 37 - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(customer, options => options - .Excluding(d => d.Age)); - - // Assert - act.Should().Throw() - .WithMessage("*Exclude*Age*"); - } - - [Fact] - public void A_member_excluded_by_predicate_is_described_in_the_failure_message() - { - // Arrange - var subject = new - { - Name = "John", - Age = 13 - }; - - var customer = new - { - Name = "Jack", - Age = 37 - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(customer, options => options - .Excluding(ctx => ctx.Path == "Age")); - - // Assert - act.Should().Throw() - .WithMessage("*Exclude member when*Age*"); - } - - [Fact] - public void When_only_the_excluded_property_doesnt_match_it_should_not_throw() - { - // Arrange - var dto = new CustomerDto - { - Age = 36, - Birthdate = new DateTime(1973, 9, 20), - Name = "John" - }; - - var customer = new Customer - { - Age = 36, - Birthdate = new DateTime(1973, 9, 20), - Name = "Dennis" - }; - - // Act / Assert - dto.Should().BeEquivalentTo(customer, options => options - .Excluding(d => d.Name) - .Excluding(d => d.Id)); - } - - [Fact] - public void When_only_the_excluded_property_doesnt_match_it_should_not_throw_if_root_is_a_collection() - { - // Arrange - var dto = new Customer - { - Age = 36, - Birthdate = new DateTime(1973, 9, 20), - Name = "John" - }; - - var customer = new Customer - { - Age = 36, - Birthdate = new DateTime(1973, 9, 20), - Name = "Dennis" - }; - - // Act / Assert - new[] { dto }.Should().BeEquivalentTo(new[] { customer }, options => options - .Excluding(d => d.Name) - .Excluding(d => d.Id)); - } - - [Fact] - [SuppressMessage("ReSharper", "StringLiteralTypo")] - public void When_excluding_members_it_should_pass_if_only_the_excluded_members_are_different() - { - // Arrange - var class1 = new ClassWithSomeFieldsAndProperties - { - Field1 = "Lorem", - Field2 = "ipsum", - Field3 = "dolor", - Property1 = "sit" - }; - - var class2 = new ClassWithSomeFieldsAndProperties - { - Field1 = "Lorem", - Field2 = "ipsum" - }; - - // Act - Action act = - () => - class1.Should().BeEquivalentTo(class2, - opts => opts.Excluding(o => o.Field3).Excluding(o => o.Property1)); - - // Assert - act.Should().NotThrow("the non-excluded fields have the same value"); - } - - [Fact] - [SuppressMessage("ReSharper", "StringLiteralTypo")] - public void When_excluding_members_it_should_fail_if_any_non_excluded_members_are_different() - { - // Arrange - var class1 = new ClassWithSomeFieldsAndProperties - { - Field1 = "Lorem", - Field2 = "ipsum", - Field3 = "dolor", - Property1 = "sit" - }; - - var class2 = new ClassWithSomeFieldsAndProperties - { - Field1 = "Lorem", - Field2 = "ipsum" - }; - - // Act - Action act = - () => class1.Should().BeEquivalentTo(class2, opts => opts.Excluding(o => o.Property1)); - - // Assert - act.Should().Throw().WithMessage("Expected*Field3*"); - } - - [Fact] - public void When_all_shared_properties_match_it_should_not_throw() - { - // Arrange - var dto = new CustomerDto - { - Age = 36, - Birthdate = new DateTime(1973, 9, 20), - Name = "John" - }; - - var customer = new Customer - { - Id = 1, - Age = 36, - Birthdate = new DateTime(1973, 9, 20), - Name = "John" - }; - - // Act - Action act = () => dto.Should().BeEquivalentTo(customer, options => options.ExcludingMissingMembers()); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_a_deeply_nested_property_with_a_value_mismatch_is_excluded_it_should_not_throw() - { - // Arrange - var subject = new Root - { - Text = "Root", - Level = new Level1 - { - Text = "Level1", - Level = new Level2 - { - Text = "Mismatch" - } - } - }; - - var expected = new RootDto - { - Text = "Root", - Level = new Level1Dto - { - Text = "Level1", - Level = new Level2Dto - { - Text = "Level2" - } - } - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expected, - options => options.Excluding(r => r.Level.Level.Text)); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_a_deeply_nested_property_with_a_value_mismatch_is_excluded_it_should_not_throw_if_root_is_a_collection() - { - // Arrange - var subject = new Root - { - Text = "Root", - Level = new Level1 - { - Text = "Level1", - Level = new Level2 - { - Text = "Mismatch" - } - } - }; - - var expected = new RootDto - { - Text = "Root", - Level = new Level1Dto - { - Text = "Level1", - Level = new Level2Dto - { - Text = "Level2" - } - } - }; - - // Act / Assert - new[] { subject }.Should().BeEquivalentTo(new[] { expected }, - options => options.Excluding(r => r.Level.Level.Text)); - } - - [Fact] - public void When_a_property_with_a_value_mismatch_is_excluded_using_a_predicate_it_should_not_throw() - { - // Arrange - var subject = new Root - { - Text = "Root", - Level = new Level1 - { - Text = "Level1", - Level = new Level2 - { - Text = "Mismatch" - } - } - }; - - var expected = new RootDto - { - Text = "Root", - Level = new Level1Dto - { - Text = "Level1", - Level = new Level2Dto - { - Text = "Level2" - } - } - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expected, config => - config.Excluding(ctx => ctx.Path == "Level.Level.Text")); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_members_are_excluded_by_the_access_modifier_of_the_getter_using_a_predicate_they_should_be_ignored() - { - // Arrange - var subject = new ClassWithAllAccessModifiersForMembers("public", "protected", - "internal", "protected-internal", "private", "private-protected"); - - var expected = new ClassWithAllAccessModifiersForMembers("public", "protected", - "ignored-internal", "ignored-protected-internal", "private", "private-protected"); - - // Act - Action act = () => subject.Should().BeEquivalentTo(expected, config => config - .IncludingInternalFields() - .Excluding(ctx => - ctx.WhichGetterHas(CSharpAccessModifier.Internal) || - ctx.WhichGetterHas(CSharpAccessModifier.ProtectedInternal))); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_members_are_excluded_by_the_access_modifier_of_the_setter_using_a_predicate_they_should_be_ignored() - { - // Arrange - var subject = new ClassWithAllAccessModifiersForMembers("public", "protected", - "internal", "protected-internal", "private", "private-protected"); - - var expected = new ClassWithAllAccessModifiersForMembers("public", "protected", - "ignored-internal", "ignored-protected-internal", "ignored-private", "private-protected"); - - // Act - Action act = () => subject.Should().BeEquivalentTo(expected, config => config - .IncludingInternalFields() - .Excluding(ctx => - ctx.WhichSetterHas(CSharpAccessModifier.Internal) || - ctx.WhichSetterHas(CSharpAccessModifier.ProtectedInternal) || - ctx.WhichSetterHas(CSharpAccessModifier.Private))); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - [SuppressMessage("ReSharper", "StringLiteralTypo")] - public void When_excluding_properties_it_should_still_compare_fields() - { - // Arrange - var class1 = new ClassWithSomeFieldsAndProperties - { - Field1 = "Lorem", - Field2 = "ipsum", - Field3 = "dolor", - Property1 = "sit", - Property2 = "amet", - Property3 = "consectetur" - }; - - var class2 = new ClassWithSomeFieldsAndProperties - { - Field1 = "Lorem", - Field2 = "ipsum", - Field3 = "color" - }; - - // Act - Action act = - () => class1.Should().BeEquivalentTo(class2, opts => opts.ExcludingProperties()); - - // Assert - act.Should().Throw().WithMessage("*color*dolor*"); - } - - [Fact] - public void When_excluding_properties_via_non_array_indexers_it_should_exclude_the_specified_paths() - { - // Arrange - var subject = new - { - List = new[] - { - new - { - Foo = 1, - Bar = 2 - }, - new - { - Foo = 3, - Bar = 4 - } - }.ToList(), - Dictionary = new Dictionary - { - ["Foo"] = new() - { - Value = 1 - }, - ["Bar"] = new() - { - Value = 2 - } - } - }; - - var expected = new - { - List = new[] - { - new - { - Foo = 1, - Bar = 2 - }, - new - { - Foo = 2, - Bar = 4 - } - }.ToList(), - Dictionary = new Dictionary - { - ["Foo"] = new() - { - Value = 1 - }, - ["Bar"] = new() - { - Value = 3 - } - } - }; - - // Act - Action act = () => - subject.Should().BeEquivalentTo(expected, - options => options - .Excluding(x => x.List[1].Foo) - .Excluding(x => x.Dictionary["Bar"].Value)); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void - When_excluding_properties_via_non_array_indexers_it_should_exclude_the_specified_paths_if_root_is_a_collection() - { - // Arrange - var subject = new - { - List = new[] - { - new - { - Foo = 1, - Bar = 2 - }, - new - { - Foo = 3, - Bar = 4 - } - }.ToList(), - Dictionary = new Dictionary - { - ["Foo"] = new() - { - Value = 1 - }, - ["Bar"] = new() - { - Value = 2 - } - } - }; - - var expected = new - { - List = new[] - { - new - { - Foo = 1, - Bar = 2 - }, - new - { - Foo = 2, - Bar = 4 - } - }.ToList(), - Dictionary = new Dictionary - { - ["Foo"] = new() - { - Value = 1 - }, - ["Bar"] = new() - { - Value = 3 - } - } - }; - - // Act / Assert - new[] { subject }.Should().BeEquivalentTo(new[] { expected }, - options => options - .Excluding(x => x.List[1].Foo) - .Excluding(x => x.Dictionary["Bar"].Value)); - } - - [Fact] - public void When_excluding_properties_via_non_array_indexers_it_should_not_exclude_paths_with_different_indexes() - { - // Arrange - var subject = new - { - List = new[] - { - new - { - Foo = 1, - Bar = 2 - }, - new - { - Foo = 3, - Bar = 4 - } - }.ToList(), - Dictionary = new Dictionary - { - ["Foo"] = new() - { - Value = 1 - }, - ["Bar"] = new() - { - Value = 2 - } - } - }; - - var expected = new - { - List = new[] - { - new - { - Foo = 5, - Bar = 2 - }, - new - { - Foo = 2, - Bar = 4 - } - }.ToList(), - Dictionary = new Dictionary - { - ["Foo"] = new() - { - Value = 6 - }, - ["Bar"] = new() - { - Value = 3 - } - } - }; - - // Act - Action act = () => - subject.Should().BeEquivalentTo(expected, - options => options - .Excluding(x => x.List[1].Foo) - .Excluding(x => x.Dictionary["Bar"].Value)); - - // Assert - act.Should().Throw(); - } - - [Fact] - [SuppressMessage("ReSharper", "StringLiteralTypo")] - public void - When_configured_for_runtime_typing_and_properties_are_excluded_the_runtime_type_should_be_used_and_properties_should_be_ignored() - { - // Arrange - object class1 = new ClassWithSomeFieldsAndProperties - { - Field1 = "Lorem", - Field2 = "ipsum", - Field3 = "dolor", - Property1 = "sit", - Property2 = "amet", - Property3 = "consectetur" - }; - - object class2 = new ClassWithSomeFieldsAndProperties - { - Field1 = "Lorem", - Field2 = "ipsum", - Field3 = "dolor" - }; - - // Act - Action act = - () => class1.Should().BeEquivalentTo(class2, opts => opts.ExcludingProperties().RespectingRuntimeTypes()); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_excluding_virtual_or_abstract_property_exclusion_works_properly() - { - var obj1 = new Derived - { - DerivedProperty1 = "Something", - DerivedProperty2 = "A" - }; - - var obj2 = new Derived - { - DerivedProperty1 = "Something", - DerivedProperty2 = "B" - }; - - obj1.Should().BeEquivalentTo(obj2, opt => opt - .Excluding(o => o.AbstractProperty) - .Excluding(o => o.VirtualProperty) - .Excluding(o => o.DerivedProperty2)); - } - -#if NET6_0_OR_GREATER - [Fact] - public void Can_exclude_a_default_interface_property_using_an_expression() - { - // Arrange - IHaveDefaultProperty subject = new ClassReceivedDefaultInterfaceProperty - { - NormalProperty = "Value" - }; - - IHaveDefaultProperty expectation = new ClassReceivedDefaultInterfaceProperty - { - NormalProperty = "Another Value" - }; - - // Act - var act = () => subject.Should().BeEquivalentTo(expectation, - x => x.Excluding(p => p.DefaultProperty)); - - // Assert - act.Should().Throw().Which.Message.Should().NotContain("subject.DefaultProperty"); - } - - [Fact] - public void Can_exclude_a_default_interface_property_using_a_name() - { - // Arrange - IHaveDefaultProperty subject = new ClassReceivedDefaultInterfaceProperty - { - NormalProperty = "Value" - }; - - IHaveDefaultProperty expectation = new ClassReceivedDefaultInterfaceProperty - { - NormalProperty = "Another Value" - }; - - // Act - var act = () => subject.Should().BeEquivalentTo(expectation, - x => x.Excluding(info => info.Name.Contains("DefaultProperty"))); - - // Assert - act.Should().Throw().Which.Message.Should().NotContain("subject.DefaultProperty"); - } - - private class ClassReceivedDefaultInterfaceProperty : IHaveDefaultProperty - { - public string NormalProperty { get; set; } - } - - private interface IHaveDefaultProperty - { - string NormalProperty { get; set; } - - int DefaultProperty => NormalProperty.Length; - } -#endif - - } - - public class Accessibility - { - [Fact] - public void When_a_property_is_write_only_it_should_be_ignored() - { - // Arrange - var expected = new ClassWithWriteOnlyProperty - { - WriteOnlyProperty = 123, - SomeOtherProperty = "whatever" - }; - - var subject = new - { - SomeOtherProperty = "whatever" - }; - - // Act - Action action = () => subject.Should().BeEquivalentTo(expected); - - // Assert - action.Should().NotThrow(); - } - - [Fact] - public void When_a_property_is_private_it_should_be_ignored() - { - // Arrange - var subject = new Customer("MyPassword") - { - Age = 36, - Birthdate = new DateTime(1973, 9, 20), - Name = "John" - }; - - var other = new Customer("SomeOtherPassword") - { - Age = 36, - Birthdate = new DateTime(1973, 9, 20), - Name = "John" - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(other); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_a_field_is_private_it_should_be_ignored() - { - // Arrange - var subject = new ClassWithAPrivateField(1234) - { - Value = 1 - }; - - var other = new ClassWithAPrivateField(54321) - { - Value = 1 - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(other); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_a_property_is_protected_it_should_be_ignored() - { - // Arrange - var subject = new Customer - { - Age = 36, - Birthdate = new DateTime(1973, 9, 20), - Name = "John" - }; - - subject.SetProtected("ActualValue"); - - var expected = new Customer - { - Age = 36, - Birthdate = new DateTime(1973, 9, 20), - Name = "John" - }; - - expected.SetProtected("ExpectedValue"); - - // Act - Action act = () => subject.Should().BeEquivalentTo(expected); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_a_property_is_internal_and_it_should_be_included_it_should_fail_the_assertion() - { - // Arrange - var actual = new ClassWithInternalProperty - { - PublicProperty = "public", - InternalProperty = "internal", - ProtectedInternalProperty = "internal" - }; - - var expected = new ClassWithInternalProperty - { - PublicProperty = "public", - InternalProperty = "also internal", - ProtectedInternalProperty = "also internal" - }; - - // Act - Action act = () => actual.Should().BeEquivalentTo(expected, options => options.IncludingInternalProperties()); - - // Assert - act.Should().Throw() - .WithMessage("*InternalProperty*also internal*internal*ProtectedInternalProperty*"); - } - - private class ClassWithInternalProperty - { - [UsedImplicitly] - public string PublicProperty { get; set; } - - [UsedImplicitly] - internal string InternalProperty { get; set; } - - [UsedImplicitly] - protected internal string ProtectedInternalProperty { get; set; } - } - - [Fact] - public void When_a_field_is_internal_it_should_be_excluded_from_the_comparison() - { - // Arrange - var actual = new ClassWithInternalField - { - PublicField = "public", - InternalField = "internal", - ProtectedInternalField = "internal" - }; - - var expected = new ClassWithInternalField - { - PublicField = "public", - InternalField = "also internal", - ProtectedInternalField = "also internal" - }; - - // Act / Assert - actual.Should().BeEquivalentTo(expected); - } - - [Fact] - public void When_a_field_is_internal_and_it_should_be_included_it_should_fail_the_assertion() - { - // Arrange - var actual = new ClassWithInternalField - { - PublicField = "public", - InternalField = "internal", - ProtectedInternalField = "internal" - }; - - var expected = new ClassWithInternalField - { - PublicField = "public", - InternalField = "also internal", - ProtectedInternalField = "also internal" - }; - - // Act - Action act = () => actual.Should().BeEquivalentTo(expected, options => options.IncludingInternalFields()); - - // Assert - act.Should().Throw().WithMessage("*InternalField*also internal*internal*ProtectedInternalField*"); - } - - private class ClassWithInternalField - { - [UsedImplicitly] - public string PublicField; - - [UsedImplicitly] - internal string InternalField; - - [UsedImplicitly] - protected internal string ProtectedInternalField; - } - - [Fact] - public void When_a_property_is_internal_it_should_be_excluded_from_the_comparison() - { - // Arrange - var actual = new ClassWithInternalProperty - { - PublicProperty = "public", - InternalProperty = "internal", - ProtectedInternalProperty = "internal" - }; - - var expected = new ClassWithInternalProperty - { - PublicProperty = "public", - InternalProperty = "also internal", - ProtectedInternalProperty = "also internal" - }; - - // Act / Assert - actual.Should().BeEquivalentTo(expected); - } - - [Fact] - public void Private_protected_properties_are_ignored() - { - // Arrange - var subject = new ClassWithPrivateProtectedProperty("Name", 13); - var other = new ClassWithPrivateProtectedProperty("Name", 37); - - // Act/Assert - subject.Should().BeEquivalentTo(other); - } - - private class ClassWithPrivateProtectedProperty - { - public ClassWithPrivateProtectedProperty(string name, int value) - { - Name = name; - Value = value; - } - - [UsedImplicitly] - public string Name { get; } - - [UsedImplicitly] - private protected int Value { get; } - } - - [Fact] - public void Private_protected_fields_are_ignored() - { - // Arrange - var subject = new ClassWithPrivateProtectedField("Name", 13); - var other = new ClassWithPrivateProtectedField("Name", 37); - - // Act/Assert - subject.Should().BeEquivalentTo(other); - } - - private class ClassWithPrivateProtectedField - { - public ClassWithPrivateProtectedField(string name, int value) - { - Name = name; - this.value = value; - } - - [UsedImplicitly] - public string Name; - - [UsedImplicitly] - private protected int value; - } - } - - public class MemberHiding - { - [Fact] - public void Ignores_properties_hidden_by_the_derived_class() - { - // Arrange - var subject = new SubclassAHidingProperty - { - Property = "DerivedValue" - }; - - ((BaseWithProperty)subject).Property = "ActualBaseValue"; - - var expectation = new SubclassBHidingProperty - { - Property = "DerivedValue" - }; - - ((AnotherBaseWithProperty)expectation).Property = "ExpectedBaseValue"; - - // Act / Assert - subject.Should().BeEquivalentTo(expectation); - } - - [Fact] - public void Ignores_properties_of_the_same_runtime_types_hidden_by_the_derived_class() - { - // Arrange - var subject = new SubclassHidingStringProperty - { - Property = "DerivedValue" - }; - - ((BaseWithStringProperty)subject).Property = "ActualBaseValue"; - - var expectation = new SubclassHidingStringProperty - { - Property = "DerivedValue" - }; - - ((BaseWithStringProperty)expectation).Property = "ExpectedBaseValue"; - - // Act / Assert - subject.Should().BeEquivalentTo(expectation); - } - - [Fact] - public void Includes_hidden_property_of_the_base_when_using_a_reference_to_the_base() - { - // Arrange - BaseWithProperty subject = new SubclassAHidingProperty - { - Property = "ActualDerivedValue" - }; - - // FA doesn't know the compile-time type of the subject, so even though we pass a reference to the base-class, - // at run-time, it'll start finding the property on the subject starting from the run-time type, and thus ignore the - // hidden base-class field - ((SubclassAHidingProperty)subject).Property = "BaseValue"; - - AnotherBaseWithProperty expectation = new SubclassBHidingProperty - { - Property = "ExpectedDerivedValue" - }; - - expectation.Property = "BaseValue"; - - // Act / Assert - subject.Should().BeEquivalentTo(expectation); - } - - [Fact] - public void Run_type_typing_ignores_hidden_properties_even_when_using_a_reference_to_the_base_class() - { - // Arrange - var subject = new SubclassAHidingProperty - { - Property = "DerivedValue" - }; - - ((BaseWithProperty)subject).Property = "ActualBaseValue"; - - AnotherBaseWithProperty expectation = new SubclassBHidingProperty - { - Property = "DerivedValue" - }; - - expectation.Property = "ExpectedBaseValue"; - - // Act / Assert - subject.Should().BeEquivalentTo(expectation, o => o.RespectingRuntimeTypes()); - } - - [Fact] - public void Including_the_derived_property_excludes_the_hidden_property() - { - // Arrange - var subject = new SubclassAHidingProperty - { - Property = "DerivedValue" - }; - - ((BaseWithProperty)subject).Property = "ActualBaseValue"; - - var expectation = new SubclassBHidingProperty - { - Property = "DerivedValue" - }; - - ((AnotherBaseWithProperty)expectation).Property = "ExpectedBaseValue"; - - // Act / Assert - subject.Should().BeEquivalentTo(expectation, opt => opt - .Including(o => o.Property)); - } - - [Fact] - public void Excluding_the_property_hiding_the_base_class_one_does_not_reveal_the_latter() - { - // Arrange - var subject = new SubclassAHidingProperty(); - - ((BaseWithProperty)subject).Property = "ActualBaseValue"; - - var expectation = new SubclassBHidingProperty(); - - ((AnotherBaseWithProperty)expectation).Property = "ExpectedBaseValue"; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expectation, o => o - .Excluding(b => b.Property)); - - // Assert - act.Should().Throw().WithMessage("*No members were found *"); - } - - private class BaseWithProperty - { - [UsedImplicitly] - public object Property { get; set; } - } - - private class SubclassAHidingProperty : BaseWithProperty - { - [UsedImplicitly] - public new T Property { get; set; } - } - - private class BaseWithStringProperty - { - [UsedImplicitly] - public string Property { get; set; } - } - - private class SubclassHidingStringProperty : BaseWithStringProperty - { - [UsedImplicitly] - public new string Property { get; set; } - } - - private class AnotherBaseWithProperty - { - [UsedImplicitly] - public object Property { get; set; } - } - - private class SubclassBHidingProperty : AnotherBaseWithProperty - { - public new T Property - { - get; - set; - } - } - - [Fact] - public void Ignores_fields_hidden_by_the_derived_class() - { - // Arrange - var subject = new SubclassAHidingField - { - Field = "DerivedValue" - }; - - ((BaseWithField)subject).Field = "ActualBaseValue"; - - var expectation = new SubclassBHidingField - { - Field = "DerivedValue" - }; - - ((AnotherBaseWithField)expectation).Field = "ExpectedBaseValue"; - - // Act / Assert - subject.Should().BeEquivalentTo(expectation, options => options.IncludingFields()); - } - - [Fact] - public void Includes_hidden_field_of_the_base_when_using_a_reference_to_the_base() - { - // Arrange - BaseWithField subject = new SubclassAHidingField - { - Field = "BaseValueFromSubject" - }; - - // FA doesn't know the compile-time type of the subject, so even though we pass a reference to the base-class, - // at run-time, it'll start finding the field on the subject starting from the run-time type, and thus ignore the - // hidden base-class field - ((SubclassAHidingField)subject).Field = "BaseValueFromExpectation"; - - AnotherBaseWithField expectation = new SubclassBHidingField - { - Field = "ExpectedDerivedValue" - }; - - expectation.Field = "BaseValueFromExpectation"; - - // Act / Assert - subject.Should().BeEquivalentTo(expectation, options => options.IncludingFields()); - } - - [Fact] - public void Run_type_typing_ignores_hidden_fields_even_when_using_a_reference_to_the_base_class() - { - // Arrange - var subject = new SubclassAHidingField - { - Field = "DerivedValue" - }; - - ((BaseWithField)subject).Field = "ActualBaseValue"; - - AnotherBaseWithField expectation = new SubclassBHidingField - { - Field = "DerivedValue" - }; - - expectation.Field = "ExpectedBaseValue"; - - // Act / Assert - subject.Should().BeEquivalentTo(expectation, options => options.IncludingFields().RespectingRuntimeTypes()); - } - - [Fact] - public void Including_the_derived_field_excludes_the_hidden_field() - { - // Arrange - var subject = new SubclassAHidingField - { - Field = "DerivedValue" - }; - - ((BaseWithField)subject).Field = "ActualBaseValue"; - - var expectation = new SubclassBHidingField - { - Field = "DerivedValue" - }; - - ((AnotherBaseWithField)expectation).Field = "ExpectedBaseValue"; - - // Act / Assert - subject.Should().BeEquivalentTo(expectation, options => options - .IncludingFields() - .Including(o => o.Field)); - } - - [Fact] - public void Excluding_the_field_hiding_the_base_class_one_does_not_reveal_the_latter() - { - // Arrange - var subject = new SubclassAHidingField(); - - ((BaseWithField)subject).Field = "ActualBaseValue"; - - var expectation = new SubclassBHidingField(); - - ((AnotherBaseWithField)expectation).Field = "ExpectedBaseValue"; - - // Act - Action act = () => subject.Should().BeEquivalentTo(expectation, options => options - .IncludingFields() - .Excluding(b => b.Field)); - - // Assert - act.Should().Throw().WithMessage("*No members were found *"); - } - - private class BaseWithField - { - [UsedImplicitly] - public string Field; - } - - private class SubclassAHidingField : BaseWithField - { - [UsedImplicitly] - public new string Field; - } - - private class AnotherBaseWithField - { - [UsedImplicitly] - public string Field; - } - - private class SubclassBHidingField : AnotherBaseWithField - { - [UsedImplicitly] - public new string Field; - } - } - - public class Interfaces - { - [Fact] - public void When_an_interface_hierarchy_is_used_it_should_include_all_inherited_properties() - { - // Arrange - ICar subject = new Car - { - VehicleId = 1, - Wheels = 4 - }; - - ICar expected = new Car - { - VehicleId = 99999, - Wheels = 4 - }; - - // Act - Action action = () => subject.Should().BeEquivalentTo(expected); - - // Assert - action - .Should().Throw() - .WithMessage("Expected*VehicleId*99999*but*1*"); - } - - [Fact] - public void When_a_reference_to_an_interface_is_provided_it_should_only_include_those_properties() - { - // Arrange - IVehicle expected = new Car - { - VehicleId = 1, - Wheels = 4 - }; - - IVehicle subject = new Car - { - VehicleId = 1, - Wheels = 99999 - }; - - // Act - Action action = () => subject.Should().BeEquivalentTo(expected); - - // Assert - action.Should().NotThrow(); - } - - [Fact] - public void When_a_reference_to_an_explicit_interface_impl_is_provided_it_should_only_include_those_properties() - { - // Arrange - IVehicle expected = new ExplicitCar - { - Wheels = 4 - }; - - IVehicle subject = new ExplicitCar - { - Wheels = 99999 - }; - - // Act - Action action = () => subject.Should().BeEquivalentTo(expected); - - // Assert - action.Should().NotThrow(); - } - - [Fact] - public void Explicitly_implemented_subject_properties_are_ignored_if_a_normal_property_exists_with_the_same_name() - { - // Arrange - IVehicle expected = new Vehicle - { - VehicleId = 1 - }; - - IVehicle subject = new ExplicitVehicle - { - VehicleId = 2 // normal property - }; - - subject.VehicleId = 1; // explicitly implemented property - - // Act - Action action = () => subject.Should().BeEquivalentTo(expected); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void - Explicitly_implemented_read_only_subject_properties_are_ignored_if_a_normal_property_exists_with_the_same_name() - { - // Arrange - IReadOnlyVehicle subject = new ExplicitReadOnlyVehicle(explicitValue: 1) - { - VehicleId = 2 // normal property - }; - - var expected = new Vehicle - { - VehicleId = 1 - }; - - // Act - Action action = () => subject.Should().BeEquivalentTo(expected); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void Explicitly_implemented_subject_properties_are_ignored_if_only_fields_are_included() - { - // Arrange - var expected = new VehicleWithField - { - VehicleId = 1 // A field named like a property - }; - - var subject = new ExplicitVehicle - { - VehicleId = 2 // A real property - }; - - // Act - Action action = () => subject.Should().BeEquivalentTo(expected, opt => opt - .IncludingFields() - .ExcludingProperties()); - - // Assert - action.Should().Throw().WithMessage("*field*VehicleId*other*"); - } - - [Fact] - public void Explicitly_implemented_subject_properties_are_ignored_if_only_fields_are_included_and_they_may_be_missing() - { - // Arrange - var expected = new VehicleWithField - { - VehicleId = 1 // A field named like a property - }; - - var subject = new ExplicitVehicle - { - VehicleId = 2 // A real property - }; - - // Act / Assert - subject.Should().BeEquivalentTo(expected, opt => opt - .IncludingFields() + // Arrange + var subject = new Root(); + var expected = new RootDto(); + + // Act / Assert + subject.Should().BeEquivalentTo(expected, + options => options + .AllowingInfiniteRecursion() + .ComparingByMembers(typeof(Root)) + .ComparingByMembers() + .ComparingByValue(typeof(Customer)) + .ComparingByValue() + .ComparingEnumsByName() + .ComparingEnumsByValue() + .ComparingRecordsByMembers() + .ComparingRecordsByValue() + .Excluding(r => r.Level) + .ExcludingFields() + .ExcludingMissingMembers() + .WithoutRecursing() + .ExcludingNonBrowsableMembers() .ExcludingProperties() - .ExcludingMissingMembers()); - } - - [Fact] - public void Normal_properties_have_priority_over_explicitly_implemented_properties() - { - var instance = new MyClass - { - MyError = 42, - }; - - var other = new MyClass - { - MyError = 42, - }; - - instance.Should().BeEquivalentTo(other); - } - - private class MyClass : Exception, IMyInterface - { - public int MyError { get; set; } - - int IMyInterface.Message => MyError; - } - - private interface IMyInterface - { - int Message { get; } - } - - [Fact] - public void Excluding_missing_members_does_not_affect_how_explicitly_implemented_subject_properties_are_dealt_with() - { - // Arrange - IVehicle expected = new Vehicle - { - VehicleId = 1 - }; - - IVehicle subject = new ExplicitVehicle - { - VehicleId = 2 // instance member - }; - - subject.VehicleId = 1; // interface member - - // Act - Action action = () => subject.Should().BeEquivalentTo(expected, opt => opt.ExcludingMissingMembers()); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_respecting_declared_types_explicit_interface_member_on_interfaced_expectation_should_be_used() - { - // Arrange - IVehicle expected = new ExplicitVehicle - { - VehicleId = 2 // instance member - }; - - expected.VehicleId = 1; // interface member - - IVehicle subject = new Vehicle - { - VehicleId = 1 - }; - - // Act - Action action = () => subject.Should().BeEquivalentTo(expected, opt => opt.RespectingDeclaredTypes()); - - // Assert - action.Should().NotThrow(); - } - - [Fact] - public void When_respecting_runtime_types_explicit_interface_member_on_interfaced_subject_should_not_be_used() - { - // Arrange - IVehicle expected = new Vehicle - { - VehicleId = 1 - }; - - IVehicle subject = new ExplicitVehicle - { - VehicleId = 2 // instance member - }; - - subject.VehicleId = 1; // interface member - - // Act - Action action = () => subject.Should().BeEquivalentTo(expected, opt => opt.RespectingRuntimeTypes()); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_respecting_runtime_types_explicit_interface_member_on_interfaced_expectation_should_not_be_used() - { - // Arrange - IVehicle expected = new ExplicitVehicle - { - VehicleId = 2 // instance member - }; - - expected.VehicleId = 1; // interface member - - IVehicle subject = new Vehicle - { - VehicleId = 1 - }; - - // Act - Action action = () => subject.Should().BeEquivalentTo(expected, opt => opt.RespectingRuntimeTypes()); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_respecting_declared_types_explicit_interface_member_on_expectation_should_not_be_used() - { - // Arrange - var expected = new ExplicitVehicle - { - VehicleId = 2 - }; - - ((IVehicle)expected).VehicleId = 1; - - var subject = new Vehicle - { - VehicleId = 1 - }; - - // Act - Action action = () => subject.Should().BeEquivalentTo(expected); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void Can_find_explicitly_implemented_property_on_the_subject() - { - // Arrange - IPerson person = new Person(); - person.Name = "Bob"; - - // Act / Assert - person.Should().BeEquivalentTo(new { Name = "Bob" }); - } - - private interface IPerson - { - string Name { get; set; } - } - - private class Person : IPerson - { - string IPerson.Name { get; set; } - } - - [Fact] - public void Excluding_an_interface_property_through_inheritance_should_work() - { - // Arrange - IInterfaceWithTwoProperties[] actual = - { - new DerivedClassImplementingInterface - { - Value1 = 1, - Value2 = 2 - } - }; - - IInterfaceWithTwoProperties[] expected = - { - new DerivedClassImplementingInterface - { - Value1 = 999, - Value2 = 2 - } - }; - - // Act / Assert - actual.Should().BeEquivalentTo(expected, options => options - .Excluding(a => a.Value1) - .RespectingRuntimeTypes()); - } - - [Fact] - public void Including_an_interface_property_through_inheritance_should_work() - { - // Arrange - IInterfaceWithTwoProperties[] actual = - { - new DerivedClassImplementingInterface - { - Value1 = 1, - Value2 = 2 - } - }; - - IInterfaceWithTwoProperties[] expected = - { - new DerivedClassImplementingInterface - { - Value1 = 999, - Value2 = 2 - } - }; - - // Act / Assert - actual.Should().BeEquivalentTo(expected, options => options - .Including(a => a.Value2) - .RespectingRuntimeTypes()); - } - - public interface IInterfaceWithTwoProperties - { - int Value1 { get; set; } - - int Value2 { get; set; } - } - - public class BaseProvidingSamePropertiesAsInterface - { - public int Value1 { get; set; } - - public int Value2 { get; set; } - } - - public class DerivedClassImplementingInterface : BaseProvidingSamePropertiesAsInterface, IInterfaceWithTwoProperties; - } - -#if NET5_0_OR_GREATER - public class Covariance - { - [Fact] - public void Excluding_a_covariant_property_should_work() - { - // Arrange - var actual = new DerivedWithCovariantOverride(new DerivedWithProperty - { - DerivedProperty = "a", - BaseProperty = "a_base" - }) - { - OtherProp = "other" - }; - - var expectation = new DerivedWithCovariantOverride(new DerivedWithProperty - { - DerivedProperty = "b", - BaseProperty = - "b_base" - }) - { - OtherProp = "other" - }; - - // Act / Assert - actual.Should().BeEquivalentTo(expectation, opts => opts - .Excluding(d => d.Property)); - } - - [Fact] - public void Excluding_a_covariant_property_through_the_base_class_excludes_the_base_class_property() - { - // Arrange - var actual = new DerivedWithCovariantOverride(new DerivedWithProperty - { - DerivedProperty = "a", - BaseProperty = "a_base" - }) - { - OtherProp = "other" - }; - - BaseWithAbstractProperty expectation = new DerivedWithCovariantOverride(new DerivedWithProperty - { - DerivedProperty = - "b", - BaseProperty = "b_base" - }) - { - OtherProp = "other" - }; - - // Act - Action act = () => actual.Should().BeEquivalentTo(expectation, opts => opts - .Excluding(d => d.Property)); - - // Assert - act.Should().Throw().WithMessage("No members*"); - } - - private class BaseWithProperty - { - [UsedImplicitly] - public string BaseProperty { get; set; } - } - - private class DerivedWithProperty : BaseWithProperty - { - [UsedImplicitly] - public string DerivedProperty { get; set; } - } - - private abstract class BaseWithAbstractProperty - { - public abstract BaseWithProperty Property { get; } - } - - private sealed class DerivedWithCovariantOverride : BaseWithAbstractProperty - { - public override DerivedWithProperty Property { get; } - - [UsedImplicitly] - public string OtherProp { get; set; } - - public DerivedWithCovariantOverride(DerivedWithProperty prop) - { - Property = prop; - } - } - } - -#endif - - public class Browsability - { - [Fact] - public void When_browsable_field_differs_excluding_non_browsable_members_should_not_affect_result() - { - // Arrange - var subject = new ClassWithNonBrowsableMembers - { - BrowsableField = 0 - }; - - var expectation = new ClassWithNonBrowsableMembers - { - BrowsableField = 1 - }; - - // Act - Action action = - () => subject.Should().BeEquivalentTo(expectation, config => config.ExcludingNonBrowsableMembers()); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_browsable_property_differs_excluding_non_browsable_members_should_not_affect_result() - { - // Arrange - var subject = new ClassWithNonBrowsableMembers - { - BrowsableProperty = 0 - }; - - var expectation = new ClassWithNonBrowsableMembers - { - BrowsableProperty = 1 - }; - - // Act - Action action = - () => subject.Should().BeEquivalentTo(expectation, config => config.ExcludingNonBrowsableMembers()); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_advanced_browsable_field_differs_excluding_non_browsable_members_should_not_affect_result() - { - // Arrange - var subject = new ClassWithNonBrowsableMembers - { - AdvancedBrowsableField = 0 - }; - - var expectation = new ClassWithNonBrowsableMembers - { - AdvancedBrowsableField = 1 - }; - - // Act - Action action = - () => subject.Should().BeEquivalentTo(expectation, config => config.ExcludingNonBrowsableMembers()); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_advanced_browsable_property_differs_excluding_non_browsable_members_should_not_affect_result() - { - // Arrange - var subject = new ClassWithNonBrowsableMembers - { - AdvancedBrowsableProperty = 0 - }; - - var expectation = new ClassWithNonBrowsableMembers - { - AdvancedBrowsableProperty = 1 - }; - - // Act - Action action = - () => subject.Should().BeEquivalentTo(expectation, config => config.ExcludingNonBrowsableMembers()); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_explicitly_browsable_field_differs_excluding_non_browsable_members_should_not_affect_result() - { - // Arrange - var subject = new ClassWithNonBrowsableMembers - { - ExplicitlyBrowsableField = 0 - }; - - var expectation = new ClassWithNonBrowsableMembers - { - ExplicitlyBrowsableField = 1 - }; - - // Act - Action action = - () => subject.Should().BeEquivalentTo(expectation, config => config.ExcludingNonBrowsableMembers()); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_explicitly_browsable_property_differs_excluding_non_browsable_members_should_not_affect_result() - { - // Arrange - var subject = new ClassWithNonBrowsableMembers - { - ExplicitlyBrowsableProperty = 0 - }; - - var expectation = new ClassWithNonBrowsableMembers - { - ExplicitlyBrowsableProperty = 1 - }; - - // Act - Action action = - () => subject.Should().BeEquivalentTo(expectation, config => config.ExcludingNonBrowsableMembers()); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_non_browsable_field_differs_excluding_non_browsable_members_should_make_it_succeed() - { - // Arrange - var subject = new ClassWithNonBrowsableMembers - { - NonBrowsableField = 0 - }; - - var expectation = new ClassWithNonBrowsableMembers - { - NonBrowsableField = 1 - }; - - // Act & Assert - subject.Should().BeEquivalentTo(expectation, config => config.ExcludingNonBrowsableMembers()); - } - - [Fact] - public void When_non_browsable_property_differs_excluding_non_browsable_members_should_make_it_succeed() - { - // Arrange - var subject = new ClassWithNonBrowsableMembers - { - NonBrowsableProperty = 0 - }; - - var expectation = new ClassWithNonBrowsableMembers - { - NonBrowsableProperty = 1 - }; - - // Act & Assert - subject.Should().BeEquivalentTo(expectation, config => config.ExcludingNonBrowsableMembers()); - } - - [Fact] - public void When_property_is_non_browsable_only_in_subject_excluding_non_browsable_members_should_not_make_it_succeed() - { - // Arrange - var subject = - new ClassWhereMemberThatCouldBeNonBrowsableIsNonBrowsable - { - PropertyThatMightBeNonBrowsable = 0 - }; - - var expectation = - new ClassWhereMemberThatCouldBeNonBrowsableIsBrowsable - { - PropertyThatMightBeNonBrowsable = 1 - }; - - // Act - Action action = - () => subject.Should().BeEquivalentTo(expectation, config => config.ExcludingNonBrowsableMembers()); - - // Assert - action.Should().Throw() - .WithMessage("Expected property subject.PropertyThatMightBeNonBrowsable to be 1, but found 0.*"); - } - - [Fact] - public void - When_property_is_non_browsable_only_in_subject_ignoring_non_browsable_members_on_subject_should_make_it_succeed() - { - // Arrange - var subject = - new ClassWhereMemberThatCouldBeNonBrowsableIsNonBrowsable - { - PropertyThatMightBeNonBrowsable = 0 - }; - - var expectation = - new ClassWhereMemberThatCouldBeNonBrowsableIsBrowsable - { - PropertyThatMightBeNonBrowsable = 1 - }; - - // Act & Assert - subject.Should().BeEquivalentTo( - expectation, - config => config.IgnoringNonBrowsableMembersOnSubject().ExcludingMissingMembers()); - } - - [Fact] - public void When_non_browsable_property_on_subject_is_ignored_but_is_present_on_expectation_it_should_fail() - { - // Arrange - var subject = - new ClassWhereMemberThatCouldBeNonBrowsableIsNonBrowsable - { - PropertyThatMightBeNonBrowsable = 0 - }; - - var expectation = - new ClassWhereMemberThatCouldBeNonBrowsableIsBrowsable - { - PropertyThatMightBeNonBrowsable = 1 - }; - - // Act - Action action = - () => subject.Should().BeEquivalentTo(expectation, config => config.IgnoringNonBrowsableMembersOnSubject()); - - // Assert - action.Should().Throw().WithMessage( - "Expectation has * subject.*ThatMightBeNonBrowsable that is non-browsable in the other object, and non-browsable " + - "members on the subject are ignored with the current configuration*"); - } - - [Fact] - public void Only_ignore_non_browsable_matching_members() - { - // Arrange - var subject = new - { - NonExisting = 0 - }; - - var expectation = new - { - Existing = 1 - }; - - // Act - Action action = () => - subject.Should().BeEquivalentTo(expectation, config => config.IgnoringNonBrowsableMembersOnSubject()); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_property_is_non_browsable_only_in_expectation_excluding_non_browsable_members_should_make_it_succeed() - { - // Arrange - var subject = new ClassWhereMemberThatCouldBeNonBrowsableIsBrowsable - { - PropertyThatMightBeNonBrowsable = 0 - }; - - var expectation = - new ClassWhereMemberThatCouldBeNonBrowsableIsNonBrowsable - { - PropertyThatMightBeNonBrowsable = 1 - }; - - // Act & Assert - subject.Should().BeEquivalentTo(expectation, config => config.ExcludingNonBrowsableMembers()); - } - - [Fact] - public void When_field_is_non_browsable_only_in_subject_excluding_non_browsable_members_should_not_make_it_succeed() - { - // Arrange - var subject = - new ClassWhereMemberThatCouldBeNonBrowsableIsNonBrowsable - { - FieldThatMightBeNonBrowsable = 0 - }; - - var expectation = - new ClassWhereMemberThatCouldBeNonBrowsableIsBrowsable - { - FieldThatMightBeNonBrowsable = 1 - }; - - // Act - Action action = - () => subject.Should().BeEquivalentTo(expectation, config => config.ExcludingNonBrowsableMembers()); - - // Assert - action.Should().Throw() - .WithMessage("Expected field subject.FieldThatMightBeNonBrowsable to be 1, but found 0.*"); - } - - [Fact] - public void When_field_is_non_browsable_only_in_subject_ignoring_non_browsable_members_on_subject_should_make_it_succeed() - { - // Arrange - var subject = - new ClassWhereMemberThatCouldBeNonBrowsableIsNonBrowsable - { - FieldThatMightBeNonBrowsable = 0 - }; - - var expectation = - new ClassWhereMemberThatCouldBeNonBrowsableIsBrowsable - { - FieldThatMightBeNonBrowsable = 1 - }; - - // Act & Assert - subject.Should().BeEquivalentTo( - expectation, - config => config.IgnoringNonBrowsableMembersOnSubject().ExcludingMissingMembers()); - } - - [Fact] - public void When_field_is_non_browsable_only_in_expectation_excluding_non_browsable_members_should_make_it_succeed() - { - // Arrange - var subject = new ClassWhereMemberThatCouldBeNonBrowsableIsBrowsable - { - FieldThatMightBeNonBrowsable = 0 - }; - - var expectation = - new ClassWhereMemberThatCouldBeNonBrowsableIsNonBrowsable - { - FieldThatMightBeNonBrowsable = 1 - }; - - // Act & Assert - subject.Should().BeEquivalentTo(expectation, config => config.ExcludingNonBrowsableMembers()); - } - - [Fact] - public void When_property_is_missing_from_subject_excluding_non_browsable_members_should_make_it_succeed() - { - // Arrange - var subject = - new - { - BrowsableField = 1, - BrowsableProperty = 1, - ExplicitlyBrowsableField = 1, - ExplicitlyBrowsableProperty = 1, - AdvancedBrowsableField = 1, - AdvancedBrowsableProperty = 1, - NonBrowsableField = 2 - /* NonBrowsableProperty missing */ - }; - - var expected = new ClassWithNonBrowsableMembers - { - BrowsableField = 1, - BrowsableProperty = 1, - ExplicitlyBrowsableField = 1, - ExplicitlyBrowsableProperty = 1, - AdvancedBrowsableField = 1, - AdvancedBrowsableProperty = 1, - NonBrowsableField = 2, - NonBrowsableProperty = 2 - }; - - // Act & Assert - subject.Should().BeEquivalentTo(expected, opt => opt.ExcludingNonBrowsableMembers()); - } - - [Fact] - public void When_field_is_missing_from_subject_excluding_non_browsable_members_should_make_it_succeed() - { - // Arrange - var subject = - new - { - BrowsableField = 1, - BrowsableProperty = 1, - ExplicitlyBrowsableField = 1, - ExplicitlyBrowsableProperty = 1, - AdvancedBrowsableField = 1, - AdvancedBrowsableProperty = 1, - /* NonBrowsableField missing */ - NonBrowsableProperty = 2 - }; - - var expected = new ClassWithNonBrowsableMembers - { - BrowsableField = 1, - BrowsableProperty = 1, - ExplicitlyBrowsableField = 1, - ExplicitlyBrowsableProperty = 1, - AdvancedBrowsableField = 1, - AdvancedBrowsableProperty = 1, - NonBrowsableField = 2, - NonBrowsableProperty = 2 - }; - - // Act & Assert - subject.Should().BeEquivalentTo(expected, opt => opt.ExcludingNonBrowsableMembers()); - } - - [Fact] - public void When_property_is_missing_from_expectation_excluding_non_browsable_members_should_make_it_succeed() - { - // Arrange - var subject = new ClassWithNonBrowsableMembers - { - BrowsableField = 1, - BrowsableProperty = 1, - ExplicitlyBrowsableField = 1, - ExplicitlyBrowsableProperty = 1, - AdvancedBrowsableField = 1, - AdvancedBrowsableProperty = 1, - NonBrowsableField = 2, - NonBrowsableProperty = 2 - }; - - var expected = - new - { - BrowsableField = 1, - BrowsableProperty = 1, - ExplicitlyBrowsableField = 1, - ExplicitlyBrowsableProperty = 1, - AdvancedBrowsableField = 1, - AdvancedBrowsableProperty = 1, - NonBrowsableField = 2 - /* NonBrowsableProperty missing */ - }; - - // Act & Assert - subject.Should().BeEquivalentTo(expected, opt => opt.ExcludingNonBrowsableMembers()); - } - - [Fact] - public void When_field_is_missing_from_expectation_excluding_non_browsable_members_should_make_it_succeed() - { - // Arrange - var subject = new ClassWithNonBrowsableMembers - { - BrowsableField = 1, - BrowsableProperty = 1, - ExplicitlyBrowsableField = 1, - ExplicitlyBrowsableProperty = 1, - AdvancedBrowsableField = 1, - AdvancedBrowsableProperty = 1, - NonBrowsableField = 2, - NonBrowsableProperty = 2 - }; - - var expected = - new - { - BrowsableField = 1, - BrowsableProperty = 1, - ExplicitlyBrowsableField = 1, - ExplicitlyBrowsableProperty = 1, - AdvancedBrowsableField = 1, - AdvancedBrowsableProperty = 1, - /* NonBrowsableField missing */ - NonBrowsableProperty = 2 - }; - - // Act & Assert - subject.Should().BeEquivalentTo(expected, opt => opt.ExcludingNonBrowsableMembers()); - } - - [Fact] - public void - When_non_browsable_members_are_excluded_it_should_still_be_possible_to_explicitly_include_non_browsable_field() - { - // Arrange - var subject = new ClassWithNonBrowsableMembers - { - NonBrowsableField = 1 - }; - - var expectation = new ClassWithNonBrowsableMembers - { - NonBrowsableField = 2 - }; - - // Act - Action action = - () => subject.Should().BeEquivalentTo( - expectation, - opt => opt.IncludingFields().ExcludingNonBrowsableMembers().Including(e => e.NonBrowsableField)); - - // Assert - action.Should().Throw() - .WithMessage("Expected field subject.NonBrowsableField to be 2, but found 1.*"); - } - - [Fact] - public void - When_non_browsable_members_are_excluded_it_should_still_be_possible_to_explicitly_include_non_browsable_property() - { - // Arrange - var subject = new ClassWithNonBrowsableMembers - { - NonBrowsableProperty = 1 - }; - - var expectation = new ClassWithNonBrowsableMembers - { - NonBrowsableProperty = 2 - }; - - // Act - Action action = - () => subject.Should().BeEquivalentTo( - expectation, - opt => opt.IncludingProperties().ExcludingNonBrowsableMembers().Including(e => e.NonBrowsableProperty)); - - // Assert - action.Should().Throw() - .WithMessage("Expected property subject.NonBrowsableProperty to be 2, but found 1.*"); - } - - private class ClassWithNonBrowsableMembers - { - [UsedImplicitly] - public int BrowsableField = -1; - - [UsedImplicitly] - public int BrowsableProperty { get; set; } - - [UsedImplicitly] - [EditorBrowsable(EditorBrowsableState.Always)] - public int ExplicitlyBrowsableField = -1; - - [UsedImplicitly] - [EditorBrowsable(EditorBrowsableState.Always)] - public int ExplicitlyBrowsableProperty { get; set; } - - [UsedImplicitly] - [EditorBrowsable(EditorBrowsableState.Advanced)] - public int AdvancedBrowsableField = -1; - - [UsedImplicitly] - [EditorBrowsable(EditorBrowsableState.Advanced)] - public int AdvancedBrowsableProperty { get; set; } - - [UsedImplicitly] - [EditorBrowsable(EditorBrowsableState.Never)] - public int NonBrowsableField = -1; - - [UsedImplicitly] - [EditorBrowsable(EditorBrowsableState.Never)] - public int NonBrowsableProperty { get; set; } - } - - private class ClassWhereMemberThatCouldBeNonBrowsableIsBrowsable - { - [UsedImplicitly] - public int BrowsableProperty { get; set; } - - [UsedImplicitly] - public int FieldThatMightBeNonBrowsable = -1; - - [UsedImplicitly] - public int PropertyThatMightBeNonBrowsable { get; set; } - } - - private class ClassWhereMemberThatCouldBeNonBrowsableIsNonBrowsable - { - [UsedImplicitly] - public int BrowsableProperty { get; set; } - - [UsedImplicitly] - [EditorBrowsable(EditorBrowsableState.Never)] - public int FieldThatMightBeNonBrowsable = -1; - - [UsedImplicitly] - [EditorBrowsable(EditorBrowsableState.Never)] - public int PropertyThatMightBeNonBrowsable { get; set; } - } + .IgnoringCyclicReferences() + .IgnoringNonBrowsableMembersOnSubject() + .Including(r => r.Level) + .IncludingAllDeclaredProperties() + .IncludingAllRuntimeProperties() + .IncludingFields() + .IncludingInternalFields() + .IncludingInternalProperties() + .IncludingNestedObjects() + .IncludingProperties() + .PreferringDeclaredMemberTypes() + .PreferringRuntimeMemberTypes() + .ThrowingOnMissingMembers() + .Using(new ExtensibilitySpecs.DoEquivalencyStep(() => { })) + .Using(new MustMatchMemberByNameRule()) + .Using(new AllFieldsSelectionRule()) + .Using(new ByteArrayOrderingRule()) + .Using(StringComparer.OrdinalIgnoreCase) + .WithAutoConversion() + .WithAutoConversionFor(_ => false) + .WithoutAutoConversionFor(_ => true) + .WithoutMatchingRules() + .WithoutSelectionRules() + .WithoutStrictOrdering() + .WithoutStrictOrderingFor(r => r.Level) + .WithStrictOrdering() + .WithStrictOrderingFor(r => r.Level) + .WithTracing() + ); } } diff --git a/Tests/FluentAssertions.Equivalency.Specs/TestTypes.cs b/Tests/FluentAssertions.Equivalency.Specs/TestTypes.cs index 06034c13f8..2b42827e78 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/TestTypes.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/TestTypes.cs @@ -171,6 +171,7 @@ internal class ClassWithCctorAndNonDefaultConstructor // ReSharper disable once EmptyConstructor static ClassWithCctorAndNonDefaultConstructor() { } + // ReSharper disable once UnusedParameter.Local public ClassWithCctorAndNonDefaultConstructor(int _) { } } diff --git a/Tests/FluentAssertions.Equivalency.Specs/TracingSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/TracingSpecs.cs new file mode 100644 index 0000000000..617f4fc276 --- /dev/null +++ b/Tests/FluentAssertions.Equivalency.Specs/TracingSpecs.cs @@ -0,0 +1,34 @@ +using System.Threading.Tasks; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Equivalency.Specs; + +[Collection("ConfigurationSpecs")] +public class TracingSpecs +{ + [Fact] + public void Tracing_must_be_safe_when_executed_concurrently() + { + try + { + // Arrange + AssertionConfiguration.Current.Equivalency.Modify(e => e.WithTracing()); + + Parallel.For(1, 10_000, (_, _) => + { + try + { + new { A = "a" }.Should().BeEquivalentTo(new { A = "b" }); + } + catch (XunitException) + { + } + }); + } + finally + { + AssertionEngine.ResetToDefaults(); + } + } +} diff --git a/Tests/FluentAssertions.Equivalency.Specs/TupleSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/TupleSpecs.cs index 229af8373e..945caef1ca 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/TupleSpecs.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/TupleSpecs.cs @@ -24,8 +24,8 @@ public void When_a_nested_member_is_a_tuple_it_should_compare_its_property_for_e public void When_a_tuple_is_compared_it_should_compare_its_components() { // Arrange - var actual = new Tuple("Hello", true, new[] { 3, 2, 1 }); - var expected = new Tuple("Hello", true, new[] { 1, 2, 3 }); + var actual = Tuple.Create("Hello", true, new[] { 3, 2, 1 }); + var expected = Tuple.Create("Hello", true, new[] { 1, 2, 3 }); // Act Action act = () => actual.Should().BeEquivalentTo(expected); diff --git a/Tests/FluentAssertions.Equivalency.Specs/TypeEqualitySpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/TypeEqualitySpecs.cs new file mode 100644 index 0000000000..b37648e1aa --- /dev/null +++ b/Tests/FluentAssertions.Equivalency.Specs/TypeEqualitySpecs.cs @@ -0,0 +1,243 @@ +using System; +using System.Collections.Generic; +using JetBrains.Annotations; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Equivalency.Specs; + +[Collection("ConfigurationSpecs")] +public class TypeEqualitySpecs +{ + [Fact] + public void Throws_when_top_level_types_are_expected_to_match() + { + // Arrange + var subject = new FooWithNestedClass(); + + var expectation = new BarWithNested(); + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, x => x.WithStrictTyping()); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject*type*TypeEqualitySpecs+BarWithNested*but*TypeEqualitySpecs+FooWithNestedClass*") + .WithMessage("*The types of the fields and properties must be the same*"); + } + + [Fact] + public void By_default_the_runtime_type_is_ignored() + { + // Arrange + Nested subject = new(); + + Nested expectation = new NestedSubtype(); + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, x => x.WithStrictTyping()); + } + + [Fact] + public void Can_check_the_runtime_type_if_that_was_requested() + { + // Arrange + Nested subject = new(); + + Nested expectation = new NestedSubtype(); + + // Act / Assert + Action act = () => subject.Should().BeEquivalentTo(expectation, x => x + .PreferringRuntimeMemberTypes() + .WithStrictTyping()); + + act.Should().Throw().WithMessage( + "Expected subject to be of type *TypeEqualitySpecs+NestedSubtype*, but found *TypeEqualitySpecs+Nested*"); + } + + [Fact] + public void Uses_the_declared_type_for_members_of_root_objects() + { + // Arrange + var subject = new FooWithNestedClass + { + Nested = new Nested() + }; + + var expectation = new BarWithNested + { + Nested = new NestedSubtype() + }; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, x => x + .WithStrictTypingFor(info => info.Path.EndsWith("Nested"))); + } + + [Fact] + public void The_collection_type_is_ignored() + { + // Arrange + var subject = new[] + { + new FooWithNestedClass + { + Nested = new Nested() + } + }; + + var expectation = new List + { + new() + { + Nested = new NestedSubtype() + } + }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, x => x + .WithStrictTyping()); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject[0]*BarWithNested*but*FooWithNestedClass*"); + } + + [Fact] + public void Can_use_the_runtime_type_for_members_of_root_objects() + { + // Arrange + var subject = new FooWithNestedClass + { + Nested = new Nested() + }; + + var expectation = new BarWithNested + { + Nested = new NestedSubtype() + }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, x => x + .PreferringRuntimeMemberTypes() + .WithStrictTypingFor(info => info.Path.EndsWith("Nested"))); + + // Assert + act.Should().Throw() + .WithMessage("Expected property subject.Nested*NestedSubtype*but*Nested*") + .WithMessage("*Use strict typing when info.Path.EndsWith(\"Nested\")*"); + } + + [Fact] + public void Requesting_strict_typing_for_nested_members_ignores_root_objects_of_mismatching_types() + { + // Arrange + var subject = new + { + Nested = new Nested(), + OtherProperty = "value" + }; + + var expectation = new + { + Nested = new Nested() + }; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, x => + x.WithStrictTypingFor(info => info.Path.EndsWith("Nested"))); + } + + [Fact] + public void Can_request_strict_typing_for_nested_members_for_mismatching_roots() + { + // Arrange + var subject = new + { + Nested = new Nested(), + OtherProperty = "value" + }; + + var expectation = new + { + Nested = new NestedSubtype() + }; + + // Act + Action act = () => subject.Should().BeEquivalentTo(expectation, x => + x.WithStrictTypingFor(info => info.Path.EndsWith("Nested"))); + + // Assert + act.Should().Throw() + .WithMessage("*subject.Nested*NestedSubtype*but*Nested*"); + } + + [Fact] + public void Can_request_strict_typing_for_the_root_only() + { + // Arrange + var subject = new + { + Nested = new Nested(), + OtherProperty = "value" + }; + + var expectation = new + { + Nested = new NestedSubtype() + }; + + // Act / Assert + Action act = () => subject.Should().BeEquivalentTo(expectation, x => + x.WithStrictTypingFor(info => info.Path.Length == 0)); + + act.Should().Throw() + .WithMessage("Expected*AnonymousType*NestedSubtype*but*AnonymousType*Nested*String*"); + } + + [Fact] + public void Can_override_globally_applied_strict_typing_for_individual_assertions() + { + try + { + // Arrange + AssertionConfiguration.Current.Equivalency.Modify(x => x.WithStrictTyping()); + + var subject = new FooWithNestedClass + { + Nested = new Nested() + }; + + var expectation = new BarWithNested + { + Nested = new Nested() + }; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, x => x.WithoutStrictTyping()); + } + finally + { + AssertionEngine.ResetToDefaults(); + } + } + + private class FooWithNestedClass + { + [UsedImplicitly] + public Nested Nested { get; set; } + } + + private class BarWithNested + { + [UsedImplicitly] + public Nested Nested { get; set; } + } + + private class Nested + { + public string Name { get; set; } + } + + private class NestedSubtype : Nested; +} diff --git a/Tests/FluentAssertions.Equivalency.Specs/TypedDataSetSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/TypedDataSetSpecs.cs deleted file mode 100644 index baf99e3dea..0000000000 --- a/Tests/FluentAssertions.Equivalency.Specs/TypedDataSetSpecs.cs +++ /dev/null @@ -1,580 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using Xunit; -using Xunit.Sdk; - -namespace FluentAssertions.Equivalency.Specs; - -public class TypedDataSetSpecs : DataSpecs -{ - [Fact] - public void When_DataSets_are_identical_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - // Act & Assert - dataSet1.Should().BeEquivalentTo(dataSet2); - } - - [Fact] - public void When_DataSets_are_both_null_it_should_succeed() - { - // Act & Assert - ((TypedDataSet)null).Should().BeEquivalentTo(null); - } - - [Fact] - public void When_DataSet_is_null_and_isnt_expected_to_be_it_should_fail() - { - // Arrange - var dataSet = CreateDummyDataSet(); - - // Act - Action action = () => ((TypedDataSet)null).Should().BeEquivalentTo(dataSet); - - // Assert - action.Should().Throw().WithMessage( - "Expected *to be non-null, but found null*"); - } - - [Fact] - public void When_DataSet_is_expected_to_be_null_and_isnt_it_should_fail() - { - // Arrange - var dataSet = CreateDummyDataSet(); - - // Act - Action action = () => dataSet.Should().BeEquivalentTo(null); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_DataSet_type_does_not_match_and_AllowMismatchedType_not_enabled_it_should_fail() - { - // Arrange - var dataSet = CreateDummyDataSet(); - - var dataSetOfMismatchedType = new TypedDataSetSubclass(dataSet); - - // Act - Action action = () => dataSet.Should().BeEquivalentTo(dataSetOfMismatchedType); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_DataSet_type_does_not_match_and_AllowMismatchedType_is_enabled_it_should_succeed() - { - // Arrange - var dataSet = CreateDummyDataSet(); - - var dataSetOfMismatchedType = new TypedDataSetSubclass(dataSet); - - // Act & Assert - dataSet.Should().BeEquivalentTo(dataSetOfMismatchedType, options => options.AllowingMismatchedTypes()); - } - - [Fact] - public void When_DataSetName_does_not_match_and_property_is_not_excluded_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - dataSet2.DataSetName += "different"; - - // Act - Action action = () => dataSet1.Should().BeEquivalentTo(dataSet2); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_DataSetName_does_not_match_and_property_is_excluded_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - dataSet2.DataSetName += "different"; - - // Act & Assert - dataSet1.Should().BeEquivalentTo(dataSet2, options => options - .Excluding(dataSet => dataSet.DataSetName) - .ExcludingRelated((DataRelation dataRelation) => dataRelation.DataSet)); - } - - [Fact] - public void When_CaseSensitive_does_not_match_and_property_is_not_excluded_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - dataSet2.CaseSensitive = !dataSet2.CaseSensitive; - - // Act - Action action = () => dataSet1.Should().BeEquivalentTo(dataSet2); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_CaseSensitive_does_not_match_and_property_is_excluded_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - dataSet2.CaseSensitive = !dataSet2.CaseSensitive; - - // Act & Assert - dataSet1.Should().BeEquivalentTo(dataSet2, options => options.Excluding(dataSet => dataSet.CaseSensitive)); - } - - [Fact] - public void When_EnforceConstraints_does_not_match_and_property_is_not_excluded_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - dataSet2.EnforceConstraints = !dataSet2.EnforceConstraints; - - // Act - Action action = () => dataSet1.Should().BeEquivalentTo(dataSet2); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_EnforceConstraints_does_not_match_and_property_is_excluded_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - dataSet2.EnforceConstraints = !dataSet2.EnforceConstraints; - - // Act & Assert - dataSet1.Should().BeEquivalentTo(dataSet2, options => options.Excluding(dataSet => dataSet.EnforceConstraints)); - } - - [Fact] - public void When_HasErrors_does_not_match_and_property_is_not_excluded_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - dataSet2.TypedDataTable1.Rows[0].RowError = "Manually added error"; - - // Act - Action action = () => dataSet1.Should().BeEquivalentTo(dataSet2, config => config.ExcludingTable("TypedDataTable1")); - - // Assert - action.Should().Throw().Which.Message.Should().Contain("HasErrors"); - } - - [Fact] - public void When_HasErrors_does_not_match_and_property_is_excluded_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - dataSet2.TypedDataTable1.Rows[0].RowError = "Manually added error"; - - // Act & Assert - dataSet1.Should().BeEquivalentTo(dataSet2, - config => config.Excluding(dataSet => dataSet.HasErrors).ExcludingTable("TypedDataTable1")); - } - - [Fact] - public void When_HasErrors_does_not_match_and_property_is_excluded_as_params_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - dataSet2.TypedDataTable1.Rows[0].RowError = "Manually added error"; - - // Act & Assert - dataSet1.Should().BeEquivalentTo(dataSet2, - config => config.Excluding(dataSet => dataSet.HasErrors).ExcludingTables("TypedDataTable1")); - } - - [Fact] - public void When_HasErrors_does_not_match_and_property_is_excluded_as_list_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - dataSet2.TypedDataTable1.Rows[0].RowError = "Manually added error"; - - // Act & Assert - IEnumerable excludedTables = new[] { "TypedDataTable1" }; - - dataSet1.Should().BeEquivalentTo(dataSet2, - config => config.Excluding(dataSet => dataSet.HasErrors).ExcludingTables(excludedTables)); - } - - [Fact] - public void When_Locale_does_not_match_and_property_is_not_excluded_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - dataSet2.Locale = new CultureInfo("fr-CA"); - - // Act - Action action = () => dataSet1.Should().BeEquivalentTo(dataSet2); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_Locale_does_not_match_and_property_is_excluded_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - dataSet2.Locale = new CultureInfo("fr-CA"); - - // Act & Assert - dataSet1.Should().BeEquivalentTo(dataSet2, options => options.Excluding(dataSet => dataSet.Locale)); - } - - [Fact] - public void When_Namespace_does_not_match_and_property_is_not_excluded_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - dataSet2.Namespace += "different"; - - // Act - Action action = () => dataSet1.Should().BeEquivalentTo(dataSet2); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_Namespace_does_not_match_and_property_is_excluded_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - dataSet2.Namespace += "different"; - - // Act & Assert - dataSet1.Should().BeEquivalentTo(dataSet2, options => options - .Excluding(dataSet => dataSet.Namespace) - .ExcludingRelated((DataTable dataTable) => dataTable.Namespace) - .ExcludingRelated((DataColumn dataColumn) => dataColumn.Namespace)); - } - - [Fact] - public void When_Prefix_does_not_match_and_property_is_not_excluded_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - dataSet2.Prefix += "different"; - - // Act - Action action = () => dataSet1.Should().BeEquivalentTo(dataSet2); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_Prefix_does_not_match_and_property_is_excluded_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - dataSet2.Prefix += "different"; - - // Act & Assert - dataSet1.Should().BeEquivalentTo(dataSet2, options => options.Excluding(dataSet => dataSet.Prefix)); - } - -#if !NET8_0_OR_GREATER - - [Fact] - public void When_RemotingFormat_does_not_match_and_property_is_not_excluded_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - dataSet2.RemotingFormat = - dataSet2.RemotingFormat == SerializationFormat.Binary - ? SerializationFormat.Xml - : SerializationFormat.Binary; - - // Act - Action action = () => dataSet1.Should().BeEquivalentTo(dataSet2); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_RemotingFormat_does_not_match_and_property_is_excluded_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - dataSet2.RemotingFormat = - dataSet2.RemotingFormat == SerializationFormat.Binary - ? SerializationFormat.Xml - : SerializationFormat.Binary; - - // Act & Assert - dataSet1.Should().BeEquivalentTo(dataSet2, options => options - .Excluding(dataSet => dataSet.RemotingFormat) - .ExcludingRelated(dataTable => dataTable.RemotingFormat)); - } - - [Fact] - public void When_SchemaSerializationMode_does_not_match_and_property_is_not_excluded_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - dataSet2.SchemaSerializationMode = - dataSet2.SchemaSerializationMode == SchemaSerializationMode.ExcludeSchema - ? SchemaSerializationMode.IncludeSchema - : SchemaSerializationMode.ExcludeSchema; - - // Act - Action action = () => dataSet1.Should().BeEquivalentTo(dataSet2); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_SchemaSerializationMode_does_not_match_and_property_is_excluded_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - dataSet2.SchemaSerializationMode = - dataSet2.SchemaSerializationMode == SchemaSerializationMode.ExcludeSchema - ? SchemaSerializationMode.IncludeSchema - : SchemaSerializationMode.ExcludeSchema; - - // Act & Assert - dataSet1.Should().BeEquivalentTo(dataSet2, options => options.Excluding(dataSet => dataSet.SchemaSerializationMode)); - } - -#endif - - [Theory] - [MemberData(nameof(AllChangeTypes))] - public void When_ExtendedProperties_do_not_match_and_property_is_not_excluded_it_should_fail(ChangeType changeType) - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - ApplyChange(dataSet2.ExtendedProperties, changeType); - - // Act - Action action = () => dataSet1.Should().BeEquivalentTo(dataSet2); - - // Assert - action.Should().Throw(); - } - - [Theory] - [MemberData(nameof(AllChangeTypes))] - public void When_ExtendedProperties_do_not_match_and_property_is_excluded_it_should_succeed(ChangeType changeType) - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - ApplyChange(dataSet2.ExtendedProperties, changeType); - - // Act & Assert - dataSet1.Should().BeEquivalentTo(dataSet2, options => options.Excluding(dataSet => dataSet.ExtendedProperties)); - } - - [Theory] - [MemberData(nameof(AllChangeTypes))] - [SuppressMessage("Style", "IDE0010:Add missing cases", Justification = "All enum values are accounted for.")] - public void When_Relations_does_not_match_and_property_is_not_excluded_it_should_fail(ChangeType changeType) - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - switch (changeType) - { - case ChangeType.Added: - dataSet1.Relations.RemoveAt(0); - break; - - case ChangeType.Changed: - dataSet2.Relations[0].RelationName += "different"; - break; - - case ChangeType.Removed: - dataSet2.Relations.RemoveAt(0); - break; - } - - // Act - Action action = () => dataSet1.Should().BeEquivalentTo(dataSet2); - - // Assert - action.Should().Throw(); - } - - [Theory] - [MemberData(nameof(AllChangeTypes))] - [SuppressMessage("Style", "IDE0010:Add missing cases", Justification = "All enum values are accounted for.")] - public void When_Relations_does_not_match_and_property_is_excluded_it_should_succeed(ChangeType changeType) - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - switch (changeType) - { - case ChangeType.Added: - dataSet1.Relations.RemoveAt(0); - break; - - case ChangeType.Changed: - dataSet2.Relations[0].RelationName += "different"; - break; - - case ChangeType.Removed: - dataSet2.Relations.RemoveAt(0); - break; - } - - // Act & Assert - dataSet1.Should().BeEquivalentTo(dataSet2, options => options - .Excluding(dataSet => dataSet.Relations) - .ExcludingRelated(dataTable => dataTable.ParentRelations) - .ExcludingRelated(dataTable => dataTable.ChildRelations)); - } - - [Fact] - public void When_Tables_are_the_same_but_in_a_different_order_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1, swapTableOrder: true); - - // Act & Assert - dataSet1.Should().BeEquivalentTo(dataSet2); - } - - [Fact] - public void When_Tables_count_does_not_match_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1, swapTableOrder: true); - - dataSet2.Tables.Add(new DataTable("ThirdWheel")); - - // Act - Action action = () => dataSet1.Should().BeEquivalentTo(dataSet2); - - // Assert - action.Should().Throw().Which.Message.Should().Contain("to contain " + dataSet2.Tables.Count); - } - - [Fact] - public void When_Tables_count_matches_but_tables_are_different_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1, swapTableOrder: true); - - dataSet2.TypedDataTable2.TableName = "DifferentTableName"; - - // Act - Action action = () => dataSet1.Should().BeEquivalentTo(dataSet2); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_Tables_contain_different_data_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1, swapTableOrder: true); - - dataSet2.TypedDataTable2[0].Guid = Guid.NewGuid(); - - // Act - Action action = () => dataSet1.Should().BeEquivalentTo(dataSet2); - - // Assert - action.Should().Throw(); - } -} diff --git a/Tests/FluentAssertions.Equivalency.Specs/TypedDataTableSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/TypedDataTableSpecs.cs deleted file mode 100644 index e30483b9fc..0000000000 --- a/Tests/FluentAssertions.Equivalency.Specs/TypedDataTableSpecs.cs +++ /dev/null @@ -1,670 +0,0 @@ -using System; -using System.Collections; -using System.Data; -using System.Globalization; -using System.Linq; -using FluentAssertions.Data; -using Xunit; -using Xunit.Sdk; - -namespace FluentAssertions.Equivalency.Specs; - -/// -/// DataTableEquivalency specs for typed data tables. -/// -public class TypedDataTableSpecs : DataSpecs -{ - [Fact] - public void When_DataTables_are_identical_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable1 = dataSet1.TypedDataTable1; - var dataTable2 = dataSet2.TypedDataTable1; - - // Act & Assert - dataTable1.Should().BeEquivalentTo(dataTable2); - } - - [Fact] - public void When_DataTables_are_both_null_it_should_succeed() - { - // Act & Assert - ((TypedDataTable1)null).Should().BeEquivalentTo(null); - } - - [Fact] - public void When_DataTable_is_null_and_isnt_expected_to_be_it_should_fail() - { - // Arrange - var dataSet = CreateDummyDataSet(); - - var dataTable = dataSet.TypedDataTable1; - - // Act - Action action = () => ((TypedDataTable1)null).Should().BeEquivalentTo(dataTable); - - // Assert - action.Should().Throw().WithMessage( - "Expected *to be non-null, but found null*"); - } - - [Fact] - public void When_DataTable_is_expected_to_be_null_and_isnt_it_should_fail() - { - // Arrange - var dataSet = CreateDummyDataSet(); - - var dataTable = dataSet.TypedDataTable1; - - // Act - Action action = () => dataTable.Should().BeEquivalentTo(null); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_DataTable_type_does_not_match_and_AllowMismatchedType_not_enabled_it_should_fail() - { - // Arrange - var dataSet = CreateDummyDataSet(identicalTables: true); - var dataSet2 = new TypedDataSetSubclass(dataSet); - - var dataTable = dataSet.TypedDataTable1; - var dataTableOfMismatchedType = dataSet2.TypedDataTable2; - - dataSet2.Tables.Remove(dataTable.TableName); - dataTableOfMismatchedType.TableName = dataTable.TableName; - - // Act - Action action = () => dataTable.Should().BeEquivalentTo(dataTableOfMismatchedType); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_DataTable_type_does_not_match_and_AllowMismatchedType_is_enabled_it_should_succeed() - { - // Arrange - var dataSet = CreateDummyDataSet(identicalTables: true); - var dataSet2 = new TypedDataSetSubclass(dataSet); - - var dataTable = dataSet.TypedDataTable1; - var dataTableOfMismatchedType = dataSet2.TypedDataTable2; - - dataSet2.Tables.Remove(dataTable.TableName); - dataTableOfMismatchedType.TableName = dataTable.TableName; - - dataTableOfMismatchedType.Rows.Clear(); - - foreach (var row in dataTable) - { - dataTableOfMismatchedType.ImportRow(row); - } - - foreach (var col in dataTableOfMismatchedType.Columns.Cast()) - { - col.ExtendedProperties.Clear(); - - foreach (var property in dataTable.Columns[col.ColumnName].ExtendedProperties.Cast()) - { - col.ExtendedProperties.Add(property.Key, property.Value); - } - } - - dataTableOfMismatchedType.AcceptChanges(); - - // Act & Assert - dataTable.Should().BeEquivalentTo(dataTableOfMismatchedType, options => options.AllowingMismatchedTypes()); - } - - [Fact] - public void When_TableName_does_not_match_and_property_is_not_excluded_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable1 = dataSet1.TypedDataTable1; - var dataTable2 = dataSet2.TypedDataTable1; - - dataTable2.TableName += "different"; - - // Act - Action action = () => dataTable1.Should().BeEquivalentTo(dataTable2); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_TableName_does_not_match_and_property_is_excluded_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(identicalTables: true); - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable1 = dataSet1.TypedDataTable1; - var dataTable2 = dataSet2.TypedDataTable1; - - dataTable2.TableName += "different"; - - // Act & Assert - dataTable1.Should().BeEquivalentTo( - dataTable2, - options => options - .Excluding(dataTable => dataTable.TableName) - .ExcludingRelated((DataColumn dataColumn) => dataColumn.Table) - .ExcludingRelated((Constraint constraint) => constraint.Table)); - } - - [Fact] - public void When_CaseSensitive_does_not_match_and_property_is_not_excluded_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - dataSet2.CaseSensitive = !dataSet2.CaseSensitive; - - var dataTable1 = dataSet1.TypedDataTable1; - var dataTable2 = dataSet2.TypedDataTable1; - - // Act - Action action = () => dataTable1.Should().BeEquivalentTo(dataTable2); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_CaseSensitive_does_not_match_and_property_is_excluded_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - dataSet2.CaseSensitive = !dataSet2.CaseSensitive; - - var dataTable1 = dataSet1.TypedDataTable1; - var dataTable2 = dataSet2.TypedDataTable1; - - // Act & Assert - dataTable1.Should().BeEquivalentTo(dataTable2, options => options.Excluding(dataTable => dataTable.CaseSensitive)); - } - - [Fact] - public void When_DisplayExpression_does_not_match_and_property_is_not_excluded_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable1 = dataSet1.TypedDataTable1; - var dataTable2 = dataSet2.TypedDataTable1; - - dataTable2.DisplayExpression = dataTable2.StringColumn.ColumnName; - - // Act - Action action = () => dataTable1.Should().BeEquivalentTo(dataTable2); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_DisplayExpression_does_not_match_and_property_is_excluded_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable1 = dataSet1.TypedDataTable1; - var dataTable2 = dataSet2.TypedDataTable1; - - dataTable2.DisplayExpression = dataTable2.StringColumn.ColumnName; - - // Act & Assert - dataTable1.Should().BeEquivalentTo(dataTable2, options => options.Excluding(dataTable => dataTable.DisplayExpression)); - } - - [Fact] - public void When_HasErrors_does_not_match_and_property_is_not_excluded_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable1 = dataSet1.TypedDataTable1; - var dataTable2 = dataSet2.TypedDataTable1; - - dataTable2.Rows[0].RowError = "Manually added error"; - - // Act - Action action = () => dataTable1.Should().BeEquivalentTo(dataTable2); - - // Assert - action.Should().Throw().Which.Message.Should().Contain("HasErrors"); - } - - [Fact] - public void When_HasErrors_does_not_match_and_property_is_excluded_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable1 = dataSet1.TypedDataTable1; - var dataTable2 = dataSet2.TypedDataTable1; - - dataTable2.Rows[0].RowError = "Manually added error"; - - // Act & Assert - dataTable1.Should().BeEquivalentTo(dataTable2, config => config - .Excluding(dataTable => dataTable.HasErrors) - .ExcludingRelated((DataRow dataRow) => dataRow.HasErrors)); - } - - [Fact] - public void When_Locale_does_not_match_and_property_is_not_excluded_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - dataSet2.Locale = new CultureInfo("fr-CA"); - - var dataTable1 = dataSet1.TypedDataTable1; - var dataTable2 = dataSet2.TypedDataTable1; - - // Act - Action action = () => dataTable1.Should().BeEquivalentTo(dataTable2); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_Locale_does_not_match_and_property_is_excluded_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - dataSet2.Locale = new CultureInfo("fr-CA"); - - var dataTable1 = dataSet1.TypedDataTable1; - var dataTable2 = dataSet2.TypedDataTable1; - - // Act & Assert - dataTable1.Should().BeEquivalentTo(dataTable2, options => options.Excluding(dataTable => dataTable.Locale)); - } - - [Fact] - public void When_Namespace_does_not_match_and_property_is_not_excluded_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - dataSet2.Namespace += "different"; - - var dataTable1 = dataSet1.TypedDataTable1; - var dataTable2 = dataSet2.TypedDataTable1; - - // Act - Action action = () => dataTable1.Should().BeEquivalentTo(dataTable2); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_Namespace_does_not_match_and_property_is_excluded_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - dataSet2.Namespace += "different"; - - var dataTable1 = dataSet1.TypedDataTable1; - var dataTable2 = dataSet2.TypedDataTable1; - - // Act & Assert - dataTable1.Should().BeEquivalentTo(dataTable2, options => options - .Excluding(dataTable => dataTable.Namespace) - .ExcludingRelated((DataColumn dataColumn) => dataColumn.Namespace)); - } - - [Fact] - public void When_Prefix_does_not_match_and_property_is_not_excluded_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable1 = dataSet1.TypedDataTable1; - var dataTable2 = dataSet2.TypedDataTable1; - - dataTable2.Prefix += "different"; - - // Act - Action action = () => dataTable1.Should().BeEquivalentTo(dataTable2); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_Prefix_does_not_match_and_property_is_excluded_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable1 = dataSet1.TypedDataTable1; - var dataTable2 = dataSet2.TypedDataTable1; - - dataTable2.Prefix += "different"; - - // Act & Assert - dataTable1.Should().BeEquivalentTo(dataTable2, options => options.Excluding(dataTable => dataTable.Prefix)); - } - -#if !NET8_0_OR_GREATER - - [Fact] - public void When_RemotingFormat_does_not_match_and_property_is_not_excluded_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - dataSet2.RemotingFormat = - dataSet2.RemotingFormat == SerializationFormat.Binary - ? SerializationFormat.Xml - : SerializationFormat.Binary; - - var dataTable1 = dataSet1.TypedDataTable1; - var dataTable2 = dataSet2.TypedDataTable1; - - // Act - Action action = () => dataTable1.Should().BeEquivalentTo(dataTable2); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_RemotingFormat_does_not_match_and_property_is_excluded_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - dataSet2.RemotingFormat = - dataSet2.RemotingFormat == SerializationFormat.Binary - ? SerializationFormat.Xml - : SerializationFormat.Binary; - - var dataTable1 = dataSet1.TypedDataTable1; - var dataTable2 = dataSet2.TypedDataTable1; - - // Act & Assert - dataTable1.Should().BeEquivalentTo(dataTable2, options => options.Excluding(dataTable => dataTable.RemotingFormat)); - } - -#endif - - [Theory] - [MemberData(nameof(AllChangeTypes))] - public void When_Columns_do_not_match_and_property_is_not_excluded_it_should_fail(ChangeType changeType) - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable1 = dataSet1.TypedDataTable1; - var dataTable2 = dataSet2.TypedDataTable1; - - ApplyChange(dataTable2.Columns, changeType); - - // Act - Action action = () => dataTable1.Should().BeEquivalentTo(dataTable2); - - // Assert - action.Should().Throw(); - } - - [Theory] - [MemberData(nameof(AllChangeTypes))] - public void When_Columns_do_not_match_and_Columns_and_Rows_are_excluded_it_should_succeed(ChangeType changeType) - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable1 = dataSet1.TypedDataTable1; - var dataTable2 = dataSet2.TypedDataTable1; - - ApplyChange(dataTable2.Columns, changeType); - - // Act & Assert - dataTable1.Should().BeEquivalentTo(dataTable2, options => options - .Excluding(dataTable => dataTable.Columns) - .Excluding(dataTable => dataTable.Rows)); - } - - [Theory] - [MemberData(nameof(AllChangeTypes))] - public void When_ExtendedProperties_do_not_match_and_property_is_not_excluded_it_should_fail(ChangeType changeType) - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable1 = dataSet1.TypedDataTable1; - var dataTable2 = dataSet2.TypedDataTable1; - - ApplyChange(dataTable2.ExtendedProperties, changeType); - - // Act - Action action = () => dataTable1.Should().BeEquivalentTo(dataTable2); - - // Assert - action.Should().Throw(); - } - - [Theory] - [MemberData(nameof(AllChangeTypes))] - public void When_ExtendedProperties_do_not_match_and_property_is_excluded_it_should_succeed(ChangeType changeType) - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable1 = dataSet1.TypedDataTable1; - var dataTable2 = dataSet2.TypedDataTable1; - - ApplyChange(dataTable2.ExtendedProperties, changeType); - - // Act & Assert - dataTable1.Should().BeEquivalentTo(dataTable2, options => options.Excluding(dataTable => dataTable.ExtendedProperties)); - } - - [Fact] - public void When_PrimaryKey_does_not_match_and_property_is_not_excluded_it_should_fail() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable1 = dataSet1.TypedDataTable2; - var dataTable2 = dataSet2.TypedDataTable2; - - dataTable2.PrimaryKey = dataTable2.Columns.Cast().Skip(2).ToArray(); - dataTable2.Columns[0].Unique = true; - - // Act - Action action = () => - dataTable1.Should().BeEquivalentTo(dataTable2, options => options - .Excluding(dataTable => dataTable.Constraints)); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void When_PrimaryKey_does_not_match_and_property_is_excluded_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - for (int i = 2; i < dataSet1.TypedDataTable2.Columns.Count; i++) - { - dataSet1.TypedDataTable2.Columns[i].AllowDBNull = false; - } - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable1 = dataSet1.TypedDataTable2; - var dataTable2 = dataSet2.TypedDataTable2; - - dataTable2.PrimaryKey = dataTable2.Columns.Cast().Skip(2).ToArray(); - dataTable2.Columns[0].Unique = true; - - // Act & Assert - dataTable1.Should().BeEquivalentTo(dataTable2, options => options - .Excluding(dataTable => dataTable.PrimaryKey) - .Excluding(dataTable => dataTable.Constraints)); - } - - [Theory] - [MemberData(nameof(AllChangeTypes))] - public void When_Constraints_do_not_match_and_property_is_not_excluded_it_should_fail(ChangeType changeType) - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable1 = dataSet1.TypedDataTable1; - var dataTable2 = dataSet2.TypedDataTable1; - - ApplyChange(dataTable2.Constraints, dataTable2.DecimalColumn, changeType); - - // Act - Action action = () => dataTable1.Should().BeEquivalentTo(dataTable2); - - // Assert - action.Should().Throw(); - } - - [Theory] - [MemberData(nameof(AllChangeTypes))] - public void When_Constraints_do_not_match_and_property_is_excluded_it_should_succeed(ChangeType changeType) - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable1 = dataSet1.TypedDataTable1; - var dataTable2 = dataSet2.TypedDataTable1; - - ApplyChange(dataTable2.Constraints, dataTable2.DecimalColumn, changeType); - - // Act & Assert - dataTable1.Should().BeEquivalentTo(dataTable2, options => options - .Excluding(dataTable => dataTable.Constraints) - .ExcludingRelated(dataColumn => dataColumn.Unique)); - } - - [Theory] - [MemberData(nameof(AllChangeTypesWithAcceptChangesValues))] - public void When_Rows_do_not_match_and_property_is_not_excluded_it_should_fail(ChangeType changeType, bool acceptChanges) - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable1 = dataSet1.TypedDataTable1; - var dataTable2 = dataSet2.TypedDataTable1; - - ApplyChange(dataTable2.Rows, dataTable2, changeType); - - if (acceptChanges) - { - dataTable2.AcceptChanges(); - } - - // Act - Action action = () => dataTable1.Should().BeEquivalentTo(dataTable2); - - // Assert - action.Should().Throw(); - } - - [Theory] - [MemberData(nameof(AllChangeTypesWithAcceptChangesValues))] - public void When_Rows_do_not_match_and_property_is_excluded_it_should_succeed(ChangeType changeType, bool acceptChanges) - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1); - - var dataTable1 = dataSet1.TypedDataTable1; - var dataTable2 = dataSet2.TypedDataTable1; - - ApplyChange(dataTable2.Rows, dataTable2, changeType); - - if (acceptChanges) - { - dataTable2.AcceptChanges(); - } - - // Act & Assert - dataTable1.Should().BeEquivalentTo(dataTable2, options => options.Excluding(dataTable => dataTable.Rows)); - } - - [Fact] - public void When_data_matches_in_different_order_and_RowMatchMode_is_PrimaryKey_it_should_succeed() - { - // Arrange - var dataSet1 = CreateDummyDataSet(); - - var dataSet2 = new TypedDataSetSubclass(dataSet1, randomizeRowOrder: true); - - var dataTable1 = dataSet1.TypedDataTable1; - var dataTable2 = dataSet2.TypedDataTable1; - - // Act & Assert - dataTable1.Should().BeEquivalentTo(dataTable2, options => options.UsingRowMatchMode(RowMatchMode.PrimaryKey)); - } -} diff --git a/Tests/FluentAssertions.Extensibility.Specs/AssertionEngineInitializer.cs b/Tests/FluentAssertions.Extensibility.Specs/AssertionEngineInitializer.cs new file mode 100644 index 0000000000..6aaaaa6b4b --- /dev/null +++ b/Tests/FluentAssertions.Extensibility.Specs/AssertionEngineInitializer.cs @@ -0,0 +1,30 @@ +using System; +using System.Threading; + +// With specific initialization code to invoke before the first assertion happens +[assembly: FluentAssertions.Extensibility.AssertionEngineInitializer( + typeof(FluentAssertions.Extensibility.Specs.AssertionEngineInitializer), + nameof(FluentAssertions.Extensibility.Specs.AssertionEngineInitializer.InitializeBeforeFirstAssertion))] + +[assembly: FluentAssertions.Extensibility.AssertionEngineInitializer( + typeof(FluentAssertions.Extensibility.Specs.AssertionEngineInitializer), + nameof(FluentAssertions.Extensibility.Specs.AssertionEngineInitializer.InitializeBeforeFirstAssertionButThrow))] + +namespace FluentAssertions.Extensibility.Specs; + +public static class AssertionEngineInitializer +{ + private static int shouldBeCalledOnlyOnce; + + public static int ShouldBeCalledOnlyOnce => shouldBeCalledOnlyOnce; + + public static void InitializeBeforeFirstAssertion() + { + Interlocked.Increment(ref shouldBeCalledOnlyOnce); + } + + public static void InitializeBeforeFirstAssertionButThrow() + { + throw new InvalidOperationException("Bogus exception to make sure the engine ignores them"); + } +} diff --git a/Tests/FluentAssertions.Specs/ExtensibilitySpecs.cs b/Tests/FluentAssertions.Extensibility.Specs/ExtensibilitySpecs.cs similarity index 59% rename from Tests/FluentAssertions.Specs/ExtensibilitySpecs.cs rename to Tests/FluentAssertions.Extensibility.Specs/ExtensibilitySpecs.cs index e4ad77ede5..bb287c11cf 100644 --- a/Tests/FluentAssertions.Specs/ExtensibilitySpecs.cs +++ b/Tests/FluentAssertions.Extensibility.Specs/ExtensibilitySpecs.cs @@ -1,13 +1,13 @@ using System; -using Xunit; +using ExampleExtensions; using Xunit.Sdk; -namespace FluentAssertions.Specs; +namespace FluentAssertions.Extensibility.Specs; public class ExtensibilitySpecs { [Fact] - public void When_a_method_is_marked_as_custom_assertion_it_should_be_ignored_during_caller_identification() + public void Methods_marked_as_custom_assertion_are_ignored_during_caller_identification() { // Arrange var myClient = new MyCustomer @@ -22,14 +22,28 @@ public void When_a_method_is_marked_as_custom_assertion_it_should_be_ignored_dur act.Should().Throw().WithMessage( "Expected myClient to be true because we don't work with old clients, but found False."); } + + [Fact] + public void Methods_in_assemblies_marked_as_custom_assertion_are_ignored_during_caller_identification() + { + // Arrange + string palindrome = "fluent"; + + // Act + Action act = () => palindrome.Should().BePalindromic(); + + // Assert + act.Should().Throw().WithMessage( + "Expected palindrome to be*tneulf*"); + } } -public class MyCustomer +internal class MyCustomer { public bool Active { get; set; } } -public static class MyCustomerExtensions +internal static class MyCustomerExtensions { public static MyCustomerAssertions Should(this MyCustomer customer) { @@ -37,7 +51,7 @@ public static MyCustomerAssertions Should(this MyCustomer customer) } } -public class MyCustomerAssertions +internal class MyCustomerAssertions { private readonly MyCustomer customer; diff --git a/Tests/FluentAssertions.Extensibility.Specs/ExtensionAssemblyAttributeSpecs.cs b/Tests/FluentAssertions.Extensibility.Specs/ExtensionAssemblyAttributeSpecs.cs new file mode 100644 index 0000000000..ee70fd8b5c --- /dev/null +++ b/Tests/FluentAssertions.Extensibility.Specs/ExtensionAssemblyAttributeSpecs.cs @@ -0,0 +1,15 @@ +namespace FluentAssertions.Extensibility.Specs; + +public class ExtensionAssemblyAttributeSpecs +{ + [Fact] + public void Calls_assembly_initialization_code_only_once() + { + for (int i = 0; i < 10; i++) + { + var act = () => AssertionEngineInitializer.ShouldBeCalledOnlyOnce.Should().Be(1); + + act.Should().NotThrow(); + } + } +} diff --git a/Tests/FluentAssertions.Extensibility.Specs/FluentAssertions.Extensibility.Specs.csproj b/Tests/FluentAssertions.Extensibility.Specs/FluentAssertions.Extensibility.Specs.csproj new file mode 100644 index 0000000000..1dba3a2c52 --- /dev/null +++ b/Tests/FluentAssertions.Extensibility.Specs/FluentAssertions.Extensibility.Specs.csproj @@ -0,0 +1,36 @@ + + + + net47;net6.0;net8.0 + True + ..\..\Src\FluentAssertions\FluentAssertions.snk + false + $(NoWarn),IDE0052,1573,1591,1712 + full + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers + + + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + diff --git a/Tests/FluentAssertions.Extensibility.Specs/HttpResponseMessageAssertions.cs b/Tests/FluentAssertions.Extensibility.Specs/HttpResponseMessageAssertions.cs new file mode 100644 index 0000000000..02f4eaae7a --- /dev/null +++ b/Tests/FluentAssertions.Extensibility.Specs/HttpResponseMessageAssertions.cs @@ -0,0 +1,27 @@ +#if NET6_0_OR_GREATER + +using System.Net.Http; +using System.Text.Json.Nodes; +using System.Threading.Tasks; + +namespace FluentAssertions.Extensibility.Specs; + +internal static class HttpResponseMessageExtensions +{ + public static HttpResponseMessageAssertions Should(this HttpResponseMessage response) + { + return new HttpResponseMessageAssertions(response); + } +} + +internal class HttpResponseMessageAssertions(HttpResponseMessage response) +{ + public async Task BeEquivalentTo(T expectation) + { + string body = await response.Content.ReadAsStringAsync(); + + JsonNode.Parse(body).Should().BeEquivalentTo(expectation); + } +} + +#endif diff --git a/Tests/FluentAssertions.Extensibility.Specs/JsonWithInlineAssertionsSpecs.cs b/Tests/FluentAssertions.Extensibility.Specs/JsonWithInlineAssertionsSpecs.cs new file mode 100644 index 0000000000..870333e3ec --- /dev/null +++ b/Tests/FluentAssertions.Extensibility.Specs/JsonWithInlineAssertionsSpecs.cs @@ -0,0 +1,88 @@ +#if NET6_0_OR_GREATER +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; + +namespace FluentAssertions.Extensibility.Specs; + +public class JsonWithInlineAssertionsSpecs +{ + [Fact] + public async Task Can_use_inline_predicates_during_json_assertions() + { + // Arrange + string json = + """ + { + "Id": "TestPackage", + "Versions": [ + { + "Version": "1.0.0", + "Description": "Test package description", + "RepositoryUrl": "https://github.com/test/package", + "Owner": "testowner" + } + ] + } + """; + + var response = new HttpResponseMessage(HttpStatusCode.OK); + response.Content = new StringContent(json); + + // Act / Assert + await response.Should().BeEquivalentTo(new + { + Id = "TestPackage", + Versions = new[] + { + new + { + Version = "1.0.0", + Description = Value.ThatMatches(s => s.Contains("Test")), + RepositoryUrl = "https://github.com/test/package", + Owner = "testowner", + } + } + }); + } + + [Fact] + public async Task Can_use_inline_assertions_during_json_assertions() + { + // Arrange + string json = + """ + { + "Id": "TestPackage", + "Versions": [ + { + "Version": "1.0.0", + "Description": "Test package description", + "RepositoryUrl": "https://github.com/test/package", + "Owner": "testowner" + } + ] + } + """; + + var response = new HttpResponseMessage(HttpStatusCode.OK); + response.Content = new StringContent(json); + + // Act / Assert + await response.Should().BeEquivalentTo(new + { + Id = "TestPackage", + Versions = new[] + { + new + { + Version = "1.0.0", + Description = Value.ThatSatisfies(s => s.Should().Contain("description")), + RepositoryUrl = "https://github.com/test/package", + Owner = "testowner", + } + } + }); + } +} +#endif diff --git a/Tests/FluentAssertions.Extensibility.Specs/Usings.cs b/Tests/FluentAssertions.Extensibility.Specs/Usings.cs new file mode 100644 index 0000000000..c802f4480b --- /dev/null +++ b/Tests/FluentAssertions.Extensibility.Specs/Usings.cs @@ -0,0 +1 @@ +global using Xunit; diff --git a/Tests/FluentAssertions.Specs/AndWhichConstraintSpecs.cs b/Tests/FluentAssertions.Specs/AndWhichConstraintSpecs.cs index b102f96dc4..d59a6c423e 100644 --- a/Tests/FluentAssertions.Specs/AndWhichConstraintSpecs.cs +++ b/Tests/FluentAssertions.Specs/AndWhichConstraintSpecs.cs @@ -1,5 +1,6 @@ using System; using FluentAssertions.Collections; +using FluentAssertions.Execution; using Xunit; using Xunit.Sdk; @@ -11,7 +12,7 @@ public class AndWhichConstraintSpecs public void When_many_objects_are_provided_accessing_which_should_throw_a_descriptive_exception() { // Arrange - var continuation = new AndWhichConstraint(null, new[] { "hello", "world" }); + var continuation = new AndWhichConstraint(null, ["hello", "world"], AssertionChain.GetOrCreate()); // Act Action act = () => _ = continuation.Which; diff --git a/Tests/FluentAssertions.Specs/AssemblyInitializer.cs b/Tests/FluentAssertions.Specs/AssemblyInitializer.cs new file mode 100644 index 0000000000..1909a8206d --- /dev/null +++ b/Tests/FluentAssertions.Specs/AssemblyInitializer.cs @@ -0,0 +1,16 @@ +using FluentAssertions.Specs; + +[assembly: FluentAssertions.Extensibility.AssertionEngineInitializer( + typeof(AssemblyInitializer), + nameof(AssemblyInitializer.AcknowledgeSoftWarning))] + +namespace FluentAssertions.Specs; + +public static class AssemblyInitializer +{ + public static void AcknowledgeSoftWarning() + { + // Suppress the soft warning about the license requirements for commercial use + License.Accepted = true; + } +} 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 0b71d3a8b4..8d6e287d63 100644 --- a/Tests/FluentAssertions.Specs/AssertionExtensionsSpecs.cs +++ b/Tests/FluentAssertions.Specs/AssertionExtensionsSpecs.cs @@ -1,8 +1,10 @@ -using System; +using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using FluentAssertions.Common; +using FluentAssertions.Execution; using FluentAssertions.Numeric; using FluentAssertions.Primitives; using FluentAssertions.Specialized; @@ -32,33 +34,33 @@ public void Assertions_classes_override_equals() private static bool OverridesEquals(Type t) { MethodInfo equals = t.GetMethod("Equals", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public, - null, new[] { typeof(object) }, null); + null, [typeof(object)], null); return equals is not null; } - public static TheoryData ClassesWithGuardEquals => new() - { - 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), + public static TheoryData ClassesWithGuardEquals => + [ + 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 - }; + ]; [Theory] [MemberData(nameof(ClassesWithGuardEquals))] @@ -81,7 +83,6 @@ public void Guarding_equals_throws(object obj) [InlineData(typeof(ExecutionTimeAssertions))] [InlineData(typeof(GuidAssertions))] [InlineData(typeof(MethodInfoSelectorAssertions))] - [InlineData(typeof(NumericAssertions>))] [InlineData(typeof(PropertyInfoSelectorAssertions))] [InlineData(typeof(SimpleTimeSpanAssertions))] [InlineData(typeof(TaskCompletionSourceAssertionsBase))] @@ -108,7 +109,7 @@ public void Fake_should_method_throws(Type type) } // Act - Action act = () => fakeOverload.Invoke(null, new object[] { null }); + Action act = () => fakeOverload.Invoke(null, [null]); // Assert act.Should() @@ -129,21 +130,21 @@ public void Should_methods_have_a_matching_overload_to_guard_against_chaining_an .Where(m => m.Name == "Should") .ToList(); - List realOverloads = shouldOverloads - .Where(m => !IsGuardOverload(m)) - .Select(t => GetMostParentType(t.ReturnType)) - .Distinct() - .Concat(new[] - { - // @jnyrup: DateTimeRangeAssertions and DateTimeOffsetRangeAssertions are manually added here, - // because they expose AndConstraints, - // and hence should have a guarding Should(DateTimeRangeAssertions _) overloads, - // but they do not have a regular Should() overload, - // as they are always constructed through the fluent API. - typeof(DateTimeRangeAssertions<>), - typeof(DateTimeOffsetRangeAssertions<>), - }) - .ToList(); + List realOverloads = + [ + ..shouldOverloads + .Where(m => !IsGuardOverload(m)) + .Select(t => GetMostParentType(t.ReturnType)) + .Distinct(), + + // @jnyrup: DateTimeRangeAssertions and DateTimeOffsetRangeAssertions are manually added here, + // because they expose AndConstraints, + // and hence should have a guarding Should(DateTimeRangeAssertions _) overloads, + // but they do not have a regular Should() overload, + // as they are always constructed through the fluent API. + typeof(DateTimeRangeAssertions<>), + typeof(DateTimeOffsetRangeAssertions<>) + ]; List fakeOverloads = shouldOverloads .Where(m => IsGuardOverload(m)) @@ -157,6 +158,45 @@ public void Should_methods_have_a_matching_overload_to_guard_against_chaining_an "AssertionExtensions.cs should have a guard overload of Should calling InvalidShouldCall()"); } + [Theory] + [MemberData(nameof(GetShouldMethods), true)] + public void Should_methods_returning_reference_or_nullable_type_assertions_are_annotated_with_not_null_attribute(MethodInfo method) + { + var notNullAttribute = method.GetParameters().Single().GetCustomAttribute(); + notNullAttribute.Should().NotBeNull(); + } + + [Theory] + [MemberData(nameof(GetShouldMethods), false)] + public void Should_methods_not_returning_reference_or_nullable_type_assertions_are_not_annotated_with_not_null_attribute(MethodInfo method) + { + var notNullAttribute = method.GetParameters().Single().GetCustomAttribute(); + notNullAttribute.Should().BeNull(); + } + + public static IEnumerable GetShouldMethods(bool referenceOrNullableTypes) + { + return AllTypes.From(typeof(FluentAssertions.AssertionExtensions).Assembly) + .ThatAreClasses() + .ThatAreStatic() + .Where(t => t.IsPublic) + .SelectMany(t => t.GetMethods(BindingFlags.Static | BindingFlags.Public)) + .Where(m => m.Name == "Should" + && !IsGuardOverload(m) + && m.GetParameters().Length == 1 + && (referenceOrNullableTypes ? IsReferenceOrNullableTypeAssertion(m) : !IsReferenceOrNullableTypeAssertion(m))) + .Select(m => new object[] { m }); + } + + private static bool ReturnsReferenceTypeAssertions(MethodInfo m) => + m.ReturnType.IsAssignableToOpenGeneric(typeof(ReferenceTypeAssertions<,>)); + + private static bool IsNullableTypeAssertion(MethodInfo m) => + m.GetParameters()[0].ParameterType.IsAssignableToOpenGeneric(typeof(Nullable<>)); + + private static bool IsReferenceOrNullableTypeAssertion(MethodInfo m) => + ReturnsReferenceTypeAssertions(m) || IsNullableTypeAssertion(m); + private static bool IsGuardOverload(MethodInfo m) => m.ReturnType == typeof(void) && m.IsDefined(typeof(ObsoleteAttribute)); 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 deleted file mode 100644 index 2fca5037e5..0000000000 --- a/Tests/FluentAssertions.Specs/AssertionOptionsSpecs.cs +++ /dev/null @@ -1,429 +0,0 @@ -using System; -using System.Collections; -using System.Linq; -using System.Threading.Tasks; -using Chill; -using FluentAssertions.Equivalency; -using FluentAssertions.Equivalency.Steps; -using FluentAssertions.Execution; -using FluentAssertions.Formatting; -using Xunit; -using Xunit.Sdk; - -namespace FluentAssertions.Specs; - -public class AssertionOptionsSpecs -{ - // Due to tests that call AssertionOptions - [CollectionDefinition("AssertionOptionsSpecs", DisableParallelization = true)] - public class AssertionOptionsSpecsDefinition; - - public abstract class Given_temporary_global_assertion_options : GivenWhenThen - { - protected override void Dispose(bool disposing) - { - AssertionOptions.AssertEquivalencyUsing(_ => new EquivalencyAssertionOptions()); - - base.Dispose(disposing); - } - } - - [Collection("AssertionOptionsSpecs")] - public class When_injecting_a_null_configurer : GivenSubject - { - public When_injecting_a_null_configurer() - { - When(() => () => AssertionOptions.AssertEquivalencyUsing(defaultsConfigurer: null)); - } - - [Fact] - public void It_should_throw() - { - Result.Should().ThrowExactly() - .WithParameterName("defaultsConfigurer"); - } - } - - [Collection("AssertionOptionsSpecs")] - public class When_concurrently_getting_equality_strategy : GivenSubject - { - public When_concurrently_getting_equality_strategy() - { - When(() => - { -#pragma warning disable CA1859 // https://github.com/dotnet/roslyn-analyzers/issues/6704 - IEquivalencyAssertionOptions equivalencyAssertionOptions = new EquivalencyAssertionOptions(); -#pragma warning restore CA1859 - - return () => Parallel.For(0, 10_000, new ParallelOptions { MaxDegreeOfParallelism = 8 }, - _ => equivalencyAssertionOptions.GetEqualityStrategy(typeof(IEnumerable)) - ); - }); - } - - [Fact] - public void It_should_not_throw() - { - Result.Should().NotThrow(); - } - } - - [Collection("AssertionOptionsSpecs")] - public class When_modifying_global_reference_type_settings_a_previous_assertion_should_not_have_any_effect - : Given_temporary_global_assertion_options - { - public When_modifying_global_reference_type_settings_a_previous_assertion_should_not_have_any_effect() - { - Given(() => - { - // Trigger a first equivalency check using the default global settings - new MyValueType { Value = 1 }.Should().BeEquivalentTo(new MyValueType { Value = 2 }); - }); - - When(() => AssertionOptions.AssertEquivalencyUsing(o => o.ComparingByMembers())); - } - - [Fact] - public void It_should_try_to_compare_the_classes_by_member_semantics_and_thus_throw() - { - Action act = () => new MyValueType { Value = 1 }.Should().BeEquivalentTo(new MyValueType { Value = 2 }); - - act.Should().Throw(); - } - - internal class MyValueType - { - public int Value { get; set; } - - public override bool Equals(object obj) => true; - - public override int GetHashCode() => 0; - } - } - - [Collection("AssertionOptionsSpecs")] - public class When_modifying_global_value_type_settings_a_previous_assertion_should_not_have_any_effect - : Given_temporary_global_assertion_options - { - public When_modifying_global_value_type_settings_a_previous_assertion_should_not_have_any_effect() - { - Given(() => - { - // Trigger a first equivalency check using the default global settings - new MyClass { Value = 1 }.Should().BeEquivalentTo(new MyClass { Value = 1 }); - }); - - When(() => AssertionOptions.AssertEquivalencyUsing(o => o.ComparingByValue())); - } - - [Fact] - public void It_should_try_to_compare_the_classes_by_value_semantics_and_thus_throw() - { - MyClass myClass = new() { Value = 1 }; - - Action act = () => myClass.Should().BeEquivalentTo(new MyClass { Value = 1 }); - - act.Should().Throw(); - } - - internal class MyClass - { - public int Value { get; set; } - } - } - - public class When_modifying_record_settings_globally : Given_temporary_global_assertion_options - { - public When_modifying_record_settings_globally() - { - When(() => - { - AssertionOptions.AssertEquivalencyUsing( - options => options.ComparingByValue(typeof(Position))); - }); - } - - [Fact] - public void It_should_use_the_global_settings_for_comparing_records() - { - new Position(123).Should().BeEquivalentTo(new Position(123)); - } - - private record Position - { - private readonly int value; - - public Position(int value) - { - this.value = value; - } - } - } - - [Collection("AssertionOptionsSpecs")] - public class When_assertion_doubles_should_always_allow_small_deviations : Given_temporary_global_assertion_options - { - public When_assertion_doubles_should_always_allow_small_deviations() - { - When(() => - { - AssertionOptions.AssertEquivalencyUsing(options => options - .Using(ctx => ctx.Subject.Should().BeApproximately(ctx.Expectation, 0.01)) - .WhenTypeIs()); - }); - } - - [Fact] - public void Then_it_should_ignore_small_differences_without_the_need_of_local_options() - { - var actual = new - { - Value = 1D / 3D - }; - - var expected = new - { - Value = 0.33D - }; - - Action act = () => actual.Should().BeEquivalentTo(expected); - - act.Should().NotThrow(); - } - } - - [Collection("AssertionOptionsSpecs")] - public class When_local_similar_options_are_used : Given_temporary_global_assertion_options - { - public When_local_similar_options_are_used() - { - When(() => - { - AssertionOptions.AssertEquivalencyUsing(options => options - .Using(ctx => ctx.Subject.Should().BeApproximately(ctx.Expectation, 0.01)) - .WhenTypeIs()); - }); - } - - [Fact] - public void Then_they_should_override_the_global_options() - { - var actual = new - { - Value = 1D / 3D - }; - - var expected = new - { - Value = 0.33D - }; - - Action act = () => actual.Should().BeEquivalentTo(expected, options => options - .Using(ctx => ctx.Subject.Should().Be(ctx.Expectation)) - .WhenTypeIs()); - - act.Should().Throw().WithMessage("Expected*"); - } - - [Fact] - public void Then_they_should_not_affect_any_other_assertions() - { - var actual = new - { - Value = 1D / 3D - }; - - var expected = new - { - Value = 0.33D - }; - - Action act = () => actual.Should().BeEquivalentTo(expected); - - act.Should().NotThrow(); - } - } - - [Collection("AssertionOptionsSpecs")] - public class Given_self_resetting_equivalency_plan : GivenWhenThen - { - protected override void Dispose(bool disposing) - { - Plan.Reset(); - base.Dispose(disposing); - } - - protected static EquivalencyPlan Plan - { - get { return AssertionOptions.EquivalencyPlan; } - } - } - - [Collection("AssertionOptionsSpecs")] - public class When_inserting_a_step : Given_self_resetting_equivalency_plan - { - public When_inserting_a_step() - { - When(() => Plan.Insert()); - } - - [Fact] - public void Then_it_should_precede_all_other_steps() - { - var addedStep = Plan.LastOrDefault(s => s is MyEquivalencyStep); - - Plan.Should().StartWith(addedStep); - } - } - - [Collection("AssertionOptionsSpecs")] - public class When_inserting_a_step_before_another : Given_self_resetting_equivalency_plan - { - public When_inserting_a_step_before_another() - { - When(() => Plan.InsertBefore()); - } - - [Fact] - public void Then_it_should_precede_that_particular_step() - { - var addedStep = Plan.LastOrDefault(s => s is MyEquivalencyStep); - var successor = Plan.LastOrDefault(s => s is DictionaryEquivalencyStep); - - Plan.Should().HaveElementPreceding(successor, addedStep); - } - } - - [Collection("AssertionOptionsSpecs")] - public class When_appending_a_step : Given_self_resetting_equivalency_plan - { - public When_appending_a_step() - { - When(() => Plan.Add()); - } - - [Fact] - public void Then_it_should_precede_the_final_builtin_step() - { - var equivalencyStep = Plan.LastOrDefault(s => s is SimpleEqualityEquivalencyStep); - var subjectStep = Plan.LastOrDefault(s => s is MyEquivalencyStep); - - Plan.Should().HaveElementPreceding(equivalencyStep, subjectStep); - } - } - - [Collection("AssertionOptionsSpecs")] - public class When_appending_a_step_after_another : Given_self_resetting_equivalency_plan - { - public When_appending_a_step_after_another() - { - When(() => Plan.AddAfter()); - } - - [Fact] - public void Then_it_should_precede_the_final_builtin_step() - { - var addedStep = Plan.LastOrDefault(s => s is MyEquivalencyStep); - var predecessor = Plan.LastOrDefault(s => s is DictionaryEquivalencyStep); - - Plan.Should().HaveElementSucceeding(predecessor, addedStep); - } - } - - [Collection("AssertionOptionsSpecs")] - public class When_appending_a_step_and_no_builtin_steps_are_there : Given_self_resetting_equivalency_plan - { - public When_appending_a_step_and_no_builtin_steps_are_there() - { - When(() => - { - Plan.Clear(); - Plan.Add(); - }); - } - - [Fact] - public void Then_it_should_precede_the_simple_equality_step() - { - var subjectStep = Plan.LastOrDefault(s => s is MyEquivalencyStep); - - Plan.Should().EndWith(subjectStep); - } - } - - [Collection("AssertionOptionsSpecs")] - public class When_removing_a_specific_step : Given_self_resetting_equivalency_plan - { - public When_removing_a_specific_step() - { - When(() => Plan.Remove()); - } - - [Fact] - public void Then_it_should_precede_the_simple_equality_step() - { - Plan.Should().NotContain(s => s is SimpleEqualityEquivalencyStep); - } - } - - [Collection("AssertionOptionsSpecs")] - public class When_removing_a_specific_step_that_doesnt_exist : Given_self_resetting_equivalency_plan - { - public When_removing_a_specific_step_that_doesnt_exist() - { - WhenAction = () => Plan.Remove(); - } - - [Fact] - public void Then_it_should_precede_the_simple_equality_step() - { - WhenAction.Should().NotThrow(); - } - } - - [Collection("AssertionOptionsSpecs")] - public class When_global_formatting_settings_are_modified : GivenWhenThen - { - private FormattingOptions oldSettings; - - public When_global_formatting_settings_are_modified() - { - Given(() => oldSettings = AssertionOptions.FormattingOptions.Clone()); - - When(() => - { - AssertionOptions.FormattingOptions.UseLineBreaks = true; - AssertionOptions.FormattingOptions.MaxDepth = 123; - AssertionOptions.FormattingOptions.MaxLines = 33; - }); - } - - [Fact] - public void Then_the_current_assertion_scope_should_use_these_settings() - { - AssertionScope.Current.FormattingOptions.UseLineBreaks.Should().BeTrue(); - AssertionScope.Current.FormattingOptions.MaxDepth.Should().Be(123); - AssertionScope.Current.FormattingOptions.MaxLines.Should().Be(33); - } - - protected override void Dispose(bool disposing) - { - AssertionOptions.FormattingOptions.MaxDepth = oldSettings.MaxDepth; - AssertionOptions.FormattingOptions.UseLineBreaks = oldSettings.UseLineBreaks; - AssertionOptions.FormattingOptions.MaxLines = oldSettings.MaxLines; - - base.Dispose(disposing); - } - } - - internal class MyEquivalencyStep : IEquivalencyStep - { - public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, - IEquivalencyValidator nestedValidator) - { - Execute.Assertion.FailWith(GetType().FullName); - - return EquivalencyResult.AssertionCompleted; - } - } -} diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.AllBeAssignableTo.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.AllBeAssignableTo.cs index 777f53fdd5..662aa1c2dd 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.AllBeAssignableTo.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.AllBeAssignableTo.cs @@ -17,7 +17,7 @@ public class AllBeAssignableTo public void When_the_types_in_a_collection_is_matched_against_a_null_type_it_should_throw() { // Arrange - var collection = Array.Empty(); + int[] collection = []; // Act Action act = () => collection.Should().AllBeAssignableTo(null); @@ -27,6 +27,16 @@ public void When_the_types_in_a_collection_is_matched_against_a_null_type_it_sho .WithParameterName("expectedType"); } + [Fact] + public void All_items_in_an_empty_collection_are_assignable_to_a_generic_type() + { + // Arrange + int[] collection = []; + + // Act / Assert + collection.Should().AllBeAssignableTo(); + } + [Fact] public void When_collection_is_null_then_all_be_assignable_to_should_fail() { @@ -45,11 +55,21 @@ public void When_collection_is_null_then_all_be_assignable_to_should_fail() .WithMessage("Expected type to be \"*.Object\" *failure message*, but found collection is ."); } + [Fact] + public void All_items_in_an_empty_collection_are_assignable_to_a_type() + { + // Arrange + int[] collection = []; + + // Act / Assert + collection.Should().AllBeAssignableTo(typeof(int)); + } + [Fact] public void When_all_of_the_types_in_a_collection_match_expected_type_it_should_succeed() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act / Assert collection.Should().AllBeAssignableTo(typeof(int)); @@ -59,7 +79,7 @@ public void When_all_of_the_types_in_a_collection_match_expected_type_it_should_ public void When_all_of_the_types_in_a_collection_match_expected_generic_type_it_should_succeed() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act / Assert collection.Should().AllBeAssignableTo(); @@ -69,7 +89,7 @@ public void When_all_of_the_types_in_a_collection_match_expected_generic_type_it public void When_matching_a_collection_against_a_type_it_should_return_the_casted_items() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act / Assert collection.Should().AllBeAssignableTo() @@ -132,7 +152,7 @@ public void When_one_of_the_elements_is_null_it_should_throw_with_a_clear_explan public void When_collection_is_of_matching_types_it_should_succeed() { // Arrange - var collection = new[] { typeof(Exception), typeof(ArgumentException) }; + Type[] collection = [typeof(Exception), typeof(ArgumentException)]; // Act / Assert collection.Should().AllBeAssignableTo(); @@ -142,7 +162,7 @@ public void When_collection_is_of_matching_types_it_should_succeed() public void When_collection_of_types_contains_one_type_that_does_not_match_it_should_throw_with_a_clear_explanation() { // Arrange - var collection = new[] { typeof(int), typeof(string), typeof(int) }; + Type[] collection = [typeof(int), typeof(string), typeof(int)]; // Act Action act = () => collection.Should().AllBeAssignableTo("because they are of different type"); diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.AllBeEquivalentTo.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.AllBeEquivalentTo.cs new file mode 100644 index 0000000000..0614e81c16 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.AllBeEquivalentTo.cs @@ -0,0 +1,53 @@ +using Xunit; + +namespace FluentAssertions.Specs.Collections; + +public partial class CollectionAssertionSpecs +{ + public class AllBeEquivalentTo + { + [Fact] + public void Can_ignore_casing_while_comparing_collections_of_strings() + { + // Arrange + var actual = new[] { "test", "tEst", "Test", "TEst", "teST" }; + var expectation = "test"; + + // Act / Assert + actual.Should().AllBeEquivalentTo(expectation, o => o.IgnoringCase()); + } + + [Fact] + public void Can_ignore_leading_whitespace_while_comparing_collections_of_strings() + { + // Arrange + var actual = new[] { " test", "test", "\ttest", "\ntest", " \t \n test" }; + var expectation = "test"; + + // Act / Assert + actual.Should().AllBeEquivalentTo(expectation, o => o.IgnoringLeadingWhitespace()); + } + + [Fact] + public void Can_ignore_trailing_whitespace_while_comparing_collections_of_strings() + { + // Arrange + var actual = new[] { "test ", "test", "test\t", "test\n", "test \t \n " }; + var expectation = "test"; + + // Act / Assert + actual.Should().AllBeEquivalentTo(expectation, o => o.IgnoringTrailingWhitespace()); + } + + [Fact] + public void Can_ignore_newline_style_while_comparing_collections_of_strings() + { + // Arrange + var actual = new[] { "A\nB\nC", "A\r\nB\r\nC", "A\r\nB\nC", "A\nB\r\nC" }; + var expectation = "A\nB\nC"; + + // Act / Assert + actual.Should().AllBeEquivalentTo(expectation, o => o.IgnoringNewlineStyle()); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.AllBeOfType.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.AllBeOfType.cs index b130455f73..9258ad4d01 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.AllBeOfType.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.AllBeOfType.cs @@ -17,7 +17,7 @@ public class AllBeOfType public void When_the_types_in_a_collection_is_matched_against_a_null_type_exactly_it_should_throw() { // Arrange - var collection = Array.Empty(); + int[] collection = []; // Act Action act = () => collection.Should().AllBeOfType(null); @@ -27,6 +27,26 @@ public void When_the_types_in_a_collection_is_matched_against_a_null_type_exactl .WithParameterName("expectedType"); } + [Fact] + public void All_items_in_an_empty_collection_are_of_a_generic_type() + { + // Arrange + int[] collection = []; + + // Act / Assert + collection.Should().AllBeOfType(); + } + + [Fact] + public void All_items_in_an_empty_collection_are_of_a_type() + { + // Arrange + int[] collection = []; + + // Act / Assert + collection.Should().AllBeOfType(typeof(int)); + } + [Fact] public void When_collection_is_null_then_all_be_of_type_should_fail() { @@ -49,7 +69,7 @@ public void When_collection_is_null_then_all_be_of_type_should_fail() public void When_all_of_the_types_in_a_collection_match_expected_type_exactly_it_should_succeed() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act / Assert collection.Should().AllBeOfType(typeof(int)); @@ -59,7 +79,7 @@ public void When_all_of_the_types_in_a_collection_match_expected_type_exactly_it public void When_all_of_the_types_in_a_collection_match_expected_generic_type_exactly_it_should_succeed() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act / Assert collection.Should().AllBeOfType(); @@ -69,7 +89,7 @@ public void When_all_of_the_types_in_a_collection_match_expected_generic_type_ex public void When_matching_a_collection_against_an_exact_type_it_should_return_the_casted_items() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act / Assert collection.Should().AllBeOfType() @@ -122,7 +142,7 @@ public void When_one_of_the_elements_is_null_for_an_exact_match_it_should_throw_ public void When_collection_of_types_match_expected_type_exactly_it_should_succeed() { // Arrange - var collection = new[] { typeof(int), typeof(int), typeof(int) }; + Type[] collection = [typeof(int), typeof(int), typeof(int)]; // Act / Assert collection.Should().AllBeOfType(); diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.AllSatisfy.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.AllSatisfy.cs index 83ab3250d8..eafd9542da 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.AllSatisfy.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.AllSatisfy.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using FluentAssertions.Execution; using Xunit; using Xunit.Sdk; @@ -18,7 +17,7 @@ public class AllSatisfy public void A_null_inspector_should_throw() { // Arrange - IEnumerable collection = new[] { 1, 2 }; + IEnumerable collection = [1, 2]; // Act Action act = () => collection.Should().AllSatisfy(null); @@ -50,27 +49,20 @@ public void A_null_collection_should_throw() } [Fact] - public void An_empty_collection_should_throw() + public void An_empty_collection_should_succeed() { // Arrange - var collection = Enumerable.Empty(); + IEnumerable collection = []; - // Act - Action act = () => - collection.Should().AllSatisfy(x => x.Should().Be(1), "because we want to test the failure {0}", "message"); - - // Assert - act.Should() - .Throw() - .WithMessage( - "Expected collection to contain only items satisfying the inspector because we want to test the failure message, but collection is empty."); + // Act / Assert + collection.Should().AllSatisfy(x => x.Should().Be(1)); } [Fact] public void All_items_satisfying_inspector_should_succeed() { // Arrange - var collection = new[] { new Customer { Age = 21, Name = "John" }, new Customer { Age = 21, Name = "Jane" } }; + Customer[] collection = [new Customer { Age = 21, Name = "John" }, new Customer { Age = 21, Name = "Jane" }]; // Act / Assert collection.Should().AllSatisfy(x => x.Age.Should().Be(21)); @@ -80,11 +72,11 @@ public void All_items_satisfying_inspector_should_succeed() public void Any_items_not_satisfying_inspector_should_throw() { // Arrange - var customers = new[] - { - new CustomerWithItems { Age = 21, Items = new[] { 1, 2 } }, - new CustomerWithItems { Age = 22, Items = new[] { 3 } } - }; + CustomerWithItems[] customers = + [ + new CustomerWithItems { Age = 21, Items = [1, 2] }, + new CustomerWithItems { Age = 22, Items = [3] } + ]; // Act Action act = () => customers.Should() @@ -102,25 +94,25 @@ public void Any_items_not_satisfying_inspector_should_throw() // Assert act.Should() .Throw() - .WithMessage( - @"Expected customers to contain only items satisfying the inspector because we want to test nested assertions: -*At index 0: -*Expected customer.Age to be less than 21, but found 21 -*Expected customer.Items to contain only items satisfying the inspector: -*At index 0: -*Expected item to be 3, but found 1 -*At index 1: -*Expected item to be 3, but found 2 -*At index 1: -*Expected customer.Age to be less than 21, but found 22 (difference of 1)" - ); + .WithMessage(""" + Expected customers to contain only items satisfying the inspector because we want to test nested assertions: + *At index 0: + *Expected customer.Age to be less than 21, but found 21 + *Expected customer.Items to contain only items satisfying the inspector: + *At index 0: + *Expected item to be 3, but found 1 + *At index 1: + *Expected item to be 3, but found 2 + *At index 1: + *Expected customer.Age to be less than 21, but found 22 (difference of 1) + """); } [Fact] public void Inspector_message_that_is_not_reformatable_should_not_throw() { // Arrange - byte[][] subject = { new byte[] { 1 } }; + byte[][] subject = [[1]]; // Act Action act = () => subject.Should().AllSatisfy(e => e.Should().BeEquivalentTo(new byte[] { 2, 3, 4 })); diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.BeEmpty.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.BeEmpty.cs index d26a2ebb3a..caf3daec76 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.BeEmpty.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.BeEmpty.cs @@ -18,7 +18,7 @@ public class BeEmpty public void When_collection_is_empty_as_expected_it_should_not_throw() { // Arrange - var collection = new int[0]; + int[] collection = []; // Act / Assert collection.Should().BeEmpty(); @@ -28,7 +28,7 @@ public void When_collection_is_empty_as_expected_it_should_not_throw() public void When_collection_is_not_empty_unexpectedly_it_should_throw() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act Action act = () => collection.Should().BeEmpty("that's what we expect"); @@ -42,7 +42,7 @@ public void When_collection_is_not_empty_unexpectedly_it_should_throw() public void When_asserting_collection_with_items_is_not_empty_it_should_succeed() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act / Assert collection.Should().NotBeEmpty(); @@ -65,7 +65,7 @@ public void When_asserting_collection_with_items_is_not_empty_it_should_enumerat public void When_asserting_collection_without_items_is_not_empty_it_should_fail() { // Arrange - var collection = new int[0]; + int[] collection = []; // Act Action act = () => collection.Should().NotBeEmpty(); @@ -78,7 +78,7 @@ public void When_asserting_collection_without_items_is_not_empty_it_should_fail( public void When_asserting_collection_without_items_is_not_empty_it_should_fail_with_descriptive_message_() { // Arrange - var collection = new int[0]; + int[] collection = []; // Act Action act = () => collection.Should().NotBeEmpty("because we want to test the failure {0}", "message"); @@ -110,7 +110,7 @@ public void When_asserting_collection_to_be_empty_but_collection_is_null_it_shou public void When_asserting_collection_to_be_empty_it_should_enumerate_only_once() { // Arrange - var collection = new CountingGenericEnumerable(new int[0]); + var collection = new CountingGenericEnumerable([]); // Act collection.Should().BeEmpty(); @@ -123,7 +123,7 @@ public void When_asserting_collection_to_be_empty_it_should_enumerate_only_once( public void When_asserting_non_empty_collection_is_empty_it_should_enumerate_only_once() { // Arrange - var collection = new CountingGenericEnumerable(new[] { 1, 2, 3 }); + var collection = new CountingGenericEnumerable([1, 2, 3]); // Act Action act = () => collection.Should().BeEmpty(); @@ -186,7 +186,7 @@ public void When_asserting_collection_to_be_not_empty_but_collection_is_null_it_ public void When_asserting_collection_to_be_not_empty_it_should_enumerate_only_once() { // Arrange - var collection = new CountingGenericEnumerable(new[] { 42 }); + var collection = new CountingGenericEnumerable([42]); // Act collection.Should().NotBeEmpty(); diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.BeEquivalentTo.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.BeEquivalentTo.cs index b640358a17..60a28858db 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.BeEquivalentTo.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.BeEquivalentTo.cs @@ -18,8 +18,8 @@ public class BeEquivalentTo public void When_two_collections_contain_the_same_elements_it_should_treat_them_as_equivalent() { // Arrange - var collection1 = new[] { 1, 2, 3 }; - var collection2 = new[] { 3, 1, 2 }; + int[] collection1 = [1, 2, 3]; + int[] collection2 = [3, 1, 2]; // Act / Assert collection1.Should().BeEquivalentTo(collection2); @@ -29,10 +29,10 @@ public void When_two_collections_contain_the_same_elements_it_should_treat_them_ public void When_a_collection_contains_same_elements_it_should_treat_it_as_equivalent() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act / Assert - collection.Should().BeEquivalentTo(new[] { 3, 1, 2 }); + collection.Should().BeEquivalentTo([3, 1, 2]); } [Fact] @@ -50,8 +50,8 @@ public void When_character_collections_are_equivalent_it_should_not_throw() public void When_collections_are_not_equivalent_it_should_throw() { // Arrange - var collection1 = new[] { 1, 2, 3 }; - var collection2 = new[] { 1, 2 }; + int[] collection1 = [1, 2, 3]; + int[] collection2 = [1, 2]; // Act Action act = () => collection1.Should().BeEquivalentTo(collection2, "we treat {0} alike", "all"); @@ -65,8 +65,8 @@ public void When_collections_are_not_equivalent_it_should_throw() public void When_collections_with_duplicates_are_not_equivalent_it_should_throw() { // Arrange - var collection1 = new[] { 1, 2, 3, 1 }; - var collection2 = new[] { 1, 2, 3, 3 }; + int[] collection1 = [1, 2, 3, 1]; + int[] collection2 = [1, 2, 3, 3]; // Act Action act = () => collection1.Should().BeEquivalentTo(collection2); @@ -80,8 +80,8 @@ public void When_collections_with_duplicates_are_not_equivalent_it_should_throw( public void When_testing_for_equivalence_against_empty_collection_it_should_throw() { // Arrange - var subject = new[] { 1, 2, 3 }; - var otherCollection = new int[0]; + int[] subject = [1, 2, 3]; + int[] otherCollection = []; // Act Action act = () => subject.Should().BeEquivalentTo(otherCollection); @@ -95,8 +95,8 @@ public void When_testing_for_equivalence_against_empty_collection_it_should_thro public void When_two_collections_are_both_empty_it_should_treat_them_as_equivalent() { // Arrange - var subject = new int[0]; - var otherCollection = new int[0]; + int[] subject = []; + int[] otherCollection = []; // Act Action act = () => subject.Should().BeEquivalentTo(otherCollection); @@ -109,7 +109,7 @@ public void When_two_collections_are_both_empty_it_should_treat_them_as_equivale public void When_testing_for_equivalence_against_null_collection_it_should_throw() { // Arrange - var collection1 = new[] { 1, 2, 3 }; + int[] collection1 = [1, 2, 3]; int[] collection2 = null; // Act @@ -125,7 +125,7 @@ public void When_asserting_collections_to_be_equivalent_but_subject_collection_i { // Arrange int[] collection = null; - var collection1 = new[] { 1, 2, 3 }; + int[] collection1 = [1, 2, 3]; // Act Action act = @@ -158,6 +158,50 @@ public void Default_immutable_lists_should_be_equivalent() // Act / Assert collection.Should().BeEquivalentTo(collection1); } + + [Fact] + public void Can_ignore_casing_while_comparing_collections_of_strings() + { + // Arrange + var actual = new[] { "first", "test", "last" }; + var expectation = new[] { "first", "TEST", "last" }; + + // Act / Assert + actual.Should().BeEquivalentTo(expectation, o => o.IgnoringCase()); + } + + [Fact] + public void Can_ignore_leading_whitespace_while_comparing_collections_of_strings() + { + // Arrange + var actual = new[] { "first", " test", "last" }; + var expectation = new[] { "first", "test", "last" }; + + // Act / Assert + actual.Should().BeEquivalentTo(expectation, o => o.IgnoringLeadingWhitespace()); + } + + [Fact] + public void Can_ignore_trailing_whitespace_while_comparing_collections_of_strings() + { + // Arrange + var actual = new[] { "first", "test ", "last" }; + var expectation = new[] { "first", "test", "last" }; + + // Act / Assert + actual.Should().BeEquivalentTo(expectation, o => o.IgnoringTrailingWhitespace()); + } + + [Fact] + public void Can_ignore_newline_style_while_comparing_collections_of_strings() + { + // Arrange + var actual = new[] { "first", "A\nB\r\nC", "last" }; + var expectation = new[] { "first", "A\r\nB\nC", "last" }; + + // Act / Assert + actual.Should().BeEquivalentTo(expectation, o => o.IgnoringNewlineStyle()); + } } public class NotBeEquivalentTo @@ -166,8 +210,8 @@ public class NotBeEquivalentTo public void When_collection_is_not_equivalent_to_another_smaller_collection_it_should_succeed() { // Arrange - var collection1 = new[] { 1, 2, 3 }; - var collection2 = new[] { 3, 1 }; + int[] collection1 = [1, 2, 3]; + int[] collection2 = [3, 1]; // Act / Assert collection1.Should().NotBeEquivalentTo(collection2); @@ -191,8 +235,8 @@ public void When_large_collection_is_equivalent_to_another_equally_size_collecti public void When_collection_is_not_equivalent_to_another_equally_sized_collection_it_should_succeed() { // Arrange - var collection1 = new[] { 1, 2, 3 }; - var collection2 = new[] { 3, 1, 4 }; + int[] collection1 = [1, 2, 3]; + int[] collection2 = [3, 1, 4]; // Act / Assert collection1.Should().NotBeEquivalentTo(collection2); @@ -202,8 +246,8 @@ public void When_collection_is_not_equivalent_to_another_equally_sized_collectio public void When_collections_are_unexpectedly_equivalent_it_should_throw() { // Arrange - var collection1 = new[] { 1, 2, 3 }; - var collection2 = new[] { 3, 1, 2 }; + int[] collection1 = [1, 2, 3]; + int[] collection2 = [3, 1, 2]; // Act Action act = () => collection1.Should().NotBeEquivalentTo(collection2); @@ -218,7 +262,7 @@ public void When_asserting_collections_not_to_be_equivalent_but_subject_collecti { // Arrange int[] actual = null; - var expectation = new[] { 1, 2, 3 }; + int[] expectation = [1, 2, 3]; // Act Action act = () => @@ -236,8 +280,8 @@ public void When_asserting_collections_not_to_be_equivalent_but_subject_collecti public void When_non_empty_collection_is_not_expected_to_be_equivalent_to_an_empty_collection_it_should_succeed() { // Arrange - var collection1 = new[] { 1, 2, 3 }; - var collection2 = new int[0]; + int[] collection1 = [1, 2, 3]; + int[] collection2 = []; // Act Action act = () => collection1.Should().NotBeEquivalentTo(collection2); @@ -250,7 +294,7 @@ public void When_non_empty_collection_is_not_expected_to_be_equivalent_to_an_emp public void When_testing_collections_not_to_be_equivalent_against_null_collection_it_should_throw() { // Arrange - var collection1 = new[] { 1, 2, 3 }; + int[] collection1 = [1, 2, 3]; int[] collection2 = null; // Act @@ -266,7 +310,7 @@ public void When_testing_collections_not_to_be_equivalent_against_null_collectio public void When_testing_collections_not_to_be_equivalent_against_same_collection_it_should_throw() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; var collection1 = collection; // Act @@ -282,8 +326,8 @@ public void When_testing_collections_not_to_be_equivalent_against_same_collectio public void When_a_collections_is_equivalent_to_an_approximate_copy_it_should_throw() { // Arrange - var collection = new[] { 1.0, 2.0, 3.0 }; - var collection1 = new[] { 1.5, 2.5, 3.5 }; + double[] collection = [1.0, 2.0, 3.0]; + double[] collection1 = [1.5, 2.5, 3.5]; // Act Action act = () => collection.Should().NotBeEquivalentTo(collection1, opt => opt @@ -301,7 +345,7 @@ public void When_asserting_collections_not_to_be_equivalent_with_options_but_sub { // Arrange int[] actual = null; - int[] expectation = { 1, 2, 3 }; + int[] expectation = [1, 2, 3]; // Act Action act = () => @@ -319,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.BeInAscendingOrder.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.BeInAscendingOrder.cs index faf106ce2b..4c01511d9d 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.BeInAscendingOrder.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.BeInAscendingOrder.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; using FluentAssertions.Execution; using Xunit; @@ -37,7 +36,7 @@ public void When_asserting_a_null_collection_to_be_in_ascending_order_it_should_ public void When_asserting_the_items_in_an_ascendingly_ordered_collection_are_ordered_ascending_it_should_succeed() { // Arrange - var collection = new[] { 1, 2, 2, 3 }; + int[] collection = [1, 2, 2, 3]; // Act / Assert collection.Should().BeInAscendingOrder(); @@ -48,7 +47,7 @@ public void When_asserting_the_items_in_an_ascendingly_ordered_collection_are_ordered_ascending_using_the_given_comparer_it_should_succeed() { // Arrange - var collection = new[] { 1, 2, 2, 3 }; + int[] collection = [1, 2, 2, 3]; // Act / Assert collection.Should().BeInAscendingOrder(Comparer.Default); @@ -58,7 +57,7 @@ public void public void When_asserting_the_items_in_an_unordered_collection_are_ordered_ascending_it_should_throw() { // Arrange - var collection = new[] { 1, 6, 12, 15, 12, 17, 26 }; + int[] collection = [1, 6, 12, 15, 12, 17, 26]; // Act Action action = () => collection.Should().BeInAscendingOrder("because numbers are ordered"); @@ -74,7 +73,7 @@ public void When_asserting_the_items_in_an_unordered_collection_are_ordered_ascending_using_the_given_comparer_it_should_throw() { // Arrange - var collection = new[] { 1, 6, 12, 15, 12, 17, 26 }; + int[] collection = [1, 6, 12, 15, 12, 17, 26]; // Act Action action = () => collection.Should().BeInAscendingOrder(Comparer.Default, "because numbers are ordered"); @@ -89,7 +88,7 @@ public void public void Items_can_be_ordered_by_the_identity_function() { // Arrange - var collection = new[] { 1, 2 }; + int[] collection = [1, 2]; // Act Action action = () => collection.Should().BeInAscendingOrder(x => x); @@ -102,7 +101,7 @@ public void Items_can_be_ordered_by_the_identity_function() public void When_asserting_empty_collection_with_no_parameters_ordered_in_ascending_it_should_succeed() { // Arrange - var collection = new int[] { }; + int[] collection = []; // Act Action act = () => collection.Should().BeInAscendingOrder(); @@ -115,7 +114,7 @@ public void When_asserting_empty_collection_with_no_parameters_ordered_in_ascend public void When_asserting_empty_collection_by_property_expression_ordered_in_ascending_it_should_succeed() { // Arrange - var collection = Enumerable.Empty(); + IEnumerable collection = []; // Act Action act = () => collection.Should().BeInAscendingOrder(o => o.Number); @@ -128,7 +127,7 @@ public void When_asserting_empty_collection_by_property_expression_ordered_in_as public void When_asserting_single_element_collection_with_no_parameters_ordered_in_ascending_it_should_succeed() { // Arrange - var collection = new[] { 42 }; + int[] collection = [42]; // Act Action act = () => collection.Should().BeInAscendingOrder(); @@ -170,10 +169,10 @@ public void Can_use_a_cast_expression_in_the_ordering_expression() public void Can_use_an_index_into_a_list_in_the_ordering_expression() { // Arrange - var collection = new[] - { - new List { new() { Text = "a", Number = 1 } } - }; + List[] collection = + [ + [new() { Text = "a", Number = 1 }] + ]; // Act & Assert collection.Should().BeInAscendingOrder(o => o[0].Number); @@ -183,10 +182,10 @@ public void Can_use_an_index_into_a_list_in_the_ordering_expression() public void Can_use_an_index_into_an_array_in_the_ordering_expression() { // Arrange - var collection = new[] - { - new[] { new SomeClass { Text = "a", Number = 1 } } - }; + SomeClass[][] collection = + [ + [new SomeClass { Text = "a", Number = 1 }] + ]; // Act & Assert collection.Should().BeInAscendingOrder(o => o[0].Number); @@ -292,7 +291,7 @@ public void public void When_strings_are_in_ascending_order_it_should_succeed() { // Arrange - string[] strings = { "alpha", "beta", "theta" }; + string[] strings = ["alpha", "beta", "theta"]; // Act Action act = () => strings.Should().BeInAscendingOrder(); @@ -305,7 +304,7 @@ public void When_strings_are_in_ascending_order_it_should_succeed() public void When_strings_are_not_in_ascending_order_it_should_throw() { // Arrange - string[] strings = { "theta", "alpha", "beta" }; + string[] strings = ["theta", "alpha", "beta"]; // Act Action act = () => strings.Should().BeInAscendingOrder("of {0}", "reasons"); @@ -320,7 +319,7 @@ public void When_strings_are_not_in_ascending_order_it_should_throw() public void When_strings_are_in_ascending_order_according_to_a_custom_comparer_it_should_succeed() { // Arrange - string[] strings = { "alpha", "beta", "theta" }; + string[] strings = ["alpha", "beta", "theta"]; // Act Action act = () => strings.Should().BeInAscendingOrder(new ByLastCharacterComparer()); @@ -333,7 +332,7 @@ public void When_strings_are_in_ascending_order_according_to_a_custom_comparer_i public void When_strings_are_not_in_ascending_order_according_to_a_custom_comparer_it_should_throw() { // Arrange - string[] strings = { "dennis", "roy", "thomas" }; + string[] strings = ["dennis", "roy", "thomas"]; // Act Action act = () => strings.Should().BeInAscendingOrder(new ByLastCharacterComparer(), "of {0}", "reasons"); @@ -348,10 +347,10 @@ public void When_strings_are_not_in_ascending_order_according_to_a_custom_compar public void When_strings_are_in_ascending_order_according_to_a_custom_lambda_it_should_succeed() { // Arrange - string[] strings = { "alpha", "beta", "theta" }; + string[] strings = ["alpha", "beta", "theta"]; // Act - Action act = () => strings.Should().BeInAscendingOrder((sut, exp) => sut.Last().CompareTo(exp.Last())); + Action act = () => strings.Should().BeInAscendingOrder((sut, exp) => sut[^1].CompareTo(exp[^1])); // Assert act.Should().NotThrow(); @@ -361,11 +360,11 @@ public void When_strings_are_in_ascending_order_according_to_a_custom_lambda_it_ public void When_strings_are_not_in_ascending_order_according_to_a_custom_lambda_it_should_throw() { // Arrange - string[] strings = { "dennis", "roy", "thomas" }; + string[] strings = ["dennis", "roy", "thomas"]; // Act Action act = () => - strings.Should().BeInAscendingOrder((sut, exp) => sut.Last().CompareTo(exp.Last()), "of {0}", "reasons"); + strings.Should().BeInAscendingOrder((sut, exp) => sut[^1].CompareTo(exp[^1]), "of {0}", "reasons"); // Assert act.Should() @@ -420,7 +419,7 @@ public void public void When_asserting_the_items_in_a_collection_are_ordered_and_the_specified_property_is_null_it_should_throw() { // Arrange - var collection = Enumerable.Empty(); + IEnumerable collection = []; // Act Action act = () => collection.Should().BeInAscendingOrder((Expression>)null); @@ -435,7 +434,7 @@ public void When_asserting_the_items_in_a_collection_are_ordered_and_the_specifi public void When_asserting_the_items_in_a_collection_are_ordered_and_the_given_comparer_is_null_it_should_throw() { // Arrange - var collection = Enumerable.Empty(); + IEnumerable collection = []; // Act Action act = () => collection.Should().BeInAscendingOrder(comparer: null); @@ -450,7 +449,7 @@ public void When_asserting_the_items_in_a_collection_are_ordered_and_the_given_c public void When_asserting_the_items_in_ay_collection_are_ordered_using_an_invalid_property_expression_it_should_throw() { // Arrange - var collection = Enumerable.Empty(); + IEnumerable collection = []; // Act Action act = () => collection.Should().BeInAscendingOrder(o => o.GetHashCode()); @@ -481,7 +480,7 @@ public void When_asserting_a_null_collection_to_not_be_in_ascending_order_it_sho public void When_asserting_the_items_in_an_unordered_collection_are_not_in_ascending_order_it_should_succeed() { // Arrange - var collection = new[] { 1, 5, 3 }; + int[] collection = [1, 5, 3]; // Act / Assert collection.Should().NotBeInAscendingOrder(); @@ -492,7 +491,7 @@ public void When_asserting_the_items_in_an_unordered_collection_are_not_in_ascending_order_using_the_given_comparer_it_should_succeed() { // Arrange - var collection = new[] { 1, 5, 3 }; + int[] collection = [1, 5, 3]; // Act / Assert collection.Should().NotBeInAscendingOrder(Comparer.Default); @@ -502,7 +501,7 @@ public void public void When_asserting_the_items_in_an_ascendingly_ordered_collection_are_not_in_ascending_order_it_should_throw() { // Arrange - var collection = new[] { 1, 2, 2, 3 }; + int[] collection = [1, 2, 2, 3]; // Act Action action = () => collection.Should().NotBeInAscendingOrder("because numbers are not ordered"); @@ -518,7 +517,7 @@ public void When_asserting_the_items_in_an_ascendingly_ordered_collection_are_not_in_ascending_order_using_the_given_comparer_it_should_throw() { // Arrange - var collection = new[] { 1, 2, 2, 3 }; + int[] collection = [1, 2, 2, 3]; // Act Action action = () => @@ -534,7 +533,7 @@ public void public void When_asserting_empty_collection_by_property_expression_to_not_be_ordered_in_ascending_it_should_throw() { // Arrange - var collection = Enumerable.Empty(); + IEnumerable collection = []; // Act Action act = () => collection.Should().NotBeInAscendingOrder(o => o.Number); @@ -548,7 +547,7 @@ public void When_asserting_empty_collection_by_property_expression_to_not_be_ord public void When_asserting_empty_collection_with_no_parameters_not_be_ordered_in_ascending_it_should_throw() { // Arrange - var collection = new int[] { }; + int[] collection = []; // Act Action act = () => collection.Should().NotBeInAscendingOrder("because I say {0}", "so"); @@ -562,7 +561,7 @@ public void When_asserting_empty_collection_with_no_parameters_not_be_ordered_in public void When_asserting_single_element_collection_with_no_parameters_not_be_ordered_in_ascending_it_should_throw() { // Arrange - var collection = new[] { 42 }; + int[] collection = [42]; // Act Action act = () => collection.Should().NotBeInAscendingOrder(); @@ -594,7 +593,7 @@ public void When_asserting_the_items_in_a_ascending_ordered_collection_are_not_ordered_ascending_using_the_given_comparer_it_should_throw() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act Action act = () => collection.Should().NotBeInAscendingOrder(Comparer.Default, "it should not be sorted"); @@ -609,7 +608,7 @@ public void When_asserting_the_items_not_in_an_ascendingly_ordered_collection_are_not_ordered_ascending_using_the_given_comparer_it_should_succeed() { // Arrange - var collection = new[] { 3, 2, 1 }; + int[] collection = [3, 2, 1]; // Act Action act = () => collection.Should().NotBeInAscendingOrder(Comparer.Default); @@ -702,7 +701,7 @@ public void public void When_strings_are_not_in_ascending_order_it_should_succeed() { // Arrange - string[] strings = { "beta", "alpha", "theta" }; + string[] strings = ["beta", "alpha", "theta"]; // Act Action act = () => strings.Should().NotBeInAscendingOrder(); @@ -715,7 +714,7 @@ public void When_strings_are_not_in_ascending_order_it_should_succeed() public void When_strings_are_in_ascending_order_unexpectedly_it_should_throw() { // Arrange - string[] strings = { "alpha", "beta", "theta" }; + string[] strings = ["alpha", "beta", "theta"]; // Act Action act = () => strings.Should().NotBeInAscendingOrder("of {0}", "reasons"); @@ -730,7 +729,7 @@ public void When_strings_are_in_ascending_order_unexpectedly_it_should_throw() public void When_strings_are_not_in_ascending_order_according_to_a_custom_comparer_it_should_succeed() { // Arrange - string[] strings = { "dennis", "roy", "barbara" }; + string[] strings = ["dennis", "roy", "barbara"]; // Act Action act = () => strings.Should().NotBeInAscendingOrder(new ByLastCharacterComparer()); @@ -743,7 +742,7 @@ public void When_strings_are_not_in_ascending_order_according_to_a_custom_compar public void When_strings_are_unexpectedly_in_ascending_order_according_to_a_custom_comparer_it_should_throw() { // Arrange - string[] strings = { "dennis", "thomas", "roy" }; + string[] strings = ["dennis", "thomas", "roy"]; // Act Action act = () => strings.Should().NotBeInAscendingOrder(new ByLastCharacterComparer(), "of {0}", "reasons"); @@ -758,10 +757,10 @@ public void When_strings_are_unexpectedly_in_ascending_order_according_to_a_cust public void When_strings_are_not_in_ascending_order_according_to_a_custom_lambda_it_should_succeed() { // Arrange - string[] strings = { "roy", "dennis", "thomas" }; + string[] strings = ["roy", "dennis", "thomas"]; // Act - Action act = () => strings.Should().NotBeInAscendingOrder((sut, exp) => sut.Last().CompareTo(exp.Last())); + Action act = () => strings.Should().NotBeInAscendingOrder((sut, exp) => sut[^1].CompareTo(exp[^1])); // Assert act.Should().NotThrow(); @@ -771,11 +770,11 @@ public void When_strings_are_not_in_ascending_order_according_to_a_custom_lambda public void When_strings_are_unexpectedly_in_ascending_order_according_to_a_custom_lambda_it_should_throw() { // Arrange - string[] strings = { "barbara", "dennis", "roy" }; + string[] strings = ["barbara", "dennis", "roy"]; // Act Action act = () => - strings.Should().NotBeInAscendingOrder((sut, exp) => sut.Last().CompareTo(exp.Last()), "of {0}", "reasons"); + strings.Should().NotBeInAscendingOrder((sut, exp) => sut[^1].CompareTo(exp[^1]), "of {0}", "reasons"); // Assert act.Should() @@ -838,7 +837,7 @@ public void public void When_asserting_the_items_in_a_collection_are_not_ordered_and_the_specified_property_is_null_it_should_throw() { // Arrange - var collection = Enumerable.Empty(); + IEnumerable collection = []; // Act Action act = () => collection.Should().NotBeInAscendingOrder((Expression>)null); @@ -852,7 +851,7 @@ public void When_asserting_the_items_in_a_collection_are_not_ordered_and_the_spe public void When_asserting_the_items_in_a_collection_are_not_ordered_and_the_given_comparer_is_null_it_should_throw() { // Arrange - var collection = Enumerable.Empty(); + IEnumerable collection = []; // Act Action act = () => collection.Should().NotBeInAscendingOrder(comparer: null); @@ -867,7 +866,7 @@ public void When_asserting_the_items_in_ay_collection_are_not_ordered_using_an_invalid_property_expression_it_should_throw() { // Arrange - var collection = Enumerable.Empty(); + IEnumerable collection = []; // Act Action act = () => collection.Should().NotBeInAscendingOrder(o => o.GetHashCode()); diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.BeInDescendingOrder.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.BeInDescendingOrder.cs index 702fb674c0..fa025d9b27 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.BeInDescendingOrder.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.BeInDescendingOrder.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using Xunit; using Xunit.Sdk; @@ -17,7 +16,7 @@ public class BeInDescendingOrder public void When_asserting_the_items_in_an_descendingly_ordered_collection_are_ordered_descending_it_should_succeed() { // Arrange - var collection = new[] { "z", "y", "x" }; + string[] collection = ["z", "y", "x"]; // Act / Assert collection.Should().BeInDescendingOrder(); @@ -27,7 +26,7 @@ public void When_asserting_the_items_in_an_descendingly_ordered_collection_are_o public void When_asserting_the_items_in_an_unordered_collection_are_ordered_descending_it_should_throw() { // Arrange - var collection = new[] { "z", "x", "y" }; + string[] collection = ["z", "x", "y"]; // Act Action action = () => collection.Should().BeInDescendingOrder("because letters are ordered"); @@ -42,7 +41,7 @@ public void When_asserting_the_items_in_an_unordered_collection_are_ordered_desc public void When_asserting_empty_collection_by_property_expression_ordered_in_descending_it_should_succeed() { // Arrange - var collection = Enumerable.Empty(); + IEnumerable collection = []; // Act Action act = () => collection.Should().BeInDescendingOrder(o => o.Number); @@ -55,7 +54,7 @@ public void When_asserting_empty_collection_by_property_expression_ordered_in_de public void When_asserting_empty_collection_with_no_parameters_ordered_in_descending_it_should_succeed() { // Arrange - var collection = new int[] { }; + int[] collection = []; // Act Action act = () => collection.Should().BeInDescendingOrder(); @@ -68,7 +67,7 @@ public void When_asserting_empty_collection_with_no_parameters_ordered_in_descen public void When_asserting_single_element_collection_with_no_parameters_ordered_in_descending_it_should_succeed() { // Arrange - var collection = new[] { 42 }; + int[] collection = [42]; // Act Action act = () => collection.Should().BeInDescendingOrder(); @@ -98,7 +97,7 @@ public void When_asserting_the_items_in_an_unordered_collection_are_ordered_descending_using_the_given_comparer_it_should_throw() { // Arrange - var collection = new[] { "z", "x", "y" }; + string[] collection = ["z", "x", "y"]; // Act Action action = () => @@ -115,7 +114,7 @@ public void When_asserting_the_items_in_an_descendingly_ordered_collection_are_ordered_descending_using_the_given_comparer_it_should_succeed() { // Arrange - var collection = new[] { "z", "y", "x" }; + string[] collection = ["z", "y", "x"]; // Act / Assert collection.Should().BeInDescendingOrder(Comparer.Default); @@ -204,7 +203,7 @@ public void public void When_strings_are_in_descending_order_it_should_succeed() { // Arrange - string[] strings = { "theta", "beta", "alpha" }; + string[] strings = ["theta", "beta", "alpha"]; // Act Action act = () => strings.Should().BeInDescendingOrder(); @@ -217,7 +216,7 @@ public void When_strings_are_in_descending_order_it_should_succeed() public void When_strings_are_not_in_descending_order_it_should_throw() { // Arrange - string[] strings = { "theta", "alpha", "beta" }; + string[] strings = ["theta", "alpha", "beta"]; // Act Action act = () => strings.Should().BeInDescendingOrder("of {0}", "reasons"); @@ -232,7 +231,7 @@ public void When_strings_are_not_in_descending_order_it_should_throw() public void When_strings_are_in_descending_order_based_on_a_custom_comparer_it_should_succeed() { // Arrange - string[] strings = { "roy", "dennis", "barbara" }; + string[] strings = ["roy", "dennis", "barbara"]; // Act Action act = () => strings.Should().BeInDescendingOrder(new ByLastCharacterComparer()); @@ -245,7 +244,7 @@ public void When_strings_are_in_descending_order_based_on_a_custom_comparer_it_s public void When_strings_are_not_in_descending_order_based_on_a_custom_comparer_it_should_throw() { // Arrange - string[] strings = { "dennis", "roy", "barbara" }; + string[] strings = ["dennis", "roy", "barbara"]; // Act Action act = () => strings.Should().BeInDescendingOrder(new ByLastCharacterComparer(), "of {0}", "reasons"); @@ -260,10 +259,10 @@ public void When_strings_are_not_in_descending_order_based_on_a_custom_comparer_ public void When_strings_are_in_descending_order_based_on_a_custom_lambda_it_should_succeed() { // Arrange - string[] strings = { "roy", "dennis", "barbara" }; + string[] strings = ["roy", "dennis", "barbara"]; // Act - Action act = () => strings.Should().BeInDescendingOrder((sut, exp) => sut.Last().CompareTo(exp.Last())); + Action act = () => strings.Should().BeInDescendingOrder((sut, exp) => sut[^1].CompareTo(exp[^1])); // Assert act.Should().NotThrow(); @@ -273,11 +272,11 @@ public void When_strings_are_in_descending_order_based_on_a_custom_lambda_it_sho public void When_strings_are_not_in_descending_order_based_on_a_custom_lambda_it_should_throw() { // Arrange - string[] strings = { "dennis", "roy", "barbara" }; + string[] strings = ["dennis", "roy", "barbara"]; // Act Action act = () => - strings.Should().BeInDescendingOrder((sut, exp) => sut.Last().CompareTo(exp.Last()), "of {0}", "reasons"); + strings.Should().BeInDescendingOrder((sut, exp) => sut[^1].CompareTo(exp[^1]), "of {0}", "reasons"); // Assert act.Should() @@ -292,7 +291,7 @@ public class NotBeInDescendingOrder public void When_asserting_the_items_in_an_unordered_collection_are_not_in_descending_order_it_should_succeed() { // Arrange - var collection = new[] { "x", "y", "x" }; + string[] collection = ["x", "y", "x"]; // Act / Assert collection.Should().NotBeInDescendingOrder(); @@ -303,7 +302,7 @@ public void When_asserting_the_items_in_an_unordered_collection_are_not_in_descending_order_using_the_given_comparer_it_should_succeed() { // Arrange - var collection = new[] { "x", "y", "x" }; + string[] collection = ["x", "y", "x"]; // Act / Assert collection.Should().NotBeInDescendingOrder(Comparer.Default); @@ -313,7 +312,7 @@ public void public void When_asserting_the_items_in_a_descending_ordered_collection_are_not_in_descending_order_it_should_throw() { // Arrange - var collection = new[] { "c", "b", "a" }; + string[] collection = ["c", "b", "a"]; // Act Action action = () => collection.Should().NotBeInDescendingOrder("because numbers are not ordered"); @@ -329,7 +328,7 @@ public void When_asserting_the_items_in_a_descending_ordered_collection_are_not_in_descending_order_using_the_given_comparer_it_should_throw() { // Arrange - var collection = new[] { "c", "b", "a" }; + string[] collection = ["c", "b", "a"]; // Act Action action = () => @@ -345,7 +344,7 @@ public void public void When_asserting_empty_collection_by_property_expression_to_not_be_ordered_in_descending_it_should_throw() { // Arrange - var collection = Enumerable.Empty(); + IEnumerable collection = []; // Act Action act = () => collection.Should().NotBeInDescendingOrder(o => o.Number); @@ -359,7 +358,7 @@ public void When_asserting_empty_collection_by_property_expression_to_not_be_ord public void When_asserting_empty_collection_with_no_parameters_not_be_ordered_in_descending_it_should_throw() { // Arrange - var collection = new int[] { }; + int[] collection = []; // Act Action act = () => collection.Should().NotBeInDescendingOrder("because I say {0}", "so"); @@ -373,7 +372,7 @@ public void When_asserting_empty_collection_with_no_parameters_not_be_ordered_in public void When_asserting_single_element_collection_with_no_parameters_not_be_ordered_in_descending_it_should_throw() { // Arrange - var collection = new[] { 42 }; + int[] collection = [42]; // Act Action act = () => collection.Should().NotBeInDescendingOrder(); @@ -405,7 +404,7 @@ public void When_asserting_the_items_in_a_descending_ordered_collection_are_not_ordered_descending_using_the_given_comparer_it_should_throw() { // Arrange - var collection = new[] { 3, 2, 1 }; + int[] collection = [3, 2, 1]; // Act Action act = () => collection.Should().NotBeInDescendingOrder(Comparer.Default, "it should not be sorted"); @@ -420,7 +419,7 @@ public void When_asserting_the_items_not_in_an_descendingly_ordered_collection_are_not_ordered_descending_using_the_given_comparer_it_should_succeed() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act Action act = () => collection.Should().NotBeInDescendingOrder(Comparer.Default); @@ -513,7 +512,7 @@ public void public void When_strings_are_not_in_descending_order_it_should_succeed() { // Arrange - string[] strings = { "beta", "theta", "alpha" }; + string[] strings = ["beta", "theta", "alpha"]; // Act Action act = () => strings.Should().NotBeInDescendingOrder(); @@ -526,7 +525,7 @@ public void When_strings_are_not_in_descending_order_it_should_succeed() public void When_strings_are_unexpectedly_in_descending_order_it_should_throw() { // Arrange - string[] strings = { "theta", "beta", "alpha" }; + string[] strings = ["theta", "beta", "alpha"]; // Act Action act = () => strings.Should().NotBeInDescendingOrder("of {0}", "reasons"); @@ -541,7 +540,7 @@ public void When_strings_are_unexpectedly_in_descending_order_it_should_throw() public void When_strings_are_not_in_descending_order_based_on_a_custom_comparer_it_should_succeed() { // Arrange - string[] strings = { "roy", "barbara", "dennis" }; + string[] strings = ["roy", "barbara", "dennis"]; // Act Action act = () => strings.Should().NotBeInDescendingOrder(new ByLastCharacterComparer()); @@ -554,7 +553,7 @@ public void When_strings_are_not_in_descending_order_based_on_a_custom_comparer_ public void When_strings_are_unexpectedly_in_descending_order_based_on_a_custom_comparer_it_should_throw() { // Arrange - string[] strings = { "roy", "dennis", "barbara" }; + string[] strings = ["roy", "dennis", "barbara"]; // Act Action act = () => strings.Should().NotBeInDescendingOrder(new ByLastCharacterComparer(), "of {0}", "reasons"); @@ -569,10 +568,10 @@ public void When_strings_are_unexpectedly_in_descending_order_based_on_a_custom_ public void When_strings_are_not_in_descending_order_based_on_a_custom_lambda_it_should_succeed() { // Arrange - string[] strings = { "dennis", "roy", "barbara" }; + string[] strings = ["dennis", "roy", "barbara"]; // Act - Action act = () => strings.Should().NotBeInDescendingOrder((sut, exp) => sut.Last().CompareTo(exp.Last())); + Action act = () => strings.Should().NotBeInDescendingOrder((sut, exp) => sut[^1].CompareTo(exp[^1])); // Assert act.Should().NotThrow(); @@ -582,11 +581,11 @@ public void When_strings_are_not_in_descending_order_based_on_a_custom_lambda_it public void When_strings_are_unexpectedly_in_descending_order_based_on_a_custom_lambda_it_should_throw() { // Arrange - string[] strings = { "roy", "dennis", "barbara" }; + string[] strings = ["roy", "dennis", "barbara"]; // Act Action act = () => - strings.Should().NotBeInDescendingOrder((sut, exp) => sut.Last().CompareTo(exp.Last()), "of {0}", "reasons"); + strings.Should().NotBeInDescendingOrder((sut, exp) => sut[^1].CompareTo(exp[^1]), "of {0}", "reasons"); // Assert act.Should() diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.BeNullOrEmpty.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.BeNullOrEmpty.cs index 28c1246187..46c06c14af 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.BeNullOrEmpty.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.BeNullOrEmpty.cs @@ -27,7 +27,7 @@ public void When_asserting_an_empty_collection_to_be_null_or_empty_it_should_succeed() { // Arrange - var collection = new int[0]; + int[] collection = []; // Act / Assert collection.Should().BeNullOrEmpty(); @@ -38,7 +38,7 @@ public void When_asserting_non_empty_collection_to_be_null_or_empty_it_should_throw() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act Action act = () => collection.Should().BeNullOrEmpty("because we want to test the failure {0}", "message"); @@ -53,7 +53,7 @@ public void public void When_asserting_collection_to_be_null_or_empty_it_should_enumerate_only_once() { // Arrange - var collection = new CountingGenericEnumerable(new int[0]); + var collection = new CountingGenericEnumerable([]); // Act collection.Should().BeNullOrEmpty(); @@ -66,7 +66,7 @@ public void When_asserting_collection_to_be_null_or_empty_it_should_enumerate_on public void When_asserting_non_empty_collection_is_null_or_empty_it_should_enumerate_only_once() { // Arrange - var collection = new CountingGenericEnumerable(new[] { 1, 2, 3 }); + var collection = new CountingGenericEnumerable([1, 2, 3]); // Act Action act = () => collection.Should().BeNullOrEmpty(); @@ -85,7 +85,7 @@ public void When_asserting_non_empty_collection_to_not_be_null_or_empty_it_should_succeed() { // Arrange - var collection = new[] { new object() }; + object[] collection = [new object()]; // Act / Assert collection.Should().NotBeNullOrEmpty(); @@ -110,7 +110,7 @@ public void When_asserting_empty_collection_to_not_be_null_or_empty_it_should_throw() { // Arrange - var collection = new int[0]; + int[] collection = []; // Act Action act = () => collection.Should().NotBeNullOrEmpty(); @@ -123,7 +123,7 @@ public void public void When_asserting_collection_to_not_be_null_or_empty_it_should_enumerate_only_once() { // Arrange - var collection = new CountingGenericEnumerable(new[] { 42 }); + var collection = new CountingGenericEnumerable([42]); // Act collection.Should().NotBeNullOrEmpty(); diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.BeSubsetOf.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.BeSubsetOf.cs index 5c4deecc10..42a9ab342f 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.BeSubsetOf.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.BeSubsetOf.cs @@ -16,8 +16,8 @@ public class BeSubsetOf public void When_collection_is_subset_of_a_specified_collection_it_should_not_throw() { // Arrange - var subset = new[] { 1, 2 }; - var superset = new[] { 1, 2, 3 }; + int[] subset = [1, 2]; + int[] superset = [1, 2, 3]; // Act / Assert subset.Should().BeSubsetOf(superset); @@ -27,8 +27,8 @@ public void When_collection_is_subset_of_a_specified_collection_it_should_not_th public void When_collection_is_not_a_subset_of_another_it_should_throw_with_the_reason() { // Arrange - var subset = new[] { 1, 2, 3, 6 }; - var superset = new[] { 1, 2, 4, 5 }; + int[] subset = [1, 2, 3, 6]; + int[] superset = [1, 2, 4, 5]; // Act Action act = () => subset.Should().BeSubsetOf(superset, "because we want to test the failure {0}", "message"); @@ -43,8 +43,8 @@ public void When_collection_is_not_a_subset_of_another_it_should_throw_with_the_ public void When_an_empty_collection_is_tested_against_a_superset_it_should_succeed() { // Arrange - var subset = new int[0]; - var superset = new[] { 1, 2, 4, 5 }; + int[] subset = []; + int[] superset = [1, 2, 4, 5]; // Act Action act = () => subset.Should().BeSubsetOf(superset); @@ -57,7 +57,7 @@ public void When_an_empty_collection_is_tested_against_a_superset_it_should_succ public void When_a_subset_is_tested_against_a_null_superset_it_should_throw_with_a_clear_explanation() { // Arrange - var subset = new[] { 1, 2, 3 }; + int[] subset = [1, 2, 3]; int[] superset = null; // Act @@ -72,8 +72,8 @@ public void When_a_subset_is_tested_against_a_null_superset_it_should_throw_with public void When_a_set_is_expected_to_be_not_a_subset_it_should_succeed() { // Arrange - var subject = new[] { 1, 2, 4 }; - var otherSet = new[] { 1, 2, 3 }; + int[] subject = [1, 2, 4]; + int[] otherSet = [1, 2, 3]; // Act / Assert subject.Should().NotBeSubsetOf(otherSet); @@ -86,8 +86,8 @@ public class NotBeSubsetOf public void When_an_empty_set_is_not_supposed_to_be_a_subset_of_another_set_it_should_throw() { // Arrange - var subject = new int[] { }; - var otherSet = new[] { 1, 2, 3 }; + int[] subject = []; + int[] otherSet = [1, 2, 3]; // Act Action act = () => subject.Should().NotBeSubsetOf(otherSet); @@ -101,8 +101,8 @@ public void When_an_empty_set_is_not_supposed_to_be_a_subset_of_another_set_it_s public void Should_fail_when_asserting_collection_is_not_subset_of_a_superset_collection() { // Arrange - var subject = new[] { 1, 2 }; - var otherSet = new[] { 1, 2, 3 }; + int[] subject = [1, 2]; + int[] otherSet = [1, 2, 3]; // Act Action act = () => subject.Should().NotBeSubsetOf(otherSet, "because I'm {0}", "mistaken"); @@ -117,7 +117,7 @@ public void When_asserting_collection_to_be_subset_against_null_collection_it_sh { // Arrange int[] collection = null; - var collection1 = new[] { 1, 2, 3 }; + int[] collection1 = [1, 2, 3]; // Act Action act = () => @@ -135,7 +135,7 @@ public void When_asserting_collection_to_be_subset_against_null_collection_it_sh public void When_asserting_collection_to_not_be_subset_against_same_collection_it_should_throw() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; var collection1 = collection; // Act @@ -157,7 +157,7 @@ public void When_asserting_collection_to_not_be_subset_against_null_collection_i Action act = () => { using var _ = new AssertionScope(); - collection.Should().NotBeSubsetOf(new[] { 1, 2, 3 }, "we want to test the failure {0}", "message"); + collection.Should().NotBeSubsetOf([1, 2, 3], "we want to test the failure {0}", "message"); }; // Assert diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.Contain.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.Contain.cs index fba270d0b8..4fb71e62ee 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.Contain.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.Contain.cs @@ -17,7 +17,7 @@ public class Contain public void Should_succeed_when_asserting_collection_contains_an_item_from_the_collection() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act / Assert collection.Should().Contain(1); @@ -27,17 +27,17 @@ public void Should_succeed_when_asserting_collection_contains_an_item_from_the_c public void Should_succeed_when_asserting_collection_contains_multiple_items_from_the_collection_in_any_order() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act / Assert - collection.Should().Contain(new[] { 2, 1 }); + collection.Should().Contain([2, 1]); } [Fact] public void When_a_collection_does_not_contain_single_item_it_should_throw_with_clear_explanation() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act Action act = () => collection.Should().Contain(4, "because {0}", "we do"); @@ -69,10 +69,10 @@ public void When_asserting_collection_does_contain_item_against_null_collection_ public void When_a_collection_does_not_contain_another_collection_it_should_throw_with_clear_explanation() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act - Action act = () => collection.Should().Contain(new[] { 3, 4, 5 }, "because {0}", "we do"); + Action act = () => collection.Should().Contain([3, 4, 5], "because {0}", "we do"); // Assert act.Should().Throw().WithMessage( @@ -83,10 +83,10 @@ public void When_a_collection_does_not_contain_another_collection_it_should_thro public void When_a_collection_does_not_contain_a_single_element_collection_it_should_throw_with_clear_explanation() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act - Action act = () => collection.Should().Contain(new[] { 4 }, "because {0}", "we do"); + Action act = () => collection.Should().Contain([4], "because {0}", "we do"); // Assert act.Should().Throw().WithMessage( @@ -98,13 +98,14 @@ public void When_a_collection_does_not_contain_other_collection_with_assertion_scope_it_should_throw_with_clear_explanation() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act Action act = () => { using var _ = new AssertionScope(); - collection.Should().Contain(new[] { 4 }).And.Contain(new[] { 5, 6 }); + collection.Should().Contain([4]); + collection.Should().Contain([5, 6]); }; // Assert @@ -116,10 +117,10 @@ public void public void When_the_contents_of_a_collection_are_checked_against_an_empty_collection_it_should_throw_clear_explanation() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act - Action act = () => collection.Should().Contain(new int[0]); + Action act = () => collection.Should().Contain([]); // Assert act.Should().Throw().WithMessage( @@ -136,7 +137,7 @@ public void When_asserting_collection_does_contain_a_list_of_items_against_null_ Action act = () => { using var _ = new AssertionScope(); - collection.Should().Contain(new[] { 1, 2 }, "we want to test the failure {0}", "message"); + collection.Should().Contain([1, 2], "we want to test the failure {0}", "message"); }; // Assert @@ -148,7 +149,7 @@ public void When_asserting_collection_does_contain_a_list_of_items_against_null_ public void When_injecting_a_null_predicate_into_Contain_it_should_throw() { // Arrange - IEnumerable collection = new int[] { }; + IEnumerable collection = []; // Act Action act = () => collection.Should().Contain(predicate: null); @@ -162,7 +163,7 @@ public void When_injecting_a_null_predicate_into_Contain_it_should_throw() public void When_collection_does_not_contain_an_expected_item_matching_a_predicate_it_should_throw() { // Arrange - IEnumerable collection = new[] { 1, 2, 3 }; + IEnumerable collection = [1, 2, 3]; // Act Action act = () => collection.Should().Contain(item => item > 3, "at least {0} item should be larger than 3", 1); @@ -176,7 +177,7 @@ public void When_collection_does_not_contain_an_expected_item_matching_a_predica public void When_collection_does_contain_an_expected_item_matching_a_predicate_it_should_allow_chaining_it() { // Arrange - IEnumerable collection = new[] { 1, 2, 3 }; + IEnumerable collection = [1, 2, 3]; // Act Action act = () => collection.Should().Contain(item => item == 2).Which.Should().BeGreaterThan(4); @@ -186,11 +187,25 @@ 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() { // Arrange - IEnumerable collection = new[] { 1, 2, 3 }; + IEnumerable collection = [1, 2, 3]; // Act / Assert collection.Should().Contain(item => item == 2); @@ -200,7 +215,7 @@ public void When_collection_does_contain_an_expected_item_matching_a_predicate_i public void When_a_collection_of_strings_contains_the_expected_string_it_should_not_throw() { // Arrange - IEnumerable strings = new[] { "string1", "string2", "string3" }; + IEnumerable strings = ["string1", "string2", "string3"]; // Act / Assert strings.Should().Contain("string2"); @@ -210,7 +225,7 @@ public void When_a_collection_of_strings_contains_the_expected_string_it_should_ public void When_a_collection_of_strings_does_not_contain_the_expected_string_it_should_throw() { // Arrange - IEnumerable strings = new[] { "string1", "string2", "string3" }; + IEnumerable strings = ["string1", "string2", "string3"]; // Act Action act = () => strings.Should().Contain("string4", "because {0} is required", "4"); @@ -244,7 +259,7 @@ public void When_the_multiple_matching_objects_exists_it_continuation_using_the_ // Arrange DateTime now = DateTime.Now; - IEnumerable collection = new[] { now, DateTime.SpecifyKind(now, DateTimeKind.Unspecified) }; + IEnumerable collection = [now, DateTime.SpecifyKind(now, DateTimeKind.Unspecified)]; // Act Action act = () => collection.Should().Contain(now).Which.Kind.Should().Be(DateTimeKind.Local); @@ -278,7 +293,7 @@ public class NotContain public void Should_succeed_when_asserting_collection_does_not_contain_an_item_that_is_not_in_the_collection() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act / Assert collection.Should().NotContain(4); @@ -288,17 +303,17 @@ public void Should_succeed_when_asserting_collection_does_not_contain_an_item_th public void Should_succeed_when_asserting_collection_does_not_contain_any_items_that_is_not_in_the_collection() { // Arrange - IEnumerable collection = new[] { 1, 2, 3 }; + IEnumerable collection = [1, 2, 3]; // Act / Assert - collection.Should().NotContain(new[] { 4, 5 }); + collection.Should().NotContain([4, 5]); } [Fact] public void When_collection_contains_an_unexpected_item_it_should_throw() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act Action act = () => collection.Should().NotContain(1, "because we {0} like it, but found it anyhow", "don't"); @@ -312,7 +327,7 @@ public void When_collection_contains_an_unexpected_item_it_should_throw() public void When_injecting_a_null_predicate_into_NotContain_it_should_throw() { // Arrange - IEnumerable collection = new int[] { }; + IEnumerable collection = []; // Act Action act = () => collection.Should().NotContain(predicate: null); @@ -326,7 +341,7 @@ public void When_injecting_a_null_predicate_into_NotContain_it_should_throw() public void When_collection_does_contain_an_unexpected_item_matching_a_predicate_it_should_throw() { // Arrange - IEnumerable collection = new[] { 1, 2, 3 }; + IEnumerable collection = [1, 2, 3]; // Act Action act = () => collection.Should().NotContain(item => item == 2, "because {0}s are evil", 2); @@ -340,7 +355,7 @@ public void When_collection_does_contain_an_unexpected_item_matching_a_predicate public void When_collection_does_not_contain_an_unexpected_item_matching_a_predicate_it_should_not_throw() { // Arrange - IEnumerable collection = new[] { 1, 2, 3 }; + IEnumerable collection = [1, 2, 3]; // Act / Assert collection.Should().NotContain(item => item == 4); @@ -368,11 +383,11 @@ public void When_asserting_collection_does_not_contain_item_against_null_collect public void When_collection_contains_unexpected_item_it_should_throw() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act Action act = () => collection.Should() - .NotContain(new[] { 2 }, "because we {0} like them", "don't"); + .NotContain([2], "because we {0} like them", "don't"); // Assert act.Should().Throw().WithMessage( @@ -383,11 +398,11 @@ public void When_collection_contains_unexpected_item_it_should_throw() public void When_collection_contains_unexpected_items_it_should_throw() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act Action act = () => collection.Should() - .NotContain(new[] { 1, 2, 4 }, "because we {0} like them", "don't"); + .NotContain([1, 2, 4], "because we {0} like them", "don't"); // Assert act.Should().Throw().WithMessage( @@ -395,31 +410,31 @@ 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 - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act Action act = () => { using var _ = new AssertionScope(); - collection.Should().NotContain(new[] { 1, 2 }).And.NotContain(new[] { 3 }); + collection.Should().NotContain([1, 2]).And.NotContain([3]); }; // Assert act.Should().Throw().WithMessage( - "*to not contain {1, 2}*to not contain 3*"); + "*but found {1, 2}."); } [Fact] public void When_asserting_collection_to_not_contain_an_empty_collection_it_should_throw() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act - Action act = () => collection.Should().NotContain(Array.Empty()); + Action act = () => collection.Should().NotContain([]); // Assert act.Should().Throw().WithMessage("Cannot verify*"); @@ -453,7 +468,7 @@ public void When_asserting_collection_does_not_contain_a_list_of_items_against_n Action act = () => { using var _ = new AssertionScope(); - collection.Should().NotContain(new[] { 1, 2, 4 }, "we want to test the failure {0}", "message"); + collection.Should().NotContain([1, 2, 4], "we want to test the failure {0}", "message"); }; // Assert diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainEquivalentOf.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainEquivalentOf.cs index 44fbdec445..b8f3028b4b 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainEquivalentOf.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainEquivalentOf.cs @@ -17,7 +17,7 @@ public void When_collection_contains_object_equal_of_another_it_should_succeed() { // Arrange var item = new Customer { Name = "John" }; - var collection = new[] { new Customer { Name = "Jane" }, item }; + Customer[] collection = [new Customer { Name = "Jane" }, item]; // Act / Assert collection.Should().ContainEquivalentOf(item); @@ -27,7 +27,7 @@ public void When_collection_contains_object_equal_of_another_it_should_succeed() public void When_collection_contains_object_equivalent_of_another_it_should_succeed() { // Arrange - var collection = new[] { new Customer { Name = "Jane" }, new Customer { Name = "John" } }; + Customer[] collection = [new Customer { Name = "Jane" }, new Customer { Name = "John" }]; var item = new Customer { Name = "John" }; // Act / Assert @@ -45,11 +45,26 @@ 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() { // Arrange - string[] collection = { "a", "b", "c" }; + string[] collection = ["a", "b", "c"]; string item = "C"; // Act @@ -64,7 +79,7 @@ public void When_string_collection_does_contain_same_string_with_other_case_it_s public void When_string_collection_does_contain_same_string_it_should_throw_with_a_useful_message() { // Arrange - string[] collection = { "a" }; + string[] collection = ["a"]; string item = "b"; // Act @@ -80,7 +95,7 @@ public void When_string_collection_does_contain_same_string_it_should_throw_with public void When_collection_does_not_contain_object_equivalent_of_another_it_should_throw() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; int item = 4; // Act @@ -113,7 +128,7 @@ public void When_asserting_collection_to_contain_equivalent_but_collection_is_nu public void When_collection_contains_equivalent_null_object_it_should_succeed() { // Arrange - var collection = new[] { 1, 2, 3, (int?)null }; + int?[] collection = [1, 2, 3, null]; int? item = null; // Act @@ -127,7 +142,7 @@ public void When_collection_contains_equivalent_null_object_it_should_succeed() public void When_collection_does_not_contain_equivalent_null_object_it_should_throw() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; int? item = null; // Act @@ -141,7 +156,7 @@ public void When_collection_does_not_contain_equivalent_null_object_it_should_th public void When_empty_collection_does_not_contain_equivalent_it_should_throw() { // Arrange - var collection = new int[0]; + int[] collection = []; int item = 1; // Act @@ -155,8 +170,8 @@ public void When_empty_collection_does_not_contain_equivalent_it_should_throw() public void When_collection_does_not_contain_equivalent_because_of_second_property_it_should_throw() { // Arrange - var subject = new[] - { + Customer[] subject = + [ new Customer { Name = "John", @@ -167,7 +182,7 @@ public void When_collection_does_not_contain_equivalent_because_of_second_proper Name = "Jane", Age = 18 } - }; + ]; var item = new Customer { Name = "John", Age = 20 }; @@ -182,8 +197,8 @@ public void When_collection_does_not_contain_equivalent_because_of_second_proper public void When_collection_does_contain_equivalent_by_including_single_property_it_should_not_throw() { // Arrange - var collection = new[] - { + Customer[] collection = + [ new Customer { Name = "John", @@ -194,7 +209,7 @@ public void When_collection_does_contain_equivalent_by_including_single_property Name = "Jane", Age = 18 } - }; + ]; var item = new Customer { Name = "John", Age = 20 }; @@ -206,8 +221,8 @@ public void When_collection_does_contain_equivalent_by_including_single_property public void Tracing_should_be_included_in_the_assertion_output() { // Arrange - var collection = new[] - { + Customer[] collection = + [ new Customer { Name = "John", @@ -218,7 +233,7 @@ public void Tracing_should_be_included_in_the_assertion_output() Name = "Jane", Age = 18 } - }; + ]; var item = new Customer { Name = "John", Age = 21 }; @@ -248,7 +263,7 @@ public void When_injecting_a_null_config_to_ContainEquivalentOf_it_should_throw( public void When_collection_contains_object_equivalent_of_boxed_object_it_should_succeed() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; object boxedValue = 2; // Act / Assert @@ -263,7 +278,7 @@ public void When_collection_contains_object_equal_to_another_it_should_throw() { // Arrange var item = 1; - var collection = new[] { 0, 1 }; + int[] collection = [0, 1]; // Act Action act = () => @@ -280,7 +295,7 @@ public void When_collection_contains_several_objects_equal_to_another_it_should_ { // Arrange var item = 1; - var collection = new[] { 0, 1, 1 }; + int[] collection = [0, 1, 1]; // Act Action act = () => @@ -325,7 +340,7 @@ public void When_injecting_a_null_config_to_NotContainEquivalentOf_it_should_thr public void When_asserting_empty_collection_to_not_contain_equivalent_it_should_succeed() { // Arrange - var collection = new int[0]; + int[] collection = []; int item = 4; // Act / Assert @@ -354,7 +369,7 @@ public void When_asserting_a_null_collection_to_not_contain_equivalent_of__then_ public void When_collection_does_not_contain_object_equivalent_of_unexpected_it_should_succeed() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; int item = 4; // Act / Assert @@ -365,8 +380,8 @@ public void When_collection_does_not_contain_object_equivalent_of_unexpected_it_ public void When_asserting_collection_to_not_contain_equivalent_it_should_respect_config() { // Arrange - var collection = new[] - { + Customer[] collection = + [ new Customer { Name = "John", @@ -377,7 +392,7 @@ public void When_asserting_collection_to_not_contain_equivalent_it_should_respec Name = "Jane", Age = 18 } - }; + ]; var item = new Customer { Name = "John", Age = 20 }; @@ -392,23 +407,23 @@ public void When_asserting_collection_to_not_contain_equivalent_it_should_respec public void When_asserting_collection_to_not_contain_equivalent_it_should_allow_combining_inside_assertion_scope() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; int another = 3; // 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.ContainInConsecutiveOrder.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainInConsecutiveOrder.cs index 788e6865cf..3105cc3201 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainInConsecutiveOrder.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainInConsecutiveOrder.cs @@ -16,7 +16,7 @@ public class ContainInConsecutiveOrder public void When_the_first_collection_contains_a_duplicate_item_without_affecting_the_explicit_order_it_should_not_throw() { // Arrange - var collection = new[] { 1, 2, 3, 2 }; + int[] collection = [1, 2, 3, 2]; // Act / Assert collection.Should().ContainInConsecutiveOrder(1, 2, 3); @@ -26,7 +26,7 @@ public void When_the_first_collection_contains_a_duplicate_item_without_affectin public void When_the_second_collection_contains_just_1_item_included_in_the_first_it_should_not_throw() { // Arrange - var collection = new[] { 1, 2, 3, 2 }; + int[] collection = [1, 2, 3, 2]; // Act / Assert collection.Should().ContainInConsecutiveOrder(2); @@ -37,7 +37,7 @@ public void When_the_first_collection_contains_a_partial_duplicate_sequence_at_the_start_without_affecting_the_explicit_order_it_should_not_throw() { // Arrange - var collection = new[] { 1, 2, 1, 2, 3, 2 }; + int[] collection = [1, 2, 1, 2, 3, 2]; // Act / Assert collection.Should().ContainInConsecutiveOrder(1, 2, 3); @@ -47,7 +47,7 @@ public void public void When_two_collections_contain_the_same_duplicate_items_in_the_same_explicit_order_it_should_not_throw() { // Arrange - var collection = new[] { 1, 2, 1, 2, 12, 2, 2 }; + int[] collection = [1, 2, 1, 2, 12, 2, 2]; // Act / Assert collection.Should().ContainInConsecutiveOrder(1, 2, 1, 2, 12, 2, 2); @@ -57,7 +57,7 @@ public void When_two_collections_contain_the_same_duplicate_items_in_the_same_ex public void When_checking_for_an_empty_list_it_should_not_throw() { // Arrange - var collection = new[] { 1, 2, 1, 2, 12, 2, 2 }; + int[] collection = [1, 2, 1, 2, 12, 2, 2]; // Act / Assert collection.Should().ContainInConsecutiveOrder(); @@ -77,7 +77,7 @@ public void When_collection_contains_null_value_it_should_not_throw() public void When_two_collections_contain_the_same_items_but_not_in_the_same_explicit_order_it_should_throw() { // Arrange - var collection = new[] { 1, 2, 2, 3 }; + int[] collection = [1, 2, 2, 3]; // Act / Assert Action act = () => collection.Should().ContainInConsecutiveOrder(1, 2, 3); @@ -91,7 +91,7 @@ public void When_two_collections_contain_the_same_items_but_not_in_the_same_expl public void When_the_second_collection_contains_just_1_item_not_included_in_the_first_it_should_throw() { // Arrange - var collection = new[] { 1, 2, 2, 3 }; + int[] collection = [1, 2, 2, 3]; // Act / Assert Action act = () => collection.Should().ContainInConsecutiveOrder(4); @@ -105,7 +105,7 @@ public void When_the_second_collection_contains_just_1_item_not_included_in_the_ public void When_end_of_first_collection_is_a_partial_match_of_second_at_end_it_should_throw() { // Arrange - var collection = new[] { 1, 3, 1, 2 }; + int[] collection = [1, 3, 1, 2]; // Act / Assert Action act = () => collection.Should().ContainInConsecutiveOrder(1, 2, 3); @@ -119,7 +119,7 @@ public void When_end_of_first_collection_is_a_partial_match_of_second_at_end_it_ public void When_a_collection_does_not_contain_a_range_twice_it_should_throw() { // Arrange - var collection = new[] { 1, 2, 1, 2, 3, 12, 2, 2 }; + int[] collection = [1, 2, 1, 2, 3, 12, 2, 2]; // Act Action act = () => collection.Should().ContainInConsecutiveOrder(1, 2, 1, 1, 2); @@ -133,7 +133,7 @@ public void When_a_collection_does_not_contain_a_range_twice_it_should_throw() public void When_two_collections_contain_the_same_items_but_in_different_order_it_should_throw_with_a_clear_explanation() { // Act - Action act = () => new[] { 1, 2, 3 }.Should().ContainInConsecutiveOrder(new[] { 3, 1 }, "because we said so"); + Action act = () => new[] { 1, 2, 3 }.Should().ContainInConsecutiveOrder([3, 1], "because we said so"); // Assert act.Should().Throw().WithMessage( @@ -144,7 +144,7 @@ public void When_two_collections_contain_the_same_items_but_in_different_order_i public void When_a_collection_does_not_contain_an_ordered_item_it_should_throw_with_a_clear_explanation() { // Act - Action act = () => new[] { 1, 2, 3 }.Should().ContainInConsecutiveOrder(new[] { 4, 1 }, "we failed"); + Action act = () => new[] { 1, 2, 3 }.Should().ContainInConsecutiveOrder([4, 1], "we failed"); // Assert act.Should().Throw().WithMessage( @@ -175,7 +175,7 @@ public void When_asserting_collection_contains_some_values_in_order_but_collecti using var _ = new AssertionScope(); collection.Should() - .ContainInConsecutiveOrder(new[] { 4 }, "because we're checking how it reacts to a null subject"); + .ContainInConsecutiveOrder([4], "because we're checking how it reacts to a null subject"); }; // Assert @@ -190,7 +190,7 @@ public class NotContainInConsecutiveOrder public void When_two_collections_contain_the_same_items_but_in_different_order_it_should_not_throw() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act / Assert collection.Should().NotContainInConsecutiveOrder(2, 1); @@ -200,7 +200,7 @@ public void When_two_collections_contain_the_same_items_but_in_different_order_i public void When_the_second_collection_contains_just_1_item_not_included_in_the_first_it_should_not_throw() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act / Assert collection.Should().NotContainInConsecutiveOrder(4); @@ -210,7 +210,7 @@ public void When_the_second_collection_contains_just_1_item_not_included_in_the_ public void When_a_collection_does_not_contain_an_ordered_item_it_should_not_throw() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act / Assert collection.Should().NotContainInConsecutiveOrder(4, 1); @@ -220,7 +220,7 @@ public void When_a_collection_does_not_contain_an_ordered_item_it_should_not_thr public void When_checking_for_an_empty_list_it_should_not_throw() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act / Assert collection.Should().NotContainInConsecutiveOrder(); @@ -230,7 +230,7 @@ public void When_checking_for_an_empty_list_it_should_not_throw() public void When_a_collection_contains_less_items_it_should_not_throw() { // Arrange - var collection = new[] { 1, 2 }; + int[] collection = [1, 2]; // Act / Assert collection.Should().NotContainInConsecutiveOrder(1, 2, 3); @@ -240,7 +240,7 @@ public void When_a_collection_contains_less_items_it_should_not_throw() public void When_a_collection_does_not_contain_a_range_twice_it_should_not_throw() { // Arrange - var collection = new[] { 1, 2, 1, 2, 3, 12, 2, 2 }; + int[] collection = [1, 2, 1, 2, 3, 12, 2, 2]; // Act / Assert collection.Should().NotContainInConsecutiveOrder(1, 2, 1, 1, 2); @@ -250,10 +250,10 @@ public void When_a_collection_does_not_contain_a_range_twice_it_should_not_throw public void When_two_collections_contain_the_same_items_not_in_the_same_explicit_order_it_should_not_throw() { // Arrange - var collection = new[] { 1, 2, 1, 2, 2, 3 }; + int[] collection = [1, 2, 1, 2, 2, 3]; // Act - collection.Should().NotContainInConsecutiveOrder(new[] { 1, 2, 3 }, "that's what we expect"); + collection.Should().NotContainInConsecutiveOrder([1, 2, 3], "that's what we expect"); } [Fact] @@ -280,7 +280,7 @@ public void When_collection_is_null_then_not_contain_in_order_should_fail() Action act = () => { using var _ = new AssertionScope(); - collection.Should().NotContainInConsecutiveOrder(new[] { 1, 2, 3 }, "we want to test the failure {0}", "message"); + collection.Should().NotContainInConsecutiveOrder([1, 2, 3], "we want to test the failure {0}", "message"); }; // Assert @@ -322,7 +322,7 @@ public void When_the_second_collection_contains_just_1_item_included_in_the_firs public void When_the_first_collection_contains_a_duplicate_item_without_affecting_the_order_it_should_throw() { // Arrange - var collection = new[] { 1, 2, 3, 2 }; + int[] collection = [1, 2, 3, 2]; // Act Action act = () => collection.Should().NotContainInConsecutiveOrder(1, 2, 3); @@ -337,7 +337,7 @@ public void When_the_first_collection_contains_a_duplicate_item_without_affectin public void When_the_first_collection_contains_a_duplicate_item_not_at_start_without_affecting_the_order_it_should_throw() { // Arrange - var collection = new[] { 1, 2, 1, 2, 3, 4, 5, 1, 2 }; + int[] collection = [1, 2, 1, 2, 3, 4, 5, 1, 2]; // Act Action act = () => collection.Should().NotContainInConsecutiveOrder(1, 2, 3); @@ -352,7 +352,7 @@ public void When_the_first_collection_contains_a_duplicate_item_not_at_start_wit public void When_two_collections_contain_the_same_duplicate_items_in_the_same_order_it_should_throw() { // Arrange - var collection = new[] { 1, 2, 1, 2, 12, 2, 2 }; + int[] collection = [1, 2, 1, 2, 12, 2, 2]; // Act Action act = () => collection.Should().NotContainInConsecutiveOrder(1, 2, 1, 2, 12, 2, 2); @@ -367,7 +367,7 @@ public void When_two_collections_contain_the_same_duplicate_items_in_the_same_or public void When_passing_in_null_while_checking_for_absence_of_ordered_containment_it_should_throw() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act Action act = () => collection.Should().NotContainInConsecutiveOrder(null); diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainInOrder.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainInOrder.cs index 2ad07202f5..0efa455e49 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainInOrder.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainInOrder.cs @@ -16,7 +16,7 @@ public class ContainInOrder public void When_two_collections_contain_the_same_items_in_the_same_order_it_should_not_throw() { // Arrange - var collection = new[] { 1, 2, 2, 3 }; + int[] collection = [1, 2, 2, 3]; // Act / Assert collection.Should().ContainInOrder(1, 2, 3); @@ -36,7 +36,7 @@ public void When_collection_contains_null_value_it_should_not_throw() public void When_the_first_collection_contains_a_duplicate_item_without_affecting_the_order_it_should_not_throw() { // Arrange - var collection = new[] { 1, 2, 3, 2 }; + int[] collection = [1, 2, 3, 2]; // Act / Assert collection.Should().ContainInOrder(1, 2, 3); @@ -46,7 +46,7 @@ public void When_the_first_collection_contains_a_duplicate_item_without_affectin public void When_two_collections_contain_the_same_duplicate_items_in_the_same_order_it_should_not_throw() { // Arrange - var collection = new[] { 1, 2, 1, 2, 12, 2, 2 }; + int[] collection = [1, 2, 1, 2, 12, 2, 2]; // Act / Assert collection.Should().ContainInOrder(1, 2, 1, 2, 12, 2, 2); @@ -56,7 +56,7 @@ public void When_two_collections_contain_the_same_duplicate_items_in_the_same_or public void When_a_collection_does_not_contain_a_range_twice_it_should_throw() { // Arrange - var collection = new[] { 1, 2, 1, 3, 12, 2, 2 }; + int[] collection = [1, 2, 1, 3, 12, 2, 2]; // Act Action act = () => collection.Should().ContainInOrder(1, 2, 1, 1, 2); @@ -70,7 +70,7 @@ public void When_a_collection_does_not_contain_a_range_twice_it_should_throw() public void When_two_collections_contain_the_same_items_but_in_different_order_it_should_throw_with_a_clear_explanation() { // Act - Action act = () => new[] { 1, 2, 3 }.Should().ContainInOrder(new[] { 3, 1 }, "because we said so"); + Action act = () => new[] { 1, 2, 3 }.Should().ContainInOrder([3, 1], "because we said so"); // Assert act.Should().Throw().WithMessage( @@ -81,7 +81,7 @@ public void When_two_collections_contain_the_same_items_but_in_different_order_i public void When_a_collection_does_not_contain_an_ordered_item_it_should_throw_with_a_clear_explanation() { // Act - Action act = () => new[] { 1, 2, 3 }.Should().ContainInOrder(new[] { 4, 1 }, "we failed"); + Action act = () => new[] { 1, 2, 3 }.Should().ContainInOrder([4, 1], "we failed"); // Assert act.Should().Throw().WithMessage( @@ -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] @@ -139,7 +138,7 @@ public void When_asserting_collection_contains_some_values_in_order_but_collecti Action act = () => { using var _ = new AssertionScope(); - ints.Should().ContainInOrder(new[] { 4 }, "because we're checking how it reacts to a null subject"); + ints.Should().ContainInOrder([4], "because we're checking how it reacts to a null subject"); }; // Assert @@ -154,7 +153,7 @@ public class NotContainInOrder public void When_two_collections_contain_the_same_items_but_in_different_order_it_should_not_throw() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act / Assert collection.Should().NotContainInOrder(2, 1); @@ -164,7 +163,7 @@ public void When_two_collections_contain_the_same_items_but_in_different_order_i public void When_a_collection_does_not_contain_an_ordered_item_it_should_not_throw() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act / Assert collection.Should().NotContainInOrder(4, 1); @@ -174,7 +173,7 @@ public void When_a_collection_does_not_contain_an_ordered_item_it_should_not_thr public void When_a_collection_contains_less_items_it_should_not_throw() { // Arrange - var collection = new[] { 1, 2 }; + int[] collection = [1, 2]; // Act / Assert collection.Should().NotContainInOrder(1, 2, 3); @@ -184,7 +183,7 @@ public void When_a_collection_contains_less_items_it_should_not_throw() public void When_a_collection_does_not_contain_a_range_twice_it_should_not_throw() { // Arrange - var collection = new[] { 1, 2, 1, 3, 12, 2, 2 }; + int[] collection = [1, 2, 1, 3, 12, 2, 2]; // Act / Assert collection.Should().NotContainInOrder(1, 2, 1, 1, 2); @@ -208,10 +207,10 @@ public void When_asserting_collection_does_not_contain_some_values_in_order_but_ public void When_two_collections_contain_the_same_items_in_the_same_order_it_should_throw() { // Arrange - var collection = new[] { 1, 2, 2, 3 }; + int[] collection = [1, 2, 2, 3]; // Act - Action act = () => collection.Should().NotContainInOrder(new[] { 1, 2, 3 }, "that's what we expect"); + Action act = () => collection.Should().NotContainInOrder([1, 2, 3], "that's what we expect"); // Assert act.Should().Throw().WithMessage( @@ -229,7 +228,7 @@ public void When_collection_is_null_then_not_contain_in_order_should_fail() Action act = () => { using var _ = new AssertionScope(); - collection.Should().NotContainInOrder(new[] { 1, 2, 3 }, "we want to test the failure {0}", "message"); + collection.Should().NotContainInOrder([1, 2, 3], "we want to test the failure {0}", "message"); }; // Assert @@ -256,7 +255,7 @@ public void When_collection_contains_contain_the_same_items_in_the_same_order_wi public void When_the_first_collection_contains_a_duplicate_item_without_affecting_the_order_it_should_throw() { // Arrange - var collection = new[] { 1, 2, 3, 2 }; + int[] collection = [1, 2, 3, 2]; // Act Action act = () => collection.Should().NotContainInOrder(1, 2, 3); @@ -271,7 +270,7 @@ public void When_the_first_collection_contains_a_duplicate_item_without_affectin public void When_two_collections_contain_the_same_duplicate_items_in_the_same_order_it_should_throw() { // Arrange - var collection = new[] { 1, 2, 1, 2, 12, 2, 2 }; + int[] collection = [1, 2, 1, 2, 12, 2, 2]; // Act Action act = () => collection.Should().NotContainInOrder(1, 2, 1, 2, 12, 2, 2); @@ -286,7 +285,7 @@ public void When_two_collections_contain_the_same_duplicate_items_in_the_same_or public void When_passing_in_null_while_checking_for_absence_of_ordered_containment_it_should_throw() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act Action act = () => collection.Should().NotContainInOrder(null); diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainItemsAssignableTo.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainItemsAssignableTo.cs index 8faa500717..9d06884ecb 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainItemsAssignableTo.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainItemsAssignableTo.cs @@ -17,7 +17,7 @@ public class ContainItemsAssignableTo public void Should_succeed_when_asserting_collection_with_all_items_of_same_type_only_contains_item_of_one_type() { // Arrange - var collection = new[] { "1", "2", "3" }; + string[] collection = ["1", "2", "3"]; // Act / Assert collection.Should().ContainItemsAssignableTo(); @@ -59,7 +59,7 @@ public void When_asserting_collection_contains_item_assignable_to_against_null_c public void When_a_collection_is_empty_an_exception_should_be_thrown() { // Arrange - int[] collection = Array.Empty(); + int[] collection = []; // Act Action act = () => collection.Should().ContainItemsAssignableTo(); diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainSingle.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainSingle.cs index c64c1c21bb..15bf529510 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainSingle.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainSingle.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; using FluentAssertions.Execution; using Xunit; @@ -17,7 +16,7 @@ public partial class CollectionAssertionSpecs public void When_injecting_a_null_predicate_into_ContainSingle_it_should_throw() { // Arrange - IEnumerable collection = new int[] { }; + IEnumerable collection = []; // Act Action act = () => collection.Should().ContainSingle(predicate: null); @@ -31,21 +30,18 @@ public void When_injecting_a_null_predicate_into_ContainSingle_it_should_throw() public void When_a_collection_contains_a_single_item_matching_a_predicate_it_should_succeed() { // Arrange - IEnumerable collection = new[] { 1, 2, 3 }; + 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] public void When_asserting_an_empty_collection_contains_a_single_item_matching_a_predicate_it_should_throw() { // Arrange - IEnumerable collection = Enumerable.Empty(); + IEnumerable collection = []; Expression> expression = item => item == 2; // Act @@ -83,7 +79,7 @@ public void When_asserting_a_null_collection_contains_a_single_item_matching_a_p public void When_non_empty_collection_does_not_contain_a_single_item_matching_a_predicate_it_should_throw() { // Arrange - IEnumerable collection = new[] { 1, 3 }; + IEnumerable collection = [1, 3]; Expression> expression = item => item == 2; // Act @@ -100,7 +96,7 @@ public void When_non_empty_collection_does_not_contain_a_single_item_matching_a_ public void When_non_empty_collection_contains_more_than_a_single_item_matching_a_predicate_it_should_throw() { // Arrange - IEnumerable collection = new[] { 1, 2, 2, 2, 3 }; + IEnumerable collection = [1, 2, 2, 2, 3]; Expression> expression = item => item == 2; // Act @@ -117,20 +113,40 @@ public void When_non_empty_collection_contains_more_than_a_single_item_matching_ public void When_single_item_matching_a_predicate_is_found_it_should_allow_continuation() { // Arrange - IEnumerable collection = new[] { 1, 2, 3 }; + IEnumerable collection = [1, 2, 3]; // Act 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] public void When_single_item_contains_brackets_it_should_format_them_properly() { // Arrange - IEnumerable collection = new[] { "" }; + IEnumerable collection = [""]; // Act Action act = () => collection.Should().ContainSingle(item => item == "{123}"); @@ -144,7 +160,7 @@ public void When_single_item_contains_brackets_it_should_format_them_properly() public void When_single_item_contains_string_interpolation_it_should_format_brackets_properly() { // Arrange - IEnumerable collection = new[] { "" }; + IEnumerable collection = [""]; // Act Action act = () => collection.Should().ContainSingle(item => item == $"{123}"); @@ -158,7 +174,7 @@ public void When_single_item_contains_string_interpolation_it_should_format_brac public void When_a_collection_contains_a_single_item_it_should_succeed() { // Arrange - IEnumerable collection = new[] { 1 }; + IEnumerable collection = [1]; // Act Action act = () => collection.Should().ContainSingle(); @@ -171,7 +187,7 @@ public void When_a_collection_contains_a_single_item_it_should_succeed() public void When_asserting_an_empty_collection_contains_a_single_item_it_should_throw() { // Arrange - IEnumerable collection = Enumerable.Empty(); + IEnumerable collection = []; // Act Action act = () => collection.Should().ContainSingle("more is not allowed"); @@ -204,7 +220,7 @@ public void When_asserting_a_null_collection_contains_a_single_item_it_should_th public void When_non_empty_collection_does_not_contain_a_single_item_it_should_throw() { // Arrange - IEnumerable collection = new[] { 1, 3 }; + IEnumerable collection = [1, 3]; // Act Action act = () => collection.Should().ContainSingle(); @@ -219,7 +235,7 @@ public void When_non_empty_collection_does_not_contain_a_single_item_it_should_t public void When_non_empty_collection_contains_more_than_a_single_item_it_should_throw() { // Arrange - IEnumerable collection = new[] { 1, 2 }; + IEnumerable collection = [1, 2]; // Act Action act = () => collection.Should().ContainSingle(); @@ -234,15 +250,14 @@ public void When_non_empty_collection_contains_more_than_a_single_item_it_should public void When_single_item_is_found_it_should_allow_continuation() { // Arrange - IEnumerable collection = new[] { 3 }; + IEnumerable collection = [3]; // Act 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.EndWith.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.EndWith.cs index c29465afe3..85407dbf6a 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.EndWith.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.EndWith.cs @@ -17,7 +17,7 @@ public class EndWith public void When_collection_does_not_end_with_a_specific_element_it_should_throw() { // Arrange - var collection = new[] { "john", "jane", "mike" }; + string[] collection = ["john", "jane", "mike"]; // Act Action act = () => collection.Should().EndWith("ryan", "of some reason"); @@ -31,11 +31,16 @@ public void When_collection_does_not_end_with_a_specific_element_it_should_throw public void When_collection_does_end_with_a_specific_element_and_because_format_is_incorrect_it_should_not_fail() { // Arrange - var collection = new[] { "john", "jane", "mike" }; + string[] collection = ["john", "jane", "mike"]; // Act +#pragma warning disable CA2241 + // ReSharper disable FormatStringProblem Action act = () => collection.Should().EndWith("mike", "of some reason {0,abc}", 1, 2); + // ReSharper restore FormatStringProblem +#pragma warning restore CA2241 + // Assert act.Should().NotThrow(); } @@ -44,10 +49,10 @@ public void When_collection_does_end_with_a_specific_element_and_because_format_ public void When_collection_does_not_end_with_a_specific_element_in_a_sequence_it_should_throw() { // Arrange - var collection = new[] { "john", "bill", "jane", "mike" }; + string[] collection = ["john", "bill", "jane", "mike"]; // Act - Action act = () => collection.Should().EndWith(new[] { "bill", "ryan", "mike" }, "of some reason"); + Action act = () => collection.Should().EndWith(["bill", "ryan", "mike"], "of some reason"); // Assert act.Should().Throw().WithMessage( @@ -58,7 +63,7 @@ public void When_collection_does_not_end_with_a_specific_element_in_a_sequence_i public void When_collection_does_not_end_with_a_null_sequence_it_should_throw() { // Arrange - var collection = new[] { "john" }; + string[] collection = ["john"]; // Act Action act = () => collection.Should().EndWith((IEnumerable)null); @@ -72,7 +77,7 @@ public void When_collection_does_not_end_with_a_null_sequence_it_should_throw() public void When_collection_does_not_end_with_a_null_sequence_using_a_comparer_it_should_throw() { // Arrange - var collection = new[] { "john" }; + string[] collection = ["john"]; // Act Action act = () => collection.Should().EndWith((IEnumerable)null, (_, _) => true); @@ -87,10 +92,10 @@ public void When_collection_does_not_end_with_a_specific_element_in_a_sequence_using_custom_equality_comparison_it_should_throw() { // Arrange - var collection = new[] { "john", "bill", "jane", "mike" }; + string[] collection = ["john", "bill", "jane", "mike"]; // Act - Action act = () => collection.Should().EndWith(new[] { "bill", "ryan", "mike" }, + Action act = () => collection.Should().EndWith(["bill", "ryan", "mike"], (s1, s2) => string.Equals(s1, s2, StringComparison.Ordinal), "of some reason"); // Assert @@ -102,7 +107,7 @@ public void public void When_collection_ends_with_the_specific_element_it_should_not_throw() { // Arrange - var collection = new[] { "john", "jane", "mike" }; + string[] collection = ["john", "jane", "mike"]; // Act Action act = () => collection.Should().EndWith("mike"); @@ -115,10 +120,10 @@ public void When_collection_ends_with_the_specific_element_it_should_not_throw() public void When_collection_ends_with_the_specific_sequence_of_elements_it_should_not_throw() { // Arrange - var collection = new[] { "john", "bill", "jane", "mike" }; + string[] collection = ["john", "bill", "jane", "mike"]; // Act - Action act = () => collection.Should().EndWith(new[] { "jane", "mike" }); + Action act = () => collection.Should().EndWith(["jane", "mike"]); // Assert act.Should().NotThrow(); @@ -129,10 +134,10 @@ public void When_collection_ends_with_the_specific_sequence_of_elements_using_custom_equality_comparison_it_should_not_throw() { // Arrange - var collection = new[] { "john", "bill", "jane", "mike" }; + string[] collection = ["john", "bill", "jane", "mike"]; // Act - Action act = () => collection.Should().EndWith(new[] { "JaNe", "mIkE" }, + Action act = () => collection.Should().EndWith(["JaNe", "mIkE"], (s1, s2) => string.Equals(s1, s2, StringComparison.OrdinalIgnoreCase)); // Assert @@ -143,7 +148,7 @@ public void public void When_collection_ends_with_the_specific_null_element_it_should_not_throw() { // Arrange - var collection = new[] { "jane", "mike", null }; + string[] collection = ["jane", "mike", null]; // Act Action act = () => collection.Should().EndWith((string)null); @@ -156,10 +161,10 @@ public void When_collection_ends_with_the_specific_null_element_it_should_not_th public void When_collection_ends_with_the_specific_sequence_with_null_elements_it_should_not_throw() { // Arrange - var collection = new[] { "john", "bill", "jane", null, "mike", null }; + string[] collection = ["john", "bill", "jane", null, "mike", null]; // Act - Action act = () => collection.Should().EndWith(new[] { "jane", null, "mike", null }); + Action act = () => collection.Should().EndWith(["jane", null, "mike", null]); // Assert act.Should().NotThrow(); @@ -170,10 +175,10 @@ public void When_collection_ends_with_the_specific_sequence_with_null_elements_using_custom_equality_comparison_it_should_not_throw() { // Arrange - var collection = new[] { "john", "bill", "jane", null, "mike", null }; + string[] collection = ["john", "bill", "jane", null, "mike", null]; // Act - Action act = () => collection.Should().EndWith(new[] { "JaNe", null, "mIkE", null }, + Action act = () => collection.Should().EndWith(["JaNe", null, "mIkE", null], (s1, s2) => string.Equals(s1, s2, StringComparison.OrdinalIgnoreCase)); // Assert @@ -184,7 +189,7 @@ public void public void When_collection_ends_with_null_but_that_wasnt_expected_it_should_throw() { // Arrange - var collection = new[] { "jane", "mike", null }; + string[] collection = ["jane", "mike", null]; // Act Action act = () => collection.Should().EndWith("john"); @@ -216,7 +221,7 @@ public void When_null_collection_is_expected_to_end_with_an_element_it_should_th public void When_non_empty_collection_ends_with_the_empty_sequence_it_should_not_throw() { // Arrange - var collection = new[] { "jane", "mike" }; + string[] collection = ["jane", "mike"]; // Act Action act = () => collection.Should().EndWith(new string[] { }); @@ -229,7 +234,7 @@ public void When_non_empty_collection_ends_with_the_empty_sequence_it_should_not public void When_empty_collection_ends_with_the_empty_sequence_it_should_not_throw() { // Arrange - var collection = new string[] { }; + string[] collection = []; // Act Action act = () => collection.Should().EndWith(new string[] { }); diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.Equal.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.Equal.cs index 0f87d4b6fa..18fd6120af 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.Equal.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.Equal.cs @@ -17,8 +17,8 @@ public class Equal public void Should_succeed_when_asserting_collection_is_equal_to_the_same_collection() { // Arrange - var collection1 = new[] { 1, 2, 3 }; - var collection2 = new[] { 1, 2, 3 }; + int[] collection1 = [1, 2, 3]; + int[] collection2 = [1, 2, 3]; // Act / Assert collection1.Should().Equal(collection2); @@ -28,7 +28,7 @@ public void Should_succeed_when_asserting_collection_is_equal_to_the_same_collec public void Should_succeed_when_asserting_collection_is_equal_to_the_same_list_of_elements() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act / Assert collection.Should().Equal(1, 2, 3); @@ -65,8 +65,8 @@ public void When_two_collections_containing_nulls_are_equal_it_should_not_throw( public void When_two_collections_are_not_equal_because_one_item_differs_it_should_throw_using_the_reason() { // Arrange - var collection1 = new[] { 1, 2, 3 }; - var collection2 = new[] { 1, 2, 5 }; + int[] collection1 = [1, 2, 3]; + int[] collection2 = [1, 2, 5]; // Act Action act = () => collection1.Should().Equal(collection2, "because we want to test the failure {0}", "message"); @@ -81,8 +81,8 @@ public void When_two_collections_are_not_equal_because_the_actual_collection_contains_more_items_it_should_throw_using_the_reason() { // Arrange - var collection1 = new[] { 1, 2, 3 }; - var collection2 = new[] { 1, 2 }; + int[] collection1 = [1, 2, 3]; + int[] collection2 = [1, 2]; // Act Action act = () => collection1.Should().Equal(collection2, "because we want to test the failure {0}", "message"); @@ -97,8 +97,8 @@ public void When_two_collections_are_not_equal_because_the_actual_collection_contains_less_items_it_should_throw_using_the_reason() { // Arrange - var collection1 = new[] { 1, 2, 3 }; - var collection2 = new[] { 1, 2, 3, 4 }; + int[] collection1 = [1, 2, 3]; + int[] collection2 = [1, 2, 3, 4]; // Act Action act = () => collection1.Should().Equal(collection2, "because we want to test the failure {0}", "message"); @@ -112,8 +112,8 @@ public void public void When_two_multidimensional_collections_are_not_equal_and_it_should_format_the_collections_properly() { // Arrange - var collection1 = new[] { new[] { 1, 2 }, new[] { 3, 4 } }; - var collection2 = new[] { new[] { 5, 6 }, new[] { 7, 8 } }; + object[][] collection1 = [[1, 2], [3, 4]]; + object[][] collection2 = [[5, 6], [7, 8]]; // Act Action act = () => collection1.Should().Equal(collection2); @@ -128,7 +128,7 @@ public void When_asserting_collections_to_be_equal_but_subject_collection_is_nul { // Arrange int[] collection = null; - var collection1 = new[] { 1, 2, 3 }; + int[] collection1 = [1, 2, 3]; // Act Action act = () => @@ -143,7 +143,7 @@ public void When_asserting_collections_to_be_equal_but_subject_collection_is_nul public void When_asserting_collections_to_be_equal_but_expected_collection_is_null_it_should_throw() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; int[] collection1 = null; // Act @@ -160,8 +160,8 @@ public void When_asserting_collections_to_be_equal_but_expected_collection_is_nu public void When_an_empty_collection_is_compared_for_equality_to_a_non_empty_collection_it_should_throw() { // Arrange - var collection1 = new int[0]; - var collection2 = new[] { 1, 2, 3 }; + int[] collection1 = []; + int[] collection2 = [1, 2, 3]; // Act Action act = () => collection1.Should().Equal(collection2); @@ -175,8 +175,8 @@ public void When_an_empty_collection_is_compared_for_equality_to_a_non_empty_col public void When_a_non_empty_collection_is_compared_for_equality_to_an_empty_collection_it_should_throw() { // Arrange - var collection1 = new[] { 1, 2, 3 }; - var collection2 = new int[0]; + int[] collection1 = [1, 2, 3]; + int[] collection2 = []; // Act Action act = () => collection1.Should().Equal(collection2); @@ -250,8 +250,8 @@ public void When_both_collections_are_empty_it_should_them_as_equal() public void When_asserting_identical_collections_to_be_equal_it_should_enumerate_the_subject_only_once() { // Arrange - var actual = new CountingGenericEnumerable(new[] { 1, 2, 3 }); - var expected = new[] { 1, 2, 3 }; + var actual = new CountingGenericEnumerable([1, 2, 3]); + int[] expected = [1, 2, 3]; // Act actual.Should().Equal(expected); @@ -264,8 +264,8 @@ public void When_asserting_identical_collections_to_be_equal_it_should_enumerate public void When_asserting_identical_collections_to_not_be_equal_it_should_enumerate_the_subject_only_once() { // Arrange - var actual = new CountingGenericEnumerable(new[] { 1, 2, 3 }); - var expected = new[] { 1, 2, 3 }; + var actual = new CountingGenericEnumerable([1, 2, 3]); + int[] expected = [1, 2, 3]; // Act try @@ -285,8 +285,8 @@ public void When_asserting_identical_collections_to_not_be_equal_it_should_enume public void When_asserting_different_collections_to_be_equal_it_should_enumerate_the_subject_once() { // Arrange - var actual = new CountingGenericEnumerable(new[] { 1, 2, 3 }); - var expected = new[] { 1, 2, 4 }; + var actual = new CountingGenericEnumerable([1, 2, 3]); + int[] expected = [1, 2, 4]; // Act try @@ -306,8 +306,8 @@ public void When_asserting_different_collections_to_be_equal_it_should_enumerate public void When_asserting_different_collections_to_not_be_equal_it_should_enumerate_the_subject_only_once() { // Arrange - var actual = new CountingGenericEnumerable(new[] { 1, 2, 3 }); - var expected = new[] { 1, 2, 4 }; + var actual = new CountingGenericEnumerable([1, 2, 3]); + int[] expected = [1, 2, 4]; // Act actual.Should().NotEqual(expected); @@ -321,7 +321,7 @@ public void When_asserting_equality_with_a_collection_built_from_params_arguments_that_are_assignable_to_the_subjects_type_parameter_it_should_succeed_by_treating_the_arguments_as_of_that_type() { // Arrange - byte[] byteArray = { 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10 }; + byte[] byteArray = [0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10]; // Act Action act = () => byteArray.Should().Equal(0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10); @@ -337,8 +337,8 @@ public class NotEqual public void Should_succeed_when_asserting_collection_is_not_equal_to_a_different_collection() { // Arrange - var collection1 = new[] { 1, 2, 3 }; - var collection2 = new[] { 3, 1, 2 }; + int[] collection1 = [1, 2, 3]; + int[] collection2 = [3, 1, 2]; // Act / Assert collection1.Should() @@ -349,8 +349,8 @@ public void Should_succeed_when_asserting_collection_is_not_equal_to_a_different public void When_two_equal_collections_are_not_expected_to_be_equal_it_should_throw() { // Arrange - var collection1 = new[] { 1, 2, 3 }; - var collection2 = new[] { 1, 2, 3 }; + int[] collection1 = [1, 2, 3]; + int[] collection2 = [1, 2, 3]; // Act Action act = () => collection1.Should().NotEqual(collection2); @@ -364,8 +364,8 @@ public void When_two_equal_collections_are_not_expected_to_be_equal_it_should_th public void When_two_equal_collections_are_not_expected_to_be_equal_it_should_report_a_clear_explanation() { // Arrange - var collection1 = new[] { 1, 2, 3 }; - var collection2 = new[] { 1, 2, 3 }; + int[] collection1 = [1, 2, 3]; + int[] collection2 = [1, 2, 3]; // Act Action act = () => collection1.Should().NotEqual(collection2, "because we want to test the failure {0}", "message"); @@ -380,7 +380,7 @@ public void When_asserting_collections_not_to_be_equal_subject_but_collection_is { // Arrange int[] collection = null; - var collection1 = new[] { 1, 2, 3 }; + int[] collection1 = [1, 2, 3]; // Act Action act = () => @@ -398,7 +398,7 @@ public void When_asserting_collections_not_to_be_equal_subject_but_collection_is public void When_asserting_collections_not_to_be_equal_but_expected_collection_is_null_it_should_throw() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; int[] collection1 = null; // Act @@ -414,7 +414,7 @@ public void When_asserting_collections_not_to_be_equal_but_expected_collection_i [Fact] public void When_asserting_collections_not_to_be_equal_but_both_collections_reference_the_same_object_it_should_throw() { - var collection1 = new[] { "one", "two", "three" }; + string[] collection1 = ["one", "two", "three"]; var collection2 = collection1; // Act diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveCount.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveCount.cs index 1b08a6d9b0..91f58b5fc1 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveCount.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveCount.cs @@ -16,7 +16,7 @@ public class HaveCount public void Should_succeed_when_asserting_collection_has_a_count_that_equals_the_number_of_items() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act / Assert collection.Should().HaveCount(3); @@ -26,7 +26,7 @@ public void Should_succeed_when_asserting_collection_has_a_count_that_equals_the public void Should_fail_when_asserting_collection_has_a_count_that_is_different_from_the_number_of_items() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act Action act = () => collection.Should().HaveCount(4); @@ -40,7 +40,7 @@ public void When_collection_has_a_count_that_is_different_from_the_number_of_items_it_should_fail_with_descriptive_message_() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act Action action = () => collection.Should().HaveCount(4, "because we want to test the failure {0}", "message"); @@ -55,17 +55,17 @@ public void public void When_collection_has_a_count_larger_than_the_minimum_it_should_not_throw() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act / Assert collection.Should().HaveCount(c => c >= 3); } [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 - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act Action act = () => @@ -75,15 +75,14 @@ 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] public void When_collection_has_a_count_that_not_matches_the_predicate_it_should_throw() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act Action act = () => collection.Should().HaveCount(c => c >= 4, "a minimum of 4 is required"); @@ -97,7 +96,7 @@ public void When_collection_has_a_count_that_not_matches_the_predicate_it_should public void When_collection_count_is_matched_against_a_null_predicate_it_should_throw() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act Action act = () => collection.Should().HaveCount(null); @@ -147,7 +146,7 @@ public void When_collection_count_is_matched_against_a_predicate_and_collection_ public void When_collection_count_is_matched_against_a_predicate_it_should_not_throw() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act Action act = () => collection.Should().HaveCount(c => (c % 2) == 1); @@ -160,7 +159,7 @@ public void When_collection_count_is_matched_against_a_predicate_it_should_not_t public void When_collection_count_is_matched_against_a_predicate_it_should_throw() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act Action act = () => collection.Should().HaveCount(c => (c % 2) == 0); @@ -173,7 +172,7 @@ public void When_collection_count_is_matched_against_a_predicate_it_should_throw public void When_counting_generic_enumerable_it_should_enumerate() { // Arrange - var enumerable = new CountingGenericEnumerable(new[] { 1, 2, 3 }); + var enumerable = new CountingGenericEnumerable([1, 2, 3]); // Act enumerable.Should().HaveCount(3); @@ -186,7 +185,7 @@ public void When_counting_generic_enumerable_it_should_enumerate() public void When_counting_generic_collection_it_should_not_enumerate() { // Arrange - var collection = new CountingGenericCollection(new[] { 1, 2, 3 }); + var collection = new CountingGenericCollection([1, 2, 3]); // Act collection.Should().HaveCount(3); @@ -203,7 +202,7 @@ public class NotHaveCount public void Should_succeed_when_asserting_collection_has_a_count_different_from_the_number_of_items() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act / Assert collection.Should().NotHaveCount(2); @@ -213,7 +212,7 @@ public void Should_succeed_when_asserting_collection_has_a_count_different_from_ public void Should_fail_when_asserting_collection_has_a_count_that_equals_the_number_of_items() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act Action act = () => collection.Should().NotHaveCount(3); @@ -226,7 +225,7 @@ public void Should_fail_when_asserting_collection_has_a_count_that_equals_the_nu public void When_collection_has_a_count_that_equals_than_the_number_of_items_it_should_fail_with_descriptive_message_() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act Action action = () => collection.Should().NotHaveCount(3, "because we want to test the failure {0}", "message"); diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveCountGreaterThan.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveCountGreaterThan.cs index ee4f8b9b85..f8ccf91f8f 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveCountGreaterThan.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveCountGreaterThan.cs @@ -17,7 +17,7 @@ public class HaveCountGreaterThan public void Should_succeed_when_asserting_collection_has_a_count_greater_than_less_the_number_of_items() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act / Assert collection.Should().HaveCountGreaterThan(2); @@ -27,7 +27,7 @@ public void Should_succeed_when_asserting_collection_has_a_count_greater_than_le public void Should_fail_when_asserting_collection_has_a_count_greater_than_the_number_of_items() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act Action act = () => collection.Should().HaveCountGreaterThan(3); @@ -40,7 +40,7 @@ public void Should_fail_when_asserting_collection_has_a_count_greater_than_the_n public void When_collection_has_a_count_greater_than_the_number_of_items_it_should_fail_with_descriptive_message_() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act Action action = () => diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveCountGreaterThanOrEqualTo.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveCountGreaterThanOrEqualTo.cs index a6bd1f0e3d..753f249016 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveCountGreaterThanOrEqualTo.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveCountGreaterThanOrEqualTo.cs @@ -16,7 +16,7 @@ public class HaveCountGreaterThanOrEqualTo public void Should_succeed_when_asserting_collection_has_a_count_greater_than_or_equal_to_less_the_number_of_items() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act / Assert collection.Should().HaveCountGreaterThanOrEqualTo(3); @@ -26,7 +26,7 @@ public void Should_succeed_when_asserting_collection_has_a_count_greater_than_or public void Should_fail_when_asserting_collection_has_a_count_greater_than_or_equal_to_the_number_of_items() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act Action act = () => collection.Should().HaveCountGreaterThanOrEqualTo(4); @@ -40,7 +40,7 @@ public void When_collection_has_a_count_greater_than_or_equal_to_the_number_of_items_it_should_fail_with_descriptive_message_() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act Action action = () => @@ -69,5 +69,15 @@ public void When_collection_count_is_greater_than_or_equal_to_and_collection_is_ act.Should().Throw() .WithMessage("*at least*1*we want to test the behaviour with a null subject*found *"); } + + [Fact] + public void Chaining_after_one_assertion() + { + // Arrange + int[] collection = [1, 2, 3]; + + // Act / Assert + collection.Should().HaveCountGreaterThanOrEqualTo(3).And.Contain(1); + } } } diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveCountLessThan.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveCountLessThan.cs index 190002e44a..7f9af0675a 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveCountLessThan.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveCountLessThan.cs @@ -16,7 +16,7 @@ public class HaveCountLessThan public void Should_succeed_when_asserting_collection_has_a_count_less_than_less_the_number_of_items() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act / Assert collection.Should().HaveCountLessThan(4); @@ -26,7 +26,7 @@ public void Should_succeed_when_asserting_collection_has_a_count_less_than_less_ public void Should_fail_when_asserting_collection_has_a_count_less_than_the_number_of_items() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act Action act = () => collection.Should().HaveCountLessThan(3); @@ -39,7 +39,7 @@ public void Should_fail_when_asserting_collection_has_a_count_less_than_the_numb public void When_collection_has_a_count_less_than_the_number_of_items_it_should_fail_with_descriptive_message_() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act Action action = () => collection.Should().HaveCountLessThan(3, "because we want to test the failure {0}", "message"); diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveCountLessThanOrEqualTo.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveCountLessThanOrEqualTo.cs index 3ce98f3d43..7d249f8deb 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveCountLessThanOrEqualTo.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveCountLessThanOrEqualTo.cs @@ -16,7 +16,7 @@ public class HaveCountLessThanOrEqualTo public void Should_succeed_when_asserting_collection_has_a_count_less_than_or_equal_to_less_the_number_of_items() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act / Assert collection.Should().HaveCountLessThanOrEqualTo(3); @@ -26,7 +26,7 @@ public void Should_succeed_when_asserting_collection_has_a_count_less_than_or_eq public void Should_fail_when_asserting_collection_has_a_count_less_than_or_equal_to_the_number_of_items() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act Action act = () => collection.Should().HaveCountLessThanOrEqualTo(2); @@ -40,7 +40,7 @@ public void When_collection_has_a_count_less_than_or_equal_to_the_number_of_items_it_should_fail_with_descriptive_message_() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act Action action = () => @@ -69,5 +69,15 @@ public void When_collection_count_is_less_than_or_equal_to_and_collection_is_nul act.Should().Throw() .WithMessage("*at most*1*we want to test the behaviour with a null subject*found *"); } + + [Fact] + public void Chaining_after_one_assertion() + { + // Arrange + int[] collection = [1, 2, 3]; + + // Act / Assert + collection.Should().HaveCountLessThanOrEqualTo(3).And.Contain(1); + } } } diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveElementAt.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveElementAt.cs index 0f3765f8a3..fa9c3c0a6e 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveElementAt.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveElementAt.cs @@ -16,17 +16,31 @@ public class HaveElementAt public void When_collection_has_expected_element_at_specific_index_it_should_not_throw() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act / Assert 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() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act Action act = () => collection.Should().HaveElementAt(1, 3, "we put it {0}", "there"); @@ -40,7 +54,7 @@ public void When_collection_does_not_have_the_expected_element_at_specific_index public void When_collection_does_not_have_an_element_at_the_specific_index_it_should_throw() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act Action act = () => collection.Should().HaveElementAt(4, 3, "we put it {0}", "there"); diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveElementPreceding.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveElementPreceding.cs index e4d736cae3..001364c140 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveElementPreceding.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveElementPreceding.cs @@ -19,7 +19,7 @@ public class HaveElementPreceding public void When_collection_has_the_correct_element_preceding_another_it_should_not_throw() { // Arrange - var collection = new[] { "cris", "mick", "john" }; + string[] collection = ["cris", "mick", "john"]; // Act Action act = () => collection.Should().HaveElementPreceding("mick", "cris"); @@ -33,7 +33,7 @@ public void When_collection_has_the_correct_element_preceding_another_it_should_ public void When_collection_has_the_wrong_element_preceding_another_it_should_not_throw() { // Arrange - var collection = new[] { "cris", "mick", "john" }; + string[] collection = ["cris", "mick", "john"]; // Act Action act = () => collection.Should().HaveElementPreceding("john", "cris", "because of some reason"); @@ -48,7 +48,7 @@ public void When_collection_has_the_wrong_element_preceding_another_it_should_no public void When_nothing_is_preceding_an_element_it_should_throw() { // Arrange - var collection = new[] { "cris", "mick", "john" }; + string[] collection = ["cris", "mick", "john"]; // Act Action act = () => collection.Should().HaveElementPreceding("cris", "jane"); @@ -77,7 +77,7 @@ public void When_expecting_an_element_to_precede_another_but_collection_is_empty public void When_a_null_element_is_preceding_another_element_it_should_not_throw() { // Arrange - var collection = new[] { null, "mick", "john" }; + string[] collection = [null, "mick", "john"]; // Act Action act = () => collection.Should().HaveElementPreceding("mick", null); @@ -91,7 +91,7 @@ public void When_a_null_element_is_preceding_another_element_it_should_not_throw public void When_a_null_element_is_not_preceding_another_element_it_should_throw() { // Arrange - var collection = new[] { "cris", "mick", "john" }; + string[] collection = ["cris", "mick", "john"]; // Act Action act = () => collection.Should().HaveElementPreceding("mick", null); @@ -105,7 +105,7 @@ public void When_a_null_element_is_not_preceding_another_element_it_should_throw public void When_an_element_is_preceding_a_null_element_it_should_not_throw() { // Arrange - var collection = new[] { "mick", null, "john" }; + string[] collection = ["mick", null, "john"]; // Act Action act = () => collection.Should().HaveElementPreceding(null, "mick"); @@ -118,7 +118,7 @@ public void When_an_element_is_preceding_a_null_element_it_should_not_throw() public void When_an_element_is_not_preceding_a_null_element_it_should_throw() { // Arrange - var collection = new[] { "mick", null, "john" }; + string[] collection = ["mick", null, "john"]; // Act Action act = () => collection.Should().HaveElementPreceding(null, "cris"); diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveElementSucceeding.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveElementSucceeding.cs index 61981764a5..038dcf0f8e 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveElementSucceeding.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveElementSucceeding.cs @@ -19,7 +19,7 @@ public class HaveElementSucceeding public void When_collection_has_the_correct_element_succeeding_another_it_should_not_throw() { // Arrange - var collection = new[] { "cris", "mick", "john" }; + string[] collection = ["cris", "mick", "john"]; // Act Action act = () => collection.Should().HaveElementSucceeding("cris", "mick"); @@ -33,7 +33,7 @@ public void When_collection_has_the_correct_element_succeeding_another_it_should public void When_collection_has_the_wrong_element_succeeding_another_it_should_not_throw() { // Arrange - var collection = new[] { "cris", "mick", "john" }; + string[] collection = ["cris", "mick", "john"]; // Act Action act = () => collection.Should().HaveElementSucceeding("mick", "cris", "because of some reason"); @@ -48,7 +48,7 @@ public void When_collection_has_the_wrong_element_succeeding_another_it_should_n public void When_nothing_is_succeeding_an_element_it_should_throw() { // Arrange - var collection = new[] { "cris", "mick", "john" }; + string[] collection = ["cris", "mick", "john"]; // Act Action act = () => collection.Should().HaveElementSucceeding("john", "jane"); @@ -76,7 +76,7 @@ public void When_expecting_an_element_to_succeed_another_but_the_collection_is_e public void When_a_null_element_is_succeeding_another_element_it_should_not_throw() { // Arrange - var collection = new[] { "mick", null, "john" }; + string[] collection = ["mick", null, "john"]; // Act Action act = () => collection.Should().HaveElementSucceeding("mick", null); @@ -89,7 +89,7 @@ public void When_a_null_element_is_succeeding_another_element_it_should_not_thro public void When_a_null_element_is_not_succeeding_another_element_it_should_throw() { // Arrange - var collection = new[] { "cris", "mick", "john" }; + string[] collection = ["cris", "mick", "john"]; // Act Action act = () => collection.Should().HaveElementSucceeding("mick", null); @@ -103,7 +103,7 @@ public void When_a_null_element_is_not_succeeding_another_element_it_should_thro public void When_an_element_is_succeeding_a_null_element_it_should_not_throw() { // Arrange - var collection = new[] { "mick", null, "john" }; + string[] collection = ["mick", null, "john"]; // Act Action act = () => collection.Should().HaveElementSucceeding(null, "john"); @@ -116,7 +116,7 @@ public void When_an_element_is_succeeding_a_null_element_it_should_not_throw() public void When_an_element_is_not_succeeding_a_null_element_it_should_throw() { // Arrange - var collection = new[] { "mick", null, "john" }; + string[] collection = ["mick", null, "john"]; // Act Action act = () => collection.Should().HaveElementSucceeding(null, "cris"); diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveSameCount.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveSameCount.cs index 43a3947f6f..760ee56397 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveSameCount.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.HaveSameCount.cs @@ -16,8 +16,8 @@ public class HaveSameCount public void When_both_collections_have_the_same_number_elements_it_should_succeed() { // Arrange - var firstCollection = new[] { 1, 2, 3 }; - var secondCollection = new[] { 4, 5, 6 }; + int[] firstCollection = [1, 2, 3]; + int[] secondCollection = [4, 5, 6]; // Act / Assert firstCollection.Should().HaveSameCount(secondCollection); @@ -27,8 +27,8 @@ public void When_both_collections_have_the_same_number_elements_it_should_succee public void When_both_collections_do_not_have_the_same_number_of_elements_it_should_fail() { // Arrange - var firstCollection = new[] { 1, 2, 3 }; - var secondCollection = new[] { 4, 6 }; + int[] firstCollection = [1, 2, 3]; + int[] secondCollection = [4, 6]; // Act Action act = () => firstCollection.Should().HaveSameCount(secondCollection); @@ -42,8 +42,8 @@ public void When_both_collections_do_not_have_the_same_number_of_elements_it_sho public void When_comparing_item_counts_and_a_reason_is_specified_it_should_it_in_the_exception() { // Arrange - var firstCollection = new[] { 1, 2, 3 }; - var secondCollection = new[] { 4, 6 }; + int[] firstCollection = [1, 2, 3]; + int[] secondCollection = [4, 6]; // Act Action act = () => firstCollection.Should().HaveSameCount(secondCollection, "we want to test the {0}", "reason"); @@ -58,7 +58,7 @@ public void When_asserting_collections_to_have_same_count_against_null_collectio { // Arrange int[] collection = null; - var collection1 = new[] { 1, 2, 3 }; + int[] collection1 = [1, 2, 3]; // Act Action act = () => @@ -76,7 +76,7 @@ public void When_asserting_collections_to_have_same_count_against_null_collectio public void When_asserting_collections_to_have_same_count_against_an_other_null_collection_it_should_throw() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; int[] otherCollection = null; // Act @@ -94,8 +94,8 @@ public class NotHaveSameCount public void When_asserting_not_same_count_and_collections_have_different_number_elements_it_should_succeed() { // Arrange - var firstCollection = new[] { 1, 2, 3 }; - var secondCollection = new[] { 4, 6 }; + int[] firstCollection = [1, 2, 3]; + int[] secondCollection = [4, 6]; // Act / Assert firstCollection.Should().NotHaveSameCount(secondCollection); @@ -105,8 +105,8 @@ public void When_asserting_not_same_count_and_collections_have_different_number_ public void When_asserting_not_same_count_and_both_collections_have_the_same_number_elements_it_should_fail() { // Arrange - var firstCollection = new[] { 1, 2, 3 }; - var secondCollection = new[] { 4, 5, 6 }; + int[] firstCollection = [1, 2, 3]; + int[] secondCollection = [4, 5, 6]; // Act Action act = () => firstCollection.Should().NotHaveSameCount(secondCollection); @@ -120,8 +120,8 @@ public void When_asserting_not_same_count_and_both_collections_have_the_same_num public void When_comparing_not_same_item_counts_and_a_reason_is_specified_it_should_it_in_the_exception() { // Arrange - var firstCollection = new[] { 1, 2, 3 }; - var secondCollection = new[] { 4, 5, 6 }; + int[] firstCollection = [1, 2, 3]; + int[] secondCollection = [4, 5, 6]; // Act Action act = () => firstCollection.Should().NotHaveSameCount(secondCollection, "we want to test the {0}", "reason"); @@ -136,7 +136,7 @@ public void When_asserting_collections_to_not_have_same_count_against_null_colle { // Arrange int[] collection = null; - var collection1 = new[] { 1, 2, 3 }; + int[] collection1 = [1, 2, 3]; // Act Action act = () => @@ -154,7 +154,7 @@ public void When_asserting_collections_to_not_have_same_count_against_null_colle public void When_asserting_collections_to_not_have_same_count_against_an_other_null_collection_it_should_throw() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; int[] otherCollection = null; // Act @@ -170,7 +170,7 @@ public void When_asserting_collections_to_not_have_same_count_but_both_collections_references_the_same_object_it_should_throw() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; var collection1 = collection; // Act diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.IntersectWith.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.IntersectWith.cs index 69aaffb3be..574743fff5 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.IntersectWith.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.IntersectWith.cs @@ -17,8 +17,8 @@ public class IntersectWith public void When_asserting_the_items_in_an_two_intersecting_collections_intersect_it_should_succeed() { // Arrange - var collection = new[] { 1, 2, 3 }; - var otherCollection = new[] { 3, 4, 5 }; + int[] collection = [1, 2, 3]; + int[] otherCollection = [3, 4, 5]; // Act / Assert collection.Should().IntersectWith(otherCollection); @@ -28,8 +28,8 @@ public void When_asserting_the_items_in_an_two_intersecting_collections_intersec public void When_asserting_the_items_in_an_two_non_intersecting_collections_intersect_it_should_throw() { // Arrange - var collection = new[] { 1, 2, 3 }; - var otherCollection = new[] { 4, 5 }; + int[] collection = [1, 2, 3]; + int[] otherCollection = [4, 5]; // Act Action action = () => collection.Should().IntersectWith(otherCollection, "they should share items"); @@ -50,7 +50,7 @@ public void When_collection_is_null_then_intersect_with_should_fail() Action act = () => { using var _ = new AssertionScope(); - collection.Should().IntersectWith(new[] { 4, 5 }, "we want to test the failure {0}", "message"); + collection.Should().IntersectWith([4, 5], "we want to test the failure {0}", "message"); }; // Assert @@ -65,8 +65,8 @@ public class NotIntersectWith public void When_asserting_the_items_in_an_two_non_intersecting_collections_do_not_intersect_it_should_succeed() { // Arrange - var collection = new[] { 1, 2, 3 }; - var otherCollection = new[] { 4, 5 }; + int[] collection = [1, 2, 3]; + int[] otherCollection = [4, 5]; // Act / Assert collection.Should().NotIntersectWith(otherCollection); @@ -76,8 +76,8 @@ public void When_asserting_the_items_in_an_two_non_intersecting_collections_do_n public void When_asserting_the_items_in_an_two_intersecting_collections_do_not_intersect_it_should_throw() { // Arrange - var collection = new[] { 1, 2, 3 }; - var otherCollection = new[] { 2, 3, 4 }; + int[] collection = [1, 2, 3]; + int[] otherCollection = [2, 3, 4]; // Act Action action = () => collection.Should().NotIntersectWith(otherCollection, "they should not share items"); @@ -92,7 +92,7 @@ public void When_asserting_the_items_in_an_two_intersecting_collections_do_not_i public void When_asserting_collection_to_not_intersect_with_same_collection_it_should_throw() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; var otherCollection = collection; // Act @@ -114,7 +114,7 @@ public void When_collection_is_null_then_not_intersect_with_should_fail() Action act = () => { using var _ = new AssertionScope(); - collection.Should().NotIntersectWith(new[] { 4, 5 }, "we want to test the failure {0}", "message"); + collection.Should().NotIntersectWith([4, 5], "we want to test the failure {0}", "message"); }; // Assert diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.NotContainItemsAssignableTo.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.NotContainItemsAssignableTo.cs index 3981430166..66f3a9c02d 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.NotContainItemsAssignableTo.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.NotContainItemsAssignableTo.cs @@ -12,7 +12,7 @@ public class NotContainItemsAssignableTo public void Succeeds_when_the_collection_does_not_contain_items_of_the_unexpected_type() { // Arrange - var collection = new[] { "1", "2", "3" }; + string[] collection = ["1", "2", "3"]; // Act / Assert collection.Should().NotContainItemsAssignableTo(); @@ -43,7 +43,7 @@ public void Throws_when_the_collection_contains_an_item_of_the_unexpected_type() public void Succeeds_when_collection_is_empty() { // Arrange - var collection = Array.Empty(); + int[] collection = []; // Act / Assert collection.Should().NotContainItemsAssignableTo(); @@ -53,7 +53,7 @@ public void Succeeds_when_collection_is_empty() public void Throws_when_the_passed_type_argument_is_null() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act var act = () => collection.Should().NotContainItemsAssignableTo(null); @@ -62,6 +62,16 @@ public void Throws_when_the_passed_type_argument_is_null() act.Should().Throw(); } + [Fact] + public void Succeed_when_type_as_parameter_is_valid_type() + { + // Arrange + int[] collection = [1, 2, 3]; + + // Act / Assert + collection.Should().NotContainItemsAssignableTo(typeof(string)); + } + [Fact] public void Throws_when_the_collection_is_null() { diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.NotContainNulls.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.NotContainNulls.cs index 1243c9cc28..af3d47a559 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.NotContainNulls.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.NotContainNulls.cs @@ -17,7 +17,7 @@ public class NotContainNulls public void When_collection_does_not_contain_nulls_it_should_not_throw() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act / Assert collection.Should().NotContainNulls(); @@ -27,7 +27,7 @@ public void When_collection_does_not_contain_nulls_it_should_not_throw() public void When_collection_contains_nulls_that_are_unexpected_it_should_throw() { // Arrange - var collection = new[] { new object(), null }; + object[] collection = [new object(), null]; // Act Action act = () => collection.Should().NotContainNulls("because they are {0}", "evil"); @@ -41,7 +41,7 @@ public void When_collection_contains_nulls_that_are_unexpected_it_should_throw() public void When_collection_contains_nulls_that_are_unexpected_it_supports_chaining() { // Arrange - var collection = new[] { new object(), null }; + object[] collection = [new object(), null]; // Act Action act = () => @@ -59,7 +59,7 @@ public void When_collection_contains_nulls_that_are_unexpected_it_supports_chain public void When_collection_contains_multiple_nulls_that_are_unexpected_it_should_throw() { // Arrange - var collection = new[] { new object(), null, new object(), null }; + object[] collection = [new object(), null, new object(), null]; // Act Action act = () => collection.Should().NotContainNulls("because they are {0}", "evil"); @@ -73,7 +73,7 @@ public void When_collection_contains_multiple_nulls_that_are_unexpected_it_shoul public void When_collection_contains_multiple_nulls_that_are_unexpected_it_supports_chaining() { // Arrange - var collection = new[] { new object(), null, new object(), null }; + object[] collection = [new object(), null, new object(), null]; // Act Action act = () => @@ -105,7 +105,7 @@ public void When_asserting_collection_to_not_contain_nulls_but_collection_is_nul public void When_injecting_a_null_predicate_into_NotContainNulls_it_should_throw() { // Arrange - IEnumerable collection = new SomeClass[] { }; + IEnumerable collection = []; // Act Action act = () => collection.Should().NotContainNulls(predicate: null); @@ -119,12 +119,12 @@ public void When_injecting_a_null_predicate_into_NotContainNulls_it_should_throw public void When_collection_does_not_contain_nulls_with_a_predicate_it_should_not_throw() { // Arrange - IEnumerable collection = new[] - { + IEnumerable collection = + [ new SomeClass { Text = "one" }, new SomeClass { Text = "two" }, new SomeClass { Text = "three" } - }; + ]; // Act / Assert collection.Should().NotContainNulls(e => e.Text); @@ -134,11 +134,11 @@ public void When_collection_does_not_contain_nulls_with_a_predicate_it_should_no public void When_collection_contains_nulls_that_are_unexpected_with_a_predicate_it_should_throw() { // Arrange - IEnumerable collection = new[] - { + IEnumerable collection = + [ new SomeClass { Text = "" }, new SomeClass { Text = null } - }; + ]; // Act Action act = () => collection.Should().NotContainNulls(e => e.Text, "because they are {0}", "evil"); @@ -152,13 +152,13 @@ public void When_collection_contains_nulls_that_are_unexpected_with_a_predicate_ public void When_collection_contains_multiple_nulls_that_are_unexpected_with_a_predicate_it_should_throw() { // Arrange - IEnumerable collection = new[] - { + IEnumerable collection = + [ new SomeClass { Text = "" }, new SomeClass { Text = null }, new SomeClass { Text = "" }, new SomeClass { Text = null } - }; + ]; // Act Action act = () => collection.Should().NotContainNulls(e => e.Text, "because they are {0}", "evil"); diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.OnlyContain.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.OnlyContain.cs index c45061fe20..8a01b6dd19 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.OnlyContain.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.OnlyContain.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using FluentAssertions.Execution; using Xunit; using Xunit.Sdk; @@ -32,7 +31,7 @@ public void When_a_collection_does_not_contain_the_unexpected_items_it_should_no public void When_injecting_a_null_predicate_into_OnlyContain_it_should_throw() { // Arrange - IEnumerable collection = new int[] { }; + IEnumerable collection = []; // Act Action act = () => collection.Should().OnlyContain(predicate: null); @@ -46,7 +45,7 @@ public void When_injecting_a_null_predicate_into_OnlyContain_it_should_throw() public void When_a_collection_contains_items_not_matching_a_predicate_it_should_throw() { // Arrange - IEnumerable collection = new[] { 2, 12, 3, 11, 2 }; + IEnumerable collection = [2, 12, 3, 11, 2]; // Act Action act = () => collection.Should().OnlyContain(i => i <= 10, "10 is the maximum"); @@ -60,21 +59,17 @@ public void When_a_collection_contains_items_not_matching_a_predicate_it_should_ public void When_a_collection_is_empty_and_should_contain_only_items_matching_a_predicate_it_should_throw() { // Arrange - IEnumerable strings = Enumerable.Empty(); + IEnumerable strings = []; - // Act - Action act = () => strings.Should().OnlyContain(e => e.Length > 0); - - // Assert - act.Should().Throw() - .WithMessage("Expected strings to contain only items matching (e.Length > 0), but the collection is empty."); + // Act / Assert + strings.Should().OnlyContain(e => e.Length > 0); } [Fact] public void When_a_collection_contains_only_items_matching_a_predicate_it_should_not_throw() { // Arrange - IEnumerable collection = new[] { 2, 9, 3, 8, 2 }; + IEnumerable collection = [2, 9, 3, 8, 2]; // Act Action act = () => collection.Should().OnlyContain(i => i <= 10); diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.OnlyHaveUniqueItems.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.OnlyHaveUniqueItems.cs index f658ff8553..b07d84886b 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.OnlyHaveUniqueItems.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.OnlyHaveUniqueItems.cs @@ -17,7 +17,7 @@ public class OnlyHaveUniqueItems public void Should_succeed_when_asserting_collection_with_unique_items_contains_only_unique_items() { // Arrange - var collection = new[] { 1, 2, 3, 4 }; + int[] collection = [1, 2, 3, 4]; // Act / Assert collection.Should().OnlyHaveUniqueItems(); @@ -27,7 +27,7 @@ public void Should_succeed_when_asserting_collection_with_unique_items_contains_ public void When_a_collection_contains_duplicate_items_it_should_throw() { // Arrange - var collection = new[] { 1, 2, 3, 3 }; + int[] collection = [1, 2, 3, 3]; // Act Action act = () => collection.Should().OnlyHaveUniqueItems("{0} don't like {1}", "we", "duplicates"); @@ -41,7 +41,7 @@ public void When_a_collection_contains_duplicate_items_it_should_throw() public void When_a_collection_contains_duplicate_items_it_supports_chaining() { // Arrange - var collection = new[] { 1, 2, 3, 3 }; + int[] collection = [1, 2, 3, 3]; // Act Action act = () => @@ -59,7 +59,7 @@ public void When_a_collection_contains_duplicate_items_it_supports_chaining() public void When_a_collection_contains_multiple_duplicate_items_it_should_throw() { // Arrange - var collection = new[] { 1, 2, 2, 3, 3 }; + int[] collection = [1, 2, 2, 3, 3]; // Act Action act = () => collection.Should().OnlyHaveUniqueItems("{0} don't like {1}", "we", "duplicates"); @@ -73,7 +73,7 @@ public void When_a_collection_contains_multiple_duplicate_items_it_should_throw( public void When_a_collection_contains_multiple_duplicate_items_it_supports_chaining() { // Arrange - var collection = new[] { 1, 2, 2, 3, 3 }; + int[] collection = [1, 2, 2, 3, 3]; // Act Action act = () => @@ -109,7 +109,7 @@ public void When_asserting_collection_to_only_have_unique_items_but_collection_i public void When_injecting_a_null_predicate_into_OnlyHaveUniqueItems_it_should_throw() { // Arrange - IEnumerable collection = new SomeClass[] { }; + IEnumerable collection = []; // Act Action act = () => collection.Should().OnlyHaveUniqueItems(predicate: null); @@ -123,13 +123,13 @@ public void When_injecting_a_null_predicate_into_OnlyHaveUniqueItems_it_should_t public void Should_succeed_when_asserting_with_a_predicate_a_collection_with_unique_items_contains_only_unique_items() { // Arrange - IEnumerable collection = new[] - { + IEnumerable collection = + [ new SomeClass { Text = "one" }, new SomeClass { Text = "two" }, new SomeClass { Text = "three" }, new SomeClass { Text = "four" } - }; + ]; // Act / Assert collection.Should().OnlyHaveUniqueItems(e => e.Text); @@ -139,13 +139,13 @@ public void Should_succeed_when_asserting_with_a_predicate_a_collection_with_uni public void When_a_collection_contains_duplicate_items_with_predicate_it_should_throw() { // Arrange - IEnumerable collection = new[] - { + IEnumerable collection = + [ new SomeClass { Text = "one" }, new SomeClass { Text = "two" }, new SomeClass { Text = "three" }, new SomeClass { Text = "three" } - }; + ]; // Act Action act = () => collection.Should().OnlyHaveUniqueItems(e => e.Text, "{0} don't like {1}", "we", "duplicates"); @@ -159,14 +159,14 @@ public void When_a_collection_contains_duplicate_items_with_predicate_it_should_ public void When_a_collection_contains_multiple_duplicate_items_with_a_predicate_it_should_throw() { // Arrange - IEnumerable collection = new[] - { + IEnumerable collection = + [ new SomeClass { Text = "one" }, new SomeClass { Text = "two" }, new SomeClass { Text = "two" }, new SomeClass { Text = "three" }, new SomeClass { Text = "three" } - }; + ]; // Act Action act = () => collection.Should().OnlyHaveUniqueItems(e => e.Text, "{0} don't like {1}", "we", "duplicates"); @@ -177,17 +177,17 @@ 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 = new[] - { + IEnumerable collection = + [ new SomeClass { Text = "one", Number = 1 }, new SomeClass { Text = "two", Number = 2 }, new SomeClass { Text = "two", Number = 2 }, new SomeClass { Text = "three", Number = 3 }, new SomeClass { Text = "three", Number = 4 } - }; + ]; // Act Action act = () => @@ -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.Satisfy.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.Satisfy.cs index 7f1af33412..d5e2769c1a 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.Satisfy.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.Satisfy.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; using FluentAssertions.Execution; using Xunit; @@ -19,7 +18,7 @@ public class Satisfy public void When_collection_element_at_each_position_matches_predicate_at_same_position_should_not_throw() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act Action act = () => collection.Should().Satisfy( @@ -35,7 +34,7 @@ public void When_collection_element_at_each_position_matches_predicate_at_same_p public void When_collection_element_at_each_position_matches_predicate_at_reverse_position_should_not_throw() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act Action act = () => collection.Should().Satisfy( @@ -51,7 +50,7 @@ public void When_collection_element_at_each_position_matches_predicate_at_revers public void When_one_element_does_not_have_matching_predicate_Satisfy_should_throw() { // Arrange - var collection = new[] { 1, 2 }; + int[] collection = [1, 2]; // Act Action act = () => collection.Should().Satisfy( @@ -60,10 +59,11 @@ public void When_one_element_does_not_have_matching_predicate_Satisfy_should_thr element => element == 2); // Assert - act.Should().Throw().WithMessage( - @"Expected collection to satisfy all predicates, but: -*The following predicates did not have matching elements: -*(element == 3)"); + act.Should().Throw().WithMessage(""" + Expected collection to satisfy all predicates, but: + *The following predicates did not have matching elements: + *(element == 3) + """); } [Fact] @@ -71,7 +71,7 @@ public void When_some_predicates_have_multiple_matching_elements_and_most_restricitve_predicates_are_last_should_not_throw() { // Arrange - var collection = new[] { 1, 2, 3, 4 }; + int[] collection = [1, 2, 3, 4]; // Act Action act = () => collection.Should().Satisfy( @@ -89,7 +89,7 @@ public void When_some_predicates_have_multiple_matching_elements_and_most_restricitve_predicates_are_first_should_not_throw() { // Arrange - var collection = new[] { 1, 2, 3, 4 }; + int[] collection = [1, 2, 3, 4]; // Act Action act = () => collection.Should().Satisfy( @@ -106,7 +106,7 @@ public void public void When_second_predicate_matches_first_and_last_element_and_solution_exists_should_not_throw() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act Action act = () => collection.Should().Satisfy( @@ -123,13 +123,13 @@ public void When_assertion_fails_then_failure_message_must_contain_predicates_without_matching_elements_and_elements_without_matching_predicates() { // Arrange - IEnumerable collection = new[] - { + IEnumerable collection = + [ new SomeClass { Text = "one", Number = 1 }, new SomeClass { Text = "two", Number = 3 }, new SomeClass { Text = "three", Number = 3 }, new SomeClass { Text = "four", Number = 4 }, - }; + ]; // Act Action act = () => collection.Should().Satisfy( @@ -142,8 +142,7 @@ public void becauseArgs: "args"); // Assert - act.Should().Throw().WithMessage( - """ + act.Should().Throw().WithMessage(""" Expected collection to satisfy all predicates because we want to test formatting (args), but: *The following predicates did not have matching elements: *(element.Text == "two") AndAlso (element.Number == 2) @@ -158,7 +157,7 @@ Expected collection to satisfy all predicates because we want to test formatting public void When_Satisfy_asserting_against_null_inspectors_it_should_throw_with_clear_explanation() { // Arrange - IEnumerable collection = new[] { 1, 2 }; + IEnumerable collection = [1, 2]; // Act Action act = () => collection.Should().Satisfy(null); @@ -172,7 +171,7 @@ public void When_Satisfy_asserting_against_null_inspectors_it_should_throw_with_ public void When_asserting_against_empty_inspectors_should_throw_with_clear_explanation() { // Arrange - IEnumerable collection = new[] { 1, 2 }; + IEnumerable collection = [1, 2]; // Act Action act = () => collection.Should().Satisfy(); @@ -212,7 +211,7 @@ public void When_asserting_collection_which_is_null_should_throw() public void When_asserting_collection_which_is_empty_should_throw() { // Arrange - var collection = Enumerable.Empty(); + IEnumerable collection = []; // Act Action act = () => collection.Should().Satisfy( @@ -240,11 +239,12 @@ public void When_elements_are_integers_assertion_fails_then_failure_message_must element => element == 2); // Assert - act.Should().Throw().WithMessage( - @"Expected collection to satisfy all predicates, but: -*The following elements did not match any predicate: -*Index: 0, Element: 1 -*Index: 2, Element: 3"); + act.Should().Throw().WithMessage(""" + Expected collection to satisfy all predicates, but: + *The following elements did not match any predicate: + *Index: 0, Element: 1 + *Index: 2, Element: 3 + """); } } diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.SatisfyRespectively.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.SatisfyRespectively.cs index a905e8a8e6..9273fdc5fa 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.SatisfyRespectively.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.SatisfyRespectively.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using FluentAssertions.Execution; using Xunit; using Xunit.Sdk; @@ -18,7 +17,7 @@ public class SatisfyRespectively public void When_collection_asserting_against_null_inspectors_it_should_throw_with_clear_explanation() { // Arrange - IEnumerable collection = new[] { 1, 2 }; + IEnumerable collection = [1, 2]; // Act Action act = () => collection.Should().SatisfyRespectively(null); @@ -32,7 +31,7 @@ public void When_collection_asserting_against_null_inspectors_it_should_throw_wi public void When_collection_asserting_against_empty_inspectors_it_should_throw_with_clear_explanation() { // Arrange - IEnumerable collection = new[] { 1, 2 }; + IEnumerable collection = [1, 2]; // Act Action act = () => collection.Should().SatisfyRespectively(); @@ -66,7 +65,7 @@ public void When_collection_which_is_asserting_against_inspectors_is_null_it_sho public void When_collection_which_is_asserting_against_inspectors_is_empty_it_should_throw() { // Arrange - var collection = Enumerable.Empty(); + IEnumerable collection = []; // Act Action act = () => collection.Should().SatisfyRespectively(new Action[] { x => x.Should().Be(1) }, @@ -81,7 +80,7 @@ public void When_collection_which_is_asserting_against_inspectors_is_empty_it_sh public void When_asserting_collection_satisfies_all_inspectors_it_should_succeed() { // Arrange - var collection = new[] { new Customer { Age = 21, Name = "John" }, new Customer { Age = 22, Name = "Jane" } }; + Customer[] collection = [new Customer { Age = 21, Name = "John" }, new Customer { Age = 22, Name = "Jane" }]; // Act / Assert collection.Should().SatisfyRespectively( @@ -101,11 +100,11 @@ public void When_asserting_collection_satisfies_all_inspectors_it_should_succeed public void When_asserting_collection_does_not_satisfy_any_inspector_it_should_throw() { // Arrange - var customers = new[] - { - new CustomerWithItems { Age = 21, Items = new[] { 1, 2 } }, - new CustomerWithItems { Age = 22, Items = new[] { 3 } } - }; + CustomerWithItems[] customers = + [ + new CustomerWithItems { Age = 21, Items = [1, 2] }, + new CustomerWithItems { Age = 22, Items = [3] } + ]; // Act Action act = () => customers.Should().SatisfyRespectively( @@ -127,28 +126,28 @@ public void When_asserting_collection_does_not_satisfy_any_inspector_it_should_t }, "because we want to test {0}", "nested assertions"); // Assert - act.Should().Throw().WithMessage( - @"Expected customers to satisfy all inspectors because we want to test nested assertions, but some inspectors are not satisfied: -*At index 0: -*Expected customer.Age to be less than 21, but found 21 -*Expected customer.Items to satisfy all inspectors, but some inspectors are not satisfied: -*At index 0: -*Expected item to be 2, but found 1 -*At index 1: -*Expected item to be 1, but found 2 -*At index 1: -*Expected customer.Age to be less than 22, but found 22 -*Expected customer.Items to satisfy all inspectors, but some inspectors are not satisfied: -*At index 0: -*Expected item to be 2, but found 3" - ); + act.Should().Throw().WithMessage(""" + Expected customers to satisfy all inspectors because we want to test nested assertions, but some inspectors are not satisfied: + *At index 0: + *Expected customer.Age to be less than 21, but found 21 + *Expected customer.Items to satisfy all inspectors, but some inspectors are not satisfied: + *At index 0: + *Expected item to be 2, but found 1 + *At index 1: + *Expected item to be 1, but found 2 + *At index 1: + *Expected customer.Age to be less than 22, but found 22 + *Expected customer.Items to satisfy all inspectors, but some inspectors are not satisfied: + *At index 0: + *Expected item to be 2, but found 3 + """); } [Fact] public void When_inspector_message_is_not_reformatable_it_should_not_throw() { // Arrange - byte[][] subject = { new byte[] { 1 } }; + byte[][] subject = [[1]]; // Act Action act = () => subject.Should().SatisfyRespectively(e => e.Should().BeEquivalentTo(new byte[] { 2, 3, 4 })); @@ -161,7 +160,7 @@ public void When_inspector_message_is_not_reformatable_it_should_not_throw() public void When_inspectors_count_does_not_equal_asserting_collection_length_it_should_throw_with_a_useful_message() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act Action act = () => collection.Should().SatisfyRespectively( @@ -177,7 +176,7 @@ public void When_inspectors_count_does_not_equal_asserting_collection_length_it_ public void When_inspectors_count_does_not_equal_asserting_collection_length_it_should_fail_with_a_useful_message() { // Arrange - var collection = new int[0]; + int[] collection = []; // Act Action act = () => collection.Should().SatisfyRespectively( diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.StartWith.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.StartWith.cs index d39342f3d1..b1933d949c 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.StartWith.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.StartWith.cs @@ -17,7 +17,7 @@ public class StartWith public void When_collection_does_not_start_with_a_specific_element_it_should_throw() { // Arrange - var collection = new[] { "john", "jane", "mike" }; + string[] collection = ["john", "jane", "mike"]; // Act Action act = () => collection.Should().StartWith("ryan", "of some reason"); @@ -31,7 +31,7 @@ public void When_collection_does_not_start_with_a_specific_element_it_should_thr public void When_collection_does_not_start_with_a_null_sequence_it_should_throw() { // Arrange - var collection = new[] { "john" }; + string[] collection = ["john"]; // Act Action act = () => collection.Should().StartWith((IEnumerable)null); @@ -45,7 +45,7 @@ public void When_collection_does_not_start_with_a_null_sequence_it_should_throw( public void When_collection_does_not_start_with_a_null_sequence_using_a_comparer_it_should_throw() { // Arrange - var collection = new[] { "john" }; + string[] collection = ["john"]; // Act Action act = () => collection.Should().StartWith((IEnumerable)null, (_, _) => true); @@ -59,10 +59,10 @@ public void When_collection_does_not_start_with_a_null_sequence_using_a_comparer public void When_collection_does_not_start_with_a_specific_element_in_a_sequence_it_should_throw() { // Arrange - var collection = new[] { "john", "bill", "jane", "mike" }; + string[] collection = ["john", "bill", "jane", "mike"]; // Act - Action act = () => collection.Should().StartWith(new[] { "john", "ryan", "jane" }, "of some reason"); + Action act = () => collection.Should().StartWith(["john", "ryan", "jane"], "of some reason"); // Assert act.Should().Throw().WithMessage( @@ -74,10 +74,10 @@ public void When_collection_does_not_start_with_a_specific_element_in_a_sequence_using_custom_equality_comparison_it_should_throw() { // Arrange - var collection = new[] { "john", "bill", "jane", "mike" }; + string[] collection = ["john", "bill", "jane", "mike"]; // Act - Action act = () => collection.Should().StartWith(new[] { "john", "ryan", "jane" }, + Action act = () => collection.Should().StartWith(["john", "ryan", "jane"], (s1, s2) => string.Equals(s1, s2, StringComparison.Ordinal), "of some reason"); // Assert @@ -89,7 +89,7 @@ public void public void When_collection_starts_with_the_specific_element_it_should_not_throw() { // Arrange - var collection = new[] { "john", "jane", "mike" }; + string[] collection = ["john", "jane", "mike"]; // Act Action act = () => collection.Should().StartWith("john"); @@ -102,10 +102,10 @@ public void When_collection_starts_with_the_specific_element_it_should_not_throw public void When_collection_starts_with_the_specific_sequence_of_elements_it_should_not_throw() { // Arrange - var collection = new[] { "john", "bill", "jane", "mike" }; + string[] collection = ["john", "bill", "jane", "mike"]; // Act - Action act = () => collection.Should().StartWith(new[] { "john", "bill" }); + Action act = () => collection.Should().StartWith(["john", "bill"]); // Assert act.Should().NotThrow(); @@ -116,10 +116,10 @@ public void When_collection_starts_with_the_specific_sequence_of_elements_using_custom_equality_comparison_it_should_not_throw() { // Arrange - var collection = new[] { "john", "bill", "jane", "mike" }; + string[] collection = ["john", "bill", "jane", "mike"]; // Act - Action act = () => collection.Should().StartWith(new[] { "JoHn", "bIlL" }, + Action act = () => collection.Should().StartWith(["JoHn", "bIlL"], (s1, s2) => string.Equals(s1, s2, StringComparison.OrdinalIgnoreCase)); // Assert @@ -130,7 +130,7 @@ public void public void When_collection_starts_with_the_specific_null_element_it_should_not_throw() { // Arrange - var collection = new[] { null, "jane", "mike" }; + string[] collection = [null, "jane", "mike"]; // Act Action act = () => collection.Should().StartWith((string)null); @@ -143,7 +143,7 @@ public void When_collection_starts_with_the_specific_null_element_it_should_not_ public void When_non_empty_collection_starts_with_the_empty_sequence_it_should_not_throw() { // Arrange - var collection = new[] { "jane", "mike" }; + string[] collection = ["jane", "mike"]; // Act Action act = () => collection.Should().StartWith(new string[] { }); @@ -156,7 +156,7 @@ public void When_non_empty_collection_starts_with_the_empty_sequence_it_should_n public void When_empty_collection_starts_with_the_empty_sequence_it_should_not_throw() { // Arrange - var collection = new string[] { }; + string[] collection = []; // Act Action act = () => collection.Should().StartWith(new string[] { }); @@ -169,10 +169,10 @@ public void When_empty_collection_starts_with_the_empty_sequence_it_should_not_t public void When_collection_starts_with_the_specific_sequence_with_null_elements_it_should_not_throw() { // Arrange - var collection = new[] { null, "john", null, "bill", "jane", "mike" }; + string[] collection = [null, "john", null, "bill", "jane", "mike"]; // Act - Action act = () => collection.Should().StartWith(new[] { null, "john", null, "bill" }); + Action act = () => collection.Should().StartWith([null, "john", null, "bill"]); // Assert act.Should().NotThrow(); @@ -183,10 +183,10 @@ public void When_collection_starts_with_the_specific_sequence_with_null_elements_using_custom_equality_comparison_it_should_not_throw() { // Arrange - var collection = new[] { null, "john", null, "bill", "jane", "mike" }; + string[] collection = [null, "john", null, "bill", "jane", "mike"]; // Act - Action act = () => collection.Should().StartWith(new[] { null, "JoHn", null, "bIlL" }, + Action act = () => collection.Should().StartWith([null, "JoHn", null, "bIlL"], (s1, s2) => string.Equals(s1, s2, StringComparison.OrdinalIgnoreCase)); // Assert @@ -197,7 +197,7 @@ public void public void When_collection_starts_with_null_but_that_wasnt_expected_it_should_throw() { // Arrange - var collection = new[] { null, "jane", "mike" }; + string[] collection = [null, "jane", "mike"]; // Act Action act = () => collection.Should().StartWith("john"); diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.cs index 49c2f4b692..1713431bcb 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.cs @@ -12,13 +12,27 @@ namespace FluentAssertions.Specs.Collections; /// public partial class CollectionAssertionSpecs { - public class Chainings + public class Chaining { + [Fact] + public void Chaining_something_should_do_something() + { + // Arrange + var languages = new[] { "C#" }; + + // Act + 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() { // Arrange - var collection = new[] { 1, 2, 3 }; + int[] collection = [1, 2, 3]; // Act / Assert collection.Should() @@ -33,13 +47,13 @@ public void Should_support_chaining_constraints_with_and() public void When_the_collection_is_ordered_according_to_the_subsequent_ascending_assertion_it_should_succeed() { // Arrange - var collection = new[] - { + (int, string)[] collection = + [ (1, "a"), (2, "b"), (2, "c"), (3, "a") - }; + ]; // Act Action action = () => collection.Should() @@ -55,13 +69,13 @@ public void When_the_collection_is_ordered_according_to_the_subsequent_ascending public void When_the_collection_is_not_ordered_according_to_the_subsequent_ascending_assertion_it_should_fail() { // Arrange - var collection = new[] - { + (int, string)[] collection = + [ (1, "a"), (2, "b"), (2, "c"), (3, "a") - }; + ]; // Act Action action = () => collection.Should() @@ -71,7 +85,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] @@ -79,13 +93,13 @@ public void When_the_collection_is_ordered_according_to_the_subsequent_ascending_assertion_with_comparer_it_should_succeed() { // Arrange - var collection = new[] - { + (int, string)[] collection = + [ (1, "a"), (2, "B"), (2, "b"), (3, "a") - }; + ]; // Act Action action = () => collection.Should() @@ -101,13 +115,13 @@ public void public void When_the_collection_is_ordered_according_to_the_multiple_subsequent_ascending_assertions_it_should_succeed() { // Arrange - var collection = new[] - { + (int, string, double)[] collection = + [ (1, "a", 1.1), (2, "b", 1.2), (2, "c", 1.3), (3, "a", 1.1) - }; + ]; // Act Action action = () => collection.Should() @@ -125,13 +139,13 @@ public void When_the_collection_is_ordered_according_to_the_multiple_subsequent_ public void When_the_collection_is_ordered_according_to_the_subsequent_descending_assertion_it_should_succeed() { // Arrange - var collection = new[] - { + (int, string)[] collection = + [ (3, "a"), (2, "c"), (2, "b"), (1, "a") - }; + ]; // Act Action action = () => collection.Should() @@ -147,13 +161,13 @@ public void When_the_collection_is_ordered_according_to_the_subsequent_descendin public void When_the_collection_is_not_ordered_according_to_the_subsequent_descending_assertion_it_should_fail() { // Arrange - var collection = new[] - { + (int, string)[] collection = + [ (3, "a"), (2, "c"), (2, "b"), (1, "a") - }; + ]; // Act Action action = () => collection.Should() @@ -163,7 +177,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] @@ -171,13 +185,13 @@ public void When_the_collection_is_ordered_according_to_the_subsequent_descending_assertion_with_comparer_it_should_succeed() { // Arrange - var collection = new[] - { + (int, string)[] collection = + [ (3, "a"), (2, "b"), (2, "B"), (1, "a") - }; + ]; // Act Action action = () => collection.Should() @@ -193,13 +207,13 @@ public void public void When_the_collection_is_ordered_according_to_the_multiple_subsequent_descending_assertions_it_should_succeed() { // Arrange - var collection = new[] - { + (int, string, double)[] collection = + [ (3, "a", 1.1), (2, "c", 1.3), (2, "b", 1.2), (1, "a", 1.1) - }; + ]; // Act Action action = () => collection.Should() @@ -218,7 +232,7 @@ private class ByLastCharacterComparer : IComparer { public int Compare(string x, string y) { - return x.Last().CompareTo(y.Last()); + return Nullable.Compare(x?[^1], y?[^1]); } } } @@ -295,12 +309,9 @@ public int Count internal sealed class TrackingTestEnumerable : IEnumerable { - private readonly int[] values; - public TrackingTestEnumerable(params int[] values) { - this.values = values; - Enumerator = new TrackingEnumerator(this.values); + Enumerator = new TrackingEnumerator(values); } public TrackingEnumerator Enumerator { get; } diff --git a/Tests/FluentAssertions.Specs/Collections/Data/DataColumnCollectionAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Collections/Data/DataColumnCollectionAssertionSpecs.cs deleted file mode 100644 index e59f3e6979..0000000000 --- a/Tests/FluentAssertions.Specs/Collections/Data/DataColumnCollectionAssertionSpecs.cs +++ /dev/null @@ -1,435 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; -using Xunit; -using Xunit.Sdk; - -namespace FluentAssertions.Specs.Collections.Data; - -public static class DataColumnCollectionAssertionSpecs -{ - public class BeSameAs - { - [Fact] - public void Succeeds_for_references_to_the_same_object() - { - // Arrange - var dataTable = new DataTable("Test"); - - var columnCollection1 = dataTable.Columns; - var columnCollection2 = columnCollection1; - - // Act & Assert - columnCollection1.Should().BeSameAs(columnCollection2); - } - - [Fact] - public void Throws_for_different_references() - { - // Arrange - var dataTable1 = new DataTable("Test1"); - var dataTable2 = new DataTable("Test2"); - - var columnCollection1 = dataTable1.Columns; - var columnCollection2 = dataTable2.Columns; - - // Act - Action action = - () => columnCollection1.Should().BeSameAs(columnCollection2); - - // Assert - action.Should().Throw().WithMessage( - "Expected columnCollection1 to refer to *, but found * (different underlying object)."); - } - - [Fact] - public void When_generic_collection_is_tested_against_typed_collection_it_should_fail() - { - // Arrange - var dataTable = new DataTable("Test"); - - var columnCollection = dataTable.Columns; - - var genericCollection = columnCollection.Cast(); - - // Act - Action action = - () => genericCollection.Should().BeSameAs(columnCollection, because: "we {0}", "care"); - - // Assert - action.Should().Throw().WithMessage( - "Invalid expectation: Expected genericCollection to refer to an instance of DataColumnCollection " + - "because we care, but found *."); - } - } - - public class NotBeSameAs - { - [Fact] - public void When_references_are_the_same_it_should_fail() - { - // Arrange - var dataTable = new DataTable("Test"); - - var columnCollection1 = dataTable.Columns; - var columnCollection2 = columnCollection1; - - // Act - Action action = - () => columnCollection1.Should().NotBeSameAs(columnCollection2); - - // Assert - action.Should().Throw().WithMessage("Did not expect columnCollection1 to refer to *."); - } - - [Fact] - public void When_references_are_different_it_should_succeed() - { - // Arrange - var dataTable1 = new DataTable("Test1"); - var dataTable2 = new DataTable("Test2"); - - var columnCollection1 = dataTable1.Columns; - var columnCollection2 = dataTable2.Columns; - - // Act & Assert - columnCollection1.Should().NotBeSameAs(columnCollection2); - } - - [Fact] - public void When_generic_collection_is_tested_against_typed_collection_it_should_fail() - { - // Arrange - var dataTable = new DataTable("Test"); - - var columnCollection = dataTable.Columns; - - var genericCollection = columnCollection.Cast(); - - // Act - Action action = - () => genericCollection.Should().NotBeSameAs(columnCollection, because: "we {0}", "care"); - - // Assert - action.Should().Throw().WithMessage( - "Invalid expectation: Expected genericCollection to refer to a different instance of " + - "DataColumnCollection because we care, but found *."); - } - } - - public class HaveSameCount - { - [Fact] - public void When_subject_is_null_it_should_fail() - { - // Arrange - var subject = default(DataColumnCollection); - - var expectation = new DataTable().Columns; - - // Act - Action action = - () => subject.Should().HaveSameCount(expectation, because: "we {0}", "care"); - - // Assert - action.Should().Throw().WithMessage( - "Expected * to have the same count as * because we care, but found .*"); - } - - [Fact] - public void When_expectation_is_null_it_should_fail() - { - // Arrange - var dataTable = new DataTable(); - - dataTable.Columns.Add(new DataColumn("Column0")); - dataTable.Columns.Add(new DataColumn("Column1")); - dataTable.Columns.Add(new DataColumn("Column2")); - - var nullReference = default(DataColumnCollection); - - // Act - Action action = - () => dataTable.Columns.Should().HaveSameCount(nullReference); - - // Assert - action.Should().Throw().WithMessage( - "Cannot verify count against a collection.*"); - } - - public class DataColumnCollectionAssertions - { - [Fact] - public void When_two_collections_have_the_same_number_of_columns_it_should_succeed() - { - // Arrange - var firstDataTable = new DataTable(); - var secondDataTable = new DataTable(); - - firstDataTable.Columns.Add(new DataColumn("Column0")); - firstDataTable.Columns.Add(new DataColumn("Column1")); - firstDataTable.Columns.Add(new DataColumn("Column2")); - - secondDataTable.Columns.Add(new DataColumn("Column10")); - secondDataTable.Columns.Add(new DataColumn("Column11")); - secondDataTable.Columns.Add(new DataColumn("Column12")); - - // Act & Assert - firstDataTable.Columns.Should().HaveSameCount(secondDataTable.Columns); - } - - [Fact] - public void When_two_collections_do_not_have_the_same_number_of_columns_it_should_fail() - { - // Arrange - var firstDataTable = new DataTable(); - var secondDataTable = new DataTable(); - - firstDataTable.Columns.Add(new DataColumn("Column0")); - firstDataTable.Columns.Add(new DataColumn("Column1")); - firstDataTable.Columns.Add(new DataColumn("Column2")); - - secondDataTable.Columns.Add(new DataColumn("Column10")); - secondDataTable.Columns.Add(new DataColumn("Column12")); - - // Act - Action action = - () => firstDataTable.Columns.Should().HaveSameCount(secondDataTable.Columns); - - // Assert - action.Should().Throw().WithMessage( - "Expected firstDataTable.Columns to have 2 column(s), but found 3."); - } - } - - public class GenericCollectionAssertions - { - [Fact] - public void When_collection_is_compared_with_null_it_should_fail() - { - // Arrange - var dataTable = new DataTable(); - - dataTable.Columns.Add(new DataColumn("Column0")); - dataTable.Columns.Add(new DataColumn("Column1")); - dataTable.Columns.Add(new DataColumn("Column2")); - - List nullDataColumns = null; - - // Act - Action action = - () => nullDataColumns.Should().HaveSameCount(dataTable.Columns, because: "we {0}", "care"); - - // Assert - action.Should().Throw().WithMessage( - "Expected nullDataColumns to have the same count as * because we care, but found ."); - } - - [Fact] - public void When_collection_is_compared_with_typed_collection_with_same_number_of_columns_it_should_succeed() - { - // Arrange - var firstDataTable = new DataTable(); - var secondDataTable = new DataTable(); - - firstDataTable.Columns.Add(new DataColumn("Column0")); - firstDataTable.Columns.Add(new DataColumn("Column1")); - firstDataTable.Columns.Add(new DataColumn("Column2")); - - secondDataTable.Columns.Add(new DataColumn("Column10")); - secondDataTable.Columns.Add(new DataColumn("Column11")); - secondDataTable.Columns.Add(new DataColumn("Column12")); - - var genericDataColumnCollection = firstDataTable.Columns.Cast(); - - // Act & Assert - genericDataColumnCollection.Should().HaveSameCount(secondDataTable.Columns); - } - - [Fact] - public void When_collection_is_compared_with_typed_collection_with_different_number_of_columns_it_should_fail() - { - // Arrange - var firstDataTable = new DataTable(); - var secondDataTable = new DataTable(); - - firstDataTable.Columns.Add(new DataColumn("Column0")); - firstDataTable.Columns.Add(new DataColumn("Column1")); - firstDataTable.Columns.Add(new DataColumn("Column2")); - - secondDataTable.Columns.Add(new DataColumn("Column10")); - secondDataTable.Columns.Add(new DataColumn("Column12")); - - var genericDataColumnCollection = firstDataTable.Columns.Cast(); - - // Act - Action action = - () => genericDataColumnCollection.Should().HaveSameCount(secondDataTable.Columns, - because: "we {0}", "care"); - - // Assert - action.Should().Throw().WithMessage( - "Expected genericDataColumnCollection to have 2 column(s) because we care, but found 3."); - } - } - } - - public class NotHaveSameCount - { - [Fact] - public void When_subject_is_null_it_should_fail() - { - // Arrange - var subject = default(DataColumnCollection); - - var expectation = new DataTable().Columns; - - // Act - Action action = - () => subject.Should().NotHaveSameCount(expectation, because: "we {0}", "care"); - - // Assert - action.Should().Throw().WithMessage( - "Expected * to not have the same count as * because we care, but found .*"); - } - - [Fact] - public void When_expectation_is_null_it_should_fail() - { - // Arrange - var dataTable = new DataTable(); - - dataTable.Columns.Add(new DataColumn("Column0")); - dataTable.Columns.Add(new DataColumn("Column1")); - dataTable.Columns.Add(new DataColumn("Column2")); - - var nullReference = default(DataColumnCollection); - - // Act - Action action = - () => dataTable.Columns.Should().NotHaveSameCount(nullReference); - - // Assert - action.Should().Throw().WithMessage( - "Cannot verify count against a collection.*"); - } - - public class DataColumnCollectionAssertions - { - [Fact] - public void When_two_collections_have_different_number_of_columns_it_should_succeed() - { - // Arrange - var firstDataTable = new DataTable(); - var secondDataTable = new DataTable(); - - firstDataTable.Columns.Add(new DataColumn("Column0")); - firstDataTable.Columns.Add(new DataColumn("Column1")); - firstDataTable.Columns.Add(new DataColumn("Column2")); - - secondDataTable.Columns.Add(new DataColumn("Column10")); - secondDataTable.Columns.Add(new DataColumn("Column12")); - - // Act & Assert - firstDataTable.Columns.Should().NotHaveSameCount(secondDataTable.Columns); - } - - [Fact] - public void When_two_collections_have_the_same_number_of_columns_it_should_fail() - { - // Arrange - var firstDataTable = new DataTable(); - var secondDataTable = new DataTable(); - - firstDataTable.Columns.Add(new DataColumn("Column0")); - firstDataTable.Columns.Add(new DataColumn("Column1")); - firstDataTable.Columns.Add(new DataColumn("Column2")); - - secondDataTable.Columns.Add(new DataColumn("Column10")); - secondDataTable.Columns.Add(new DataColumn("Column11")); - secondDataTable.Columns.Add(new DataColumn("Column12")); - - // Act - Action action = - () => firstDataTable.Columns.Should().NotHaveSameCount(secondDataTable.Columns, - because: "we {0}", "care"); - - // Assert - action.Should().Throw().WithMessage( - "Expected firstDataTable.Columns to not have 3 column(s) because we care, but found 3."); - } - } - - public class GenericCollectionAssertions - { - [Fact] - public void When_collection_is_compared_with_null_it_should_fail() - { - // Arrange - var dataTable = new DataTable(); - - dataTable.Columns.Add(new DataColumn("Column0")); - dataTable.Columns.Add(new DataColumn("Column1")); - dataTable.Columns.Add(new DataColumn("Column2")); - - List nullDataColumns = null; - - // Act - Action action = - () => nullDataColumns.Should().NotHaveSameCount(dataTable.Columns, because: "we {0}", "care"); - - // Assert - action.Should().Throw().WithMessage( - "Expected nullDataColumns to not have the same count as * because we care, but found ."); - } - - [Fact] - public void When_collection_is_compared_with_typed_collection_with_same_number_of_columns_it_should_fail() - { - // Arrange - var firstDataTable = new DataTable(); - var secondDataTable = new DataTable(); - - firstDataTable.Columns.Add(new DataColumn("Column0")); - firstDataTable.Columns.Add(new DataColumn("Column1")); - firstDataTable.Columns.Add(new DataColumn("Column2")); - - secondDataTable.Columns.Add(new DataColumn("Column10")); - secondDataTable.Columns.Add(new DataColumn("Column11")); - secondDataTable.Columns.Add(new DataColumn("Column12")); - - var genericDataColumnCollection = firstDataTable.Columns.Cast(); - - // Act - Action action = - () => genericDataColumnCollection.Should().NotHaveSameCount(secondDataTable.Columns, - because: "we {0}", "care"); - - // Assert - action.Should().Throw().WithMessage( - "Expected genericDataColumnCollection to not have 3 column(s) because we care, but found 3."); - } - - [Fact] - public void When_collection_is_compared_with_typed_collection_with_different_number_of_columns_it_should_succeed() - { - // Arrange - var firstDataTable = new DataTable(); - var secondDataTable = new DataTable(); - - firstDataTable.Columns.Add(new DataColumn("Column0")); - firstDataTable.Columns.Add(new DataColumn("Column1")); - firstDataTable.Columns.Add(new DataColumn("Column2")); - - secondDataTable.Columns.Add(new DataColumn("Column10")); - secondDataTable.Columns.Add(new DataColumn("Column12")); - - var genericDataColumnCollection = firstDataTable.Columns.Cast(); - - // Act & Assert - genericDataColumnCollection.Should().NotHaveSameCount(secondDataTable.Columns); - } - } - } -} diff --git a/Tests/FluentAssertions.Specs/Collections/Data/DataRowCollectionAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Collections/Data/DataRowCollectionAssertionSpecs.cs deleted file mode 100644 index 6780b8a2c5..0000000000 --- a/Tests/FluentAssertions.Specs/Collections/Data/DataRowCollectionAssertionSpecs.cs +++ /dev/null @@ -1,625 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; -using Xunit; -using Xunit.Sdk; - -namespace FluentAssertions.Specs.Collections.Data; - -public static class DataRowCollectionAssertionSpecs -{ - public class BeSameAs - { - [Fact] - public void When_references_are_the_same_it_should_succeed() - { - // Arrange - var dataTable = new DataTable("Test"); - - var rowCollection1 = dataTable.Rows; - var rowCollection2 = rowCollection1; - - // Act & Assert - rowCollection1.Should().BeSameAs(rowCollection2); - } - - [Fact] - public void When_references_are_different_it_should_fail() - { - // Arrange - var dataTable1 = new DataTable("Test1"); - var dataTable2 = new DataTable("Test2"); - - var rowCollection1 = dataTable1.Rows; - var rowCollection2 = dataTable2.Rows; - - // Act - Action action = - () => rowCollection1.Should().BeSameAs(rowCollection2); - - // Assert - action.Should().Throw().WithMessage( - "Expected rowCollection1 to refer to *, but found * (different underlying object)."); - } - - [Fact] - public void When_generic_collection_is_tested_against_typed_collection_it_should_fail() - { - // Arrange - var dataTable = new DataTable("Test"); - - var rowCollection = dataTable.Rows; - - var genericCollection = rowCollection.Cast(); - - // Act - Action action = - () => genericCollection.Should().BeSameAs(rowCollection, because: "we {0}", "care"); - - // Assert - action.Should().Throw().WithMessage( - "Invalid expectation: Expected genericCollection to refer to an instance of DataRowCollection " + - "because we care, but found *."); - } - } - - public class NotBeSameAs - { - [Fact] - public void When_references_are_the_same_object_it_should_fail() - { - // Arrange - var dataTable = new DataTable("Test"); - - var rowCollection1 = dataTable.Rows; - var rowCollection2 = rowCollection1; - - // Act - Action action = - () => rowCollection1.Should().NotBeSameAs(rowCollection2); - - // Assert - action.Should().Throw().WithMessage("Did not expect rowCollection1 to refer to *."); - } - - [Fact] - public void When_references_are_different_it_should_succeed() - { - // Arrange - var dataTable1 = new DataTable("Test1"); - var dataTable2 = new DataTable("Test2"); - - var rowCollection1 = dataTable1.Rows; - var rowCollection2 = dataTable2.Rows; - - // Act & Assert - rowCollection1.Should().NotBeSameAs(rowCollection2); - } - - [Fact] - public void When_generic_collection_is_tested_against_typed_collection_it_should_fail() - { - // Arrange - var dataTable = new DataTable("Test"); - - var rowCollection = dataTable.Rows; - - var genericCollection = rowCollection.Cast(); - - // Act - Action action = - () => genericCollection.Should().NotBeSameAs(rowCollection, because: "we {0}", "care"); - - // Assert - action.Should().Throw().WithMessage( - "Invalid expectation: Expected genericCollection to refer to a different instance of " + - "DataRowCollection because we care, but found *."); - } - } - - public class HaveSameCount - { - [Fact] - public void When_subject_is_null_it_should_fail() - { - // Arrange - var subject = default(DataRowCollection); - - var expectation = new DataTable().Rows; - - // Act - Action action = - () => subject.Should().HaveSameCount(expectation, because: "we {0}", "care"); - - // Assert - action.Should().Throw().WithMessage( - "Expected * to have the same count as * because we care, but found .*"); - } - - [Fact] - public void When_expectation_is_null_it_should_fail() - { - // Arrange - var dataTable = new DataTable(); - - var nullReference = default(DataRowCollection); - - // Act - Action action = - () => dataTable.Rows.Should().HaveSameCount(nullReference); - - // Assert - action.Should().Throw().WithMessage( - "Cannot verify count against a collection.*"); - } - - public class DataRowCollectionAssertions - { - [Fact] - public void When_two_collections_have_the_same_number_of_rows_it_should_succeed() - { - // Arrange - var firstDataTable = new DataTable(); - var secondDataTable = new DataTable(); - - firstDataTable.Rows.Add(); - firstDataTable.Rows.Add(); - firstDataTable.Rows.Add(); - - secondDataTable.Rows.Add(); - secondDataTable.Rows.Add(); - secondDataTable.Rows.Add(); - - // Act & Assert - firstDataTable.Rows.Should().HaveSameCount(secondDataTable.Rows); - } - - [Fact] - public void When_two_collections_do_not_have_the_same_number_of_rows_it_should_fail() - { - // Arrange - var firstDataTable = new DataTable(); - var secondDataTable = new DataTable(); - - firstDataTable.Rows.Add(); - firstDataTable.Rows.Add(); - firstDataTable.Rows.Add(); - - secondDataTable.Rows.Add(); - secondDataTable.Rows.Add(); - - // Act - Action action = - () => firstDataTable.Rows.Should().HaveSameCount(secondDataTable.Rows); - - // Assert - action.Should().Throw().WithMessage( - "Expected firstDataTable.Rows to have 2 row(s), but found 3."); - } - } - - public class GenericCollectionAssertions - { - [Fact] - public void When_collection_is_compared_with_null_it_should_fail() - { - // Arrange - var dataTable = new DataTable(); - - List nullDataRows = null; - - // Act - Action action = - () => nullDataRows.Should().HaveSameCount(dataTable.Rows, because: "we {0}", "care"); - - // Assert - action.Should().Throw().WithMessage( - "Expected nullDataRows to have the same count as * because we care, but found ."); - } - - [Fact] - public void When_collection_is_compared_with_typed_collection_with_same_number_of_rows_it_should_succeed() - { - // Arrange - var firstDataTable = new DataTable(); - var secondDataTable = new DataTable(); - - firstDataTable.Rows.Add(); - firstDataTable.Rows.Add(); - firstDataTable.Rows.Add(); - - secondDataTable.Rows.Add(); - secondDataTable.Rows.Add(); - secondDataTable.Rows.Add(); - - var genericDataRowCollection = firstDataTable.Rows.Cast(); - - // Act & Assert - genericDataRowCollection.Should().HaveSameCount(secondDataTable.Rows); - } - - [Fact] - public void When_collection_is_compared_with_typed_collection_with_different_number_of_rows_it_should_fail() - { - // Arrange - var firstDataTable = new DataTable(); - var secondDataTable = new DataTable(); - - firstDataTable.Rows.Add(); - firstDataTable.Rows.Add(); - firstDataTable.Rows.Add(); - - secondDataTable.Rows.Add(); - secondDataTable.Rows.Add(); - - var genericDataRowCollection = firstDataTable.Rows.Cast(); - - // Act - Action action = - () => genericDataRowCollection.Should().HaveSameCount(secondDataTable.Rows, because: "we {0}", "care"); - - // Assert - action.Should().Throw().WithMessage( - "Expected genericDataRowCollection to have 2 row(s) because we care, but found 3."); - } - } - } - - public class NotHaveSameCount - { - [Fact] - public void When_subject_is_null_it_should_fail() - { - // Arrange - var subject = default(DataRowCollection); - - var expectation = new DataTable().Rows; - - // Act - Action action = - () => subject.Should().NotHaveSameCount(expectation, because: "we {0}", "care"); - - // Assert - action.Should().Throw().WithMessage( - "Expected * to not have the same count as * because we care, but found .*"); - } - - [Fact] - public void When_expectation_is_null_it_should_fail() - { - // Arrange - var dataTable = new DataTable(); - - var nullReference = default(DataRowCollection); - - // Act - Action action = - () => dataTable.Rows.Should().NotHaveSameCount(nullReference); - - // Assert - action.Should().Throw().WithMessage( - "Cannot verify count against a collection.*"); - } - - public class DataRowCollectionAssertions - { - [Fact] - public void When_two_collections_have_different_number_of_rows_it_should_succeed() - { - // Arrange - var firstDataTable = new DataTable(); - var secondDataTable = new DataTable(); - - firstDataTable.Rows.Add(); - firstDataTable.Rows.Add(); - firstDataTable.Rows.Add(); - - secondDataTable.Rows.Add(); - secondDataTable.Rows.Add(); - - // Act & Assert - firstDataTable.Rows.Should().NotHaveSameCount(secondDataTable.Rows); - } - - [Fact] - public void When_two_collections_have_the_same_number_rows_it_should_fail() - { - // Arrange - var firstDataTable = new DataTable(); - var secondDataTable = new DataTable(); - - firstDataTable.Rows.Add(); - firstDataTable.Rows.Add(); - firstDataTable.Rows.Add(); - - secondDataTable.Rows.Add(); - secondDataTable.Rows.Add(); - secondDataTable.Rows.Add(); - - // Act - Action action = - () => firstDataTable.Rows.Should().NotHaveSameCount(secondDataTable.Rows, because: "we {0}", "care"); - - // Assert - action.Should().Throw().WithMessage( - "Expected firstDataTable.Rows to not have 3 row(s) because we care, but found 3."); - } - } - - public class GenericCollectionAssertions - { - [Fact] - public void When_collection_is_compared_with_null_it_should_fail() - { - // Arrange - var dataTable = new DataTable(); - - dataTable.Rows.Add(); - dataTable.Rows.Add(); - dataTable.Rows.Add(); - - List nullDataRows = null; - - // Act - Action action = - () => nullDataRows.Should().NotHaveSameCount(dataTable.Rows, because: "we {0}", "care"); - - // Assert - action.Should().Throw().WithMessage( - "Expected nullDataRows to not have the same count as * because we care, but found ."); - } - - [Fact] - public void When_collection_is_compared_with_typed_collection_with_same_number_of_rows_it_should_fail() - { - // Arrange - var firstDataTable = new DataTable(); - var secondDataTable = new DataTable(); - - firstDataTable.Rows.Add(); - firstDataTable.Rows.Add(); - firstDataTable.Rows.Add(); - - secondDataTable.Rows.Add(); - secondDataTable.Rows.Add(); - secondDataTable.Rows.Add(); - - var genericDataRowCollection = firstDataTable.Rows.Cast(); - - // Act - Action action = - () => genericDataRowCollection.Should().NotHaveSameCount(secondDataTable.Rows, because: "we {0}", "care"); - - // Assert - action.Should().Throw().WithMessage( - "Expected genericDataRowCollection to not have 3 row(s) because we care, but found 3."); - } - - [Fact] - public void When_collection_is_compared_with_typed_collection_with_different_number_of_rows_it_should_succeed() - { - // Arrange - var firstDataTable = new DataTable(); - var secondDataTable = new DataTable(); - - firstDataTable.Rows.Add(); - firstDataTable.Rows.Add(); - firstDataTable.Rows.Add(); - - secondDataTable.Rows.Add(); - secondDataTable.Rows.Add(); - - var genericDataRowCollection = firstDataTable.Rows.Cast(); - - // Act & Assert - genericDataRowCollection.Should().NotHaveSameCount(secondDataTable.Rows); - } - } - } - - // These tests are present to ensure that we can trust DataRow equivalency in the context of ContainEquivalentOf. - public class ContainEquivalentOf - { - [Fact] - public void When_subject_is_null_it_should_fail() - { - // Arrange - var subject = default(DataRowCollection); - - var expectation = new DataTable().Rows; - - // Act - Action action = - () => subject.Should().ContainEquivalentOf(expectation, because: "we {0}", "care"); - - // Assert - action.Should().Throw().WithMessage( - "Expected * to contain equivalent of * because we care, but found .*"); - } - - [Fact] - public void When_collection_contains_equivalent_row_it_should_succeed() - { - // Arrange - var dataTable = new DataTable(); - - dataTable.Columns.Add("RowID", typeof(Guid)); - dataTable.Columns.Add("Description", typeof(string)); - dataTable.Columns.Add("Number", typeof(int)); - dataTable.Columns.Add("Flag", typeof(bool)); - dataTable.Columns.Add("Timestamp", typeof(DateTime)); - - dataTable.Rows.Add(new Guid("6f460c1a-755d-d8e4-ad67-65d5f519dbc8"), "1851925803", 2137491580, true, - new DateTime(638898932425580731)); - - dataTable.Rows.Add(new Guid("8286d046-9740-a3e4-95cf-ff46699c73c4"), "607156385", 1321446349, true, - new DateTime(641752306337096884)); - - dataTable.Rows.Add(new Guid("95c69371-b924-6fe3-7c38-98b7dd200bc1"), "1509870614", 505401118, true, - new DateTime(623130841631129390)); - - var subjectTable = new DataTable(); - - subjectTable.Columns.Add("RowID", typeof(Guid)); - subjectTable.Columns.Add("Description", typeof(string)); - subjectTable.Columns.Add("Number", typeof(int)); - subjectTable.Columns.Add("Flag", typeof(bool)); - subjectTable.Columns.Add("Timestamp", typeof(DateTime)); - - subjectTable.Rows.Add(new Guid("95c69371-b924-6fe3-7c38-98b7dd200bc1"), "1509870614", 505401118, true, - new DateTime(623130841631129390)); - - var subjectRow = subjectTable.Rows[0]; - - // Act & Assert - dataTable.Rows.Should().ContainEquivalentOf(subjectRow); - } - - [Fact] - public void When_collection_does_not_contain_equivalent_row_it_should_fail() - { - // Arrange - var dataTable = new DataTable(); - - dataTable.Columns.Add("RowID", typeof(Guid)); - dataTable.Columns.Add("Description", typeof(string)); - dataTable.Columns.Add("Number", typeof(int)); - dataTable.Columns.Add("Flag", typeof(bool)); - dataTable.Columns.Add("Timestamp", typeof(DateTime)); - - dataTable.Rows.Add(new Guid("8286d046-9740-a3e4-95cf-ff46699c73c4"), "607156385", 1321446349, true, - new DateTime(641752306337096884)); - - dataTable.Rows.Add(new Guid("95c69371-b924-6fe3-7c38-98b7dd200bc1"), "1509870614", 505401118, true, - new DateTime(623130841631129390)); - - dataTable.Rows.Add(new Guid("a905569d-db07-3ae3-63a0-322750a4a3bd"), "265101196", 1836839534, true, - new DateTime(625984215542645543)); - - var subjectTable = new DataTable(); - - subjectTable.Columns.Add("RowID", typeof(Guid)); - subjectTable.Columns.Add("Description", typeof(string)); - subjectTable.Columns.Add("Number", typeof(int)); - subjectTable.Columns.Add("Flag", typeof(bool)); - subjectTable.Columns.Add("Timestamp", typeof(DateTime)); - - subjectTable.Rows.Add(new Guid("bc4519c8-fdeb-06e2-4a08-cc98c4273aba"), "1167815425", 1020794303, true, - new DateTime(628837589454161696)); - - var subjectRow = subjectTable.Rows[0]; - - // Act - Action action = - () => dataTable.Rows.Should().ContainEquivalentOf(subjectRow, because: "we {0}", "care"); - - // Assert - action.Should().Throw().WithMessage( - "Expected dataTable.Rows * to contain equivalent of System.Data.DataRow* because we care.*"); - } - } - - // These tests are present to ensure that we can trust DataRow equivalency in the context of NotContainEquivalentOf. - public class NotContainEquivalentOf - { - [Fact] - public void When_subject_is_null_it_should_fail() - { - // Arrange - var subject = default(DataRowCollection); - - var expectation = new DataTable().Rows; - - // Act - Action action = - () => subject.Should().NotContainEquivalentOf(expectation, because: "we {0}", "care"); - - // Assert - action.Should().Throw().WithMessage( - "Expected * not to contain equivalent of * because we care, but collection is .*"); - } - - [Fact] - public void When_collection_contains_equivalent_row_it_should_fail() - { - // Arrange - var dataTable = new DataTable(); - - dataTable.Columns.Add("RowID", typeof(Guid)); - dataTable.Columns.Add("Description", typeof(string)); - dataTable.Columns.Add("Number", typeof(int)); - dataTable.Columns.Add("Flag", typeof(bool)); - dataTable.Columns.Add("Timestamp", typeof(DateTime)); - - dataTable.Rows.Add(new Guid("8286d046-9740-a3e4-95cf-ff46699c73c4"), "607156385", 1321446349, true, - new DateTime(641752306337096884)); - - dataTable.Rows.Add(new Guid("95c69371-b924-6fe3-7c38-98b7dd200bc1"), "1509870614", 505401118, true, - new DateTime(623130841631129390)); - - dataTable.Rows.Add(new Guid("a905569d-db07-3ae3-63a0-322750a4a3bd"), "265101196", 1836839534, true, - new DateTime(625984215542645543)); - - var subjectTable = new DataTable(); - - subjectTable.Columns.Add("RowID", typeof(Guid)); - subjectTable.Columns.Add("Description", typeof(string)); - subjectTable.Columns.Add("Number", typeof(int)); - subjectTable.Columns.Add("Flag", typeof(bool)); - subjectTable.Columns.Add("Timestamp", typeof(DateTime)); - - subjectTable.Rows.Add(new Guid("95c69371-b924-6fe3-7c38-98b7dd200bc1"), "1509870614", 505401118, true, - new DateTime(623130841631129390)); - - var subjectRow = subjectTable.Rows[0]; - - // Act - Action action = - () => dataTable.Rows.Should().NotContainEquivalentOf(subjectRow, because: "we {0}", "care"); - - // Assert - action.Should().Throw() - .WithMessage("Expected dataTable.Rows * not to contain equivalent of System.Data.DataRow* because we " + - "care, but found one at index 1.*"); - } - - [Fact] - public void When_collection_does_not_contain_equivalent_row_it_should_succeed() - { - // Arrange - var dataTable = new DataTable(); - - dataTable.Columns.Add("RowID", typeof(Guid)); - dataTable.Columns.Add("Description", typeof(string)); - dataTable.Columns.Add("Number", typeof(int)); - dataTable.Columns.Add("Flag", typeof(bool)); - dataTable.Columns.Add("Timestamp", typeof(DateTime)); - - dataTable.Rows.Add(new Guid("8286d046-9740-a3e4-95cf-ff46699c73c4"), "607156385", 1321446349, true, - new DateTime(641752306337096884)); - - dataTable.Rows.Add(new Guid("95c69371-b924-6fe3-7c38-98b7dd200bc1"), "1509870614", 505401118, true, - new DateTime(623130841631129390)); - - dataTable.Rows.Add(new Guid("a905569d-db07-3ae3-63a0-322750a4a3bd"), "265101196", 1836839534, true, - new DateTime(625984215542645543)); - - var subjectTable = new DataTable(); - - subjectTable.Columns.Add("RowID", typeof(Guid)); - subjectTable.Columns.Add("Description", typeof(string)); - subjectTable.Columns.Add("Number", typeof(int)); - subjectTable.Columns.Add("Flag", typeof(bool)); - subjectTable.Columns.Add("Timestamp", typeof(DateTime)); - - subjectTable.Rows.Add(new Guid("bc4519c8-fdeb-06e2-4a08-cc98c4273aba"), "1167815425", 1020794303, true, - new DateTime(628837589454161696)); - - var subjectRow = subjectTable.Rows[0]; - - // Act & Assert - dataTable.Rows.Should().NotContainEquivalentOf(subjectRow); - } - } -} diff --git a/Tests/FluentAssertions.Specs/Collections/Data/DataTableCollectionAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Collections/Data/DataTableCollectionAssertionSpecs.cs deleted file mode 100644 index c33decc33b..0000000000 --- a/Tests/FluentAssertions.Specs/Collections/Data/DataTableCollectionAssertionSpecs.cs +++ /dev/null @@ -1,539 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; -using Xunit; -using Xunit.Sdk; - -namespace FluentAssertions.Specs.Collections.Data; - -public static class DataTableCollectionAssertionSpecs -{ - public class BeSameAs - { - [Fact] - public void When_references_are_the_same_it_should_succeed() - { - // Arrange - var dataSet = new DataSet(); - - dataSet.Tables.Add(new DataTable("Table1")); - - var tableCollection1 = dataSet.Tables; - var tableCollection2 = tableCollection1; - - // Act & Assert - tableCollection1.Should().BeSameAs(tableCollection2); - } - - [Fact] - public void When_references_are_different_it_should_fail() - { - // Arrange - var dataSet1 = new DataSet(); - var dataSet2 = new DataSet(); - - dataSet1.Tables.Add(new DataTable("Table1")); - dataSet2.Tables.Add(new DataTable("Table1")); - - var tableCollection1 = dataSet1.Tables; - var tableCollection2 = dataSet2.Tables; - - // Act - Action action = - () => tableCollection1.Should().BeSameAs(tableCollection2); - - // Assert - action.Should().Throw().WithMessage( - "Expected tableCollection1 to refer to *, but found * (different underlying object)."); - } - - [Fact] - public void When_generic_collection_is_tested_against_typed_collection_it_should_fail() - { - // Arrange - var dataSet = new DataSet(); - - var tableCollection = dataSet.Tables; - - var genericCollection = tableCollection.Cast(); - - // Act - Action action = - () => genericCollection.Should().BeSameAs(tableCollection, because: "we {0}", "care"); - - // Assert - action.Should().Throw().WithMessage( - "Invalid expectation: Expected genericCollection to refer to an instance of DataTableCollection " + - "because we care, but found *."); - } - } - - public class NotBeSameAs - { - [Fact] - public void When_references_are_the_same_it_should_fail() - { - // Arrange - var dataSet = new DataSet(); - - dataSet.Tables.Add(new DataTable("Table1")); - - var tableCollection1 = dataSet.Tables; - var tableCollection2 = tableCollection1; - - // Act - Action action = - () => tableCollection1.Should().NotBeSameAs(tableCollection2); - - // Assert - action.Should().Throw().WithMessage( - "Did not expect tableCollection1 to refer to *."); - } - - [Fact] - public void When_references_are_different_it_should_succeed() - { - // Arrange - var dataSet1 = new DataSet(); - var dataSet2 = new DataSet(); - - dataSet1.Tables.Add(new DataTable("Table1")); - dataSet2.Tables.Add(new DataTable("Table1")); - - var tableCollection1 = dataSet1.Tables; - var tableCollection2 = dataSet2.Tables; - - // Act & Assert - tableCollection1.Should().NotBeSameAs(tableCollection2); - } - - [Fact] - public void When_generic_collection_is_tested_against_typed_collection_it_should_fail() - { - // Arrange - var dataSet = new DataSet(); - - var tableCollection = dataSet.Tables; - - var genericCollection = tableCollection.Cast(); - - // Act - Action action = - () => genericCollection.Should().NotBeSameAs(tableCollection, because: "we {0}", "care"); - - // Assert - action.Should().Throw().WithMessage( - "Invalid expectation: Expected genericCollection to refer to a different instance of " + - "DataTableCollection because we care, but found *."); - } - } - - public class HaveSameCount - { - [Fact] - public void When_subject_is_null_it_should_fail() - { - // Arrange - var subject = default(DataTableCollection); - - var expectation = new DataSet().Tables; - - // Act - Action action = - () => subject.Should().HaveSameCount(expectation, because: "we {0}", "care"); - - // Assert - action.Should().Throw().WithMessage( - "Expected * to have the same count as * because we care, but found .*"); - } - - [Fact] - public void When_expectation_is_null_it_should_fail() - { - // Arrange - var dataSet = new DataSet(); - - var nullReference = default(DataTableCollection); - - // Act - Action action = - () => dataSet.Tables.Should().HaveSameCount(nullReference); - - // Assert - action.Should().Throw().WithMessage( - "Cannot verify count against a collection.*"); - } - - public class DataSetAssertions - { - [Fact] - public void When_collections_have_the_same_number_of_tables_it_should_succeed() - { - // Arrange - var firstDataSet = new DataSet(); - var secondDataSet = new DataSet(); - - firstDataSet.Tables.Add(new DataTable("Table0")); - firstDataSet.Tables.Add(new DataTable("Table1")); - firstDataSet.Tables.Add(new DataTable("Table2")); - - secondDataSet.Tables.Add(new DataTable("Table10")); - secondDataSet.Tables.Add(new DataTable("Table11")); - secondDataSet.Tables.Add(new DataTable("Table12")); - - // Ensure that the table schema isn't important for the count comparison. - secondDataSet.Tables[0].Columns.Add("Column1", typeof(int)); - - // Act & Assert - firstDataSet.Tables.Should().HaveSameCount(secondDataSet, because: "we {0}", "care"); - } - - [Fact] - public void When_collections_do_not_have_the_same_number_of_tables_it_should_fail() - { - // Arrange - var firstDataSet = new DataSet(); - var secondDataSet = new DataSet(); - - firstDataSet.Tables.Add(new DataTable("Table0")); - firstDataSet.Tables.Add(new DataTable("Table1")); - firstDataSet.Tables.Add(new DataTable("Table2")); - - secondDataSet.Tables.Add(new DataTable("Table10")); - secondDataSet.Tables.Add(new DataTable("Table12")); - - // Act - Action action = - () => firstDataSet.Tables.Should().HaveSameCount(secondDataSet, because: "we {0}", "care"); - - // Assert - action.Should().Throw().WithMessage( - "Expected firstDataSet.Tables to have 2 table(s) because we care, but found 3."); - } - } - - public class DataTableCollectionAssertions - { - [Fact] - public void When_collections_have_the_same_number_of_tables_it_should_succeed() - { - // Arrange - var firstDataSet = new DataSet(); - var secondDataSet = new DataSet(); - - firstDataSet.Tables.Add(new DataTable("Table0")); - firstDataSet.Tables.Add(new DataTable("Table1")); - firstDataSet.Tables.Add(new DataTable("Table2")); - - secondDataSet.Tables.Add(new DataTable("Table10")); - secondDataSet.Tables.Add(new DataTable("Table11")); - secondDataSet.Tables.Add(new DataTable("Table12")); - - // Ensure that the table schema isn't important for the count comparison. - secondDataSet.Tables[0].Columns.Add("Column1", typeof(int)); - - // Act & Assert - firstDataSet.Tables.Should().HaveSameCount(secondDataSet.Tables); - } - - [Fact] - public void When_collections_do_not_have_the_same_number_of_tables_it_should_fail() - { - // Arrange - var firstDataSet = new DataSet(); - var secondDataSet = new DataSet(); - - firstDataSet.Tables.Add(new DataTable("Table0")); - firstDataSet.Tables.Add(new DataTable("Table1")); - firstDataSet.Tables.Add(new DataTable("Table2")); - - secondDataSet.Tables.Add(new DataTable("Table10")); - secondDataSet.Tables.Add(new DataTable("Table12")); - - // Act - Action action = - () => firstDataSet.Tables.Should().HaveSameCount(secondDataSet.Tables); - - // Assert - action.Should().Throw().WithMessage( - "Expected firstDataSet.Tables to have 2 table(s), but found 3."); - } - } - - public class GenericCollectionAssertions - { - [Fact] - public void When_collection_is_compared_with_null_it_should_fail() - { - // Arrange - var dataSet = new DataSet(); - - dataSet.Tables.Add(new DataTable("Table0")); - dataSet.Tables.Add(new DataTable("Table1")); - dataSet.Tables.Add(new DataTable("Table2")); - - List nullDataTables = null; - - // Act - Action action = - () => nullDataTables.Should().HaveSameCount(dataSet.Tables, because: "we {0}", "care"); - - // Assert - action.Should().Throw().WithMessage( - "Expected nullDataTables to have the same count as * because we care, but found ."); - } - - [Fact] - public void When_collection_is_compared_with_typed_collection_with_same_number_of_tables_it_should_succeed() - { - // Arrange - var firstDataSet = new DataSet(); - var secondDataSet = new DataSet(); - - firstDataSet.Tables.Add(new DataTable("Table0")); - firstDataSet.Tables.Add(new DataTable("Table1")); - firstDataSet.Tables.Add(new DataTable("Table2")); - - secondDataSet.Tables.Add(new DataTable("Table10")); - secondDataSet.Tables.Add(new DataTable("Table11")); - secondDataSet.Tables.Add(new DataTable("Table12")); - - // Ensure that the table schema isn't important for the count comparison. - secondDataSet.Tables[0].Columns.Add("Column1", typeof(int)); - - var genericDataTableCollection = firstDataSet.Tables.Cast(); - - // Act & Assert - genericDataTableCollection.Should().HaveSameCount(secondDataSet.Tables); - } - - [Fact] - public void When_collection_is_compared_with_typed_collection_with_different_number_of_tables_it_should_fail() - { - // Arrange - var firstDataSet = new DataSet(); - var secondDataSet = new DataSet(); - - firstDataSet.Tables.Add(new DataTable("Table0")); - firstDataSet.Tables.Add(new DataTable("Table1")); - firstDataSet.Tables.Add(new DataTable("Table2")); - - secondDataSet.Tables.Add(new DataTable("Table10")); - secondDataSet.Tables.Add(new DataTable("Table12")); - - var genericDataTableCollection = firstDataSet.Tables.Cast(); - - // Act - Action action = - () => genericDataTableCollection.Should().HaveSameCount(secondDataSet.Tables, because: "we {0}", "care"); - - // Assert - action.Should().Throw().WithMessage( - "Expected genericDataTableCollection to have 2 table(s) because we care, but found 3."); - } - } - } - - public class NotHaveSameCount - { - [Fact] - public void When_subject_is_null_it_should_fail() - { - // Arrange - var subject = default(DataTableCollection); - - var expectation = new DataSet().Tables; - - // Act - Action action = - () => subject.Should().NotHaveSameCount(expectation, because: "we {0}", "care"); - - // Assert - action.Should().Throw().WithMessage( - "Expected * to not have the same count as * because we care, but found .*"); - } - - [Fact] - public void When_expectation_is_null_it_should_fail() - { - // Arrange - var dataSet = new DataSet(); - - dataSet.Tables.Add(new DataTable("Table0")); - dataSet.Tables.Add(new DataTable("Table1")); - dataSet.Tables.Add(new DataTable("Table2")); - - var nullReference = default(DataTableCollection); - - // Act - Action action = - () => dataSet.Tables.Should().NotHaveSameCount(nullReference); - - // Assert - action.Should().Throw().WithMessage( - "Cannot verify count against a collection.*"); - } - - public class DataTableCollectionAssertions - { - [Fact] - public void When_two_collections_have_different_number_of_tables_it_should_succeed() - { - // Arrange - var firstDataSet = new DataSet(); - var secondDataSet = new DataSet(); - - firstDataSet.Tables.Add(new DataTable("Table0")); - firstDataSet.Tables.Add(new DataTable("Table1")); - firstDataSet.Tables.Add(new DataTable("Table2")); - - secondDataSet.Tables.Add(new DataTable("Table10")); - secondDataSet.Tables.Add(new DataTable("Table12")); - - // Act & Assert - firstDataSet.Tables.Should().NotHaveSameCount(secondDataSet.Tables); - } - - [Fact] - public void When_two_collections_have_the_same_number_of_tables_it_should_fail() - { - // Arrange - var firstDataSet = new DataSet(); - var secondDataSet = new DataSet(); - - firstDataSet.Tables.Add(new DataTable("Table0")); - firstDataSet.Tables.Add(new DataTable("Table1")); - firstDataSet.Tables.Add(new DataTable("Table2")); - - secondDataSet.Tables.Add(new DataTable("Table10")); - secondDataSet.Tables.Add(new DataTable("Table11")); - secondDataSet.Tables.Add(new DataTable("Table12")); - - // Act - Action action = - () => firstDataSet.Tables.Should().NotHaveSameCount(secondDataSet.Tables, because: "we {0}", "care"); - - // Assert - action.Should().Throw().WithMessage( - "Expected firstDataSet.Tables to not have 3 table(s) because we care, but found 3."); - } - } - - public class DataSetAssertions - { - [Fact] - public void When_two_collections_have_different_number_of_tables_it_should_succeed() - { - // Arrange - var firstDataSet = new DataSet(); - var secondDataSet = new DataSet(); - - firstDataSet.Tables.Add(new DataTable("Table0")); - firstDataSet.Tables.Add(new DataTable("Table1")); - firstDataSet.Tables.Add(new DataTable("Table2")); - - secondDataSet.Tables.Add(new DataTable("Table10")); - secondDataSet.Tables.Add(new DataTable("Table12")); - - // Act & Assert - firstDataSet.Tables.Should().NotHaveSameCount(secondDataSet, because: "we {0}", "care"); - } - - [Fact] - public void When_two_collections_have_the_same_number_of_tables_it_should_fail() - { - // Arrange - var firstDataSet = new DataSet(); - var secondDataSet = new DataSet(); - - firstDataSet.Tables.Add(new DataTable("Table0")); - firstDataSet.Tables.Add(new DataTable("Table1")); - firstDataSet.Tables.Add(new DataTable("Table2")); - - secondDataSet.Tables.Add(new DataTable("Table10")); - secondDataSet.Tables.Add(new DataTable("Table11")); - secondDataSet.Tables.Add(new DataTable("Table12")); - - // Act - Action action = - () => firstDataSet.Tables.Should().NotHaveSameCount(secondDataSet, because: "we {0}", "care"); - - // Assert - action.Should().Throw().WithMessage( - "Expected firstDataSet.Tables to not have 3 table(s) because we care, but found 3."); - } - } - - public class GenericCollectionAssertions - { - [Fact] - public void When_collection_is_compared_with_null_it_should_fail() - { - // Arrange - var dataSet = new DataSet(); - - dataSet.Tables.Add(new DataTable("Table0")); - dataSet.Tables.Add(new DataTable("Table1")); - dataSet.Tables.Add(new DataTable("Table2")); - - List nullDataTables = null; - - // Act - Action action = - () => nullDataTables.Should().NotHaveSameCount(dataSet.Tables, because: "we {0}", "care"); - - // Assert - action.Should().Throw().WithMessage( - "Expected nullDataTables to not have the same count as * because we care, but found ."); - } - - [Fact] - public void When_collection_is_compared_with_typed_collection_with_same_number_of_tables_it_should_fail() - { - // Arrange - var firstDataSet = new DataSet(); - var secondDataSet = new DataSet(); - - firstDataSet.Tables.Add(new DataTable("Table0")); - firstDataSet.Tables.Add(new DataTable("Table1")); - firstDataSet.Tables.Add(new DataTable("Table2")); - - secondDataSet.Tables.Add(new DataTable("Table10")); - secondDataSet.Tables.Add(new DataTable("Table11")); - secondDataSet.Tables.Add(new DataTable("Table12")); - - var genericDataTableCollection = firstDataSet.Tables.Cast(); - - // Act - Action action = - () => genericDataTableCollection.Should().NotHaveSameCount(secondDataSet.Tables, - because: "we {0}", "care"); - - // Assert - action.Should().Throw().WithMessage( - "Expected genericDataTableCollection to not have 3 table(s) because we care, but found 3."); - } - - [Fact] - public void When_collection_is_compared_with_typed_collection_with_different_number_of_tables_it_should_succeed() - { - // Arrange - var firstDataSet = new DataSet(); - var secondDataSet = new DataSet(); - - firstDataSet.Tables.Add(new DataTable("Table0")); - firstDataSet.Tables.Add(new DataTable("Table1")); - firstDataSet.Tables.Add(new DataTable("Table2")); - - secondDataSet.Tables.Add(new DataTable("Table10")); - secondDataSet.Tables.Add(new DataTable("Table12")); - - var genericDataTableCollection = firstDataSet.Tables.Cast(); - - // Act & Assert - genericDataTableCollection.Should().NotHaveSameCount(secondDataSet.Tables); - } - } - } -} diff --git a/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.AllSatisfy.cs b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.AllSatisfy.cs index aefdfd2f3d..a620661cf4 100644 --- a/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.AllSatisfy.cs +++ b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.AllSatisfy.cs @@ -4,9 +4,6 @@ namespace FluentAssertions.Specs.Collections; -/// -/// This part contains tests that address AllSatisfy -/// public partial class GenericCollectionAssertionOfStringSpecs { public class AllSatisfy @@ -15,7 +12,7 @@ public class AllSatisfy public void All_items_satisfying_inspector_should_succeed() { // Arrange - string[] collection = { "John", "John" }; + string[] collection = ["John", "John"]; // Act / Assert collection.Should().AllSatisfy(value => value.Should().Be("John")); @@ -25,7 +22,7 @@ public void All_items_satisfying_inspector_should_succeed() public void Any_items_not_satisfying_inspector_should_throw() { // Arrange - string[] collection = { "Jack", "Jessica" }; + string[] collection = ["Jack", "Jessica"]; // Act Action act = () => collection.Should() diff --git a/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.BeEmpty.cs b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.BeEmpty.cs new file mode 100644 index 0000000000..d51df7c328 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.BeEmpty.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Collections; + +public partial class GenericCollectionAssertionOfStringSpecs +{ + public class BeEmpty + { + [Fact] + public void Should_fail_when_asserting_collection_with_items_is_empty() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + + // Act + Action act = () => collection.Should().BeEmpty(); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Should_succeed_when_asserting_collection_without_items_is_empty() + { + // Arrange + IEnumerable collection = new string[0]; + + // Act / Assert + collection.Should().BeEmpty(); + } + + [Fact] + public void When_asserting_collection_to_be_empty_but_collection_is_null_it_should_throw() + { + // Arrange + IEnumerable collection = null; + + // Act + Action act = () => collection.Should().BeEmpty("because we want to test the behaviour with a null subject"); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection to be empty because we want to test the behaviour with a null subject, but found ."); + } + + [Fact] + public void When_the_collection_is_not_empty_unexpectedly_it_should_throw() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + + // Act + Action act = () => collection.Should().BeEmpty("because we want to test the failure {0}", "message"); + + // Assert + act + .Should().Throw() + .WithMessage( + "Expected collection to be empty because we want to test the failure message, but found at least one item*one*"); + } + } + + public class NotBeEmpty + { + [Fact] + public void When_asserting_collection_to_be_not_empty_but_collection_is_null_it_should_throw() + { + // Arrange + IEnumerable collection = null; + + // Act + Action act = () => collection.Should().NotBeEmpty("because we want to test the behaviour with a null subject"); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection not to be empty because we want to test the behaviour with a null subject, but found ."); + } + + [Fact] + public void When_asserting_collection_with_items_is_not_empty_it_should_succeed() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + + // Act / Assert + collection.Should().NotBeEmpty(); + } + + [Fact] + public void When_asserting_collection_without_items_is_not_empty_it_should_fail() + { + // Arrange + IEnumerable collection = new string[0]; + + // Act + Action act = () => collection.Should().NotBeEmpty(); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_asserting_collection_without_items_is_not_empty_it_should_fail_with_descriptive_message_() + { + // Arrange + IEnumerable collection = new string[0]; + + // Act + Action act = () => collection.Should().NotBeEmpty("because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw() + .WithMessage("Expected collection not to be empty because we want to test the failure message."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.BeEquivalentTo.cs b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.BeEquivalentTo.cs new file mode 100644 index 0000000000..d23523d701 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.BeEquivalentTo.cs @@ -0,0 +1,200 @@ +using System; +using System.Collections.Generic; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Collections; + +public partial class GenericCollectionAssertionOfStringSpecs +{ + public class BeEquivalentTo + { + [Fact] + public void When_asserting_collections_to_be_equivalent_but_subject_collection_is_null_it_should_throw() + { + // Arrange + IEnumerable collection = null; + IEnumerable collection1 = ["one", "two", "three"]; + + // Act + Action act = + () => collection.Should() + .BeEquivalentTo(collection1, "because we want to test the behaviour with a null subject"); + + // Assert + act.Should().Throw() + .WithMessage("Expected collection*not to be *"); + } + + [Fact] + public void When_collections_with_duplicates_are_not_equivalent_it_should_throw() + { + // Arrange + IEnumerable collection1 = ["one", "two", "three", "one"]; + IEnumerable collection2 = ["one", "two", "three", "three"]; + + // Act + Action act = () => collection1.Should().BeEquivalentTo(collection2); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection1[3]*to be \"three\" with a length of 5, but \"one\" has a length of 3*"); + } + + [Fact] + public void When_testing_for_equivalence_against_empty_collection_it_should_throw() + { + // Arrange + IEnumerable subject = ["one", "two", "three"]; + IEnumerable otherCollection = new string[0]; + + // Act + Action act = () => subject.Should().BeEquivalentTo(otherCollection); + + // Assert + act.Should().Throw().WithMessage( + "Expected subject*to be a collection with 0 item(s), but*contains 3 item(s)*"); + } + + [Fact] + public void When_testing_for_equivalence_against_null_collection_it_should_throw() + { + // Arrange + IEnumerable collection1 = ["one", "two", "three"]; + IEnumerable collection2 = null; + + // Act + Action act = () => collection1.Should().BeEquivalentTo(collection2); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection1*to be , but found {\"one\", \"two\", \"three\"}*"); + } + + [Fact] + public void When_two_collections_are_both_empty_it_should_treat_them_as_equivalent() + { + // Arrange + IEnumerable subject = new string[0]; + IEnumerable otherCollection = new string[0]; + + // Act + Action act = () => subject.Should().BeEquivalentTo(otherCollection); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_two_collections_contain_the_same_elements_it_should_treat_them_as_equivalent() + { + // Arrange + IEnumerable collection1 = ["one", "two", "three"]; + IEnumerable collection2 = ["three", "two", "one"]; + + // Act / Assert + collection1.Should().BeEquivalentTo(collection2); + } + + [Fact] + public void When_two_arrays_contain_the_same_elements_it_should_treat_them_as_equivalent() + { + // Arrange + string[] array1 = ["one", "two", "three"]; + string[] array2 = ["three", "two", "one"]; + + // Act / Assert + array1.Should().BeEquivalentTo(array2); + } + } + + public class NotBeEquivalentTo + { + [Fact] + public void Should_succeed_when_asserting_collection_is_not_equivalent_to_a_different_collection() + { + // Arrange + IEnumerable collection1 = ["one", "two", "three"]; + IEnumerable collection2 = ["three", "one"]; + + // Act / Assert + collection1.Should().NotBeEquivalentTo(collection2); + } + + [Fact] + public void When_asserting_collections_not_to_be_equivalent_but_subject_collection_is_null_it_should_throw() + { + // Arrange + IEnumerable actual = null; + IEnumerable expectation = ["one", "two", "three"]; + + // Act + Action act = () => actual.Should().NotBeEquivalentTo(expectation, + "because we want to test the behaviour with a null subject"); + + // Assert + act.Should().Throw().WithMessage( + "Expected actual not to be equivalent because we want to test the behaviour with a null subject, but found ."); + } + + [Fact] + public void When_collections_are_unexpectedly_equivalent_it_should_throw() + { + // Arrange + IEnumerable collection1 = ["one", "two", "three"]; + IEnumerable collection2 = ["three", "one", "two"]; + + // Act + Action act = () => collection1.Should().NotBeEquivalentTo(collection2); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection1 {\"one\", \"two\", \"three\"} not*equivalent*{\"three\", \"one\", \"two\"}."); + } + + [Fact] + public void When_non_empty_collection_is_not_expected_to_be_equivalent_to_an_empty_collection_it_should_succeed() + { + // Arrange + IEnumerable collection1 = ["one", "two", "three"]; + IEnumerable collection2 = new string[0]; + + // Act + Action act = () => collection1.Should().NotBeEquivalentTo(collection2); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_testing_collections_not_to_be_equivalent_against_null_collection_it_should_throw() + { + // Arrange + IEnumerable collection1 = ["one", "two", "three"]; + IEnumerable collection2 = null; + + // Act + Action act = () => collection1.Should().NotBeEquivalentTo(collection2); + + // Assert + act.Should().Throw().WithMessage( + "Cannot verify inequivalence against a collection.*"); + } + + [Fact] + public void When_testing_collections_not_to_be_equivalent_against_same_collection_it_should_throw() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + IEnumerable collection1 = collection; + + // Act + Action act = () => collection.Should().NotBeEquivalentTo(collection1, + "because we want to test the behaviour with same objects"); + + // Assert + act.Should().Throw().WithMessage( + "*not to be equivalent*because we want to test the behaviour with same objects*but they both reference the same object."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.BeNull.cs b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.BeNull.cs new file mode 100644 index 0000000000..433a253111 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.BeNull.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Collections; + +public partial class GenericCollectionAssertionOfStringSpecs +{ + public class BeNull + { + [Fact] + public void When_collection_is_expected_to_be_null_and_it_is_it_should_not_throw() + { + // Arrange + IEnumerable someCollection = null; + + // Act / Assert + someCollection.Should().BeNull(); + } + + [Fact] + public void When_collection_is_expected_to_be_null_and_it_isnt_it_should_throw() + { + // Arrange + IEnumerable someCollection = new string[0]; + + // Act + Action act = () => someCollection.Should().BeNull("because {0} is valid", "null"); + + // Assert + act.Should().Throw().WithMessage( + "Expected someCollection to be because null is valid, but found {empty}."); + } + } + + public class NotBeNull + { + [Fact] + public void When_collection_is_not_expected_to_be_null_and_it_is_it_should_throw() + { + // Arrange + IEnumerable someCollection = null; + + // Act + Action act = () => someCollection.Should().NotBeNull("because {0} should not", "someCollection"); + + // Assert + act.Should().Throw().WithMessage( + "Expected someCollection not to be because someCollection should not."); + } + + [Fact] + public void When_collection_is_not_expected_to_be_null_and_it_isnt_it_should_not_throw() + { + // Arrange + IEnumerable someCollection = new string[0]; + + // Act / Assert + someCollection.Should().NotBeNull(); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.BeSubsetOf.cs b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.BeSubsetOf.cs new file mode 100644 index 0000000000..d6f8fa106b --- /dev/null +++ b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.BeSubsetOf.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Collections; + +public partial class GenericCollectionAssertionOfStringSpecs +{ + public class BeSubsetOf + { + [Fact] + public void When_a_subset_is_tested_against_a_null_superset_it_should_throw_with_a_clear_explanation() + { + // Arrange + IEnumerable subset = ["one", "two", "three"]; + IEnumerable superset = null; + + // Act + Action act = () => subset.Should().BeSubsetOf(superset); + + // Assert + act.Should().Throw().WithMessage( + "Cannot verify a subset against a collection.*"); + } + + [Fact] + public void When_an_empty_collection_is_tested_against_a_superset_it_should_succeed() + { + // Arrange + IEnumerable subset = new string[0]; + IEnumerable superset = ["one", "two", "four", "five"]; + + // Act + Action act = () => subset.Should().BeSubsetOf(superset); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_collection_to_be_subset_against_null_collection_it_should_throw() + { + // Arrange + IEnumerable collection = null; + IEnumerable collection1 = ["one", "two", "three"]; + + // Act + Action act = + () => collection.Should().BeSubsetOf(collection1, "because we want to test the behaviour with a null subject"); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection to be a subset of {\"one\", \"two\", \"three\"} because we want to test the behaviour with a null subject, but found ."); + } + + [Fact] + public void When_collection_is_not_a_subset_of_another_it_should_throw_with_the_reason() + { + // Arrange + IEnumerable subset = ["one", "two", "three", "six"]; + IEnumerable superset = ["one", "two", "four", "five"]; + + // Act + Action act = () => subset.Should().BeSubsetOf(superset, "because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw().WithMessage( + "Expected subset to be a subset of {\"one\", \"two\", \"four\", \"five\"} because we want to test the failure message, " + + "but items {\"three\", \"six\"} are not part of the superset."); + } + + [Fact] + public void When_collection_is_subset_of_a_specified_collection_it_should_not_throw() + { + // Arrange + IEnumerable subset = ["one", "two"]; + IEnumerable superset = ["one", "two", "three"]; + + // Act / Assert + subset.Should().BeSubsetOf(superset); + } + } + + public class NotBeSubsetOf + { + [Fact] + public void Should_fail_when_asserting_collection_is_not_subset_of_a_superset_collection() + { + // Arrange + IEnumerable subject = ["one", "two"]; + IEnumerable otherSet = ["one", "two", "three"]; + + // Act + Action act = () => subject.Should().NotBeSubsetOf(otherSet, "because I'm {0}", "mistaken"); + + // Assert + act.Should().Throw().WithMessage( + "Did not expect subject {\"one\", \"two\"} to be a subset of {\"one\", \"two\", \"three\"} because I'm mistaken."); + } + + [Fact] + public void When_a_set_is_expected_to_be_not_a_subset_it_should_succeed() + { + // Arrange + IEnumerable subject = ["one", "two", "four"]; + IEnumerable otherSet = ["one", "two", "three"]; + + // Act / Assert + subject.Should().NotBeSubsetOf(otherSet); + } + + [Fact] + public void When_an_empty_set_is_not_supposed_to_be_a_subset_of_another_set_it_should_throw() + { + // Arrange + IEnumerable subject = []; + IEnumerable otherSet = ["one", "two", "three"]; + + // Act + Action act = () => subject.Should().NotBeSubsetOf(otherSet); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect subject {empty} to be a subset of {\"one\", \"two\", \"three\"}."); + } + + [Fact] + public void When_asserting_collection_to_not_be_subset_against_same_collection_it_should_throw() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + IEnumerable otherCollection = collection; + + // Act + Action act = () => collection.Should().NotBeSubsetOf(otherCollection, + "because we want to test the behaviour with same objects"); + + // Assert + act.Should().Throw().WithMessage( + "Did not expect*to be a subset of*because we want to test the behaviour with same objects*but they both reference the same object."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.Contain.cs b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.Contain.cs new file mode 100644 index 0000000000..fc150f0483 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.Contain.cs @@ -0,0 +1,177 @@ +using System; +using System.Collections.Generic; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Collections; + +public partial class GenericCollectionAssertionOfStringSpecs +{ + public class Contain + { + [Fact] + public void When_a_collection_does_not_contain_another_collection_it_should_throw_with_clear_explanation() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + + // Act + Action act = () => collection.Should().Contain(["three", "four", "five"], "because {0}", "we do"); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection {\"one\", \"two\", \"three\"} to contain {\"three\", \"four\", \"five\"} because we do, but could not find {\"four\", \"five\"}."); + } + + [Fact] + public void When_a_collection_does_not_contain_single_item_it_should_throw_with_clear_explanation() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + + // Act + Action act = () => collection.Should().Contain("four", "because {0}", "we do"); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection {\"one\", \"two\", \"three\"} to contain \"four\" because we do."); + } + + [Fact] + public void + When_asserting_a_string_collection_contains_an_element_it_should_allow_specifying_the_reason_via_named_parameter() + { + // Arrange + var expected = new List { "hello", "world" }; + var actual = new List { "hello", "world" }; + + // Act + Action act = () => expected.Should().Contain(actual, "they are in the collection"); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_collection_contains_an_item_from_the_collection_it_should_succeed() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + + // Act + Action act = () => collection.Should().Contain("one"); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_collection_contains_multiple_items_from_the_collection_in_any_order_it_should_succeed() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + + // Act + Action act = () => collection.Should().Contain(["two", "one"]); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_the_contents_of_a_collection_are_checked_against_an_empty_collection_it_should_throw_clear_explanation() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + + // Act + Action act = () => collection.Should().Contain(new string[0]); + + // Assert + act.Should().Throw().WithMessage( + "Cannot verify containment against an empty collection*"); + } + + [Fact] + public void When_the_expected_object_exists_it_should_allow_chaining_additional_assertions() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + + // Act + Action act = () => collection.Should().Contain("one").Which.Should().HaveLength(4); + + // Assert + act.Should().Throw().WithMessage("Expected*length*4*3*"); + } + } + + public class NotContain + { + [Fact] + public void When_asserting_collection_does_not_contain_item_against_null_collection_it_should_throw() + { + // Arrange + IEnumerable collection = null; + + // Act + Action act = () => collection.Should() + .NotContain("one", "because we want to test the behaviour with a null subject"); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection to not contain \"one\" because we want to test the behaviour with a null subject, but found ."); + } + + [Fact] + public void When_collection_contains_an_unexpected_item_it_should_throw() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + + // Act + Action act = () => collection.Should().NotContain("one", "because we {0} like it, but found it anyhow", "don't"); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection {\"one\", \"two\", \"three\"} to not contain \"one\" because we don't like it, but found it anyhow."); + } + + [Fact] + public void When_collection_does_contain_an_unexpected_item_matching_a_predicate_it_should_throw() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + + // Act + Action act = () => collection.Should().NotContain(item => item == "two", "because {0}s are evil", "two"); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection {\"one\", \"two\", \"three\"} to not have any items matching (item == \"two\") because twos are evil,*{\"two\"}*"); + } + + [Fact] + public void When_collection_does_not_contain_an_item_that_is_not_in_the_collection_it_should_not_throw() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + + // Act + Action act = () => collection.Should().NotContain("four"); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_collection_does_not_contain_an_unexpected_item_matching_a_predicate_it_should_not_throw() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + + // Act / Assert + collection.Should().NotContain(item => item == "four"); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.ContainInOrder.cs b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.ContainInOrder.cs new file mode 100644 index 0000000000..8b0dc7af79 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.ContainInOrder.cs @@ -0,0 +1,248 @@ +using System; +using System.Collections.Generic; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Collections; + +public partial class GenericCollectionAssertionOfStringSpecs +{ + public class ContainInOrder + { + [Fact] + public void When_a_collection_does_not_contain_a_range_twice_it_should_throw() + { + // Arrange + IEnumerable collection = ["one", "two", "one", "three", "twelve", "two", "two"]; + + // Act + Action act = () => collection.Should().ContainInOrder("one", "two", "one", "one", "two"); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection {\"one\", \"two\", \"one\", \"three\", \"twelve\", \"two\", \"two\"} to contain items {\"one\", \"two\", \"one\", \"one\", \"two\"} in order, but \"one\" (index 3) did not appear (in the right order)."); + } + + [Fact] + public void When_a_collection_does_not_contain_an_ordered_item_it_should_throw_with_a_clear_explanation() + { + // Act + Action act = () => new[] { "one", "two", "three" }.Should().ContainInOrder(["four", "one"], "we failed"); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection {\"one\", \"two\", \"three\"} to contain items {\"four\", \"one\"} in order because we failed, " + + "but \"four\" (index 0) did not appear (in the right order)."); + } + + [Fact] + public void When_asserting_collection_contains_some_values_in_order_but_collection_is_null_it_should_throw() + { + // Arrange + IEnumerable strings = null; + + // Act + Action act = + () => strings.Should() + .ContainInOrder(["string4"], "because we're checking how it reacts to a null subject"); + + // Assert + act.Should().Throw().WithMessage( + "Expected strings to contain {\"string4\"} in order because we're checking how it reacts to a null subject, but found ."); + } + + [Fact] + public void When_collection_contains_null_value_it_should_not_throw() + { + // Arrange + IEnumerable collection = ["one", null, "two", "string"]; + + // Act / Assert + collection.Should().ContainInOrder("one", null, "string"); + } + + [Fact] + public void When_passing_in_null_while_checking_for_ordered_containment_it_should_throw_with_a_clear_explanation() + { + // Act + Action act = () => new[] { "one", "two", "three" }.Should().ContainInOrder(null); + + // Assert + act.Should().Throw().WithMessage( + "Cannot verify ordered containment against a collection.*"); + } + + [Fact] + public void When_the_first_collection_contains_a_duplicate_item_without_affecting_the_order_it_should_not_throw() + { + // Arrange + IEnumerable collection = ["one", "two", "three", "two"]; + + // Act / Assert + collection.Should().ContainInOrder("one", "two", "three"); + } + + [Fact] + public void When_two_collections_contain_the_same_duplicate_items_in_the_same_order_it_should_not_throw() + { + // Arrange + IEnumerable collection = ["one", "two", "one", "two", "twelve", "two", "two"]; + + // Act / Assert + collection.Should().ContainInOrder("one", "two", "one", "two", "twelve", "two", "two"); + } + + [Fact] + public void When_two_collections_contain_the_same_items_but_in_different_order_it_should_throw_with_a_clear_explanation() + { + // Act + Action act = () => + new[] { "one", "two", "three" }.Should().ContainInOrder(["three", "one"], "because we said so"); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection {\"one\", \"two\", \"three\"} to contain items {\"three\", \"one\"} in order because we said so, but \"one\" (index 1) did not appear (in the right order)."); + } + + [Fact] + public void When_two_collections_contain_the_same_items_in_the_same_order_it_should_not_throw() + { + // Arrange + IEnumerable collection = ["one", "two", "two", "three"]; + + // Act / Assert + collection.Should().ContainInOrder("one", "two", "three"); + } + } + + public class NotContainInOrder + { + [Fact] + public void When_two_collections_contain_the_same_items_but_in_different_order_it_should_not_throw() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + + // Act / Assert + collection.Should().NotContainInOrder("two", "one"); + } + + [Fact] + public void When_a_collection_does_not_contain_an_ordered_item_it_should_not_throw() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + + // Act / Assert + collection.Should().NotContainInOrder("four", "one"); + } + + [Fact] + public void When_a_collection_contains_less_items_it_should_not_throw() + { + // Arrange + IEnumerable collection = ["one", "two"]; + + // Act / Assert + collection.Should().NotContainInOrder("one", "two", "three"); + } + + [Fact] + public void When_a_collection_does_not_contain_a_range_twice_it_should_not_throw() + { + // Arrange + IEnumerable collection = ["one", "two", "one", "three", "twelve", "two", "two"]; + + // Act / Assert + collection.Should().NotContainInOrder("one", "two", "one", "one", "two"); + } + + [Fact] + public void When_asserting_collection_does_not_contain_some_values_in_order_but_collection_is_null_it_should_throw() + { + // Arrange + IEnumerable collection = null; + + // Act + Action act = () => collection.Should().NotContainInOrder("four"); + + // Assert + act.Should().Throw() + .WithMessage("Cannot verify absence of ordered containment in a collection."); + } + + [Fact] + public void When_two_collections_contain_the_same_items_in_the_same_order_it_should_throw() + { + // Arrange + IEnumerable collection = ["one", "two", "two", "three"]; + + // Act + Action act = () => collection.Should().NotContainInOrder(["one", "two", "three"], "that's what we expect"); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection {\"one\", \"two\", \"two\", \"three\"} to not contain items {\"one\", \"two\", \"three\"} " + + "in order because that's what we expect, but items appeared in order ending at index 3."); + } + + [Fact] + public void When_collection_contains_contain_the_same_items_in_the_same_order_with_null_value_it_should_throw() + { + // Arrange + IEnumerable collection = ["one", null, "two", "three"]; + + // Act + Action act = () => collection.Should().NotContainInOrder("one", null, "three"); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection {\"one\", , \"two\", \"three\"} to not contain items {\"one\", , \"three\"} in order, " + + "but items appeared in order ending at index 3."); + } + + [Fact] + public void When_the_first_collection_contains_a_duplicate_item_without_affecting_the_order_it_should_throw() + { + // Arrange + IEnumerable collection = ["one", "two", "three", "two"]; + + // Act + Action act = () => collection.Should().NotContainInOrder("one", "two", "three"); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection {\"one\", \"two\", \"three\", \"two\"} to not contain items {\"one\", \"two\", \"three\"} in order, " + + "but items appeared in order ending at index 2."); + } + + [Fact] + public void When_two_collections_contain_the_same_duplicate_items_in_the_same_order_it_should_throw() + { + // Arrange + IEnumerable collection = ["one", "two", "one", "twelve", "two"]; + + // Act + Action act = () => collection.Should().NotContainInOrder("one", "two", "one", "twelve", "two"); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection {\"one\", \"two\", \"one\", \"twelve\", \"two\"} to not contain items " + + "{\"one\", \"two\", \"one\", \"twelve\", \"two\"} in order, but items appeared in order ending at index 4."); + } + + [Fact] + public void When_passing_in_null_while_checking_for_absence_of_ordered_containment_it_should_throw() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + + // Act + Action act = () => collection.Should().NotContainInOrder(null); + + // Assert + act.Should().Throw().WithMessage( + "Cannot verify absence of ordered containment against a collection.*"); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.ContainMatch.cs b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.ContainMatch.cs new file mode 100644 index 0000000000..6901a1e685 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.ContainMatch.cs @@ -0,0 +1,283 @@ +using System; +using System.Collections.Generic; +using FluentAssertions.Execution; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Collections; + +public partial class GenericCollectionAssertionOfStringSpecs +{ + public class ContainMatch + { + [Fact] + public void When_collection_contains_a_match_it_should_not_throw() + { + // Arrange + IEnumerable collection = ["build succeded", "test failed"]; + + // Act + Action action = () => collection.Should().ContainMatch("* failed"); + + // Assert + action.Should().NotThrow(); + } + + [Fact] + public void When_collection_contains_multiple_matches_it_should_not_throw() + { + // Arrange + IEnumerable collection = ["build succeded", "test failed", "pack failed"]; + + // Act + Action action = () => collection.Should().ContainMatch("* failed"); + + // Assert + action.Should().NotThrow(); + } + + [Fact] + 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"]; + + // Act + Action action = () => _ = collection.Should().ContainMatch("* failed").Which; + + // Assert + action.Should().Throw() + .WithMessage("More than one object found. FluentAssertions cannot determine which object is meant.*") + .WithMessage("*Found objects:*\"test failed\"*\"pack failed\""); + } + + [Fact] + public void When_collection_does_not_contain_a_match_it_should_throw() + { + // Arrange + IEnumerable collection = ["build succeded", "test failed"]; + + // Act + Action action = () => collection.Should().ContainMatch("* stopped", "because {0}", "we do"); + + // Assert + action.Should().Throw() + .WithMessage( + "Expected collection {\"build succeded\", \"test failed\"} to contain a match of \"* stopped\" because we do."); + } + + [Fact] + public void When_collection_contains_a_match_that_differs_in_casing_it_should_throw() + { + // Arrange + IEnumerable collection = ["build succeded", "test failed"]; + + // Act + Action action = () => collection.Should().ContainMatch("* Failed"); + + // Assert + action.Should().Throw() + .WithMessage("Expected collection {\"build succeded\", \"test failed\"} to contain a match of \"* Failed\"."); + } + + [Fact] + public void When_asserting_empty_collection_for_match_it_should_throw() + { + // Arrange + IEnumerable collection = []; + + // Act + Action action = () => collection.Should().ContainMatch("* failed"); + + // Assert + action.Should().Throw() + .WithMessage("Expected collection {empty} to contain a match of \"* failed\"."); + } + + [Fact] + public void When_asserting_null_collection_for_match_it_should_throw() + { + // Arrange + IEnumerable collection = null; + + // Act + Action action = () => + { + using var _ = new AssertionScope(); + collection.Should().ContainMatch("* failed", "because {0}", "we do"); + }; + + // Assert + action.Should().Throw() + .WithMessage("Expected collection to contain a match of \"* failed\" because we do, but found ."); + } + + [Fact] + public void When_asserting_collection_to_have_null_match_it_should_throw() + { + // Arrange + IEnumerable collection = ["build succeded", "test failed"]; + + // Act + Action action = () => collection.Should().ContainMatch(null); + + // Assert + action.Should().Throw() + .WithMessage( + "Cannot match strings in collection against . Provide a wildcard pattern or use the Contain method.*") + .WithParameterName("wildcardPattern"); + } + + [Fact] + public void When_asserting_collection_to_have_empty_string_match_it_should_throw() + { + // Arrange + IEnumerable collection = ["build succeded", "test failed"]; + + // Act + Action action = () => collection.Should().ContainMatch(string.Empty); + + // Assert + action.Should().Throw() + .WithMessage( + "Cannot match strings in collection against an empty string. Provide a wildcard pattern or use the Contain method.*") + .WithParameterName("wildcardPattern"); + } + } + + public class NotContainMatch + { + [Fact] + public void When_collection_doesnt_contain_a_match_it_should_not_throw() + { + // Arrange + IEnumerable collection = ["build succeded", "test"]; + + // Act + Action action = () => collection.Should().NotContainMatch("* failed"); + + // Assert + action.Should().NotThrow(); + } + + [Fact] + public void When_collection_doesnt_contain_multiple_matches_it_should_not_throw() + { + // Arrange + IEnumerable collection = ["build succeded", "test", "pack"]; + + // Act + Action action = () => collection.Should().NotContainMatch("* failed"); + + // Assert + action.Should().NotThrow(); + } + + [Fact] + public void When_collection_contains_a_match_it_should_throw() + { + // Arrange + IEnumerable collection = ["build succeded", "test failed"]; + + // Act + Action action = () => collection.Should().NotContainMatch("* failed", "because {0}", "it shouldn't"); + + // Assert + action.Should().Throw() + .WithMessage( + "Did not expect collection {\"build succeded\", \"test failed\"} to contain a match of \"* failed\" because it shouldn't."); + } + + [Fact] + public void When_collection_contains_multiple_matches_it_should_throw() + { + // Arrange + IEnumerable collection = ["build failed", "test failed"]; + + // Act + Action action = () => collection.Should().NotContainMatch("* failed", "because {0}", "it shouldn't"); + + // Assert + action.Should().Throw() + .WithMessage( + "Did not expect collection {\"build failed\", \"test failed\"} to contain a match of \"* failed\" because it shouldn't."); + } + + [Fact] + public void When_collection_contains_a_match_with_different_casing_it_should_not_throw() + { + // Arrange + IEnumerable collection = ["build succeded", "test failed"]; + + // Act + Action action = () => collection.Should().NotContainMatch("* Failed"); + + // Assert + action.Should().NotThrow(); + } + + [Fact] + public void When_asserting_collection_to_not_have_null_match_it_should_throw() + { + // Arrange + IEnumerable collection = ["build succeded", "test failed"]; + + // Act + Action action = () => collection.Should().NotContainMatch(null); + + // Assert + action.Should().Throw() + .WithMessage( + "Cannot match strings in collection against . Provide a wildcard pattern or use the NotContain method.*") + .WithParameterName("wildcardPattern"); + } + + [Fact] + public void When_asserting_collection_to_not_have_empty_string_match_it_should_throw() + { + // Arrange + IEnumerable collection = ["build succeded", "test failed"]; + + // Act + Action action = () => collection.Should().NotContainMatch(string.Empty); + + // Assert + action.Should().Throw() + .WithMessage( + "Cannot match strings in collection against an empty string. Provide a wildcard pattern or use the NotContain method.*") + .WithParameterName("wildcardPattern"); + } + + [Fact] + public void When_asserting_null_collection_to_not_have_null_match_it_should_throw() + { + // Arrange + IEnumerable collection = null; + + // Act + Action action = () => + { + using var _ = new AssertionScope(); + collection.Should().NotContainMatch("* Failed", "we want to test the failure {0}", "message"); + }; + + // Assert + action.Should().Throw() + .WithMessage("Did not expect collection to contain a match of \"* failed\" *failure message*, but found ."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.Equal.cs b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.Equal.cs new file mode 100644 index 0000000000..1a3ffbfe3f --- /dev/null +++ b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.Equal.cs @@ -0,0 +1,296 @@ +using System; +using System.Collections.Generic; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Collections; + +public partial class GenericCollectionAssertionOfStringSpecs +{ + public class Equal + { + [Fact] + public void Should_succeed_when_asserting_collection_is_equal_to_the_same_collection() + { + // Arrange + IEnumerable collection1 = ["one", "two", "three"]; + IEnumerable collection2 = ["one", "two", "three"]; + + // Act / Assert + collection1.Should().Equal(collection2); + } + + [Fact] + public void Should_succeed_when_asserting_collection_is_equal_to_the_same_list_of_elements() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + + // Act / Assert + collection.Should().Equal("one", "two", "three"); + } + + [Fact] + public void When_all_items_match_according_to_a_predicate_it_should_succeed() + { + // Arrange + var actual = new List { "ONE", "TWO", "THREE", "FOUR" }; + var expected = new List { "One", "Two", "Three", "Four" }; + + // Act + Action action = () => actual.Should().Equal(expected, + (a, e) => string.Equals(a, e, StringComparison.OrdinalIgnoreCase)); + + // Assert + action.Should().NotThrow(); + } + + [Fact] + public void When_an_empty_collection_is_compared_for_equality_to_a_non_empty_collection_it_should_throw() + { + // Arrange + var collection1 = new string[0]; + IEnumerable collection2 = ["one", "two", "three"]; + + // Act + Action act = () => collection1.Should().Equal(collection2); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection1 to be equal to {\"one\", \"two\", \"three\"}, but found empty collection."); + } + + [Fact] + public void When_any_item_does_not_match_according_to_a_predicate_it_should_throw() + { + // Arrange + var actual = new List { "ONE", "TWO", "THREE", "FOUR" }; + var expected = new List { "One", "Two", "Three", "Five" }; + + // Act + Action action = () => actual.Should().Equal(expected, + (a, e) => string.Equals(a, e, StringComparison.OrdinalIgnoreCase)); + + // Assert + action + .Should().Throw() + .WithMessage("Expected*equal to*, but*differs at index 3."); + } + + [Fact] + public void When_injecting_a_null_comparer_it_should_throw() + { + // Arrange + var actual = new List(); + var expected = new List(); + + // Act + Action action = () => actual.Should().Equal(expected, equalityComparison: null); + + // Assert + action + .Should().ThrowExactly() + .WithParameterName("equalityComparison"); + } + + [Fact] + public void When_asserting_collections_to_be_equal_but_expected_collection_is_null_it_should_throw() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + IEnumerable collection1 = null; + + // Act + Action act = () => + collection.Should().Equal(collection1, "because we want to test the behaviour with a null subject"); + + // Assert + act.Should().Throw() + .WithMessage("Cannot compare collection with .*") + .WithParameterName("expectation"); + } + + [Fact] + public void When_asserting_collections_to_be_equal_but_subject_collection_is_null_it_should_throw() + { + // Arrange + IEnumerable collection = null; + IEnumerable collection1 = ["one", "two", "three"]; + + // Act + Action act = () => + collection.Should().Equal(collection1, "because we want to test the behaviour with a null subject"); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection to be equal to {\"one\", \"two\", \"three\"} because we want to test the behaviour with a null subject, but found ."); + } + + [Fact] + public void When_both_collections_are_null_it_should_succeed() + { + // Arrange + IEnumerable nullColl = null; + + // Act + Action act = () => nullColl.Should().Equal(null); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_two_collections_are_not_equal_because_one_item_differs_it_should_throw_using_the_reason() + { + // Arrange + IEnumerable collection1 = ["one", "two", "three"]; + IEnumerable collection2 = ["one", "two", "five"]; + + // Act + Action act = () => collection1.Should().Equal(collection2, "because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection1 to be equal to {\"one\", \"two\", \"five\"} because we want to test the failure message, but {\"one\", \"two\", \"three\"} differs at index 2."); + } + + [Fact] + public void + When_two_collections_are_not_equal_because_the_actual_collection_contains_less_items_it_should_throw_using_the_reason() + { + // Arrange + IEnumerable collection1 = ["one", "two", "three"]; + IEnumerable collection2 = ["one", "two", "three", "four"]; + + // Act + Action act = () => collection1.Should().Equal(collection2, "because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection1 to be equal to {\"one\", \"two\", \"three\", \"four\"} because we want to test the failure message, but {\"one\", \"two\", \"three\"} contains 1 item(s) less."); + } + + [Fact] + public void + When_two_collections_are_not_equal_because_the_actual_collection_contains_more_items_it_should_throw_using_the_reason() + { + // Arrange + IEnumerable collection1 = ["one", "two", "three"]; + IEnumerable collection2 = ["one", "two"]; + + // Act + Action act = () => collection1.Should().Equal(collection2, "because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection1 to be equal to {\"one\", \"two\"} because we want to test the failure message, but {\"one\", \"two\", \"three\"} contains 1 item(s) too many."); + } + + [Fact] + public void When_two_collections_containing_nulls_are_equal_it_should_not_throw() + { + // Arrange + var subject = new List { "aaa", null }; + var expected = new List { "aaa", null }; + + // Act + Action action = () => subject.Should().Equal(expected); + + // Assert + action.Should().NotThrow(); + } + } + + public class NotEqual + { + [Fact] + public void Should_succeed_when_asserting_collection_is_not_equal_to_a_different_collection() + { + // Arrange + IEnumerable collection1 = ["one", "two", "three"]; + IEnumerable collection2 = ["three", "one", "two"]; + + // Act / Assert + collection1.Should() + .NotEqual(collection2); + } + + [Fact] + public void When_asserting_collections_not_to_be_equal_but_both_collections_reference_the_same_object_it_should_throw() + { + IEnumerable collection1 = ["one", "two", "three"]; + IEnumerable collection2 = collection1; + + // Act + Action act = () => + collection1.Should().NotEqual(collection2, "because we want to test the behaviour with same objects"); + + // Assert + act.Should().Throw().WithMessage( + "Expected collections not to be equal because we want to test the behaviour with same objects, but they both reference the same object."); + } + + [Fact] + public void When_asserting_collections_not_to_be_equal_but_expected_collection_is_null_it_should_throw() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + IEnumerable collection1 = null; + + // Act + Action act = + () => collection.Should().NotEqual(collection1, "because we want to test the behaviour with a null subject"); + + // Assert + act.Should().Throw() + .WithMessage("Cannot compare collection with .*") + .WithParameterName("unexpected"); + } + + [Fact] + public void When_asserting_collections_not_to_be_equal_subject_but_collection_is_null_it_should_throw() + { + // Arrange + IEnumerable collection = null; + IEnumerable collection1 = ["one", "two", "three"]; + + // Act + Action act = + () => collection.Should().NotEqual(collection1, "because we want to test the behaviour with a null subject"); + + // Assert + act.Should().Throw().WithMessage( + "Expected collections not to be equal because we want to test the behaviour with a null subject, but found ."); + } + + [Fact] + public void When_two_equal_collections_are_not_expected_to_be_equal_it_should_report_a_clear_explanation() + { + // Arrange + IEnumerable collection1 = ["one", "two", "three"]; + IEnumerable collection2 = ["one", "two", "three"]; + + // Act + Action act = () => collection1.Should().NotEqual(collection2, "because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw().WithMessage( + "Did not expect collections {\"one\", \"two\", \"three\"} and {\"one\", \"two\", \"three\"} to be equal because we want to test the failure message."); + } + + [Fact] + public void When_two_equal_collections_are_not_expected_to_be_equal_it_should_throw() + { + // Arrange + IEnumerable collection1 = ["one", "two", "three"]; + IEnumerable collection2 = ["one", "two", "three"]; + + // Act + Action act = () => collection1.Should().NotEqual(collection2); + + // Assert + act.Should().Throw().WithMessage( + "Did not expect collections {\"one\", \"two\", \"three\"} and {\"one\", \"two\", \"three\"} to be equal."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.HaveCount.cs b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.HaveCount.cs new file mode 100644 index 0000000000..a52842604e --- /dev/null +++ b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.HaveCount.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections.Generic; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Collections; + +public partial class GenericCollectionAssertionOfStringSpecs +{ + public class HaveCount + { + [Fact] + public void Should_fail_when_asserting_collection_has_a_count_that_is_different_from_the_number_of_items() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + + // Act + Action act = () => collection.Should().HaveCount(4); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Should_succeed_when_asserting_collection_has_a_count_that_equals_the_number_of_items() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + + // Act / Assert + collection.Should().HaveCount(3); + } + + [Fact] + public void Should_support_chaining_constraints_with_and() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + + // Act / Assert + collection.Should() + .HaveCount(3) + .And + .HaveElementAt(1, "two") + .And.NotContain("four"); + } + + [Fact] + public void When_collection_count_is_matched_against_a_null_predicate_it_should_throw() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + + // Act + Action act = () => collection.Should().HaveCount(null); + + // Assert + act.Should().Throw().WithMessage( + "Cannot compare collection count against a predicate.*"); + } + + [Fact] + public void When_collection_count_is_matched_against_a_predicate_and_collection_is_null_it_should_throw() + { + // Arrange + IEnumerable collection = null; + + // Act + Action act = + () => collection.Should().HaveCount(c => c < 3, "we want to test the behaviour with a null subject"); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection to contain (c < 3) items because we want to test the behaviour with a null subject, but found ."); + } + + [Fact] + public void When_collection_count_is_matched_and_collection_is_null_it_should_throw() + { + // Arrange + IEnumerable collection = null; + + // Act + Action act = () => collection.Should().HaveCount(1, "we want to test the behaviour with a null subject"); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection to contain 1 item(s) because we want to test the behaviour with a null subject, but found ."); + } + + [Fact] + public void When_collection_has_a_count_larger_than_the_minimum_it_should_not_throw() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + + // Act / Assert + collection.Should().HaveCount(c => c >= 3); + } + + [Fact] + public void + When_collection_has_a_count_that_is_different_from_the_number_of_items_it_should_fail_with_descriptive_message_() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + + // Act + Action action = () => collection.Should().HaveCount(4, "because we want to test the failure {0}", "message"); + + // Assert + action.Should().Throw() + .WithMessage( + "Expected collection to contain 4 item(s) because we want to test the failure message, but found 3: {\"one\", \"two\", \"three\"}."); + } + + [Fact] + public void When_collection_has_a_count_that_not_matches_the_predicate_it_should_throw() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + + // Act + Action act = () => collection.Should().HaveCount(c => c >= 4, "a minimum of 4 is required"); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection to have a count (c >= 4) because a minimum of 4 is required, but count is 3: {\"one\", \"two\", \"three\"}."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.HaveElementAt.cs b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.HaveElementAt.cs new file mode 100644 index 0000000000..e3a2f2245e --- /dev/null +++ b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.HaveElementAt.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Collections; + +public partial class GenericCollectionAssertionOfStringSpecs +{ + public class HaveElementAt + { + [Fact] + public void When_asserting_collection_has_element_at_specific_index_against_null_collection_it_should_throw() + { + // Arrange + IEnumerable collection = null; + + // Act + Action act = () => collection.Should().HaveElementAt(1, "one", + "because we want to test the behaviour with a null subject"); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection to have element at index 1 because we want to test the behaviour with a null subject, but found ."); + } + + [Fact] + public void When_collection_does_not_have_an_element_at_the_specific_index_it_should_throw() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + + // Act + Action act = () => collection.Should().HaveElementAt(4, "three", "we put it {0}", "there"); + + // Assert + act.Should().Throw().WithMessage( + "Expected \"three\" at index 4 because we put it there, but found no element."); + } + + [Fact] + public void When_collection_does_not_have_the_expected_element_at_specific_index_it_should_throw() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + + // Act + Action act = () => collection.Should().HaveElementAt(1, "three", "we put it {0}", "there"); + + // Assert + act.Should().Throw().WithMessage( + "Expected \"three\" at index 1 because we put it there, but found \"two\"."); + } + + [Fact] + public void When_collection_has_expected_element_at_specific_index_it_should_not_throw() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + + // Act / Assert + collection.Should().HaveElementAt(1, "two"); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.HaveSameCount.cs b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.HaveSameCount.cs new file mode 100644 index 0000000000..593c1bb7e3 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.HaveSameCount.cs @@ -0,0 +1,176 @@ +using System; +using System.Collections.Generic; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Collections; + +public partial class GenericCollectionAssertionOfStringSpecs +{ + public class HaveSameCount + { + [Fact] + public void When_asserting_collections_to_have_same_count_against_an_other_null_collection_it_should_throw() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + IEnumerable otherCollection = null; + + // Act + Action act = () => collection.Should().HaveSameCount(otherCollection); + + // Assert + act.Should().Throw().WithMessage( + "Cannot verify count against a collection.*"); + } + + [Fact] + public void When_asserting_collections_to_have_same_count_against_null_collection_it_should_throw() + { + // Arrange + IEnumerable collection = null; + IEnumerable collection1 = ["one", "two", "three"]; + + // Act + Action act = () => collection.Should().HaveSameCount(collection1, + "because we want to test the behaviour with a null subject"); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection to have the same count as {\"one\", \"two\", \"three\"} because we want to test the behaviour with a null subject, but found ."); + } + + [Fact] + public void When_both_collections_do_not_have_the_same_number_of_elements_it_should_fail() + { + // Arrange + IEnumerable firstCollection = ["one", "two", "three"]; + IEnumerable secondCollection = ["four", "six"]; + + // Act + Action act = () => firstCollection.Should().HaveSameCount(secondCollection); + + // Assert + act.Should().Throw().WithMessage( + "Expected firstCollection to have 2 item(s), but found 3."); + } + + [Fact] + public void When_both_collections_have_the_same_number_elements_it_should_succeed() + { + // Arrange + IEnumerable firstCollection = ["one", "two", "three"]; + IEnumerable secondCollection = ["four", "five", "six"]; + + // Act / Assert + firstCollection.Should().HaveSameCount(secondCollection); + } + + [Fact] + public void When_comparing_item_counts_and_a_reason_is_specified_it_should_it_in_the_exception() + { + // Arrange + IEnumerable firstCollection = ["one", "two", "three"]; + IEnumerable secondCollection = ["four", "six"]; + + // Act + Action act = () => firstCollection.Should().HaveSameCount(secondCollection, "we want to test the {0}", "reason"); + + // Assert + act.Should().Throw().WithMessage( + "Expected firstCollection to have 2 item(s) because we want to test the reason, but found 3."); + } + } + + public class NotHaveSameCount + { + [Fact] + public void When_asserting_collections_to_not_have_same_count_against_an_other_null_collection_it_should_throw() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + IEnumerable otherCollection = null; + + // Act + Action act = () => collection.Should().NotHaveSameCount(otherCollection); + + // Assert + act.Should().Throw().WithMessage( + "Cannot verify count against a collection.*"); + } + + [Fact] + public void When_asserting_collections_to_not_have_same_count_against_null_collection_it_should_throw() + { + // Arrange + IEnumerable collection = null; + IEnumerable collection1 = ["one", "two", "three"]; + + // Act + Action act = () => collection.Should().NotHaveSameCount(collection1, + "because we want to test the behaviour with a null subject"); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection to not have the same count as {\"one\", \"two\", \"three\"} because we want to test the behaviour with a null subject, but found ."); + } + + [Fact] + public void + When_asserting_collections_to_not_have_same_count_but_both_collections_references_the_same_object_it_should_throw() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + IEnumerable otherCollection = collection; + + // Act + Action act = () => collection.Should().NotHaveSameCount(otherCollection, + "because we want to test the behaviour with same objects"); + + // Assert + act.Should().Throw().WithMessage( + "*not have the same count*because we want to test the behaviour with same objects*but they both reference the same object."); + } + + [Fact] + public void When_asserting_not_same_count_and_both_collections_have_the_same_number_elements_it_should_fail() + { + // Arrange + IEnumerable firstCollection = ["one", "two", "three"]; + IEnumerable secondCollection = ["four", "five", "six"]; + + // Act + Action act = () => firstCollection.Should().NotHaveSameCount(secondCollection); + + // Assert + act.Should().Throw().WithMessage( + "Expected firstCollection to not have 3 item(s), but found 3."); + } + + [Fact] + public void When_asserting_not_same_count_and_collections_have_different_number_elements_it_should_succeed() + { + // Arrange + IEnumerable firstCollection = ["one", "two", "three"]; + IEnumerable secondCollection = ["four", "six"]; + + // Act / Assert + firstCollection.Should().NotHaveSameCount(secondCollection); + } + + [Fact] + public void When_comparing_not_same_item_counts_and_a_reason_is_specified_it_should_it_in_the_exception() + { + // Arrange + IEnumerable firstCollection = ["one", "two", "three"]; + IEnumerable secondCollection = ["four", "five", "six"]; + + // Act + Action act = () => firstCollection.Should().NotHaveSameCount(secondCollection, "we want to test the {0}", "reason"); + + // Assert + act.Should().Throw().WithMessage( + "Expected firstCollection to not have 3 item(s) because we want to test the reason, but found 3."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.IntersectWith.cs b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.IntersectWith.cs new file mode 100644 index 0000000000..c61dfa7646 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.IntersectWith.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Collections; + +public partial class GenericCollectionAssertionOfStringSpecs +{ + public class IntersectWith + { + [Fact] + public void When_asserting_the_items_in_an_two_intersecting_collections_intersect_it_should_succeed() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + IEnumerable otherCollection = ["three", "four", "five"]; + + // Act / Assert + collection.Should().IntersectWith(otherCollection); + } + + [Fact] + public void When_asserting_the_items_in_an_two_non_intersecting_collections_intersect_it_should_throw() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + IEnumerable otherCollection = ["four", "five"]; + + // Act + Action action = () => collection.Should().IntersectWith(otherCollection, "they should share items"); + + // Assert + action.Should().Throw() + .WithMessage("Expected collection to intersect with {\"four\", \"five\"} because they should share items," + + " but {\"one\", \"two\", \"three\"} does not contain any shared items."); + } + } + + public class NotIntersectWith + { + [Fact] + public void When_asserting_collection_to_not_intersect_with_same_collection_it_should_throw() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + IEnumerable otherCollection = collection; + + // Act + Action act = () => collection.Should().NotIntersectWith(otherCollection, + "because we want to test the behaviour with same objects"); + + // Assert + act.Should().Throw().WithMessage( + "*to intersect with*because we want to test the behaviour with same objects*but they both reference the same object."); + } + + [Fact] + public void When_asserting_the_items_in_an_two_intersecting_collections_do_not_intersect_it_should_throw() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + IEnumerable otherCollection = ["two", "three", "four"]; + + // Act + Action action = () => collection.Should().NotIntersectWith(otherCollection, "they should not share items"); + + // Assert + action.Should().Throw() + .WithMessage( + "Did not expect collection to intersect with {\"two\", \"three\", \"four\"} because they should not share items," + + " but found the following shared items {\"two\", \"three\"}."); + } + + [Fact] + public void When_asserting_the_items_in_an_two_non_intersecting_collections_do_not_intersect_it_should_succeed() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + IEnumerable otherCollection = ["four", "five"]; + + // Act / Assert + collection.Should().NotIntersectWith(otherCollection); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.NotContainNulls.cs b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.NotContainNulls.cs new file mode 100644 index 0000000000..f55ec7d36e --- /dev/null +++ b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.NotContainNulls.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Collections; + +public partial class GenericCollectionAssertionOfStringSpecs +{ + public class NotContainNulls + { + [Fact] + public void When_asserting_collection_to_not_contain_nulls_but_collection_is_null_it_should_throw() + { + // Arrange + IEnumerable collection = null; + + // Act + Action act = () => collection.Should().NotContainNulls("because we want to test the behaviour with a null subject"); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection not to contain s because we want to test the behaviour with a null subject, but collection is ."); + } + + [Fact] + public void When_collection_contains_multiple_nulls_that_are_unexpected_it_should_throw() + { + // Arrange + IEnumerable collection = ["", null, "", null]; + + // Act + Action act = () => collection.Should().NotContainNulls("because they are {0}", "evil"); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection not to contain s*because they are evil*{1, 3}*"); + } + + [Fact] + public void When_collection_contains_nulls_that_are_unexpected_it_should_throw() + { + // Arrange + IEnumerable collection = ["", null]; + + // Act + Action act = () => collection.Should().NotContainNulls("because they are {0}", "evil"); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection not to contain s because they are evil, but found one at index 1."); + } + + [Fact] + public void When_collection_does_not_contain_nulls_it_should_not_throw() + { + // Arrange + IEnumerable collection = ["one", "two", "three"]; + + // Act / Assert + collection.Should().NotContainNulls(); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.OnlyHaveUniqueItems.cs b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.OnlyHaveUniqueItems.cs new file mode 100644 index 0000000000..242e08b4a3 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.OnlyHaveUniqueItems.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Collections; + +public partial class GenericCollectionAssertionOfStringSpecs +{ + public class OnlyHaveUniqueItems + { + [Fact] + public void Should_succeed_when_asserting_collection_with_unique_items_contains_only_unique_items() + { + // Arrange + IEnumerable collection = ["one", "two", "three", "four"]; + + // Act / Assert + collection.Should().OnlyHaveUniqueItems(); + } + + [Fact] + public void When_a_collection_contains_duplicate_items_it_should_throw() + { + // Arrange + IEnumerable collection = ["one", "two", "three", "three"]; + + // Act + Action act = () => collection.Should().OnlyHaveUniqueItems("{0} don't like {1}", "we", "duplicates"); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection to only have unique items because we don't like duplicates, but item \"three\" is not unique."); + } + + [Fact] + public void When_a_collection_contains_multiple_duplicate_items_it_should_throw() + { + // Arrange + IEnumerable collection = ["one", "two", "two", "three", "three"]; + + // Act + Action act = () => collection.Should().OnlyHaveUniqueItems("{0} don't like {1}", "we", "duplicates"); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection to only have unique items because we don't like duplicates, but items {\"two\", \"three\"} are not unique."); + } + + [Fact] + public void When_asserting_collection_to_only_have_unique_items_but_collection_is_null_it_should_throw() + { + // Arrange + IEnumerable collection = null; + + // Act + Action act = + () => collection.Should().OnlyHaveUniqueItems("because we want to test the behaviour with a null subject"); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection to only have unique items because we want to test the behaviour with a null subject, but found ."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.SatisfyRespectively.cs b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.SatisfyRespectively.cs new file mode 100644 index 0000000000..2880d6f88b --- /dev/null +++ b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.SatisfyRespectively.cs @@ -0,0 +1,44 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Collections; + +public partial class GenericCollectionAssertionOfStringSpecs +{ + public class SatisfyRespectively + { + [Fact] + public void When_string_collection_satisfies_all_inspectors_it_should_succeed() + { + // Arrange + string[] collection = ["John", "Jane"]; + + // Act / Assert + collection.Should().SatisfyRespectively( + value => value.Should().Be("John"), + value => value.Should().Be("Jane") + ); + } + + [Fact] + public void When_string_collection_does_not_satisfy_all_inspectors_it_should_throw() + { + // Arrange + string[] collection = ["Jack", "Jessica"]; + + // Act + Action act = () => collection.Should().SatisfyRespectively(new Action[] + { + value => value.Should().Be("John"), + value => value.Should().Be("Jane") + }, "because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw().WithMessage( + "Expected collection to satisfy all inspectors because we want to test the failure message, but some inspectors are not satisfied" + + "*John*Jack" + + "*Jane*Jessica*"); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.cs b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.cs index a4a7a9eb98..bf4d5fbdc5 100644 --- a/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.cs +++ b/Tests/FluentAssertions.Specs/Collections/GenericCollectionAssertionOfStringSpecs.cs @@ -3,9 +3,7 @@ using System.Linq; using System.Reflection; using FluentAssertions.Collections; -using FluentAssertions.Execution; using Xunit; -using Xunit.Sdk; namespace FluentAssertions.Specs.Collections; @@ -14,1657 +12,6 @@ namespace FluentAssertions.Specs.Collections; /// public partial class GenericCollectionAssertionOfStringSpecs { - [Fact] - public void Should_fail_when_asserting_collection_has_a_count_that_is_different_from_the_number_of_items() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - - // Act - Action act = () => collection.Should().HaveCount(4); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void Should_fail_when_asserting_collection_is_not_subset_of_a_superset_collection() - { - // Arrange - IEnumerable subject = new[] { "one", "two" }; - IEnumerable otherSet = new[] { "one", "two", "three" }; - - // Act - Action act = () => subject.Should().NotBeSubsetOf(otherSet, "because I'm {0}", "mistaken"); - - // Assert - act.Should().Throw().WithMessage( - "Did not expect subject {\"one\", \"two\"} to be a subset of {\"one\", \"two\", \"three\"} because I'm mistaken."); - } - - [Fact] - public void Should_fail_when_asserting_collection_with_items_is_empty() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - - // Act - Action act = () => collection.Should().BeEmpty(); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void Should_succeed_when_asserting_collection_has_a_count_that_equals_the_number_of_items() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - - // Act / Assert - collection.Should().HaveCount(3); - } - - [Fact] - public void Should_succeed_when_asserting_collection_is_equal_to_the_same_collection() - { - // Arrange - IEnumerable collection1 = new[] { "one", "two", "three" }; - IEnumerable collection2 = new[] { "one", "two", "three" }; - - // Act / Assert - collection1.Should().Equal(collection2); - } - - [Fact] - public void Should_succeed_when_asserting_collection_is_equal_to_the_same_list_of_elements() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - - // Act / Assert - collection.Should().Equal("one", "two", "three"); - } - - [Fact] - public void Should_succeed_when_asserting_collection_is_not_equal_to_a_different_collection() - { - // Arrange - IEnumerable collection1 = new[] { "one", "two", "three" }; - IEnumerable collection2 = new[] { "three", "one", "two" }; - - // Act / Assert - collection1.Should() - .NotEqual(collection2); - } - - [Fact] - public void Should_succeed_when_asserting_collection_is_not_equivalent_to_a_different_collection() - { - // Arrange - IEnumerable collection1 = new[] { "one", "two", "three" }; - IEnumerable collection2 = new[] { "three", "one" }; - - // Act / Assert - collection1.Should().NotBeEquivalentTo(collection2); - } - - [Fact] - public void Should_succeed_when_asserting_collection_with_unique_items_contains_only_unique_items() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three", "four" }; - - // Act / Assert - collection.Should().OnlyHaveUniqueItems(); - } - - [Fact] - public void Should_succeed_when_asserting_collection_without_items_is_empty() - { - // Arrange - IEnumerable collection = new string[0]; - - // Act / Assert - collection.Should().BeEmpty(); - } - - [Fact] - public void Should_support_chaining_constraints_with_and() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - - // Act / Assert - collection.Should() - .HaveCount(3) - .And - .HaveElementAt(1, "two") - .And.NotContain("four"); - } - - [Fact] - public void When_a_collection_contains_duplicate_items_it_should_throw() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three", "three" }; - - // Act - Action act = () => collection.Should().OnlyHaveUniqueItems("{0} don't like {1}", "we", "duplicates"); - - // Assert - act.Should().Throw().WithMessage( - "Expected collection to only have unique items because we don't like duplicates, but item \"three\" is not unique."); - } - - [Fact] - public void When_a_collection_contains_multiple_duplicate_items_it_should_throw() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "two", "three", "three" }; - - // Act - Action act = () => collection.Should().OnlyHaveUniqueItems("{0} don't like {1}", "we", "duplicates"); - - // Assert - act.Should().Throw().WithMessage( - "Expected collection to only have unique items because we don't like duplicates, but items {\"two\", \"three\"} are not unique."); - } - - [Fact] - public void When_a_collection_does_not_contain_a_range_twice_it_should_throw() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "one", "three", "twelve", "two", "two" }; - - // Act - Action act = () => collection.Should().ContainInOrder("one", "two", "one", "one", "two"); - - // Assert - act.Should().Throw().WithMessage( - "Expected collection {\"one\", \"two\", \"one\", \"three\", \"twelve\", \"two\", \"two\"} to contain items {\"one\", \"two\", \"one\", \"one\", \"two\"} in order, but \"one\" (index 3) did not appear (in the right order)."); - } - - [Fact] - public void When_a_collection_does_not_contain_an_ordered_item_it_should_throw_with_a_clear_explanation() - { - // Act - Action act = () => new[] { "one", "two", "three" }.Should().ContainInOrder(new[] { "four", "one" }, "we failed"); - - // Assert - act.Should().Throw().WithMessage( - "Expected collection {\"one\", \"two\", \"three\"} to contain items {\"four\", \"one\"} in order because we failed, " + - "but \"four\" (index 0) did not appear (in the right order)."); - } - - [Fact] - public void When_a_collection_does_not_contain_another_collection_it_should_throw_with_clear_explanation() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - - // Act - Action act = () => collection.Should().Contain(new[] { "three", "four", "five" }, "because {0}", "we do"); - - // Assert - act.Should().Throw().WithMessage( - "Expected collection {\"one\", \"two\", \"three\"} to contain {\"three\", \"four\", \"five\"} because we do, but could not find {\"four\", \"five\"}."); - } - - [Fact] - public void When_a_collection_does_not_contain_single_item_it_should_throw_with_clear_explanation() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - - // Act - Action act = () => collection.Should().Contain("four", "because {0}", "we do"); - - // Assert - act.Should().Throw().WithMessage( - "Expected collection {\"one\", \"two\", \"three\"} to contain \"four\" because we do."); - } - - [Fact] - public void When_a_set_is_expected_to_be_not_a_subset_it_should_succeed() - { - // Arrange - IEnumerable subject = new[] { "one", "two", "four" }; - IEnumerable otherSet = new[] { "one", "two", "three" }; - - // Act / Assert - subject.Should().NotBeSubsetOf(otherSet); - } - - [Fact] - public void When_a_subset_is_tested_against_a_null_superset_it_should_throw_with_a_clear_explanation() - { - // Arrange - IEnumerable subset = new[] { "one", "two", "three" }; - IEnumerable superset = null; - - // Act - Action act = () => subset.Should().BeSubsetOf(superset); - - // Assert - act.Should().Throw().WithMessage( - "Cannot verify a subset against a collection.*"); - } - - [Fact] - public void When_all_items_match_according_to_a_predicate_it_should_succeed() - { - // Arrange - var actual = new List { "ONE", "TWO", "THREE", "FOUR" }; - var expected = new List { "One", "Two", "Three", "Four" }; - - // Act - Action action = () => actual.Should().Equal(expected, - (a, e) => string.Equals(a, e, StringComparison.OrdinalIgnoreCase)); - - // Assert - action.Should().NotThrow(); - } - - [Fact] - public void When_an_empty_collection_is_compared_for_equality_to_a_non_empty_collection_it_should_throw() - { - // Arrange - var collection1 = new string[0]; - IEnumerable collection2 = new[] { "one", "two", "three" }; - - // Act - Action act = () => collection1.Should().Equal(collection2); - - // Assert - act.Should().Throw().WithMessage( - "Expected collection1 to be equal to {\"one\", \"two\", \"three\"}, but found empty collection."); - } - - [Fact] - public void When_an_empty_collection_is_tested_against_a_superset_it_should_succeed() - { - // Arrange - IEnumerable subset = new string[0]; - IEnumerable superset = new[] { "one", "two", "four", "five" }; - - // Act - Action act = () => subset.Should().BeSubsetOf(superset); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_an_empty_set_is_not_supposed_to_be_a_subset_of_another_set_it_should_throw() - { - // Arrange - IEnumerable subject = new string[] { }; - IEnumerable otherSet = new[] { "one", "two", "three" }; - - // Act - Action act = () => subject.Should().NotBeSubsetOf(otherSet); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect subject {empty} to be a subset of {\"one\", \"two\", \"three\"}."); - } - - [Fact] - public void When_any_item_does_not_match_according_to_a_predicate_it_should_throw() - { - // Arrange - var actual = new List { "ONE", "TWO", "THREE", "FOUR" }; - var expected = new List { "One", "Two", "Three", "Five" }; - - // Act - Action action = () => actual.Should().Equal(expected, - (a, e) => string.Equals(a, e, StringComparison.OrdinalIgnoreCase)); - - // Assert - action - .Should().Throw() - .WithMessage("Expected*equal to*, but*differs at index 3."); - } - - [Fact] - public void When_injecting_a_null_comparer_it_should_throw() - { - // Arrange - var actual = new List(); - var expected = new List(); - - // Act - Action action = () => actual.Should().Equal(expected, equalityComparison: null); - - // Assert - action - .Should().ThrowExactly() - .WithParameterName("equalityComparison"); - } - - [Fact] - public void - When_asserting_a_string_collection_contains_an_element_it_should_allow_specifying_the_reason_via_named_parameter() - { - // Arrange - var expected = new List { "hello", "world" }; - var actual = new List { "hello", "world" }; - - // Act - Action act = () => expected.Should().Contain(actual, "they are in the collection"); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_collection_contains_an_item_from_the_collection_it_should_succeed() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - - // Act - Action act = () => collection.Should().Contain("one"); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_collection_contains_multiple_items_from_the_collection_in_any_order_it_should_succeed() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - - // Act - Action act = () => collection.Should().Contain(new[] { "two", "one" }); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_collection_contains_some_values_in_order_but_collection_is_null_it_should_throw() - { - // Arrange - IEnumerable strings = null; - - // Act - Action act = - () => strings.Should() - .ContainInOrder(new[] { "string4" }, "because we're checking how it reacts to a null subject"); - - // Assert - act.Should().Throw().WithMessage( - "Expected strings to contain {\"string4\"} in order because we're checking how it reacts to a null subject, but found ."); - } - - [Fact] - public void When_asserting_collection_does_not_contain_item_against_null_collection_it_should_throw() - { - // Arrange - IEnumerable collection = null; - - // Act - Action act = () => collection.Should() - .NotContain("one", "because we want to test the behaviour with a null subject"); - - // Assert - act.Should().Throw().WithMessage( - "Expected collection to not contain \"one\" because we want to test the behaviour with a null subject, but found ."); - } - - [Fact] - public void When_asserting_collection_has_element_at_specific_index_against_null_collection_it_should_throw() - { - // Arrange - IEnumerable collection = null; - - // Act - Action act = () => collection.Should().HaveElementAt(1, "one", - "because we want to test the behaviour with a null subject"); - - // Assert - act.Should().Throw().WithMessage( - "Expected collection to have element at index 1 because we want to test the behaviour with a null subject, but found ."); - } - - [Fact] - public void When_asserting_collection_to_be_empty_but_collection_is_null_it_should_throw() - { - // Arrange - IEnumerable collection = null; - - // Act - Action act = () => collection.Should().BeEmpty("because we want to test the behaviour with a null subject"); - - // Assert - act.Should().Throw().WithMessage( - "Expected collection to be empty because we want to test the behaviour with a null subject, but found ."); - } - - [Fact] - public void When_asserting_collection_to_be_not_empty_but_collection_is_null_it_should_throw() - { - // Arrange - IEnumerable collection = null; - - // Act - Action act = () => collection.Should().NotBeEmpty("because we want to test the behaviour with a null subject"); - - // Assert - act.Should().Throw().WithMessage( - "Expected collection not to be empty because we want to test the behaviour with a null subject, but found ."); - } - - [Fact] - public void When_asserting_collection_to_be_subset_against_null_collection_it_should_throw() - { - // Arrange - IEnumerable collection = null; - IEnumerable collection1 = new[] { "one", "two", "three" }; - - // Act - Action act = - () => collection.Should().BeSubsetOf(collection1, "because we want to test the behaviour with a null subject"); - - // Assert - act.Should().Throw().WithMessage( - "Expected collection to be a subset of {\"one\", \"two\", \"three\"} because we want to test the behaviour with a null subject, but found ."); - } - - [Fact] - public void When_asserting_collection_to_not_be_subset_against_same_collection_it_should_throw() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - IEnumerable otherCollection = collection; - - // Act - Action act = () => collection.Should().NotBeSubsetOf(otherCollection, - "because we want to test the behaviour with same objects"); - - // Assert - act.Should().Throw().WithMessage( - "Did not expect*to be a subset of*because we want to test the behaviour with same objects*but they both reference the same object."); - } - - [Fact] - public void When_asserting_collection_to_not_contain_nulls_but_collection_is_null_it_should_throw() - { - // Arrange - IEnumerable collection = null; - - // Act - Action act = () => collection.Should().NotContainNulls("because we want to test the behaviour with a null subject"); - - // Assert - act.Should().Throw().WithMessage( - "Expected collection not to contain s because we want to test the behaviour with a null subject, but collection is ."); - } - - [Fact] - public void When_asserting_collection_to_not_intersect_with_same_collection_it_should_throw() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - IEnumerable otherCollection = collection; - - // Act - Action act = () => collection.Should().NotIntersectWith(otherCollection, - "because we want to test the behaviour with same objects"); - - // Assert - act.Should().Throw().WithMessage( - "*to intersect with*because we want to test the behaviour with same objects*but they both reference the same object."); - } - - [Fact] - public void When_asserting_collection_to_only_have_unique_items_but_collection_is_null_it_should_throw() - { - // Arrange - IEnumerable collection = null; - - // Act - Action act = - () => collection.Should().OnlyHaveUniqueItems("because we want to test the behaviour with a null subject"); - - // Assert - act.Should().Throw().WithMessage( - "Expected collection to only have unique items because we want to test the behaviour with a null subject, but found ."); - } - - [Fact] - public void When_asserting_collection_with_items_is_not_empty_it_should_succeed() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - - // Act / Assert - collection.Should().NotBeEmpty(); - } - - [Fact] - public void When_asserting_collection_without_items_is_not_empty_it_should_fail() - { - // Arrange - IEnumerable collection = new string[0]; - - // Act - Action act = () => collection.Should().NotBeEmpty(); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_asserting_collection_without_items_is_not_empty_it_should_fail_with_descriptive_message_() - { - // Arrange - IEnumerable collection = new string[0]; - - // Act - Action act = () => collection.Should().NotBeEmpty("because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw() - .WithMessage("Expected collection not to be empty because we want to test the failure message."); - } - - [Fact] - public void When_asserting_collections_not_to_be_equal_but_both_collections_reference_the_same_object_it_should_throw() - { - IEnumerable collection1 = new[] { "one", "two", "three" }; - IEnumerable collection2 = collection1; - - // Act - Action act = () => - collection1.Should().NotEqual(collection2, "because we want to test the behaviour with same objects"); - - // Assert - act.Should().Throw().WithMessage( - "Expected collections not to be equal because we want to test the behaviour with same objects, but they both reference the same object."); - } - - [Fact] - public void When_asserting_collections_not_to_be_equal_but_expected_collection_is_null_it_should_throw() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - IEnumerable collection1 = null; - - // Act - Action act = - () => collection.Should().NotEqual(collection1, "because we want to test the behaviour with a null subject"); - - // Assert - act.Should().Throw() - .WithMessage("Cannot compare collection with .*") - .WithParameterName("unexpected"); - } - - [Fact] - public void When_asserting_collections_not_to_be_equal_subject_but_collection_is_null_it_should_throw() - { - // Arrange - IEnumerable collection = null; - IEnumerable collection1 = new[] { "one", "two", "three" }; - - // Act - Action act = - () => collection.Should().NotEqual(collection1, "because we want to test the behaviour with a null subject"); - - // Assert - act.Should().Throw().WithMessage( - "Expected collections not to be equal because we want to test the behaviour with a null subject, but found ."); - } - - [Fact] - public void When_asserting_collections_not_to_be_equivalent_but_subject_collection_is_null_it_should_throw() - { - // Arrange - IEnumerable actual = null; - IEnumerable expectation = new[] { "one", "two", "three" }; - - // Act - Action act = () => actual.Should().NotBeEquivalentTo(expectation, - "because we want to test the behaviour with a null subject"); - - // Assert - act.Should().Throw().WithMessage( - "Expected actual not to be equivalent because we want to test the behaviour with a null subject, but found ."); - } - - [Fact] - public void When_asserting_collections_to_be_equal_but_expected_collection_is_null_it_should_throw() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - IEnumerable collection1 = null; - - // Act - Action act = () => - collection.Should().Equal(collection1, "because we want to test the behaviour with a null subject"); - - // Assert - act.Should().Throw() - .WithMessage("Cannot compare collection with .*") - .WithParameterName("expectation"); - } - - [Fact] - public void When_asserting_collections_to_be_equal_but_subject_collection_is_null_it_should_throw() - { - // Arrange - IEnumerable collection = null; - IEnumerable collection1 = new[] { "one", "two", "three" }; - - // Act - Action act = () => - collection.Should().Equal(collection1, "because we want to test the behaviour with a null subject"); - - // Assert - act.Should().Throw().WithMessage( - "Expected collection to be equal to {\"one\", \"two\", \"three\"} because we want to test the behaviour with a null subject, but found ."); - } - - [Fact] - public void When_asserting_collections_to_be_equivalent_but_subject_collection_is_null_it_should_throw() - { - // Arrange - IEnumerable collection = null; - IEnumerable collection1 = new[] { "one", "two", "three" }; - - // Act - Action act = - () => collection.Should() - .BeEquivalentTo(collection1, "because we want to test the behaviour with a null subject"); - - // Assert - act.Should().Throw() - .WithMessage("Expected collection*not to be *"); - } - - [Fact] - public void When_asserting_collections_to_have_same_count_against_an_other_null_collection_it_should_throw() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - IEnumerable otherCollection = null; - - // Act - Action act = () => collection.Should().HaveSameCount(otherCollection); - - // Assert - act.Should().Throw().WithMessage( - "Cannot verify count against a collection.*"); - } - - [Fact] - public void When_asserting_collections_to_have_same_count_against_null_collection_it_should_throw() - { - // Arrange - IEnumerable collection = null; - IEnumerable collection1 = new[] { "one", "two", "three" }; - - // Act - Action act = () => collection.Should().HaveSameCount(collection1, - "because we want to test the behaviour with a null subject"); - - // Assert - act.Should().Throw().WithMessage( - "Expected collection to have the same count as {\"one\", \"two\", \"three\"} because we want to test the behaviour with a null subject, but found ."); - } - - [Fact] - public void When_asserting_collections_to_not_have_same_count_against_an_other_null_collection_it_should_throw() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - IEnumerable otherCollection = null; - - // Act - Action act = () => collection.Should().NotHaveSameCount(otherCollection); - - // Assert - act.Should().Throw().WithMessage( - "Cannot verify count against a collection.*"); - } - - [Fact] - public void When_asserting_collections_to_not_have_same_count_against_null_collection_it_should_throw() - { - // Arrange - IEnumerable collection = null; - IEnumerable collection1 = new[] { "one", "two", "three" }; - - // Act - Action act = () => collection.Should().NotHaveSameCount(collection1, - "because we want to test the behaviour with a null subject"); - - // Assert - act.Should().Throw().WithMessage( - "Expected collection to not have the same count as {\"one\", \"two\", \"three\"} because we want to test the behaviour with a null subject, but found ."); - } - - [Fact] - public void - When_asserting_collections_to_not_have_same_count_but_both_collections_references_the_same_object_it_should_throw() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - IEnumerable otherCollection = collection; - - // Act - Action act = () => collection.Should().NotHaveSameCount(otherCollection, - "because we want to test the behaviour with same objects"); - - // Assert - act.Should().Throw().WithMessage( - "*not have the same count*because we want to test the behaviour with same objects*but they both reference the same object."); - } - - [Fact] - public void When_asserting_not_same_count_and_both_collections_have_the_same_number_elements_it_should_fail() - { - // Arrange - IEnumerable firstCollection = new[] { "one", "two", "three" }; - IEnumerable secondCollection = new[] { "four", "five", "six" }; - - // Act - Action act = () => firstCollection.Should().NotHaveSameCount(secondCollection); - - // Assert - act.Should().Throw().WithMessage( - "Expected firstCollection to not have 3 item(s), but found 3."); - } - - [Fact] - public void When_asserting_not_same_count_and_collections_have_different_number_elements_it_should_succeed() - { - // Arrange - IEnumerable firstCollection = new[] { "one", "two", "three" }; - IEnumerable secondCollection = new[] { "four", "six" }; - - // Act / Assert - firstCollection.Should().NotHaveSameCount(secondCollection); - } - - [Fact] - public void When_asserting_the_items_in_an_two_intersecting_collections_do_not_intersect_it_should_throw() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - IEnumerable otherCollection = new[] { "two", "three", "four" }; - - // Act - Action action = () => collection.Should().NotIntersectWith(otherCollection, "they should not share items"); - - // Assert - action.Should().Throw() - .WithMessage( - "Did not expect collection to intersect with {\"two\", \"three\", \"four\"} because they should not share items," + - " but found the following shared items {\"two\", \"three\"}."); - } - - [Fact] - public void When_asserting_the_items_in_an_two_intersecting_collections_intersect_it_should_succeed() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - IEnumerable otherCollection = new[] { "three", "four", "five" }; - - // Act / Assert - collection.Should().IntersectWith(otherCollection); - } - - [Fact] - public void When_asserting_the_items_in_an_two_non_intersecting_collections_do_not_intersect_it_should_succeed() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - IEnumerable otherCollection = new[] { "four", "five" }; - - // Act / Assert - collection.Should().NotIntersectWith(otherCollection); - } - - [Fact] - public void When_asserting_the_items_in_an_two_non_intersecting_collections_intersect_it_should_throw() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - IEnumerable otherCollection = new[] { "four", "five" }; - - // Act - Action action = () => collection.Should().IntersectWith(otherCollection, "they should share items"); - - // Assert - action.Should().Throw() - .WithMessage("Expected collection to intersect with {\"four\", \"five\"} because they should share items," + - " but {\"one\", \"two\", \"three\"} does not contain any shared items."); - } - - [Fact] - public void When_both_collections_are_null_it_should_succeed() - { - // Arrange - IEnumerable nullColl = null; - - // Act - Action act = () => nullColl.Should().Equal(null); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_both_collections_do_not_have_the_same_number_of_elements_it_should_fail() - { - // Arrange - IEnumerable firstCollection = new[] { "one", "two", "three" }; - IEnumerable secondCollection = new[] { "four", "six" }; - - // Act - Action act = () => firstCollection.Should().HaveSameCount(secondCollection); - - // Assert - act.Should().Throw().WithMessage( - "Expected firstCollection to have 2 item(s), but found 3."); - } - - [Fact] - public void When_both_collections_have_the_same_number_elements_it_should_succeed() - { - // Arrange - IEnumerable firstCollection = new[] { "one", "two", "three" }; - IEnumerable secondCollection = new[] { "four", "five", "six" }; - - // Act / Assert - firstCollection.Should().HaveSameCount(secondCollection); - } - - [Fact] - public void When_collection_contains_an_unexpected_item_it_should_throw() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - - // Act - Action act = () => collection.Should().NotContain("one", "because we {0} like it, but found it anyhow", "don't"); - - // Assert - act.Should().Throw().WithMessage( - "Expected collection {\"one\", \"two\", \"three\"} to not contain \"one\" because we don't like it, but found it anyhow."); - } - - [Fact] - public void When_collection_contains_multiple_nulls_that_are_unexpected_it_should_throw() - { - // Arrange - IEnumerable collection = new[] { "", null, "", null }; - - // Act - Action act = () => collection.Should().NotContainNulls("because they are {0}", "evil"); - - // Assert - act.Should().Throw().WithMessage( - "Expected collection not to contain s*because they are evil*{1, 3}*"); - } - - [Fact] - public void When_collection_contains_null_value_it_should_not_throw() - { - // Arrange - IEnumerable collection = new[] { "one", null, "two", "string" }; - - // Act / Assert - collection.Should().ContainInOrder("one", null, "string"); - } - - [Fact] - public void When_collection_contains_nulls_that_are_unexpected_it_should_throw() - { - // Arrange - IEnumerable collection = new[] { "", null }; - - // Act - Action act = () => collection.Should().NotContainNulls("because they are {0}", "evil"); - - // Assert - act.Should().Throw().WithMessage( - "Expected collection not to contain s because they are evil, but found one at index 1."); - } - - [Fact] - public void When_collection_count_is_matched_against_a_null_predicate_it_should_throw() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - - // Act - Action act = () => collection.Should().HaveCount(null); - - // Assert - act.Should().Throw().WithMessage( - "Cannot compare collection count against a predicate.*"); - } - - [Fact] - public void When_collection_count_is_matched_against_a_predicate_and_collection_is_null_it_should_throw() - { - // Arrange - IEnumerable collection = null; - - // Act - Action act = - () => collection.Should().HaveCount(c => c < 3, "we want to test the behaviour with a null subject"); - - // Assert - act.Should().Throw().WithMessage( - "Expected collection to contain (c < 3) items because we want to test the behaviour with a null subject, but found ."); - } - - [Fact] - public void When_collection_count_is_matched_and_collection_is_null_it_should_throw() - { - // Arrange - IEnumerable collection = null; - - // Act - Action act = () => collection.Should().HaveCount(1, "we want to test the behaviour with a null subject"); - - // Assert - act.Should().Throw().WithMessage( - "Expected collection to contain 1 item(s) because we want to test the behaviour with a null subject, but found ."); - } - - [Fact] - public void When_collection_does_contain_an_unexpected_item_matching_a_predicate_it_should_throw() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - - // Act - Action act = () => collection.Should().NotContain(item => item == "two", "because {0}s are evil", "two"); - - // Assert - act.Should().Throw().WithMessage( - "Expected collection {\"one\", \"two\", \"three\"} to not have any items matching (item == \"two\") because twos are evil,*{\"two\"}*"); - } - - [Fact] - public void When_collection_does_not_contain_an_item_that_is_not_in_the_collection_it_should_not_throw() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - - // Act - Action act = () => collection.Should().NotContain("four"); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_collection_does_not_contain_an_unexpected_item_matching_a_predicate_it_should_not_throw() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - - // Act / Assert - collection.Should().NotContain(item => item == "four"); - } - - [Fact] - public void When_collection_does_not_contain_nulls_it_should_not_throw() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - - // Act / Assert - collection.Should().NotContainNulls(); - } - - [Fact] - public void When_collection_does_not_have_an_element_at_the_specific_index_it_should_throw() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - - // Act - Action act = () => collection.Should().HaveElementAt(4, "three", "we put it {0}", "there"); - - // Assert - act.Should().Throw().WithMessage( - "Expected \"three\" at index 4 because we put it there, but found no element."); - } - - [Fact] - public void When_collection_does_not_have_the_expected_element_at_specific_index_it_should_throw() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - - // Act - Action act = () => collection.Should().HaveElementAt(1, "three", "we put it {0}", "there"); - - // Assert - act.Should().Throw().WithMessage( - "Expected \"three\" at index 1 because we put it there, but found \"two\"."); - } - - [Fact] - public void When_collection_has_a_count_larger_than_the_minimum_it_should_not_throw() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - - // Act / Assert - collection.Should().HaveCount(c => c >= 3); - } - - [Fact] - public void - When_collection_has_a_count_that_is_different_from_the_number_of_items_it_should_fail_with_descriptive_message_() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - - // Act - Action action = () => collection.Should().HaveCount(4, "because we want to test the failure {0}", "message"); - - // Assert - action.Should().Throw() - .WithMessage( - "Expected collection to contain 4 item(s) because we want to test the failure message, but found 3: {\"one\", \"two\", \"three\"}."); - } - - [Fact] - public void When_collection_has_a_count_that_not_matches_the_predicate_it_should_throw() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - - // Act - Action act = () => collection.Should().HaveCount(c => c >= 4, "a minimum of 4 is required"); - - // Assert - act.Should().Throw().WithMessage( - "Expected collection to have a count (c >= 4) because a minimum of 4 is required, but count is 3: {\"one\", \"two\", \"three\"}."); - } - - [Fact] - public void When_collection_has_expected_element_at_specific_index_it_should_not_throw() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - - // Act / Assert - collection.Should().HaveElementAt(1, "two"); - } - - [Fact] - public void When_collection_is_expected_to_be_null_and_it_is_it_should_not_throw() - { - // Arrange - IEnumerable someCollection = null; - - // Act / Assert - someCollection.Should().BeNull(); - } - - [Fact] - public void When_collection_is_expected_to_be_null_and_it_isnt_it_should_throw() - { - // Arrange - IEnumerable someCollection = new string[0]; - - // Act - Action act = () => someCollection.Should().BeNull("because {0} is valid", "null"); - - // Assert - act.Should().Throw().WithMessage( - "Expected someCollection to be because null is valid, but found {empty}."); - } - - [Fact] - public void When_collection_is_not_a_subset_of_another_it_should_throw_with_the_reason() - { - // Arrange - IEnumerable subset = new[] { "one", "two", "three", "six" }; - IEnumerable superset = new[] { "one", "two", "four", "five" }; - - // Act - Action act = () => subset.Should().BeSubsetOf(superset, "because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw().WithMessage( - "Expected subset to be a subset of {\"one\", \"two\", \"four\", \"five\"} because we want to test the failure message, " + - "but items {\"three\", \"six\"} are not part of the superset."); - } - - [Fact] - public void When_collection_is_not_expected_to_be_null_and_it_is_it_should_throw() - { - // Arrange - IEnumerable someCollection = null; - - // Act - Action act = () => someCollection.Should().NotBeNull("because {0} should not", "someCollection"); - - // Assert - act.Should().Throw().WithMessage( - "Expected someCollection not to be because someCollection should not."); - } - - [Fact] - public void When_collection_is_not_expected_to_be_null_and_it_isnt_it_should_not_throw() - { - // Arrange - IEnumerable someCollection = new string[0]; - - // Act / Assert - someCollection.Should().NotBeNull(); - } - - [Fact] - public void When_collection_is_subset_of_a_specified_collection_it_should_not_throw() - { - // Arrange - IEnumerable subset = new[] { "one", "two" }; - IEnumerable superset = new[] { "one", "two", "three" }; - - // Act / Assert - subset.Should().BeSubsetOf(superset); - } - - [Fact] - public void When_collections_are_unexpectedly_equivalent_it_should_throw() - { - // Arrange - IEnumerable collection1 = new[] { "one", "two", "three" }; - IEnumerable collection2 = new[] { "three", "one", "two" }; - - // Act - Action act = () => collection1.Should().NotBeEquivalentTo(collection2); - - // Assert - act.Should().Throw().WithMessage( - "Expected collection1 {\"one\", \"two\", \"three\"} not*equivalent*{\"three\", \"one\", \"two\"}."); - } - - [Fact] - public void When_collections_with_duplicates_are_not_equivalent_it_should_throw() - { - // Arrange - IEnumerable collection1 = new[] { "one", "two", "three", "one" }; - IEnumerable collection2 = new[] { "one", "two", "three", "three" }; - - // Act - Action act = () => collection1.Should().BeEquivalentTo(collection2); - - // Assert - act.Should().Throw().WithMessage( - "Expected collection1[3]*to be \"three\" with a length of 5, but \"one\" has a length of 3*"); - } - - [Fact] - public void When_comparing_item_counts_and_a_reason_is_specified_it_should_it_in_the_exception() - { - // Arrange - IEnumerable firstCollection = new[] { "one", "two", "three" }; - IEnumerable secondCollection = new[] { "four", "six" }; - - // Act - Action act = () => firstCollection.Should().HaveSameCount(secondCollection, "we want to test the {0}", "reason"); - - // Assert - act.Should().Throw().WithMessage( - "Expected firstCollection to have 2 item(s) because we want to test the reason, but found 3."); - } - - [Fact] - public void When_comparing_not_same_item_counts_and_a_reason_is_specified_it_should_it_in_the_exception() - { - // Arrange - IEnumerable firstCollection = new[] { "one", "two", "three" }; - IEnumerable secondCollection = new[] { "four", "five", "six" }; - - // Act - Action act = () => firstCollection.Should().NotHaveSameCount(secondCollection, "we want to test the {0}", "reason"); - - // Assert - act.Should().Throw().WithMessage( - "Expected firstCollection to not have 3 item(s) because we want to test the reason, but found 3."); - } - - [Fact] - public void When_non_empty_collection_is_not_expected_to_be_equivalent_to_an_empty_collection_it_should_succeed() - { - // Arrange - IEnumerable collection1 = new[] { "one", "two", "three" }; - IEnumerable collection2 = new string[0]; - - // Act - Action act = () => collection1.Should().NotBeEquivalentTo(collection2); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_passing_in_null_while_checking_for_ordered_containment_it_should_throw_with_a_clear_explanation() - { - // Act - Action act = () => new[] { "one", "two", "three" }.Should().ContainInOrder(null); - - // Assert - act.Should().Throw().WithMessage( - "Cannot verify ordered containment against a collection.*"); - } - - [Fact] - public void When_testing_collections_not_to_be_equivalent_against_null_collection_it_should_throw() - { - // Arrange - IEnumerable collection1 = new[] { "one", "two", "three" }; - IEnumerable collection2 = null; - - // Act - Action act = () => collection1.Should().NotBeEquivalentTo(collection2); - - // Assert - act.Should().Throw().WithMessage( - "Cannot verify inequivalence against a collection.*"); - } - - [Fact] - public void When_testing_collections_not_to_be_equivalent_against_same_collection_it_should_throw() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - IEnumerable collection1 = collection; - - // Act - Action act = () => collection.Should().NotBeEquivalentTo(collection1, - "because we want to test the behaviour with same objects"); - - // Assert - act.Should().Throw().WithMessage( - "*not to be equivalent*because we want to test the behaviour with same objects*but they both reference the same object."); - } - - [Fact] - public void When_testing_for_equivalence_against_empty_collection_it_should_throw() - { - // Arrange - IEnumerable subject = new[] { "one", "two", "three" }; - IEnumerable otherCollection = new string[0]; - - // Act - Action act = () => subject.Should().BeEquivalentTo(otherCollection); - - // Assert - act.Should().Throw().WithMessage( - "Expected subject*to be a collection with 0 item(s), but*contains 3 item(s)*"); - } - - [Fact] - public void When_testing_for_equivalence_against_null_collection_it_should_throw() - { - // Arrange - IEnumerable collection1 = new[] { "one", "two", "three" }; - IEnumerable collection2 = null; - - // Act - Action act = () => collection1.Should().BeEquivalentTo(collection2); - - // Assert - act.Should().Throw().WithMessage( - "Expected collection1*to be , but found {\"one\", \"two\", \"three\"}*"); - } - - [Fact] - public void When_the_collection_is_not_empty_unexpectedly_it_should_throw() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - - // Act - Action act = () => collection.Should().BeEmpty("because we want to test the failure {0}", "message"); - - // Assert - act - .Should().Throw() - .WithMessage( - "Expected collection to be empty because we want to test the failure message, but found at least one item*one*"); - } - - [Fact] - public void When_the_contents_of_a_collection_are_checked_against_an_empty_collection_it_should_throw_clear_explanation() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - - // Act - Action act = () => collection.Should().Contain(new string[0]); - - // Assert - act.Should().Throw().WithMessage( - "Cannot verify containment against an empty collection*"); - } - - [Fact] - public void When_the_expected_object_exists_it_should_allow_chaining_additional_assertions() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - - // Act - Action act = () => collection.Should().Contain("one").Which.Should().HaveLength(4); - - // Assert - act.Should().Throw().WithMessage("Expected*length*4*3*"); - } - - [Fact] - public void When_the_first_collection_contains_a_duplicate_item_without_affecting_the_order_it_should_not_throw() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three", "two" }; - - // Act / Assert - collection.Should().ContainInOrder("one", "two", "three"); - } - - [Fact] - public void When_two_collections_are_both_empty_it_should_treat_them_as_equivalent() - { - // Arrange - IEnumerable subject = new string[0]; - IEnumerable otherCollection = new string[0]; - - // Act - Action act = () => subject.Should().BeEquivalentTo(otherCollection); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_two_collections_are_not_equal_because_one_item_differs_it_should_throw_using_the_reason() - { - // Arrange - IEnumerable collection1 = new[] { "one", "two", "three" }; - IEnumerable collection2 = new[] { "one", "two", "five" }; - - // Act - Action act = () => collection1.Should().Equal(collection2, "because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw().WithMessage( - "Expected collection1 to be equal to {\"one\", \"two\", \"five\"} because we want to test the failure message, but {\"one\", \"two\", \"three\"} differs at index 2."); - } - - [Fact] - public void - When_two_collections_are_not_equal_because_the_actual_collection_contains_less_items_it_should_throw_using_the_reason() - { - // Arrange - IEnumerable collection1 = new[] { "one", "two", "three" }; - IEnumerable collection2 = new[] { "one", "two", "three", "four" }; - - // Act - Action act = () => collection1.Should().Equal(collection2, "because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw().WithMessage( - "Expected collection1 to be equal to {\"one\", \"two\", \"three\", \"four\"} because we want to test the failure message, but {\"one\", \"two\", \"three\"} contains 1 item(s) less."); - } - - [Fact] - public void - When_two_collections_are_not_equal_because_the_actual_collection_contains_more_items_it_should_throw_using_the_reason() - { - // Arrange - IEnumerable collection1 = new[] { "one", "two", "three" }; - IEnumerable collection2 = new[] { "one", "two" }; - - // Act - Action act = () => collection1.Should().Equal(collection2, "because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw().WithMessage( - "Expected collection1 to be equal to {\"one\", \"two\"} because we want to test the failure message, but {\"one\", \"two\", \"three\"} contains 1 item(s) too many."); - } - - [Fact] - public void When_two_collections_contain_the_same_duplicate_items_in_the_same_order_it_should_not_throw() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "one", "two", "twelve", "two", "two" }; - - // Act / Assert - collection.Should().ContainInOrder("one", "two", "one", "two", "twelve", "two", "two"); - } - - [Fact] - public void When_two_collections_contain_the_same_elements_it_should_treat_them_as_equivalent() - { - // Arrange - IEnumerable collection1 = new[] { "one", "two", "three" }; - IEnumerable collection2 = new[] { "three", "two", "one" }; - - // Act / Assert - collection1.Should().BeEquivalentTo(collection2); - } - - [Fact] - public void When_two_arrays_contain_the_same_elements_it_should_treat_them_as_equivalent() - { - // Arrange - string[] array1 = { "one", "two", "three" }; - string[] array2 = { "three", "two", "one" }; - - // Act / Assert - array1.Should().BeEquivalentTo(array2); - } - - [Fact] - public void When_two_collections_contain_the_same_items_but_in_different_order_it_should_throw_with_a_clear_explanation() - { - // Act - Action act = () => - new[] { "one", "two", "three" }.Should().ContainInOrder(new[] { "three", "one" }, "because we said so"); - - // Assert - act.Should().Throw().WithMessage( - "Expected collection {\"one\", \"two\", \"three\"} to contain items {\"three\", \"one\"} in order because we said so, but \"one\" (index 1) did not appear (in the right order)."); - } - - [Fact] - public void When_two_collections_contain_the_same_items_in_the_same_order_it_should_not_throw() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "two", "three" }; - - // Act / Assert - collection.Should().ContainInOrder("one", "two", "three"); - } - - [Fact] - public void When_two_collections_contain_the_same_items_but_in_different_order_it_should_not_throw() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - - // Act / Assert - collection.Should().NotContainInOrder("two", "one"); - } - - [Fact] - public void When_a_collection_does_not_contain_an_ordered_item_it_should_not_throw() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - - // Act / Assert - collection.Should().NotContainInOrder("four", "one"); - } - - [Fact] - public void When_a_collection_contains_less_items_it_should_not_throw() - { - // Arrange - IEnumerable collection = new[] { "one", "two" }; - - // Act / Assert - collection.Should().NotContainInOrder("one", "two", "three"); - } - - [Fact] - public void When_a_collection_does_not_contain_a_range_twice_it_should_not_throw() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "one", "three", "twelve", "two", "two" }; - - // Act / Assert - collection.Should().NotContainInOrder("one", "two", "one", "one", "two"); - } - - [Fact] - public void When_asserting_collection_does_not_contain_some_values_in_order_but_collection_is_null_it_should_throw() - { - // Arrange - IEnumerable collection = null; - - // Act - Action act = () => collection.Should().NotContainInOrder("four"); - - // Assert - act.Should().Throw().WithMessage("Cannot verify absence of ordered containment in a collection."); - } - - [Fact] - public void When_two_collections_contain_the_same_items_in_the_same_order_it_should_throw() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "two", "three" }; - - // Act - Action act = () => collection.Should().NotContainInOrder(new[] { "one", "two", "three" }, "that's what we expect"); - - // Assert - act.Should().Throw().WithMessage( - "Expected collection {\"one\", \"two\", \"two\", \"three\"} to not contain items {\"one\", \"two\", \"three\"} " + - "in order because that's what we expect, but items appeared in order ending at index 3."); - } - - [Fact] - public void When_collection_contains_contain_the_same_items_in_the_same_order_with_null_value_it_should_throw() - { - // Arrange - IEnumerable collection = new[] { "one", null, "two", "three" }; - - // Act - Action act = () => collection.Should().NotContainInOrder("one", null, "three"); - - // Assert - act.Should().Throw().WithMessage( - "Expected collection {\"one\", , \"two\", \"three\"} to not contain items {\"one\", , \"three\"} in order, " + - "but items appeared in order ending at index 3."); - } - - [Fact] - public void When_the_first_collection_contains_a_duplicate_item_without_affecting_the_order_it_should_throw() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three", "two" }; - - // Act - Action act = () => collection.Should().NotContainInOrder("one", "two", "three"); - - // Assert - act.Should().Throw().WithMessage( - "Expected collection {\"one\", \"two\", \"three\", \"two\"} to not contain items {\"one\", \"two\", \"three\"} in order, " + - "but items appeared in order ending at index 2."); - } - - [Fact] - public void When_two_collections_contain_the_same_duplicate_items_in_the_same_order_it_should_throw() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "one", "twelve", "two" }; - - // Act - Action act = () => collection.Should().NotContainInOrder("one", "two", "one", "twelve", "two"); - - // Assert - act.Should().Throw().WithMessage( - "Expected collection {\"one\", \"two\", \"one\", \"twelve\", \"two\"} to not contain items " + - "{\"one\", \"two\", \"one\", \"twelve\", \"two\"} in order, but items appeared in order ending at index 4."); - } - - [Fact] - public void When_passing_in_null_while_checking_for_absence_of_ordered_containment_it_should_throw() - { - // Arrange - IEnumerable collection = new[] { "one", "two", "three" }; - - // Act - Action act = () => collection.Should().NotContainInOrder(null); - - // Assert - act.Should().Throw().WithMessage( - "Cannot verify absence of ordered containment against a collection.*"); - } - - [Fact] - public void When_two_collections_containing_nulls_are_equal_it_should_not_throw() - { - // Arrange - var subject = new List { "aaa", null }; - var expected = new List { "aaa", null }; - - // Act - Action action = () => subject.Should().Equal(expected); - - // Assert - action.Should().NotThrow(); - } - - [Fact] - public void When_two_equal_collections_are_not_expected_to_be_equal_it_should_report_a_clear_explanation() - { - // Arrange - IEnumerable collection1 = new[] { "one", "two", "three" }; - IEnumerable collection2 = new[] { "one", "two", "three" }; - - // Act - Action act = () => collection1.Should().NotEqual(collection2, "because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw().WithMessage( - "Did not expect collections {\"one\", \"two\", \"three\"} and {\"one\", \"two\", \"three\"} to be equal because we want to test the failure message."); - } - - [Fact] - public void When_two_equal_collections_are_not_expected_to_be_equal_it_should_throw() - { - // Arrange - IEnumerable collection1 = new[] { "one", "two", "three" }; - IEnumerable collection2 = new[] { "one", "two", "three" }; - - // Act - Action act = () => collection1.Should().NotEqual(collection2); - - // Assert - act.Should().Throw().WithMessage( - "Did not expect collections {\"one\", \"two\", \"three\"} and {\"one\", \"two\", \"three\"} to be equal."); - } - [Fact] public void When_using_StringCollectionAssertions_the_AndConstraint_should_have_the_correct_type() { @@ -1682,313 +29,15 @@ from method in methodInfo select new { method.Name, method.ReturnType }; // Assert - var expectedTypes = new[] - { + Type[] expectedTypes = + [ typeof(AndConstraint>>), typeof(AndConstraint>) - }; + ]; methods.Should().OnlyContain(method => expectedTypes.Any(e => e.IsAssignableFrom(method.ReturnType))); } - #region ContainMatch - - [Fact] - public void When_collection_contains_a_match_it_should_not_throw() - { - // Arrange - IEnumerable collection = new[] { "build succeded", "test failed" }; - - // Act - Action action = () => collection.Should().ContainMatch("* failed"); - - // Assert - action.Should().NotThrow(); - } - - [Fact] - public void When_collection_contains_multiple_matches_it_should_not_throw() - { - // Arrange - IEnumerable collection = new[] { "build succeded", "test failed", "pack failed" }; - - // Act - Action action = () => collection.Should().ContainMatch("* failed"); - - // Assert - action.Should().NotThrow(); - } - - [Fact] - public void When_collection_contains_multiple_matches_which_should_throw() - { - // Arrange - IEnumerable collection = new[] { "build succeded", "test failed", "pack failed" }; - - // Act - Action action = () => _ = collection.Should().ContainMatch("* failed").Which; - - // Assert - action.Should().Throw() - .WithMessage("More than one object found. FluentAssertions cannot determine which object is meant.*") - .WithMessage("*Found objects:*\"test failed\"*\"pack failed\""); - } - - [Fact] - public void When_collection_does_not_contain_a_match_it_should_throw() - { - // Arrange - IEnumerable collection = new[] { "build succeded", "test failed" }; - - // Act - Action action = () => collection.Should().ContainMatch("* stopped", "because {0}", "we do"); - - // Assert - action.Should().Throw() - .WithMessage( - "Expected collection {\"build succeded\", \"test failed\"} to contain a match of \"* stopped\" because we do."); - } - - [Fact] - public void When_collection_contains_a_match_that_differs_in_casing_it_should_throw() - { - // Arrange - IEnumerable collection = new[] { "build succeded", "test failed" }; - - // Act - Action action = () => collection.Should().ContainMatch("* Failed"); - - // Assert - action.Should().Throw() - .WithMessage("Expected collection {\"build succeded\", \"test failed\"} to contain a match of \"* Failed\"."); - } - - [Fact] - public void When_asserting_empty_collection_for_match_it_should_throw() - { - // Arrange - IEnumerable collection = new string[] { }; - - // Act - Action action = () => collection.Should().ContainMatch("* failed"); - - // Assert - action.Should().Throw() - .WithMessage("Expected collection {empty} to contain a match of \"* failed\"."); - } - - [Fact] - public void When_asserting_null_collection_for_match_it_should_throw() - { - // Arrange - IEnumerable collection = null; - - // Act - Action action = () => - { - using var _ = new AssertionScope(); - collection.Should().ContainMatch("* failed", "because {0}", "we do"); - }; - - // Assert - action.Should().Throw() - .WithMessage("Expected collection to contain a match of \"* failed\" because we do, but found ."); - } - - [Fact] - public void When_asserting_collection_to_have_null_match_it_should_throw() - { - // Arrange - IEnumerable collection = new[] { "build succeded", "test failed" }; - - // Act - Action action = () => collection.Should().ContainMatch(null); - - // Assert - action.Should().Throw() - .WithMessage( - "Cannot match strings in collection against . Provide a wildcard pattern or use the Contain method.*") - .WithParameterName("wildcardPattern"); - } - - [Fact] - public void When_asserting_collection_to_have_empty_string_match_it_should_throw() - { - // Arrange - IEnumerable collection = new[] { "build succeded", "test failed" }; - - // Act - Action action = () => collection.Should().ContainMatch(string.Empty); - - // Assert - action.Should().Throw() - .WithMessage( - "Cannot match strings in collection against an empty string. Provide a wildcard pattern or use the Contain method.*") - .WithParameterName("wildcardPattern"); - } - - #endregion - - #region NotContainMatch - - [Fact] - public void When_collection_doesnt_contain_a_match_it_should_not_throw() - { - // Arrange - IEnumerable collection = new[] { "build succeded", "test" }; - - // Act - Action action = () => collection.Should().NotContainMatch("* failed"); - - // Assert - action.Should().NotThrow(); - } - - [Fact] - public void When_collection_doesnt_contain_multiple_matches_it_should_not_throw() - { - // Arrange - IEnumerable collection = new[] { "build succeded", "test", "pack" }; - - // Act - Action action = () => collection.Should().NotContainMatch("* failed"); - - // Assert - action.Should().NotThrow(); - } - - [Fact] - public void When_collection_contains_a_match_it_should_throw() - { - // Arrange - IEnumerable collection = new[] { "build succeded", "test failed" }; - - // Act - Action action = () => collection.Should().NotContainMatch("* failed", "because {0}", "it shouldn't"); - - // Assert - action.Should().Throw() - .WithMessage( - "Did not expect collection {\"build succeded\", \"test failed\"} to contain a match of \"* failed\" because it shouldn't."); - } - - [Fact] - public void When_collection_contains_multiple_matches_it_should_throw() - { - // Arrange - IEnumerable collection = new[] { "build failed", "test failed" }; - - // Act - Action action = () => collection.Should().NotContainMatch("* failed", "because {0}", "it shouldn't"); - - // Assert - action.Should().Throw() - .WithMessage( - "Did not expect collection {\"build failed\", \"test failed\"} to contain a match of \"* failed\" because it shouldn't."); - } - - [Fact] - public void When_collection_contains_a_match_with_different_casing_it_should_not_throw() - { - // Arrange - IEnumerable collection = new[] { "build succeded", "test failed" }; - - // Act - Action action = () => collection.Should().NotContainMatch("* Failed"); - - // Assert - action.Should().NotThrow(); - } - - [Fact] - public void When_asserting_collection_to_not_have_null_match_it_should_throw() - { - // Arrange - IEnumerable collection = new[] { "build succeded", "test failed" }; - - // Act - Action action = () => collection.Should().NotContainMatch(null); - - // Assert - action.Should().Throw() - .WithMessage( - "Cannot match strings in collection against . Provide a wildcard pattern or use the NotContain method.*") - .WithParameterName("wildcardPattern"); - } - - [Fact] - public void When_asserting_collection_to_not_have_empty_string_match_it_should_throw() - { - // Arrange - IEnumerable collection = new[] { "build succeded", "test failed" }; - - // Act - Action action = () => collection.Should().NotContainMatch(string.Empty); - - // Assert - action.Should().Throw() - .WithMessage( - "Cannot match strings in collection against an empty string. Provide a wildcard pattern or use the NotContain method.*") - .WithParameterName("wildcardPattern"); - } - - [Fact] - public void When_asserting_null_collection_to_not_have_null_match_it_should_throw() - { - // Arrange - IEnumerable collection = null; - - // Act - Action action = () => - { - using var _ = new AssertionScope(); - collection.Should().NotContainMatch("* Failed", "we want to test the failure {0}", "message"); - }; - - // Assert - action.Should().Throw() - .WithMessage("Did not expect collection to contain a match of \"* failed\" *failure message*, but found ."); - } - - #endregion - - #region SatisfyRespectively - - [Fact] - public void When_string_collection_satisfies_all_inspectors_it_should_succeed() - { - // Arrange - string[] collection = { "John", "Jane" }; - - // Act / Assert - collection.Should().SatisfyRespectively( - value => value.Should().Be("John"), - value => value.Should().Be("Jane") - ); - } - - [Fact] - public void When_string_collection_does_not_satisfy_all_inspectors_it_should_throw() - { - // Arrange - string[] collection = { "Jack", "Jessica" }; - - // Act - Action act = () => collection.Should().SatisfyRespectively(new Action[] - { - value => value.Should().Be("John"), - value => value.Should().Be("Jane") - }, "because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw().WithMessage( - "Expected collection to satisfy all inspectors because we want to test the failure message, but some inspectors are not satisfied" - + "*John*Jack" - + "*Jane*Jessica*"); - } - - #endregion - [Fact] public void When_accidentally_using_equals_it_should_throw_a_helpful_error() { diff --git a/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.BeEmpty.cs b/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.BeEmpty.cs new file mode 100644 index 0000000000..651fe9151f --- /dev/null +++ b/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.BeEmpty.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Collections; + +public partial class GenericDictionaryAssertionSpecs +{ + public class BeEmpty + { + [Fact] + public void Should_succeed_when_asserting_dictionary_without_items_is_empty() + { + // Arrange + var dictionary = new Dictionary(); + + // Act / Assert + dictionary.Should().BeEmpty(); + } + + [Fact] + public void Should_fail_when_asserting_dictionary_with_items_is_empty() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One" + }; + + // Act + Action act = () => dictionary.Should().BeEmpty(); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Should_fail_with_descriptive_message_when_asserting_dictionary_with_items_is_empty() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One" + }; + + // Act + Action act = () => dictionary.Should().BeEmpty("because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw() + .WithMessage( + "Expected dictionary to be empty because we want to test the failure message, but found at least one item {[1, One]}."); + } + + [Fact] + public void When_asserting_dictionary_to_be_empty_but_dictionary_is_null_it_should_throw() + { + // Arrange + Dictionary dictionary = null; + + // Act + Action act = () => dictionary.Should().BeEmpty("because we want to test the behaviour with a null subject"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary to be empty because we want to test the behaviour with a null subject, but found ."); + } + } + + public class NotBeEmpty + { + [Fact] + public void When_asserting_dictionary_with_items_is_not_empty_it_should_succeed() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One" + }; + + // Act / Assert + dictionary.Should().NotBeEmpty(); + } + +#if !NET5_0_OR_GREATER + [Fact] + public void When_asserting_dictionary_with_items_is_not_empty_it_should_enumerate_the_dictionary_only_once() + { + // Arrange + var trackingDictionary = new TrackingTestDictionary(new KeyValuePair(1, "One")); + + // Act + trackingDictionary.Should().NotBeEmpty(); + + // Assert + trackingDictionary.Enumerator.LoopCount.Should().Be(1); + } + +#endif + + [Fact] + public void When_asserting_dictionary_without_items_is_not_empty_it_should_fail() + { + // Arrange + var dictionary = new Dictionary(); + + // Act + Action act = () => dictionary.Should().NotBeEmpty(); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_asserting_dictionary_without_items_is_not_empty_it_should_fail_with_descriptive_message_() + { + // Arrange + var dictionary = new Dictionary(); + + // Act + Action act = () => dictionary.Should().NotBeEmpty("because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw() + .WithMessage("Expected dictionary not to be empty because we want to test the failure message."); + } + + [Fact] + public void When_asserting_dictionary_to_be_not_empty_but_dictionary_is_null_it_should_throw() + { + // Arrange + Dictionary dictionary = null; + + // Act + Action act = () => dictionary.Should().NotBeEmpty("because we want to test the behaviour with a null subject"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary not to be empty because we want to test the behaviour with a null subject, but found ."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.BeNull.cs b/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.BeNull.cs new file mode 100644 index 0000000000..ad40258aa2 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.BeNull.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Collections; + +public partial class GenericDictionaryAssertionSpecs +{ + public class BeNull + { + [Fact] + public void When_dictionary_is_expected_to_be_null_and_it_is_it_should_not_throw() + { + // Arrange + IDictionary someDictionary = null; + + // Act / Assert + someDictionary.Should().BeNull(); + } + + [Fact] + public void When_dictionary_is_expected_to_be_null_and_it_isnt_it_should_throw() + { + // Arrange + var someDictionary = new Dictionary(); + + // Act + Action act = () => someDictionary.Should().BeNull("because {0} is valid", "null"); + + // Assert + act.Should().Throw().WithMessage( + "Expected someDictionary to be because null is valid, but found {empty}."); + } + } + + public class NotBeNull + { + [Fact] + public void When_a_custom_dictionary_implementation_is_expected_not_to_be_null_and_it_is_it_should_not_throw() + { + // Arrange + var dictionary = new TrackingTestDictionary(); + + // Act / Assert + dictionary.Should().NotBeNull(); + } + + [Fact] + public void When_dictionary_is_not_expected_to_be_null_and_it_isnt_it_should_not_throw() + { + // Arrange + IDictionary someDictionary = new Dictionary(); + + // Act / Assert + someDictionary.Should().NotBeNull(); + } + + [Fact] + public void When_dictionary_is_not_expected_to_be_null_and_it_is_it_should_throw() + { + // Arrange + IDictionary someDictionary = null; + + // Act + Action act = () => someDictionary.Should().NotBeNull("because {0} should not", "someDictionary"); + + // Assert + act.Should().Throw().WithMessage( + "Expected someDictionary not to be because someDictionary should not."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.Contain.cs b/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.Contain.cs new file mode 100644 index 0000000000..757461a10b --- /dev/null +++ b/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.Contain.cs @@ -0,0 +1,782 @@ +using System; +using System.Collections.Generic; +using FluentAssertions.Execution; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Collections; + +public partial class GenericDictionaryAssertionSpecs +{ + public class Contain + { + [Fact] + public void Should_succeed_when_asserting_dictionary_contains_single_key_value_pair() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + var keyValuePairs = new List> + { + new(1, "One") + }; + + // Act / Assert + dictionary.Should().Contain(keyValuePairs); + } + + [Fact] + public void Should_succeed_when_asserting_dictionary_contains_multiple_key_value_pair() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two", + [3] = "Three", + [4] = "Four" + }; + + var expectedKeyValuePair1 = new KeyValuePair(2, "Two"); + var expectedKeyValuePair2 = new KeyValuePair(3, "Three"); + + // Act / Assert + dictionary.Should().Contain(expectedKeyValuePair1, expectedKeyValuePair2); + } + + [Fact] + public void Should_succeed_when_asserting_dictionary_contains_multiple_key_value_pairs() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + var keyValuePairs = new List> + { + new(1, "One"), + new(2, "Two") + }; + + // Act / Assert + dictionary.Should().Contain(keyValuePairs); + } + + [Fact] + public void When_a_dictionary_does_not_contain_single_value_for_key_value_pairs_it_should_throw_with_clear_explanation() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + var keyValuePairs = new List> + { + new(1, "One"), + new(2, "Three") + }; + + // Act + Action act = () => dictionary.Should().Contain(keyValuePairs, "because {0}", "we do"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary to contain value \"Three\" at key 2 because we do, but found \"Two\"."); + } + + [Fact] + public void + When_a_dictionary_does_not_contain_multiple_values_for_key_value_pairs_it_should_throw_with_clear_explanation() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + var keyValuePairs = new List> + { + new(1, "Two"), + new(2, "Three") + }; + + // Act + Action act = () => dictionary.Should().Contain(keyValuePairs, "because {0}", "we do"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary to contain {[1, Two], [2, Three]} because we do, but dictionary differs at keys {1, 2}."); + } + + [Fact] + public void When_a_dictionary_does_not_contain_single_key_for_key_value_pairs_it_should_throw_with_clear_explanation() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + var keyValuePairs = new List> + { + new(3, "Three") + }; + + // Act + Action act = () => dictionary.Should().Contain(keyValuePairs, "because {0}", "we do"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary {[1] = \"One\", [2] = \"Two\"} to contain key 3 because we do."); + } + + [Fact] + public void When_a_dictionary_does_not_contain_multiple_keys_for_key_value_pairs_it_should_throw_with_clear_explanation() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + var keyValuePairs = new List> + { + new(1, "One"), + new(3, "Three"), + new(4, "Four") + }; + + // Act + Action act = () => dictionary.Should().Contain(keyValuePairs, "because {0}", "we do"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary {[1] = \"One\", [2] = \"Two\"} to contain key(s) {1, 3, 4} because we do, but could not find keys {3, 4}."); + } + + [Fact] + public void When_asserting_dictionary_contains_key_value_pairs_against_null_dictionary_it_should_throw() + { + // Arrange + Dictionary dictionary = null; + + List> keyValuePairs = + [ + new KeyValuePair(1, "One"), + new KeyValuePair(1, "Two") + ]; + + // Act + Action act = () => + { + using var _ = new AssertionScope(); + dictionary.Should().Contain(keyValuePairs, "because we want to test the behaviour with a null subject"); + }; + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary to contain key/value pairs {[1, One], [1, Two]} because we want to test the behaviour with a null subject, but dictionary is ."); + } + + [Fact] + public void When_asserting_dictionary_contains_key_value_pairs_but_expected_key_value_pairs_are_empty_it_should_throw() + { + // Arrange + var dictionary1 = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + List> keyValuePairs = []; + + // Act + Action act = () => dictionary1.Should().Contain(keyValuePairs, + "because we want to test the behaviour with an empty set of key/value pairs"); + + // Assert + act.Should().Throw().WithMessage( + "Cannot verify key containment against an empty collection of key/value pairs*"); + } + + [Fact] + public void When_asserting_dictionary_contains_key_value_pairs_but_expected_key_value_pairs_are_null_it_should_throw() + { + // Arrange + var dictionary1 = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + List> keyValuePairs = null; + + // Act + Action act = () => + dictionary1.Should().Contain(keyValuePairs, "because we want to test the behaviour with a null subject"); + + // Assert + act.Should().Throw() + .WithMessage("Cannot compare dictionary with .*") + .WithParameterName("expected"); + } + + [Fact] + public void When_dictionary_contains_expected_value_at_specific_key_it_should_not_throw() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act / Assert + dictionary.Should().Contain(1, "One"); + } + + [Fact] + public void When_dictionary_contains_expected_null_at_specific_key_it_should_not_throw() + { + // Arrange + var dictionary = new Dictionary + { + [1] = null + }; + + // Act / Assert + dictionary.Should().Contain(1, null); + } + + [Fact] + public void When_dictionary_contains_expected_key_value_pairs_it_should_not_throw() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act / Assert + var items = new List> + { + new(1, "One"), + new(2, "Two") + }; + + dictionary.Should().Contain(items); + } + + [Fact] + public void When_dictionary_contains_expected_key_value_pair_it_should_not_throw() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act / Assert + var item = new KeyValuePair(1, "One"); + dictionary.Should().Contain(item); + } + + [Fact] + public void When_dictionary_does_not_contain_the_expected_value_at_specific_key_it_should_throw() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act + var item = new KeyValuePair(1, "Two"); + Action act = () => dictionary.Should().Contain(item, "we put it {0}", "there"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary to contain value \"Two\" at key 1 because we put it there, but found \"One\"."); + } + + [Fact] + public void When_dictionary_does_not_contain_the_key_value_pairs_it_should_throw() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + var items = new List> + { + new(1, "Two"), + new(2, "Three") + }; + + // Act + Action act = () => dictionary.Should().Contain(items, "we put them {0}", "there"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary to contain {[1, Two], [2, Three]} because we put them there, but dictionary differs at keys {1, 2}."); + } + + [Fact] + public void When_dictionary_does_not_contain_the_key_value_pair_it_should_throw() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act + Action act = () => dictionary.Should().Contain(1, "Two", "we put it {0}", "there"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary to contain value \"Two\" at key 1 because we put it there, but found \"One\"."); + } + + [Fact] + public void When_dictionary_does_not_contain_an_value_at_the_specific_key_it_should_throw() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act + Action act = () => dictionary.Should().Contain(3, "Two", "we put it {0}", "there"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary to contain value \"Two\" at key 3 because we put it there, but the key was not found."); + } + + [Fact] + public void When_asserting_dictionary_contains_value_at_specific_key_against_null_dictionary_it_should_throw() + { + // Arrange + Dictionary dictionary = null; + + // Act + Action act = () => + { + using var _ = new AssertionScope(); + dictionary.Should().Contain(1, "One", "because we want to test the behaviour with a null subject"); + }; + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary to contain value \"One\" at key 1 because we want to test the behaviour with a null subject, but dictionary is ."); + } + + [Fact] + public void When_a_dictionary_like_collection_contains_the_default_key_it_should_succeed() + { + // Arrange + var subject = new List> + { new(0, 0) }; + + // Act + Action act = () => subject.Should().Contain(0, 0); + + // Assert + act.Should().NotThrow(); + } + } + + public class NotContain + { + [Fact] + public void Should_succeed_when_asserting_dictionary_does_not_contain_single_key_value_pair() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + var keyValuePairs = new List> + { + new(3, "Three") + }; + + // Act / Assert + dictionary.Should().NotContain(keyValuePairs); + } + + [Fact] + public void Should_succeed_when_asserting_dictionary_does_not_contain_multiple_key_value_pair() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + var unexpectedKeyValuePair1 = new KeyValuePair(3, "Three"); + var unexpectedKeyValuePair2 = new KeyValuePair(4, "Four"); + + // Act / Assert + dictionary.Should().NotContain(unexpectedKeyValuePair1, unexpectedKeyValuePair2); + } + + [Fact] + public void + Should_succeed_when_asserting_dictionary_does_not_contain_single_key_value_pair_with_existing_key_but_different_value() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + var keyValuePairs = new List> + { + new(1, "Two") + }; + + // Act / Assert + dictionary.Should().NotContain(keyValuePairs); + } + + [Fact] + public void Should_succeed_when_asserting_dictionary_does_not_contain_multiple_key_value_pairs() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + var keyValuePairs = new List> + { + new(3, "Three"), + new(4, "Four") + }; + + // Act / Assert + dictionary.Should().NotContain(keyValuePairs); + } + + [Fact] + public void + Should_succeed_when_asserting_dictionary_does_not_contain_multiple_key_value_pairs_with_existing_keys_but_different_values() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + var keyValuePairs = new List> + { + new(1, "Three"), + new(2, "Four") + }; + + // Act / Assert + dictionary.Should().NotContain(keyValuePairs); + } + + [Fact] + public void When_a_dictionary_does_contain_single_key_value_pair_it_should_throw_with_clear_explanation() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + var keyValuePairs = new List> + { + new(1, "One") + }; + + // Act + Action act = () => dictionary.Should().NotContain(keyValuePairs, "because {0}", "we do"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary to not contain value \"One\" at key 1 because we do, but found it anyhow."); + } + + [Fact] + public void When_a_dictionary_does_contain_multiple_key_value_pairs_it_should_throw_with_clear_explanation() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + var keyValuePairs = new List> + { + new(1, "One"), + new(2, "Two") + }; + + // Act + Action act = () => dictionary.Should().NotContain(keyValuePairs, "because {0}", "we do"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary to not contain key/value pairs {[1, One], [2, Two]} because we do, but found them anyhow."); + } + + [Fact] + public void When_asserting_dictionary_does_not_contain_key_value_pairs_against_null_dictionary_it_should_throw() + { + // Arrange + Dictionary dictionary = null; + + List> keyValuePairs = + [ + new KeyValuePair(1, "One"), + new KeyValuePair(1, "Two") + ]; + + // Act + Action act = () => + { + using var _ = new AssertionScope(); + dictionary.Should().NotContain(keyValuePairs, "because we want to test the behaviour with a null subject"); + }; + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary to not contain key/value pairs {[1, One], [1, Two]} because we want to test the behaviour with a null subject, but dictionary is ."); + } + + [Fact] + public void + When_asserting_dictionary_does_not_contain_key_value_pairs_but_expected_key_value_pairs_are_empty_it_should_throw() + { + // Arrange + var dictionary1 = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + List> keyValuePair = []; + + // Act + Action act = () => dictionary1.Should().NotContain(keyValuePair, + "because we want to test the behaviour with an empty set of key/value pairs"); + + // Assert + act.Should().Throw().WithMessage( + "Cannot verify key containment against an empty collection of key/value pairs*"); + } + + [Fact] + public void + When_asserting_dictionary_does_not_contain_key_value_pairs_but_expected_key_value_pairs_are_null_it_should_throw() + { + // Arrange + var dictionary1 = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + List> keyValuePairs = null; + + // Act + Action act = () => + dictionary1.Should().NotContain(keyValuePairs, "because we want to test the behaviour with a null subject"); + + // Assert + act.Should().Throw() + .WithMessage("Cannot compare dictionary with .*") + .WithParameterName("items"); + } + + [Fact] + public void When_dictionary_does_not_contain_unexpected_value_or_key_it_should_not_throw() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act / Assert + dictionary.Should().NotContain(3, "Three"); + } + + [Fact] + public void When_dictionary_does_not_contain_unexpected_value_at_existing_key_it_should_not_throw() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act / Assert + dictionary.Should().NotContain(2, "Three"); + } + + [Fact] + public void When_dictionary_does_not_have_the_unexpected_value_but_null_at_existing_key_it_should_succeed() + { + // Arrange + var dictionary = new Dictionary + { + [1] = null + }; + + // Act + Action action = () => dictionary.Should().NotContain(1, "other"); + + // Assert + action.Should().NotThrow(); + } + + [Fact] + public void When_dictionary_does_not_contain_unexpected_key_value_pairs_it_should_not_throw() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act / Assert + var items = new List> + { + new(3, "Three"), + new(4, "Four") + }; + + dictionary.Should().NotContain(items); + } + + [Fact] + public void When_dictionary_does_not_contain_unexpected_key_value_pair_it_should_not_throw() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act / Assert + var item = new KeyValuePair(3, "Three"); + dictionary.Should().NotContain(item); + } + + [Fact] + public void When_dictionary_contains_the_unexpected_value_at_specific_key_it_should_throw() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act + var item = new KeyValuePair(1, "One"); + Action act = () => dictionary.Should().NotContain(item, "we put it {0}", "there"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary not to contain value \"One\" at key 1 because we put it there, but found it anyhow."); + } + + [Fact] + public void When_dictionary_contains_the_key_value_pairs_it_should_throw() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act + var items = new List> + { + new(1, "One"), + new(2, "Two") + }; + + Action act = () => dictionary.Should().NotContain(items, "we did not put them {0}", "there"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary to not contain key/value pairs {[1, One], [2, Two]} because we did not put them there, but found them anyhow."); + } + + [Fact] + public void When_dictionary_contains_the_key_value_pair_it_should_throw() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act + Action act = () => dictionary.Should().NotContain(1, "One", "we did not put it {0}", "there"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary not to contain value \"One\" at key 1 because we did not put it there, but found it anyhow."); + } + + [Fact] + public void When_asserting_dictionary_does_not_contain_value_at_specific_key_against_null_dictionary_it_should_throw() + { + // Arrange + Dictionary dictionary = null; + + // Act + Action act = () => + { + using var _ = new AssertionScope(); + dictionary.Should().NotContain(1, "One", "because we want to test the behaviour with a null subject"); + }; + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary not to contain value \"One\" at key 1 because we want to test the behaviour with a null subject, but dictionary is ."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.ContainKey.cs b/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.ContainKey.cs new file mode 100644 index 0000000000..7d717b598f --- /dev/null +++ b/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.ContainKey.cs @@ -0,0 +1,152 @@ +using System; +using System.Collections.Generic; +using FluentAssertions.Execution; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Collections; + +public partial class GenericDictionaryAssertionSpecs +{ + public class ContainKey + { + [Fact] + public void Should_succeed_when_asserting_dictionary_contains_a_key_from_the_dictionary() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act / Assert + dictionary.Should().ContainKey(1); + } + + [Fact] + public void When_a_dictionary_has_custom_equality_comparer_the_contains_key_assertion_should_work_accordingly() + { + // Arrange + var dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + ["One"] = "One", + ["Two"] = "Two" + }; + + // Act + + // Assert + dictionary.Should().ContainKey("One"); + dictionary.Should().ContainKey("ONE"); + dictionary.Should().ContainKey("one"); + } + + [Fact] + public void When_a_dictionary_does_not_contain_single_key_it_should_throw_with_clear_explanation() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act + Action act = () => dictionary.Should().ContainKey(3, "because {0}", "we do"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary {[1] = \"One\", [2] = \"Two\"} to contain key 3 because we do."); + } + + [Fact] + public void When_the_requested_key_exists_it_should_allow_continuation_with_the_value() + { + // Arrange + var dictionary = new Dictionary + { + ["Key"] = new() { SomeProperty = 3 } + }; + + // Act + Action act = () => dictionary.Should().ContainKey("Key").WhoseValue.Should().Be(4); + + // Assert + act.Should().Throw().WithMessage("Expected*4*3*."); + } + + [Fact] + public void When_an_assertion_fails_on_ContainKey_succeeding_message_should_be_included() + { + // 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*Expected*to contain key 1*"); + } + } + + public class NotContainKey + { + [Fact] + public void When_dictionary_does_not_contain_a_key_that_is_not_in_the_dictionary_it_should_not_throw() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act + Action act = () => dictionary.Should().NotContainKey(4); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_dictionary_contains_an_unexpected_key_it_should_throw() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act + Action act = () => dictionary.Should().NotContainKey(1, "because we {0} like it", "don't"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary {[1] = \"One\", [2] = \"Two\"} not to contain key 1 because we don't like it, but found it anyhow."); + } + + [Fact] + public void When_asserting_dictionary_does_not_contain_key_against_null_dictionary_it_should_throw() + { + // Arrange + Dictionary dictionary = null; + + // Act + Action act = () => + { + using var _ = new AssertionScope(); + dictionary.Should().NotContainKey(1, "because we want to test the behaviour with a null subject"); + }; + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary not to contain key 1 because we want to test the behaviour with a null subject, but found ."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.ContainKeys.cs b/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.ContainKeys.cs new file mode 100644 index 0000000000..90c5ba3979 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.ContainKeys.cs @@ -0,0 +1,211 @@ +using System; +using System.Collections.Generic; +using FluentAssertions.Execution; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Collections; + +public partial class GenericDictionaryAssertionSpecs +{ + public class ContainKeys + { + [Fact] + public void Should_succeed_when_asserting_dictionary_contains_multiple_keys_from_the_dictionary() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act / Assert + dictionary.Should().ContainKeys(2, 1); + } + + [Fact] + public void When_a_dictionary_does_not_contain_a_list_of_keys_it_should_throw_with_clear_explanation() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act + Action act = () => dictionary.Should().ContainKeys([2, 3], "because {0}", "we do"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary {[1] = \"One\", [2] = \"Two\"} to contain keys {2, 3} because we do, but could not find {3}."); + } + + [Fact] + public void Null_dictionaries_do_not_contain_any_keys() + { + // Arrange + Dictionary dictionary = null; + + // Act + Action act = () => + { + using var _ = new AssertionScope(); + dictionary.Should().ContainKeys([2, 3], "because {0}", "we do"); + }; + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary to contain keys {2, 3} because we do, but found ."); + } + + [Fact] + public void + When_the_contents_of_a_dictionary_are_checked_against_an_empty_list_of_keys_it_should_throw_clear_explanation() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act + Action act = () => dictionary.Should().ContainKeys(); + + // Assert + act.Should().Throw().WithMessage( + "Cannot verify key containment against an empty sequence*"); + } + } + + public class NotContainKeys + { + [Fact] + public void When_dictionary_does_not_contain_multiple_keys_from_the_dictionary_it_should_not_throw() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act + Action act = () => dictionary.Should().NotContainKeys(3, 4); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_a_dictionary_contains_a_list_of_keys_it_should_throw_with_clear_explanation() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act + Action act = () => dictionary.Should().NotContainKeys([2, 3], "because {0}", "we do"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary {[1] = \"One\", [2] = \"Two\"} to not contain keys {2, 3} because we do, but found {2}."); + } + + [Fact] + public void When_a_dictionary_contains_exactly_one_of_the_keys_it_should_throw_with_clear_explanation() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act + Action act = () => dictionary.Should().NotContainKeys([2], "because {0}", "we do"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary {[1] = \"One\", [2] = \"Two\"} to not contain key 2 because we do."); + } + + [Fact] + public void Null_dictionaries_do_not_contain_any_keys() + { + // Arrange + Dictionary dictionary = null; + + // Act + Action act = () => + { + using var _ = new AssertionScope(); + dictionary.Should().NotContainKeys([2], "because {0}", "we do"); + }; + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary to not contain keys {2} because we do, but found ."); + } + + [Fact] + public void + When_the_noncontents_of_a_dictionary_are_checked_against_an_empty_list_of_keys_it_should_throw_clear_explanation() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act + Action act = () => dictionary.Should().NotContainKeys(); + + // Assert + act.Should().Throw().WithMessage( + "Cannot verify key containment against an empty sequence*"); + } + + [Fact] + public void + When_a_dictionary_checks_a_list_of_keys_not_to_be_present_it_will_honor_the_case_sensitive_equality_comparer_of_the_dictionary() + { + // Arrange + var dictionary = new Dictionary(StringComparer.Ordinal) + { + ["ONE"] = "One", + ["TWO"] = "Two" + }; + + // Act + Action act = () => dictionary.Should().NotContainKeys("One", "Two"); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void + When_a_dictionary_checks_a_list_of_keys_not_to_be_present_it_will_honor_the_case_insensitive_equality_comparer_of_the_dictionary() + { + // Arrange + var dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + ["ONE"] = "One", + ["TWO"] = "Two" + }; + + // Act + Action act = () => dictionary.Should().NotContainKeys("One", "Two"); + + // Assert + act.Should().Throw(); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.ContainValue.cs b/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.ContainValue.cs new file mode 100644 index 0000000000..3cfd9ab9d9 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.ContainValue.cs @@ -0,0 +1,176 @@ +using System; +using System.Collections.Generic; +using FluentAssertions.Execution; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Collections; + +public partial class GenericDictionaryAssertionSpecs +{ + public class ContainValue + { + [Fact] + public void When_dictionary_contains_expected_value_it_should_succeed() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act + Action act = () => dictionary.Should().ContainValue("One"); + + // Assert + 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() + { + // Arrange + Dictionary dictionary = null; + + // Act + Action act = () => + { + using var _ = new AssertionScope(); + dictionary.Should().ContainValue("One", "because {0}", "we do"); + }; + + // Assert + act.Should().Throw() + .WithMessage("Expected dictionary to contain values {\"One\"} because we do, but found ."); + } + + [Fact] + public void When_dictionary_contains_expected_null_value_it_should_succeed() + { + // Arrange + var dictionary = new Dictionary + { + [1] = null + }; + + // Act + Action act = () => dictionary.Should().ContainValue(null); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_the_specified_value_exists_it_should_allow_continuation_using_that_value() + { + // Arrange + var myClass = new MyClass + { + SomeProperty = 0 + }; + + var dictionary = new Dictionary + { + [1] = myClass + }; + + // Act + Action act = () => dictionary.Should().ContainValue(myClass).Which.SomeProperty.Should().BeGreaterThan(0); + + // Assert + act.Should().Throw().WithMessage("Expected*greater*0*0*"); + } + + [Fact] + public void When_a_dictionary_does_not_contain_single_value_it_should_throw_with_clear_explanation() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act + Action act = () => dictionary.Should().ContainValue("Three", "because {0}", "we do"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary {[1] = \"One\", [2] = \"Two\"} to contain value \"Three\" because we do."); + } + } + + public class NotContainValue + { + [Fact] + public void When_dictionary_does_not_contain_a_value_that_is_not_in_the_dictionary_it_should_not_throw() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act + Action act = () => dictionary.Should().NotContainValue("Three"); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_dictionary_contains_an_unexpected_value_it_should_throw() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act + Action act = () => dictionary.Should().NotContainValue("One", "because we {0} like it", "don't"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary {[1] = \"One\", [2] = \"Two\"} not to contain value \"One\" because we don't like it, but found it anyhow."); + } + + [Fact] + public void When_asserting_dictionary_does_not_contain_value_against_null_dictionary_it_should_throw() + { + // Arrange + Dictionary dictionary = null; + + // Act + Action act = () => + { + using var _ = new AssertionScope(); + dictionary.Should().NotContainValue("One", "because we want to test the behaviour with a null subject"); + }; + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary not to contain value \"One\" because we want to test the behaviour with a null subject, but found ."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.ContainValues.cs b/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.ContainValues.cs new file mode 100644 index 0000000000..07f898177b --- /dev/null +++ b/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.ContainValues.cs @@ -0,0 +1,177 @@ +using System; +using System.Collections.Generic; +using FluentAssertions.Execution; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Collections; + +public partial class GenericDictionaryAssertionSpecs +{ + public class ContainValues + { + [Fact] + public void When_dictionary_contains_multiple_values_from_the_dictionary_it_should_not_throw() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act + Action act = () => dictionary.Should().ContainValues("Two", "One"); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_a_dictionary_does_not_contain_a_number_of_values_it_should_throw_with_clear_explanation() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act + Action act = () => dictionary.Should().ContainValues(["Two", "Three"], "because {0}", "we do"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary {[1] = \"One\", [2] = \"Two\"} to contain values {\"Two\", \"Three\"} because we do, but could not find \"Three\"."); + } + + [Fact] + public void + When_the_contents_of_a_dictionary_are_checked_against_an_empty_list_of_values_it_should_throw_clear_explanation() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act + Action act = () => dictionary.Should().ContainValues(); + + // Assert + act.Should().Throw().WithMessage( + "Cannot verify value containment against an empty sequence*"); + } + } + + [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] + public void When_dictionary_does_not_contain_multiple_values_that_is_not_in_the_dictionary_it_should_not_throw() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act + Action act = () => dictionary.Should().NotContainValues("Three", "Four"); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_a_dictionary_contains_a_exactly_one_of_the_values_it_should_throw_with_clear_explanation() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act + Action act = () => dictionary.Should().NotContainValues(["Two"], "because {0}", "we do"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary {[1] = \"One\", [2] = \"Two\"} to not contain value \"Two\" because we do."); + } + + [Fact] + public void When_a_dictionary_contains_a_number_of_values_it_should_throw_with_clear_explanation() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act + Action act = () => dictionary.Should().NotContainValues(["Two", "Three"], "because {0}", "we do"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary {[1] = \"One\", [2] = \"Two\"} to not contain value {\"Two\", \"Three\"} because we do, but found {\"Two\"}."); + } + + [Fact] + public void + When_the_noncontents_of_a_dictionary_are_checked_against_an_empty_list_of_values_it_should_throw_clear_explanation() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act + Action act = () => dictionary.Should().NotContainValues(); + + // Assert + act.Should().Throw().WithMessage( + "Cannot verify value containment with an empty sequence*"); + } + + [Fact] + public void Null_dictionaries_do_not_contain_any_values() + { + // Arrange + Dictionary dictionary = null; + + // Act + Action act = () => + { + using var _ = new AssertionScope(); + dictionary.Should().NotContainValues(["Two", "Three"], "because {0}", "we do"); + }; + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary to not contain values {\"Two\", \"Three\"} because we do, but found ."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.Equal.cs b/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.Equal.cs new file mode 100644 index 0000000000..773e3a4576 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.Equal.cs @@ -0,0 +1,351 @@ +using System; +using System.Collections.Generic; +using FluentAssertions.Execution; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Collections; + +public partial class GenericDictionaryAssertionSpecs +{ + public class Equal + { + [Fact] + public void Should_succeed_when_asserting_dictionary_is_equal_to_the_same_dictionary() + { + // Arrange + var dictionary1 = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + var dictionary2 = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act / Assert + dictionary1.Should().Equal(dictionary2); + } + + [Fact] + public void Should_succeed_when_asserting_dictionary_with_null_value_is_equal_to_the_same_dictionary() + { + // Arrange + var dictionary1 = new Dictionary + { + [1] = "One", + [2] = null + }; + + var dictionary2 = new Dictionary + { + [1] = "One", + [2] = null + }; + + // Act / Assert + dictionary1.Should().Equal(dictionary2); + } + + [Fact] + public void When_asserting_dictionaries_to_be_equal_but_subject_dictionary_misses_a_value_it_should_throw() + { + // Arrange + var dictionary1 = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + var dictionary2 = new Dictionary + { + [1] = "One", + [22] = "Two" + }; + + // Act + Action act = () => dictionary1.Should().Equal(dictionary2, "because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary1 to be equal to {[1] = \"One\", [22] = \"Two\"} because we want to test the failure message, but could not find keys {22}."); + } + + [Fact] + public void When_asserting_dictionaries_to_be_equal_but_subject_dictionary_has_extra_key_it_should_throw() + { + // Arrange + var dictionary1 = new Dictionary + { + [1] = "One", + [2] = "Two", + [3] = "Three" + }; + + var dictionary2 = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act + Action act = () => dictionary1.Should().Equal(dictionary2, "because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary1 to be equal to {[1] = \"One\", [2] = \"Two\"} because we want to test the failure message, but found additional keys {3}."); + } + + [Fact] + public void When_two_dictionaries_are_not_equal_by_values_it_should_throw_using_the_reason() + { + // Arrange + var dictionary1 = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + var dictionary2 = new Dictionary + { + [1] = "One", + [2] = "Three" + }; + + // Act + Action act = () => dictionary1.Should().Equal(dictionary2, "because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary1 to be equal to {[1] = \"One\", [2] = \"Three\"} because we want to test the failure message, but {[1] = \"One\", [2] = \"Two\"} differs at key 2."); + } + + [Fact] + public void When_asserting_dictionaries_to_be_equal_but_subject_dictionary_is_null_it_should_throw() + { + // Arrange + Dictionary dictionary1 = null; + + var dictionary2 = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act + Action act = () => + { + using var _ = new AssertionScope(); + dictionary1.Should().Equal(dictionary2, "because we want to test the behaviour with a null subject"); + }; + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary1 to be equal to {[1] = \"One\", [2] = \"Two\"} because we want to test the behaviour with a null subject, but found ."); + } + + [Fact] + public void When_asserting_dictionaries_to_be_equal_but_expected_dictionary_is_null_it_should_throw() + { + // Arrange + var dictionary1 = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + Dictionary dictionary2 = null; + + // Act + Action act = () => + dictionary1.Should().Equal(dictionary2, "because we want to test the behaviour with a null subject"); + + // Assert + act.Should().Throw() + .WithMessage("Cannot compare dictionary with .*") + .WithParameterName("expected"); + } + + [Fact] + public void When_an_empty_dictionary_is_compared_for_equality_to_a_non_empty_dictionary_it_should_throw() + { + // Arrange + var dictionary1 = new Dictionary(); + + var dictionary2 = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act + Action act = () => dictionary1.Should().Equal(dictionary2); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary1 to be equal to {[1] = \"One\", [2] = \"Two\"}, but could not find keys {1, 2}."); + } + } + + public class NotEqual + { + [Fact] + public void Should_succeed_when_asserting_dictionary_is_not_equal_to_a_dictionary_with_different_key() + { + // Arrange + var dictionary1 = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + var dictionary2 = new Dictionary + { + [1] = "One", + [22] = "Two" + }; + + // Act / Assert + dictionary1.Should().NotEqual(dictionary2); + } + + [Fact] + public void Should_succeed_when_asserting_dictionary_is_not_equal_to_a_dictionary_with_different_value() + { + // Arrange + var dictionary1 = new Dictionary + { + [1] = "One", + [2] = null + }; + + var dictionary2 = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act / Assert + dictionary1.Should().NotEqual(dictionary2); + } + + [Fact] + public void When_two_equal_dictionaries_are_not_expected_to_be_equal_it_should_throw() + { + // Arrange + var dictionary1 = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + var dictionary2 = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act + Action act = () => dictionary1.Should().NotEqual(dictionary2); + + // Assert + act.Should().Throw().WithMessage( + "Did not expect dictionaries {[1] = \"One\", [2] = \"Two\"} and {[1] = \"One\", [2] = \"Two\"} to be equal."); + } + + [Fact] + public void When_two_equal_dictionaries_are_not_expected_to_be_equal_it_should_report_a_clear_explanation() + { + // Arrange + var dictionary1 = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + var dictionary2 = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act + Action act = () => dictionary1.Should().NotEqual(dictionary2, "because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw().WithMessage( + "Did not expect dictionaries {[1] = \"One\", [2] = \"Two\"} and {[1] = \"One\", [2] = \"Two\"} to be equal because we want to test the failure message."); + } + + [Fact] + public void When_asserting_dictionaries_not_to_be_equal_subject_but_dictionary_is_null_it_should_throw() + { + // Arrange + Dictionary dictionary1 = null; + + var dictionary2 = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + // Act + Action act = () => + { + using var _ = new AssertionScope(); + dictionary1.Should().NotEqual(dictionary2, "because we want to test the behaviour with a null subject"); + }; + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionaries not to be equal because we want to test the behaviour with a null subject, but found ."); + } + + [Fact] + public void When_asserting_dictionaries_not_to_be_equal_but_expected_dictionary_is_null_it_should_throw() + { + // Arrange + var dictionary1 = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + Dictionary dictionary2 = null; + + // Act + Action act = + () => dictionary1.Should().NotEqual(dictionary2, "because we want to test the behaviour with a null subject"); + + // Assert + act.Should().Throw() + .WithMessage("Cannot compare dictionary with .*") + .WithParameterName("unexpected"); + } + + [Fact] + public void + When_asserting_dictionaries_not_to_be_equal_subject_but_both_dictionaries_reference_the_same_object_it_should_throw() + { + // Arrange + var dictionary1 = new Dictionary + { + [1] = "One", + [2] = "Two" + }; + + var dictionary2 = dictionary1; + + // Act + Action act = + () => dictionary1.Should().NotEqual(dictionary2, "because we want to test the behaviour with same objects"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionaries not to be equal because we want to test the behaviour with same objects, but they both reference the same object."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.HaveCount.cs b/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.HaveCount.cs new file mode 100644 index 0000000000..7033636199 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.HaveCount.cs @@ -0,0 +1,216 @@ +using System; +using System.Collections.Generic; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Collections; + +public partial class GenericDictionaryAssertionSpecs +{ + public class HaveCount + { + [Fact] + public void Should_succeed_when_asserting_dictionary_has_a_count_that_equals_the_number_of_items() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two", + [3] = "Three" + }; + + // Act / Assert + dictionary.Should().HaveCount(3); + } + + [Fact] + public void Should_fail_when_asserting_dictionary_has_a_count_that_is_different_from_the_number_of_items() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two", + [3] = "Three" + }; + + // Act + Action act = () => dictionary.Should().HaveCount(4); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void + When_dictionary_has_a_count_that_is_different_from_the_number_of_items_it_should_fail_with_descriptive_message_() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two", + [3] = "Three" + }; + + // Act + Action action = () => dictionary.Should().HaveCount(4, "because we want to test the failure {0}", "message"); + + // Assert + action.Should().Throw() + .WithMessage( + "Expected dictionary to contain 4 item(s) because we want to test the failure message, but found 3: {[1] = \"One\", [2] = \"Two\", [3] = \"Three\"}."); + } + + [Fact] + public void When_dictionary_has_a_count_larger_than_the_minimum_it_should_not_throw() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two", + [3] = "Three" + }; + + // Act / Assert + dictionary.Should().HaveCount(c => c >= 3); + } + + [Fact] + public void When_dictionary_has_a_count_that_not_matches_the_predicate_it_should_throw() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two", + [3] = "Three" + }; + + // Act + Action act = () => dictionary.Should().HaveCount(c => c >= 4, "a minimum of 4 is required"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary to have a count (c >= 4) because a minimum of 4 is required, but count is 3: {[1] = \"One\", [2] = \"Two\", [3] = \"Three\"}."); + } + + [Fact] + public void When_dictionary_count_is_matched_against_a_null_predicate_it_should_throw() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two", + [3] = "Three" + }; + + // Act + Action act = () => dictionary.Should().HaveCount(null); + + // Assert + act.Should().Throw().WithMessage( + "Cannot compare collection count against a predicate.*"); + } + + [Fact] + public void When_dictionary_count_is_matched_and_dictionary_is_null_it_should_throw() + { + // Arrange + Dictionary dictionary = null; + + // Act + Action act = () => dictionary.Should().HaveCount(1, "we want to test the behaviour with a null subject"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary to contain 1 item(s) because we want to test the behaviour with a null subject, but found ."); + } + + [Fact] + public void When_dictionary_count_is_matched_against_a_predicate_and_dictionary_is_null_it_should_throw() + { + // Arrange + Dictionary dictionary = null; + + // Act + Action act = () => dictionary.Should().HaveCount(c => c < 3, "we want to test the behaviour with a null subject"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary to contain (c < 3) items because we want to test the behaviour with a null subject, but found ."); + } + } + + public class NotHaveCount + { + [Fact] + public void Should_succeed_when_asserting_dictionary_has_a_count_different_from_the_number_of_items() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two", + [3] = "Three" + }; + + // Act / Assert + dictionary.Should().NotHaveCount(2); + } + + [Fact] + public void Should_fail_when_asserting_dictionary_has_a_count_that_equals_the_number_of_items() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two", + [3] = "Three" + }; + + // Act + Action act = () => dictionary.Should().NotHaveCount(3); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_dictionary_has_a_count_that_equals_than_the_number_of_items_it_should_fail_with_descriptive_message_() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two", + [3] = "Three" + }; + + // Act + Action action = () => dictionary.Should().NotHaveCount(3, "because we want to test the failure {0}", "message"); + + // Assert + action.Should().Throw() + .WithMessage("*not contain*3*because we want to test the failure message*3*"); + } + + [Fact] + public void When_dictionary_count_is_same_than_and_dictionary_is_null_it_should_throw() + { + // Arrange + Dictionary dictionary = null; + + // Act + Action act = () => dictionary.Should().NotHaveCount(1, "we want to test the behaviour with a null subject"); + + // Assert + act.Should().Throw() + .WithMessage("*not contain*1*we want to test the behaviour with a null subject*found *"); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.HaveCountGreaterThan.cs b/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.HaveCountGreaterThan.cs new file mode 100644 index 0000000000..57848bf2b0 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.HaveCountGreaterThan.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Collections; + +public partial class GenericDictionaryAssertionSpecs +{ + public class HaveCountGreaterThan + { + [Fact] + public void Should_succeed_when_asserting_dictionary_has_a_count_greater_than_less_the_number_of_items() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two", + [3] = "Three" + }; + + // Act / Assert + dictionary.Should().HaveCountGreaterThan(2); + } + + [Fact] + public void Should_fail_when_asserting_dictionary_has_a_count_greater_than_the_number_of_items() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two", + [3] = "Three" + }; + + // Act + Action act = () => dictionary.Should().HaveCountGreaterThan(3); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_dictionary_has_a_count_greater_than_the_number_of_items_it_should_fail_with_descriptive_message_() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two", + [3] = "Three" + }; + + // Act + Action action = () => + dictionary.Should().HaveCountGreaterThan(3, "because we want to test the failure {0}", "message"); + + // Assert + action.Should().Throw() + .WithMessage( + "Expected dictionary to contain more than 3 item(s) because we want to test the failure message, but found 3: {[1] = \"One\", [2] = \"Two\", [3] = \"Three\"}."); + } + + [Fact] + public void When_dictionary_count_is_greater_than_and_dictionary_is_null_it_should_throw() + { + // Arrange + Dictionary dictionary = null; + + // Act + Action act = () => dictionary.Should().HaveCountGreaterThan(1, "we want to test the behaviour with a null subject"); + + // Assert + act.Should().Throw() + .WithMessage("*more than*1*we want to test the behaviour with a null subject*found *"); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.HaveCountGreaterThanOrEqualTo.cs b/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.HaveCountGreaterThanOrEqualTo.cs new file mode 100644 index 0000000000..6455493a19 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.HaveCountGreaterThanOrEqualTo.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Collections; + +public partial class GenericDictionaryAssertionSpecs +{ + public class HaveCountGreaterThanOrEqualTo + { + [Fact] + public void Should_succeed_when_asserting_dictionary_has_a_count_greater_than_or_equal_to_less_the_number_of_items() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two", + [3] = "Three" + }; + + // Act / Assert + dictionary.Should().HaveCountGreaterThanOrEqualTo(3); + } + + [Fact] + public void Should_fail_when_asserting_dictionary_has_a_count_greater_than_or_equal_to_the_number_of_items() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two", + [3] = "Three" + }; + + // Act + Action act = () => dictionary.Should().HaveCountGreaterThanOrEqualTo(4); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void + When_dictionary_has_a_count_greater_than_or_equal_to_the_number_of_items_it_should_fail_with_descriptive_message_() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two", + [3] = "Three" + }; + + // Act + Action action = () => + dictionary.Should().HaveCountGreaterThanOrEqualTo(4, "because we want to test the failure {0}", "message"); + + // Assert + action.Should().Throw() + .WithMessage( + "Expected dictionary to contain at least 4 item(s) because we want to test the failure message, but found 3: {[1] = \"One\", [2] = \"Two\", [3] = \"Three\"}."); + } + + [Fact] + public void When_dictionary_count_is_greater_than_or_equal_to_and_dictionary_is_null_it_should_throw() + { + // Arrange + Dictionary dictionary = null; + + // Act + Action act = () => + dictionary.Should().HaveCountGreaterThanOrEqualTo(1, "we want to test the behaviour with a null subject"); + + // Assert + act.Should().Throw() + .WithMessage("*at least*1*we want to test the behaviour with a null subject*found *"); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.HaveCountLessThan.cs b/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.HaveCountLessThan.cs new file mode 100644 index 0000000000..7549305571 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.HaveCountLessThan.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Collections; + +public partial class GenericDictionaryAssertionSpecs +{ + public class HaveCountLessThan + { + [Fact] + public void Should_succeed_when_asserting_dictionary_has_a_count_less_than_less_the_number_of_items() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two", + [3] = "Three" + }; + + // Act / Assert + dictionary.Should().HaveCountLessThan(4); + } + + [Fact] + public void Should_fail_when_asserting_dictionary_has_a_count_less_than_the_number_of_items() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two", + [3] = "Three" + }; + + // Act + Action act = () => dictionary.Should().HaveCountLessThan(3); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_dictionary_has_a_count_less_than_the_number_of_items_it_should_fail_with_descriptive_message_() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two", + [3] = "Three" + }; + + // Act + Action action = () => dictionary.Should().HaveCountLessThan(3, "because we want to test the failure {0}", "message"); + + // Assert + action.Should().Throw() + .WithMessage( + "Expected dictionary to contain fewer than 3 item(s) because we want to test the failure message, but found 3: {[1] = \"One\", [2] = \"Two\", [3] = \"Three\"}."); + } + + [Fact] + public void When_dictionary_count_is_less_than_and_dictionary_is_null_it_should_throw() + { + // Arrange + Dictionary dictionary = null; + + // Act + Action act = () => dictionary.Should().HaveCountLessThan(1, "we want to test the behaviour with a null subject"); + + // Assert + act.Should().Throw() + .WithMessage("*fewer than*1*we want to test the behaviour with a null subject*found *"); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.HaveCountLessThanOrEqualTo.cs b/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.HaveCountLessThanOrEqualTo.cs new file mode 100644 index 0000000000..b02679c290 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.HaveCountLessThanOrEqualTo.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Collections; + +public partial class GenericDictionaryAssertionSpecs +{ + public class HaveCountLessThanOrEqualTo + { + [Fact] + public void Should_succeed_when_asserting_dictionary_has_a_count_less_than_or_equal_to_less_the_number_of_items() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two", + [3] = "Three" + }; + + // Act / Assert + dictionary.Should().HaveCountLessThanOrEqualTo(3); + } + + [Fact] + public void Should_fail_when_asserting_dictionary_has_a_count_less_than_or_equal_to_the_number_of_items() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two", + [3] = "Three" + }; + + // Act + Action act = () => dictionary.Should().HaveCountLessThanOrEqualTo(2); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void + When_dictionary_has_a_count_less_than_or_equal_to_the_number_of_items_it_should_fail_with_descriptive_message_() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two", + [3] = "Three" + }; + + // Act + Action action = () => + dictionary.Should().HaveCountLessThanOrEqualTo(2, "because we want to test the failure {0}", "message"); + + // Assert + action.Should().Throw() + .WithMessage( + "Expected dictionary to contain at most 2 item(s) because we want to test the failure message, but found 3: {[1] = \"One\", [2] = \"Two\", [3] = \"Three\"}."); + } + + [Fact] + public void When_dictionary_count_is_less_than_or_equal_to_and_dictionary_is_null_it_should_throw() + { + // Arrange + Dictionary dictionary = null; + + // Act + Action act = () => + dictionary.Should().HaveCountLessThanOrEqualTo(1, "we want to test the behaviour with a null subject"); + + // Assert + act.Should().Throw() + .WithMessage("*at most*1*we want to test the behaviour with a null subject*found *"); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.HaveSameCount.cs b/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.HaveSameCount.cs new file mode 100644 index 0000000000..5ae939d0e6 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.HaveSameCount.cs @@ -0,0 +1,230 @@ +using System; +using System.Collections.Generic; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Collections; + +public partial class GenericDictionaryAssertionSpecs +{ + public class HaveSameCount + { + [Fact] + public void When_dictionary_and_collection_have_the_same_number_elements_it_should_succeed() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two", + [3] = "Three" + }; + + int[] collection = [4, 5, 6]; + + // Act / Assert + dictionary.Should().HaveSameCount(collection); + } + + [Fact] + public void When_dictionary_and_collection_do_not_have_the_same_number_of_elements_it_should_fail() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two", + [3] = "Three" + }; + + int[] collection = [4, 6]; + + // Act + Action act = () => dictionary.Should().HaveSameCount(collection); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary to have 2 item(s), but found 3."); + } + + [Fact] + public void When_comparing_item_counts_and_a_reason_is_specified_it_should_it_in_the_exception() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two", + [3] = "Three" + }; + + int[] collection = [4, 6]; + + // Act + Action act = () => dictionary.Should().HaveSameCount(collection, "we want to test the {0}", "reason"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary to have 2 item(s) because we want to test the reason, but found 3."); + } + + [Fact] + public void When_asserting_dictionary_and_collection_have_same_count_against_null_dictionary_it_should_throw() + { + // Arrange + Dictionary dictionary = null; + int[] collection = [1, 2, 3]; + + // Act + Action act = () => dictionary.Should().HaveSameCount(collection, + "because we want to test the behaviour with a null subject"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary to have the same count as {1, 2, 3} because we want to test the behaviour with a null subject, but found ."); + } + + [Fact] + public void When_asserting_dictionary_and_collection_have_same_count_against_a_null_collection_it_should_throw() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two", + [3] = "Three" + }; + + int[] collection = null; + + // Act + Action act = () => dictionary.Should().HaveSameCount(collection); + + // Assert + act.Should().Throw().WithMessage( + "Cannot verify count against a collection.*"); + } + } + + public class NotHaveSameCount + { + [Fact] + public void When_asserting_not_same_count_and_collections_have_different_number_elements_it_should_succeed() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two", + [3] = "Three" + }; + + int[] collection = [4, 6]; + + // Act / Assert + dictionary.Should().NotHaveSameCount(collection); + } + + [Fact] + public void When_asserting_not_same_count_and_both_collections_have_the_same_number_elements_it_should_fail() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two", + [3] = "Three" + }; + + int[] collection = [4, 5, 6]; + + // Act + Action act = () => dictionary.Should().NotHaveSameCount(collection); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary to not have 3 item(s), but found 3."); + } + + [Fact] + public void When_comparing_not_same_item_counts_and_a_reason_is_specified_it_should_it_in_the_exception() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two", + [3] = "Three" + }; + + int[] collection = [4, 5, 6]; + + // Act + Action act = () => dictionary.Should().NotHaveSameCount(collection, "we want to test the {0}", "reason"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary to not have 3 item(s) because we want to test the reason, but found 3."); + } + + [Fact] + public void When_asserting_dictionary_and_collection_to_not_have_same_count_against_null_dictionary_it_should_throw() + { + // Arrange + Dictionary dictionary = null; + int[] collection = [1, 2, 3]; + + // Act + Action act = () => dictionary.Should().NotHaveSameCount(collection, + "because we want to test the behaviour with a null subject"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dictionary to not have the same count as {1, 2, 3} because we want to test the behaviour with a null subject, but found ."); + } + + [Fact] + public void When_asserting_dictionary_and_collection_to_not_have_same_count_against_a_null_collection_it_should_throw() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two", + [3] = "Three" + }; + + int[] collection = null; + + // Act + Action act = () => dictionary.Should().NotHaveSameCount(collection); + + // Assert + act.Should().Throw().WithMessage( + "Cannot verify count against a collection.*"); + } + + [Fact] + public void + When_asserting_dictionary_and_collection_to_not_have_same_count_but_both_reference_the_same_object_it_should_throw() + { + // Arrange + var dictionary = new Dictionary + { + [1] = "One", + [2] = "Two", + [3] = "Three" + }; + + var collection = dictionary; + + // Act + Action act = () => dictionary.Should().NotHaveSameCount(collection, + "because we want to test the behaviour with same objects"); + + // Assert + act.Should().Throw().WithMessage( + "*not have the same count*because we want to test the behaviour with same objects*but they both reference the same object."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.cs index 3e7ce92c99..66a931ac13 100644 --- a/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.cs @@ -3,13 +3,12 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; -using FluentAssertions.Execution; using Xunit; using Xunit.Sdk; namespace FluentAssertions.Specs.Collections; -public class GenericDictionaryAssertionSpecs +public partial class GenericDictionaryAssertionSpecs { // If you try to implement support for IReadOnlyDictionary, these tests should still succeed. public class SanityChecks @@ -58,2715 +57,6 @@ public void When_a_dictionary_does_not_implement_the_read_only_interface_it_shou } } - public class BeNull - { - [Fact] - public void When_dictionary_is_expected_to_be_null_and_it_is_it_should_not_throw() - { - // Arrange - IDictionary someDictionary = null; - - // Act / Assert - someDictionary.Should().BeNull(); - } - - [Fact] - public void When_a_custom_dictionary_implementation_is_expected_not_to_be_null_and_it_is_it_should_not_throw() - { - // Arrange - var dictionary = new TrackingTestDictionary(); - - // Act / Assert - dictionary.Should().NotBeNull(); - } - - [Fact] - public void When_dictionary_is_expected_to_be_null_and_it_isnt_it_should_throw() - { - // Arrange - var someDictionary = new Dictionary(); - - // Act - Action act = () => someDictionary.Should().BeNull("because {0} is valid", "null"); - - // Assert - act.Should().Throw().WithMessage( - "Expected someDictionary to be because null is valid, but found {empty}."); - } - - [Fact] - public void When_dictionary_is_not_expected_to_be_null_and_it_isnt_it_should_not_throw() - { - // Arrange - IDictionary someDictionary = new Dictionary(); - - // Act / Assert - someDictionary.Should().NotBeNull(); - } - - [Fact] - public void When_dictionary_is_not_expected_to_be_null_and_it_is_it_should_throw() - { - // Arrange - IDictionary someDictionary = null; - - // Act - Action act = () => someDictionary.Should().NotBeNull("because {0} should not", "someDictionary"); - - // Assert - act.Should().Throw().WithMessage( - "Expected someDictionary not to be because someDictionary should not."); - } - } - - public class HaveCount - { - [Fact] - public void Should_succeed_when_asserting_dictionary_has_a_count_that_equals_the_number_of_items() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two", - [3] = "Three" - }; - - // Act / Assert - dictionary.Should().HaveCount(3); - } - - [Fact] - public void Should_fail_when_asserting_dictionary_has_a_count_that_is_different_from_the_number_of_items() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two", - [3] = "Three" - }; - - // Act - Action act = () => dictionary.Should().HaveCount(4); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void - When_dictionary_has_a_count_that_is_different_from_the_number_of_items_it_should_fail_with_descriptive_message_() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two", - [3] = "Three" - }; - - // Act - Action action = () => dictionary.Should().HaveCount(4, "because we want to test the failure {0}", "message"); - - // Assert - action.Should().Throw() - .WithMessage( - "Expected dictionary to contain 4 item(s) because we want to test the failure message, but found 3: {[1] = \"One\", [2] = \"Two\", [3] = \"Three\"}."); - } - - [Fact] - public void When_dictionary_has_a_count_larger_than_the_minimum_it_should_not_throw() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two", - [3] = "Three" - }; - - // Act / Assert - dictionary.Should().HaveCount(c => c >= 3); - } - - [Fact] - public void When_dictionary_has_a_count_that_not_matches_the_predicate_it_should_throw() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two", - [3] = "Three" - }; - - // Act - Action act = () => dictionary.Should().HaveCount(c => c >= 4, "a minimum of 4 is required"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary to have a count (c >= 4) because a minimum of 4 is required, but count is 3: {[1] = \"One\", [2] = \"Two\", [3] = \"Three\"}."); - } - - [Fact] - public void When_dictionary_count_is_matched_against_a_null_predicate_it_should_throw() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two", - [3] = "Three" - }; - - // Act - Action act = () => dictionary.Should().HaveCount(null); - - // Assert - act.Should().Throw().WithMessage( - "Cannot compare collection count against a predicate.*"); - } - - [Fact] - public void When_dictionary_count_is_matched_and_dictionary_is_null_it_should_throw() - { - // Arrange - Dictionary dictionary = null; - - // Act - Action act = () => dictionary.Should().HaveCount(1, "we want to test the behaviour with a null subject"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary to contain 1 item(s) because we want to test the behaviour with a null subject, but found ."); - } - - [Fact] - public void When_dictionary_count_is_matched_against_a_predicate_and_dictionary_is_null_it_should_throw() - { - // Arrange - Dictionary dictionary = null; - - // Act - Action act = () => dictionary.Should().HaveCount(c => c < 3, "we want to test the behaviour with a null subject"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary to contain (c < 3) items because we want to test the behaviour with a null subject, but found ."); - } - } - - public class NotHaveCount - { - [Fact] - public void Should_succeed_when_asserting_dictionary_has_a_count_different_from_the_number_of_items() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two", - [3] = "Three" - }; - - // Act / Assert - dictionary.Should().NotHaveCount(2); - } - - [Fact] - public void Should_fail_when_asserting_dictionary_has_a_count_that_equals_the_number_of_items() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two", - [3] = "Three" - }; - - // Act - Action act = () => dictionary.Should().NotHaveCount(3); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_dictionary_has_a_count_that_equals_than_the_number_of_items_it_should_fail_with_descriptive_message_() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two", - [3] = "Three" - }; - - // Act - Action action = () => dictionary.Should().NotHaveCount(3, "because we want to test the failure {0}", "message"); - - // Assert - action.Should().Throw() - .WithMessage("*not contain*3*because we want to test the failure message*3*"); - } - - [Fact] - public void When_dictionary_count_is_same_than_and_dictionary_is_null_it_should_throw() - { - // Arrange - Dictionary dictionary = null; - - // Act - Action act = () => dictionary.Should().NotHaveCount(1, "we want to test the behaviour with a null subject"); - - // Assert - act.Should().Throw() - .WithMessage("*not contain*1*we want to test the behaviour with a null subject*found *"); - } - } - - public class HaveCountGreaterThan - { - [Fact] - public void Should_succeed_when_asserting_dictionary_has_a_count_greater_than_less_the_number_of_items() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two", - [3] = "Three" - }; - - // Act / Assert - dictionary.Should().HaveCountGreaterThan(2); - } - - [Fact] - public void Should_fail_when_asserting_dictionary_has_a_count_greater_than_the_number_of_items() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two", - [3] = "Three" - }; - - // Act - Action act = () => dictionary.Should().HaveCountGreaterThan(3); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_dictionary_has_a_count_greater_than_the_number_of_items_it_should_fail_with_descriptive_message_() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two", - [3] = "Three" - }; - - // Act - Action action = () => - dictionary.Should().HaveCountGreaterThan(3, "because we want to test the failure {0}", "message"); - - // Assert - action.Should().Throw() - .WithMessage( - "Expected dictionary to contain more than 3 item(s) because we want to test the failure message, but found 3: {[1] = \"One\", [2] = \"Two\", [3] = \"Three\"}."); - } - - [Fact] - public void When_dictionary_count_is_greater_than_and_dictionary_is_null_it_should_throw() - { - // Arrange - Dictionary dictionary = null; - - // Act - Action act = () => dictionary.Should().HaveCountGreaterThan(1, "we want to test the behaviour with a null subject"); - - // Assert - act.Should().Throw() - .WithMessage("*more than*1*we want to test the behaviour with a null subject*found *"); - } - } - - public class HaveCountGreaterThanOrEqualTo - { - [Fact] - public void Should_succeed_when_asserting_dictionary_has_a_count_greater_than_or_equal_to_less_the_number_of_items() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two", - [3] = "Three" - }; - - // Act / Assert - dictionary.Should().HaveCountGreaterThanOrEqualTo(3); - } - - [Fact] - public void Should_fail_when_asserting_dictionary_has_a_count_greater_than_or_equal_to_the_number_of_items() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two", - [3] = "Three" - }; - - // Act - Action act = () => dictionary.Should().HaveCountGreaterThanOrEqualTo(4); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void - When_dictionary_has_a_count_greater_than_or_equal_to_the_number_of_items_it_should_fail_with_descriptive_message_() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two", - [3] = "Three" - }; - - // Act - Action action = () => - dictionary.Should().HaveCountGreaterThanOrEqualTo(4, "because we want to test the failure {0}", "message"); - - // Assert - action.Should().Throw() - .WithMessage( - "Expected dictionary to contain at least 4 item(s) because we want to test the failure message, but found 3: {[1] = \"One\", [2] = \"Two\", [3] = \"Three\"}."); - } - - [Fact] - public void When_dictionary_count_is_greater_than_or_equal_to_and_dictionary_is_null_it_should_throw() - { - // Arrange - Dictionary dictionary = null; - - // Act - Action act = () => - dictionary.Should().HaveCountGreaterThanOrEqualTo(1, "we want to test the behaviour with a null subject"); - - // Assert - act.Should().Throw() - .WithMessage("*at least*1*we want to test the behaviour with a null subject*found *"); - } - } - - public class HaveCountLessThan - { - [Fact] - public void Should_succeed_when_asserting_dictionary_has_a_count_less_than_less_the_number_of_items() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two", - [3] = "Three" - }; - - // Act / Assert - dictionary.Should().HaveCountLessThan(4); - } - - [Fact] - public void Should_fail_when_asserting_dictionary_has_a_count_less_than_the_number_of_items() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two", - [3] = "Three" - }; - - // Act - Action act = () => dictionary.Should().HaveCountLessThan(3); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_dictionary_has_a_count_less_than_the_number_of_items_it_should_fail_with_descriptive_message_() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two", - [3] = "Three" - }; - - // Act - Action action = () => dictionary.Should().HaveCountLessThan(3, "because we want to test the failure {0}", "message"); - - // Assert - action.Should().Throw() - .WithMessage( - "Expected dictionary to contain fewer than 3 item(s) because we want to test the failure message, but found 3: {[1] = \"One\", [2] = \"Two\", [3] = \"Three\"}."); - } - - [Fact] - public void When_dictionary_count_is_less_than_and_dictionary_is_null_it_should_throw() - { - // Arrange - Dictionary dictionary = null; - - // Act - Action act = () => dictionary.Should().HaveCountLessThan(1, "we want to test the behaviour with a null subject"); - - // Assert - act.Should().Throw() - .WithMessage("*fewer than*1*we want to test the behaviour with a null subject*found *"); - } - } - - public class HaveCountLessThanOrEqualTo - { - [Fact] - public void Should_succeed_when_asserting_dictionary_has_a_count_less_than_or_equal_to_less_the_number_of_items() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two", - [3] = "Three" - }; - - // Act / Assert - dictionary.Should().HaveCountLessThanOrEqualTo(3); - } - - [Fact] - public void Should_fail_when_asserting_dictionary_has_a_count_less_than_or_equal_to_the_number_of_items() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two", - [3] = "Three" - }; - - // Act - Action act = () => dictionary.Should().HaveCountLessThanOrEqualTo(2); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void - When_dictionary_has_a_count_less_than_or_equal_to_the_number_of_items_it_should_fail_with_descriptive_message_() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two", - [3] = "Three" - }; - - // Act - Action action = () => - dictionary.Should().HaveCountLessThanOrEqualTo(2, "because we want to test the failure {0}", "message"); - - // Assert - action.Should().Throw() - .WithMessage( - "Expected dictionary to contain at most 2 item(s) because we want to test the failure message, but found 3: {[1] = \"One\", [2] = \"Two\", [3] = \"Three\"}."); - } - - [Fact] - public void When_dictionary_count_is_less_than_or_equal_to_and_dictionary_is_null_it_should_throw() - { - // Arrange - Dictionary dictionary = null; - - // Act - Action act = () => - dictionary.Should().HaveCountLessThanOrEqualTo(1, "we want to test the behaviour with a null subject"); - - // Assert - act.Should().Throw() - .WithMessage("*at most*1*we want to test the behaviour with a null subject*found *"); - } - } - - public class HaveSameCount - { - [Fact] - public void When_dictionary_and_collection_have_the_same_number_elements_it_should_succeed() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two", - [3] = "Three" - }; - - var collection = new[] { 4, 5, 6 }; - - // Act / Assert - dictionary.Should().HaveSameCount(collection); - } - - [Fact] - public void When_dictionary_and_collection_do_not_have_the_same_number_of_elements_it_should_fail() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two", - [3] = "Three" - }; - - var collection = new[] { 4, 6 }; - - // Act - Action act = () => dictionary.Should().HaveSameCount(collection); - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary to have 2 item(s), but found 3."); - } - - [Fact] - public void When_comparing_item_counts_and_a_reason_is_specified_it_should_it_in_the_exception() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two", - [3] = "Three" - }; - - var collection = new[] { 4, 6 }; - - // Act - Action act = () => dictionary.Should().HaveSameCount(collection, "we want to test the {0}", "reason"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary to have 2 item(s) because we want to test the reason, but found 3."); - } - - [Fact] - public void When_asserting_dictionary_and_collection_have_same_count_against_null_dictionary_it_should_throw() - { - // Arrange - Dictionary dictionary = null; - var collection = new[] { 1, 2, 3 }; - - // Act - Action act = () => dictionary.Should().HaveSameCount(collection, - "because we want to test the behaviour with a null subject"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary to have the same count as {1, 2, 3} because we want to test the behaviour with a null subject, but found ."); - } - - [Fact] - public void When_asserting_dictionary_and_collection_have_same_count_against_a_null_collection_it_should_throw() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two", - [3] = "Three" - }; - - int[] collection = null; - - // Act - Action act = () => dictionary.Should().HaveSameCount(collection); - - // Assert - act.Should().Throw().WithMessage( - "Cannot verify count against a collection.*"); - } - } - - public class NotHaveSameCount - { - [Fact] - public void When_asserting_not_same_count_and_collections_have_different_number_elements_it_should_succeed() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two", - [3] = "Three" - }; - - var collection = new[] { 4, 6 }; - - // Act / Assert - dictionary.Should().NotHaveSameCount(collection); - } - - [Fact] - public void When_asserting_not_same_count_and_both_collections_have_the_same_number_elements_it_should_fail() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two", - [3] = "Three" - }; - - var collection = new[] { 4, 5, 6 }; - - // Act - Action act = () => dictionary.Should().NotHaveSameCount(collection); - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary to not have 3 item(s), but found 3."); - } - - [Fact] - public void When_comparing_not_same_item_counts_and_a_reason_is_specified_it_should_it_in_the_exception() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two", - [3] = "Three" - }; - - var collection = new[] { 4, 5, 6 }; - - // Act - Action act = () => dictionary.Should().NotHaveSameCount(collection, "we want to test the {0}", "reason"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary to not have 3 item(s) because we want to test the reason, but found 3."); - } - - [Fact] - public void When_asserting_dictionary_and_collection_to_not_have_same_count_against_null_dictionary_it_should_throw() - { - // Arrange - Dictionary dictionary = null; - var collection = new[] { 1, 2, 3 }; - - // Act - Action act = () => dictionary.Should().NotHaveSameCount(collection, - "because we want to test the behaviour with a null subject"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary to not have the same count as {1, 2, 3} because we want to test the behaviour with a null subject, but found ."); - } - - [Fact] - public void When_asserting_dictionary_and_collection_to_not_have_same_count_against_a_null_collection_it_should_throw() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two", - [3] = "Three" - }; - - int[] collection = null; - - // Act - Action act = () => dictionary.Should().NotHaveSameCount(collection); - - // Assert - act.Should().Throw().WithMessage( - "Cannot verify count against a collection.*"); - } - - [Fact] - public void - When_asserting_dictionary_and_collection_to_not_have_same_count_but_both_reference_the_same_object_it_should_throw() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two", - [3] = "Three" - }; - - var collection = dictionary; - - // Act - Action act = () => dictionary.Should().NotHaveSameCount(collection, - "because we want to test the behaviour with same objects"); - - // Assert - act.Should().Throw().WithMessage( - "*not have the same count*because we want to test the behaviour with same objects*but they both reference the same object."); - } - } - - public class BeEmpty - { - [Fact] - public void Should_succeed_when_asserting_dictionary_without_items_is_empty() - { - // Arrange - var dictionary = new Dictionary(); - - // Act / Assert - dictionary.Should().BeEmpty(); - } - - [Fact] - public void Should_fail_when_asserting_dictionary_with_items_is_empty() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One" - }; - - // Act - Action act = () => dictionary.Should().BeEmpty(); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void Should_fail_with_descriptive_message_when_asserting_dictionary_with_items_is_empty() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One" - }; - - // Act - Action act = () => dictionary.Should().BeEmpty("because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw() - .WithMessage( - "Expected dictionary to be empty because we want to test the failure message, but found at least one item {[1, One]}."); - } - - [Fact] - public void When_asserting_dictionary_with_items_is_not_empty_it_should_succeed() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One" - }; - - // Act / Assert - dictionary.Should().NotBeEmpty(); - } - -#if !NET5_0_OR_GREATER - [Fact] - public void When_asserting_dictionary_with_items_is_not_empty_it_should_enumerate_the_dictionary_only_once() - { - // Arrange - var trackingDictionary = new TrackingTestDictionary(new KeyValuePair(1, "One")); - - // Act - trackingDictionary.Should().NotBeEmpty(); - - // Assert - trackingDictionary.Enumerator.LoopCount.Should().Be(1); - } - -#endif - - [Fact] - public void When_asserting_dictionary_without_items_is_not_empty_it_should_fail() - { - // Arrange - var dictionary = new Dictionary(); - - // Act - Action act = () => dictionary.Should().NotBeEmpty(); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_asserting_dictionary_without_items_is_not_empty_it_should_fail_with_descriptive_message_() - { - // Arrange - var dictionary = new Dictionary(); - - // Act - Action act = () => dictionary.Should().NotBeEmpty("because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw() - .WithMessage("Expected dictionary not to be empty because we want to test the failure message."); - } - - [Fact] - public void When_asserting_dictionary_to_be_empty_but_dictionary_is_null_it_should_throw() - { - // Arrange - Dictionary dictionary = null; - - // Act - Action act = () => dictionary.Should().BeEmpty("because we want to test the behaviour with a null subject"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary to be empty because we want to test the behaviour with a null subject, but found ."); - } - - [Fact] - public void When_asserting_dictionary_to_be_not_empty_but_dictionary_is_null_it_should_throw() - { - // Arrange - Dictionary dictionary = null; - - // Act - Action act = () => dictionary.Should().NotBeEmpty("because we want to test the behaviour with a null subject"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary not to be empty because we want to test the behaviour with a null subject, but found ."); - } - } - - public class Equal - { - [Fact] - public void Should_succeed_when_asserting_dictionary_is_equal_to_the_same_dictionary() - { - // Arrange - var dictionary1 = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - var dictionary2 = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act / Assert - dictionary1.Should().Equal(dictionary2); - } - - [Fact] - public void Should_succeed_when_asserting_dictionary_with_null_value_is_equal_to_the_same_dictionary() - { - // Arrange - var dictionary1 = new Dictionary - { - [1] = "One", - [2] = null - }; - - var dictionary2 = new Dictionary - { - [1] = "One", - [2] = null - }; - - // Act / Assert - dictionary1.Should().Equal(dictionary2); - } - - [Fact] - public void When_asserting_dictionaries_to_be_equal_but_subject_dictionary_misses_a_value_it_should_throw() - { - // Arrange - var dictionary1 = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - var dictionary2 = new Dictionary - { - [1] = "One", - [22] = "Two" - }; - - // Act - Action act = () => dictionary1.Should().Equal(dictionary2, "because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary1 to be equal to {[1] = \"One\", [22] = \"Two\"} because we want to test the failure message, but could not find keys {22}."); - } - - [Fact] - public void When_asserting_dictionaries_to_be_equal_but_subject_dictionary_has_extra_key_it_should_throw() - { - // Arrange - var dictionary1 = new Dictionary - { - [1] = "One", - [2] = "Two", - [3] = "Three" - }; - - var dictionary2 = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act - Action act = () => dictionary1.Should().Equal(dictionary2, "because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary1 to be equal to {[1] = \"One\", [2] = \"Two\"} because we want to test the failure message, but found additional keys {3}."); - } - - [Fact] - public void When_two_dictionaries_are_not_equal_by_values_it_should_throw_using_the_reason() - { - // Arrange - var dictionary1 = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - var dictionary2 = new Dictionary - { - [1] = "One", - [2] = "Three" - }; - - // Act - Action act = () => dictionary1.Should().Equal(dictionary2, "because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary1 to be equal to {[1] = \"One\", [2] = \"Three\"} because we want to test the failure message, but {[1] = \"One\", [2] = \"Two\"} differs at key 2."); - } - - [Fact] - public void When_asserting_dictionaries_to_be_equal_but_subject_dictionary_is_null_it_should_throw() - { - // Arrange - Dictionary dictionary1 = null; - - var dictionary2 = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act - Action act = () => - { - using var _ = new AssertionScope(); - dictionary1.Should().Equal(dictionary2, "because we want to test the behaviour with a null subject"); - }; - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary1 to be equal to {[1] = \"One\", [2] = \"Two\"} because we want to test the behaviour with a null subject, but found ."); - } - - [Fact] - public void When_asserting_dictionaries_to_be_equal_but_expected_dictionary_is_null_it_should_throw() - { - // Arrange - var dictionary1 = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - Dictionary dictionary2 = null; - - // Act - Action act = () => - dictionary1.Should().Equal(dictionary2, "because we want to test the behaviour with a null subject"); - - // Assert - act.Should().Throw() - .WithMessage("Cannot compare dictionary with .*") - .WithParameterName("expected"); - } - - [Fact] - public void When_an_empty_dictionary_is_compared_for_equality_to_a_non_empty_dictionary_it_should_throw() - { - // Arrange - var dictionary1 = new Dictionary(); - - var dictionary2 = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act - Action act = () => dictionary1.Should().Equal(dictionary2); - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary1 to be equal to {[1] = \"One\", [2] = \"Two\"}, but could not find keys {1, 2}."); - } - } - - public class NotEqual - { - [Fact] - public void Should_succeed_when_asserting_dictionary_is_not_equal_to_a_dictionary_with_different_key() - { - // Arrange - var dictionary1 = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - var dictionary2 = new Dictionary - { - [1] = "One", - [22] = "Two" - }; - - // Act / Assert - dictionary1.Should().NotEqual(dictionary2); - } - - [Fact] - public void Should_succeed_when_asserting_dictionary_is_not_equal_to_a_dictionary_with_different_value() - { - // Arrange - var dictionary1 = new Dictionary - { - [1] = "One", - [2] = null - }; - - var dictionary2 = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act / Assert - dictionary1.Should().NotEqual(dictionary2); - } - - [Fact] - public void When_two_equal_dictionaries_are_not_expected_to_be_equal_it_should_throw() - { - // Arrange - var dictionary1 = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - var dictionary2 = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act - Action act = () => dictionary1.Should().NotEqual(dictionary2); - - // Assert - act.Should().Throw().WithMessage( - "Did not expect dictionaries {[1] = \"One\", [2] = \"Two\"} and {[1] = \"One\", [2] = \"Two\"} to be equal."); - } - - [Fact] - public void When_two_equal_dictionaries_are_not_expected_to_be_equal_it_should_report_a_clear_explanation() - { - // Arrange - var dictionary1 = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - var dictionary2 = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act - Action act = () => dictionary1.Should().NotEqual(dictionary2, "because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw().WithMessage( - "Did not expect dictionaries {[1] = \"One\", [2] = \"Two\"} and {[1] = \"One\", [2] = \"Two\"} to be equal because we want to test the failure message."); - } - - [Fact] - public void When_asserting_dictionaries_not_to_be_equal_subject_but_dictionary_is_null_it_should_throw() - { - // Arrange - Dictionary dictionary1 = null; - - var dictionary2 = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act - Action act = () => - { - using var _ = new AssertionScope(); - dictionary1.Should().NotEqual(dictionary2, "because we want to test the behaviour with a null subject"); - }; - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionaries not to be equal because we want to test the behaviour with a null subject, but found ."); - } - - [Fact] - public void When_asserting_dictionaries_not_to_be_equal_but_expected_dictionary_is_null_it_should_throw() - { - // Arrange - var dictionary1 = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - Dictionary dictionary2 = null; - - // Act - Action act = - () => dictionary1.Should().NotEqual(dictionary2, "because we want to test the behaviour with a null subject"); - - // Assert - act.Should().Throw() - .WithMessage("Cannot compare dictionary with .*") - .WithParameterName("unexpected"); - } - - [Fact] - public void - When_asserting_dictionaries_not_to_be_equal_subject_but_both_dictionaries_reference_the_same_object_it_should_throw() - { - // Arrange - var dictionary1 = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - var dictionary2 = dictionary1; - - // Act - Action act = - () => dictionary1.Should().NotEqual(dictionary2, "because we want to test the behaviour with same objects"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionaries not to be equal because we want to test the behaviour with same objects, but they both reference the same object."); - } - } - - public class ContainKey - { - [Fact] - public void Should_succeed_when_asserting_dictionary_contains_a_key_from_the_dictionary() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act / Assert - dictionary.Should().ContainKey(1); - } - - [Fact] - public void When_a_dictionary_has_custom_equality_comparer_the_contains_key_assertion_should_work_accordingly() - { - // Arrange - var dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - ["One"] = "One", - ["Two"] = "Two" - }; - - // Act - - // Assert - dictionary.Should().ContainKey("One"); - dictionary.Should().ContainKey("ONE"); - dictionary.Should().ContainKey("one"); - } - - [Fact] - public void Should_succeed_when_asserting_dictionary_contains_multiple_keys_from_the_dictionary() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act / Assert - dictionary.Should().ContainKeys(2, 1); - } - - [Fact] - public void When_a_dictionary_does_not_contain_single_key_it_should_throw_with_clear_explanation() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act - Action act = () => dictionary.Should().ContainKey(3, "because {0}", "we do"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary {[1] = \"One\", [2] = \"Two\"} to contain key 3 because we do."); - } - - [Fact] - public void When_the_requested_key_exists_it_should_allow_continuation_with_the_value() - { - // Arrange - var dictionary = new Dictionary - { - ["Key"] = new() { SomeProperty = 3 } - }; - - // Act - Action act = () => dictionary.Should().ContainKey("Key").WhoseValue.Should().Be(4); - - // Assert - act.Should().Throw().WithMessage("Expected*4*3*."); - } - - [Fact] - public void When_a_dictionary_does_not_contain_a_list_of_keys_it_should_throw_with_clear_explanation() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act - Action act = () => dictionary.Should().ContainKeys(new[] { 2, 3 }, "because {0}", "we do"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary {[1] = \"One\", [2] = \"Two\"} to contain keys {2, 3} because we do, but could not find {3}."); - } - - [Fact] - public void Null_dictionaries_do_not_contain_any_keys() - { - // Arrange - Dictionary dictionary = null; - - // Act - Action act = () => - { - using var _ = new AssertionScope(); - dictionary.Should().ContainKeys(new[] { 2, 3 }, "because {0}", "we do"); - }; - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary to contain keys {2, 3} because we do, but found ."); - } - - [Fact] - public void - When_the_contents_of_a_dictionary_are_checked_against_an_empty_list_of_keys_it_should_throw_clear_explanation() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act - Action act = () => dictionary.Should().ContainKeys(); - - // Assert - act.Should().Throw().WithMessage( - "Cannot verify key containment against an empty sequence*"); - } - } - - public class NotContainKey - { - [Fact] - public void When_dictionary_does_not_contain_a_key_that_is_not_in_the_dictionary_it_should_not_throw() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act - Action act = () => dictionary.Should().NotContainKey(4); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_dictionary_does_not_contain_multiple_keys_from_the_dictionary_it_should_not_throw() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act - Action act = () => dictionary.Should().NotContainKeys(3, 4); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_dictionary_contains_an_unexpected_key_it_should_throw() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act - Action act = () => dictionary.Should().NotContainKey(1, "because we {0} like it", "don't"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary {[1] = \"One\", [2] = \"Two\"} not to contain key 1 because we don't like it, but found it anyhow."); - } - - [Fact] - public void When_asserting_dictionary_does_not_contain_key_against_null_dictionary_it_should_throw() - { - // Arrange - Dictionary dictionary = null; - - // Act - Action act = () => - { - using var _ = new AssertionScope(); - dictionary.Should().NotContainKey(1, "because we want to test the behaviour with a null subject"); - }; - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary not to contain key 1 because we want to test the behaviour with a null subject, but found ."); - } - - [Fact] - public void When_a_dictionary_contains_a_list_of_keys_it_should_throw_with_clear_explanation() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act - Action act = () => dictionary.Should().NotContainKeys(new[] { 2, 3 }, "because {0}", "we do"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary {[1] = \"One\", [2] = \"Two\"} to not contain keys {2, 3} because we do, but found {2}."); - } - - [Fact] - public void When_a_dictionary_contains_exactly_one_of_the_keys_it_should_throw_with_clear_explanation() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act - Action act = () => dictionary.Should().NotContainKeys(new[] { 2 }, "because {0}", "we do"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary {[1] = \"One\", [2] = \"Two\"} to not contain key 2 because we do."); - } - - [Fact] - public void Null_dictionaries_do_not_contain_any_keys() - { - // Arrange - Dictionary dictionary = null; - - // Act - Action act = () => - { - using var _ = new AssertionScope(); - dictionary.Should().NotContainKeys(new[] { 2 }, "because {0}", "we do"); - }; - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary to not contain keys {2} because we do, but found ."); - } - - [Fact] - public void - When_the_noncontents_of_a_dictionary_are_checked_against_an_empty_list_of_keys_it_should_throw_clear_explanation() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act - Action act = () => dictionary.Should().NotContainKeys(); - - // Assert - act.Should().Throw().WithMessage( - "Cannot verify key containment against an empty sequence*"); - } - - [Fact] - public void - When_a_dictionary_checks_a_list_of_keys_not_to_be_present_it_will_honor_the_case_sensitive_equality_comparer_of_the_dictionary() - { - // Arrange - var dictionary = new Dictionary(StringComparer.Ordinal) - { - ["ONE"] = "One", - ["TWO"] = "Two" - }; - - // Act - Action act = () => dictionary.Should().NotContainKeys("One", "Two"); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void - When_a_dictionary_checks_a_list_of_keys_not_to_be_present_it_will_honor_the_case_insensitive_equality_comparer_of_the_dictionary() - { - // Arrange - var dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - ["ONE"] = "One", - ["TWO"] = "Two" - }; - - // Act - Action act = () => dictionary.Should().NotContainKeys("One", "Two"); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_an_assertion_fails_on_ContainKey_succeeding_message_should_be_included() - { - // 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*Expected*to contain key 1*"); - } - } - - public class MyClass - { - public int SomeProperty { get; set; } - - protected bool Equals(MyClass other) - { - return SomeProperty == other.SomeProperty; - } - - public override bool Equals(object obj) - { - if (obj is null) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - return Equals((MyClass)obj); - } - - public override int GetHashCode() - { - return SomeProperty; - } - } - - public class ContainValue - { - [Fact] - public void When_dictionary_contains_expected_value_it_should_succeed() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act - Action act = () => dictionary.Should().ContainValue("One"); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void Null_dictionaries_do_not_contain_any_values() - { - // Arrange - Dictionary dictionary = null; - - // Act - Action act = () => - { - using var _ = new AssertionScope(); - dictionary.Should().ContainValue("One", "because {0}", "we do"); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expected dictionary to contain values {\"One\"} because we do, but found ."); - } - - [Fact] - public void When_dictionary_contains_expected_null_value_it_should_succeed() - { - // Arrange - var dictionary = new Dictionary - { - [1] = null - }; - - // Act - Action act = () => dictionary.Should().ContainValue(null); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_the_specified_value_exists_it_should_allow_continuation_using_that_value() - { - // Arrange - var myClass = new MyClass - { - SomeProperty = 0 - }; - - var dictionary = new Dictionary - { - [1] = myClass - }; - - // Act - Action act = () => dictionary.Should().ContainValue(myClass).Which.SomeProperty.Should().BeGreaterThan(0); - - // Assert - 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_dictionary_contains_multiple_values_from_the_dictionary_it_should_not_throw() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act - Action act = () => dictionary.Should().ContainValues("Two", "One"); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_a_dictionary_does_not_contain_single_value_it_should_throw_with_clear_explanation() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act - Action act = () => dictionary.Should().ContainValue("Three", "because {0}", "we do"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary {[1] = \"One\", [2] = \"Two\"} to contain value \"Three\" because we do."); - } - - [Fact] - public void When_a_dictionary_does_not_contain_a_number_of_values_it_should_throw_with_clear_explanation() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act - Action act = () => dictionary.Should().ContainValues(new[] { "Two", "Three" }, "because {0}", "we do"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary {[1] = \"One\", [2] = \"Two\"} to contain value {\"Two\", \"Three\"} because we do, but could not find {\"Three\"}."); - } - - [Fact] - public void - When_the_contents_of_a_dictionary_are_checked_against_an_empty_list_of_values_it_should_throw_clear_explanation() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act - Action act = () => dictionary.Should().ContainValues(); - - // Assert - act.Should().Throw().WithMessage( - "Cannot verify value containment against an empty sequence*"); - } - } - - public class NotContainValue - { - [Fact] - public void When_dictionary_does_not_contain_a_value_that_is_not_in_the_dictionary_it_should_not_throw() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act - Action act = () => dictionary.Should().NotContainValue("Three"); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_dictionary_contains_an_unexpected_value_it_should_throw() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act - Action act = () => dictionary.Should().NotContainValue("One", "because we {0} like it", "don't"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary {[1] = \"One\", [2] = \"Two\"} not to contain value \"One\" because we don't like it, but found it anyhow."); - } - - [Fact] - public void When_asserting_dictionary_does_not_contain_value_against_null_dictionary_it_should_throw() - { - // Arrange - Dictionary dictionary = null; - - // Act - Action act = () => - { - using var _ = new AssertionScope(); - dictionary.Should().NotContainValue("One", "because we want to test the behaviour with a null subject"); - }; - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary not to contain value \"One\" because we want to test the behaviour with a null subject, but found ."); - } - - [Fact] - public void When_dictionary_does_not_contain_multiple_values_that_is_not_in_the_dictionary_it_should_not_throw() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act - Action act = () => dictionary.Should().NotContainValues("Three", "Four"); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_a_dictionary_contains_a_exactly_one_of_the_values_it_should_throw_with_clear_explanation() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act - Action act = () => dictionary.Should().NotContainValues(new[] { "Two" }, "because {0}", "we do"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary {[1] = \"One\", [2] = \"Two\"} to not contain value \"Two\" because we do."); - } - - [Fact] - public void When_a_dictionary_contains_a_number_of_values_it_should_throw_with_clear_explanation() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act - Action act = () => dictionary.Should().NotContainValues(new[] { "Two", "Three" }, "because {0}", "we do"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary {[1] = \"One\", [2] = \"Two\"} to not contain value {\"Two\", \"Three\"} because we do, but found {\"Two\"}."); - } - - [Fact] - public void - When_the_noncontents_of_a_dictionary_are_checked_against_an_empty_list_of_values_it_should_throw_clear_explanation() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act - Action act = () => dictionary.Should().NotContainValues(); - - // Assert - act.Should().Throw().WithMessage( - "Cannot verify value containment with an empty sequence*"); - } - - [Fact] - public void Null_dictionaries_do_not_contain_any_values() - { - // Arrange - Dictionary dictionary = null; - - // Act - Action act = () => - { - using var _ = new AssertionScope(); - dictionary.Should().NotContainValues(new[] { "Two", "Three" }, "because {0}", "we do"); - }; - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary to not contain values {\"Two\", \"Three\"} because we do, but found ."); - } - } - - public class Contain - { - [Fact] - public void Should_succeed_when_asserting_dictionary_contains_single_key_value_pair() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - var keyValuePairs = new List> - { - new(1, "One") - }; - - // Act / Assert - dictionary.Should().Contain(keyValuePairs); - } - - [Fact] - public void Should_succeed_when_asserting_dictionary_contains_multiple_key_value_pair() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two", - [3] = "Three", - [4] = "Four" - }; - - var expectedKeyValuePair1 = new KeyValuePair(2, "Two"); - var expectedKeyValuePair2 = new KeyValuePair(3, "Three"); - - // Act / Assert - dictionary.Should().Contain(expectedKeyValuePair1, expectedKeyValuePair2); - } - - [Fact] - public void Should_succeed_when_asserting_dictionary_contains_multiple_key_value_pairs() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - var keyValuePairs = new List> - { - new(1, "One"), - new(2, "Two") - }; - - // Act / Assert - dictionary.Should().Contain(keyValuePairs); - } - - [Fact] - public void When_a_dictionary_does_not_contain_single_value_for_key_value_pairs_it_should_throw_with_clear_explanation() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - var keyValuePairs = new List> - { - new(1, "One"), - new(2, "Three") - }; - - // Act - Action act = () => dictionary.Should().Contain(keyValuePairs, "because {0}", "we do"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary to contain value \"Three\" at key 2 because we do, but found \"Two\"."); - } - - [Fact] - public void - When_a_dictionary_does_not_contain_multiple_values_for_key_value_pairs_it_should_throw_with_clear_explanation() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - var keyValuePairs = new List> - { - new(1, "Two"), - new(2, "Three") - }; - - // Act - Action act = () => dictionary.Should().Contain(keyValuePairs, "because {0}", "we do"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary to contain {[1, Two], [2, Three]} because we do, but dictionary differs at keys {1, 2}."); - } - - [Fact] - public void When_a_dictionary_does_not_contain_single_key_for_key_value_pairs_it_should_throw_with_clear_explanation() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - var keyValuePairs = new List> - { - new(3, "Three") - }; - - // Act - Action act = () => dictionary.Should().Contain(keyValuePairs, "because {0}", "we do"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary {[1] = \"One\", [2] = \"Two\"} to contain key 3 because we do."); - } - - [Fact] - public void When_a_dictionary_does_not_contain_multiple_keys_for_key_value_pairs_it_should_throw_with_clear_explanation() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - var keyValuePairs = new List> - { - new(1, "One"), - new(3, "Three"), - new(4, "Four") - }; - - // Act - Action act = () => dictionary.Should().Contain(keyValuePairs, "because {0}", "we do"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary {[1] = \"One\", [2] = \"Two\"} to contain key(s) {1, 3, 4} because we do, but could not find keys {3, 4}."); - } - - [Fact] - public void When_asserting_dictionary_contains_key_value_pairs_against_null_dictionary_it_should_throw() - { - // Arrange - Dictionary dictionary = null; - - List> keyValuePairs = new() - { - new KeyValuePair(1, "One"), - new KeyValuePair(1, "Two") - }; - - // Act - Action act = () => - { - using var _ = new AssertionScope(); - dictionary.Should().Contain(keyValuePairs, "because we want to test the behaviour with a null subject"); - }; - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary to contain key/value pairs {[1, One], [1, Two]} because we want to test the behaviour with a null subject, but dictionary is ."); - } - - [Fact] - public void When_asserting_dictionary_contains_key_value_pairs_but_expected_key_value_pairs_are_empty_it_should_throw() - { - // Arrange - var dictionary1 = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - List> keyValuePairs = new(); - - // Act - Action act = () => dictionary1.Should().Contain(keyValuePairs, - "because we want to test the behaviour with an empty set of key/value pairs"); - - // Assert - act.Should().Throw().WithMessage( - "Cannot verify key containment against an empty collection of key/value pairs*"); - } - - [Fact] - public void When_asserting_dictionary_contains_key_value_pairs_but_expected_key_value_pairs_are_null_it_should_throw() - { - // Arrange - var dictionary1 = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - List> keyValuePairs = null; - - // Act - Action act = () => - dictionary1.Should().Contain(keyValuePairs, "because we want to test the behaviour with a null subject"); - - // Assert - act.Should().Throw() - .WithMessage("Cannot compare dictionary with .*") - .WithParameterName("expected"); - } - - [Fact] - public void When_dictionary_contains_expected_value_at_specific_key_it_should_not_throw() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act / Assert - dictionary.Should().Contain(1, "One"); - } - - [Fact] - public void When_dictionary_contains_expected_null_at_specific_key_it_should_not_throw() - { - // Arrange - var dictionary = new Dictionary - { - [1] = null - }; - - // Act / Assert - dictionary.Should().Contain(1, null); - } - - [Fact] - public void When_dictionary_contains_expected_key_value_pairs_it_should_not_throw() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act / Assert - var items = new List> - { - new(1, "One"), - new(2, "Two") - }; - - dictionary.Should().Contain(items); - } - - [Fact] - public void When_dictionary_contains_expected_key_value_pair_it_should_not_throw() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act / Assert - var item = new KeyValuePair(1, "One"); - dictionary.Should().Contain(item); - } - - [Fact] - public void When_dictionary_does_not_contain_the_expected_value_at_specific_key_it_should_throw() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act - var item = new KeyValuePair(1, "Two"); - Action act = () => dictionary.Should().Contain(item, "we put it {0}", "there"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary to contain value \"Two\" at key 1 because we put it there, but found \"One\"."); - } - - [Fact] - public void When_dictionary_does_not_contain_the_key_value_pairs_it_should_throw() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - var items = new List> - { - new(1, "Two"), - new(2, "Three") - }; - - // Act - Action act = () => dictionary.Should().Contain(items, "we put them {0}", "there"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary to contain {[1, Two], [2, Three]} because we put them there, but dictionary differs at keys {1, 2}."); - } - - [Fact] - public void When_dictionary_does_not_contain_the_key_value_pair_it_should_throw() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act - Action act = () => dictionary.Should().Contain(1, "Two", "we put it {0}", "there"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary to contain value \"Two\" at key 1 because we put it there, but found \"One\"."); - } - - [Fact] - public void When_dictionary_does_not_contain_an_value_at_the_specific_key_it_should_throw() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act - Action act = () => dictionary.Should().Contain(3, "Two", "we put it {0}", "there"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary to contain value \"Two\" at key 3 because we put it there, but the key was not found."); - } - - [Fact] - public void When_asserting_dictionary_contains_value_at_specific_key_against_null_dictionary_it_should_throw() - { - // Arrange - Dictionary dictionary = null; - - // Act - Action act = () => - { - using var _ = new AssertionScope(); - dictionary.Should().Contain(1, "One", "because we want to test the behaviour with a null subject"); - }; - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary to contain value \"One\" at key 1 because we want to test the behaviour with a null subject, but dictionary is ."); - } - - [Fact] - public void When_a_dictionary_like_collection_contains_the_default_key_it_should_succeed() - { - // Arrange - var subject = new List> - { new(0, 0) }; - - // Act - Action act = () => subject.Should().Contain(0, 0); - - // Assert - act.Should().NotThrow(); - } - } - - public class NotContain - { - [Fact] - public void Should_succeed_when_asserting_dictionary_does_not_contain_single_key_value_pair() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - var keyValuePairs = new List> - { - new(3, "Three") - }; - - // Act / Assert - dictionary.Should().NotContain(keyValuePairs); - } - - [Fact] - public void Should_succeed_when_asserting_dictionary_does_not_contain_multiple_key_value_pair() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - var unexpectedKeyValuePair1 = new KeyValuePair(3, "Three"); - var unexpectedKeyValuePair2 = new KeyValuePair(4, "Four"); - - // Act / Assert - dictionary.Should().NotContain(unexpectedKeyValuePair1, unexpectedKeyValuePair2); - } - - [Fact] - public void - Should_succeed_when_asserting_dictionary_does_not_contain_single_key_value_pair_with_existing_key_but_different_value() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - var keyValuePairs = new List> - { - new(1, "Two") - }; - - // Act / Assert - dictionary.Should().NotContain(keyValuePairs); - } - - [Fact] - public void Should_succeed_when_asserting_dictionary_does_not_contain_multiple_key_value_pairs() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - var keyValuePairs = new List> - { - new(3, "Three"), - new(4, "Four") - }; - - // Act / Assert - dictionary.Should().NotContain(keyValuePairs); - } - - [Fact] - public void - Should_succeed_when_asserting_dictionary_does_not_contain_multiple_key_value_pairs_with_existing_keys_but_different_values() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - var keyValuePairs = new List> - { - new(1, "Three"), - new(2, "Four") - }; - - // Act / Assert - dictionary.Should().NotContain(keyValuePairs); - } - - [Fact] - public void When_a_dictionary_does_contain_single_key_value_pair_it_should_throw_with_clear_explanation() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - var keyValuePairs = new List> - { - new(1, "One") - }; - - // Act - Action act = () => dictionary.Should().NotContain(keyValuePairs, "because {0}", "we do"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary to not contain value \"One\" at key 1 because we do, but found it anyhow."); - } - - [Fact] - public void When_a_dictionary_does_contain_multiple_key_value_pairs_it_should_throw_with_clear_explanation() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - var keyValuePairs = new List> - { - new(1, "One"), - new(2, "Two") - }; - - // Act - Action act = () => dictionary.Should().NotContain(keyValuePairs, "because {0}", "we do"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary to not contain key/value pairs {[1, One], [2, Two]} because we do, but found them anyhow."); - } - - [Fact] - public void When_asserting_dictionary_does_not_contain_key_value_pairs_against_null_dictionary_it_should_throw() - { - // Arrange - Dictionary dictionary = null; - - List> keyValuePairs = new() - { - new KeyValuePair(1, "One"), - new KeyValuePair(1, "Two") - }; - - // Act - Action act = () => - { - using var _ = new AssertionScope(); - dictionary.Should().NotContain(keyValuePairs, "because we want to test the behaviour with a null subject"); - }; - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary to not contain key/value pairs {[1, One], [1, Two]} because we want to test the behaviour with a null subject, but dictionary is ."); - } - - [Fact] - public void - When_asserting_dictionary_does_not_contain_key_value_pairs_but_expected_key_value_pairs_are_empty_it_should_throw() - { - // Arrange - var dictionary1 = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - List> keyValuePair = new(); - - // Act - Action act = () => dictionary1.Should().NotContain(keyValuePair, - "because we want to test the behaviour with an empty set of key/value pairs"); - - // Assert - act.Should().Throw().WithMessage( - "Cannot verify key containment against an empty collection of key/value pairs*"); - } - - [Fact] - public void - When_asserting_dictionary_does_not_contain_key_value_pairs_but_expected_key_value_pairs_are_null_it_should_throw() - { - // Arrange - var dictionary1 = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - List> keyValuePairs = null; - - // Act - Action act = () => - dictionary1.Should().NotContain(keyValuePairs, "because we want to test the behaviour with a null subject"); - - // Assert - act.Should().Throw() - .WithMessage("Cannot compare dictionary with .*") - .WithParameterName("items"); - } - - [Fact] - public void When_dictionary_does_not_contain_unexpected_value_or_key_it_should_not_throw() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act / Assert - dictionary.Should().NotContain(3, "Three"); - } - - [Fact] - public void When_dictionary_does_not_contain_unexpected_value_at_existing_key_it_should_not_throw() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act / Assert - dictionary.Should().NotContain(2, "Three"); - } - - [Fact] - public void When_dictionary_does_not_have_the_unexpected_value_but_null_at_existing_key_it_should_succeed() - { - // Arrange - var dictionary = new Dictionary - { - [1] = null - }; - - // Act - Action action = () => dictionary.Should().NotContain(1, "other"); - - // Assert - action.Should().NotThrow(); - } - - [Fact] - public void When_dictionary_does_not_contain_unexpected_key_value_pairs_it_should_not_throw() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act / Assert - var items = new List> - { - new(3, "Three"), - new(4, "Four") - }; - - dictionary.Should().NotContain(items); - } - - [Fact] - public void When_dictionary_does_not_contain_unexpected_key_value_pair_it_should_not_throw() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act / Assert - var item = new KeyValuePair(3, "Three"); - dictionary.Should().NotContain(item); - } - - [Fact] - public void When_dictionary_contains_the_unexpected_value_at_specific_key_it_should_throw() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act - var item = new KeyValuePair(1, "One"); - Action act = () => dictionary.Should().NotContain(item, "we put it {0}", "there"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary not to contain value \"One\" at key 1 because we put it there, but found it anyhow."); - } - - [Fact] - public void When_dictionary_contains_the_key_value_pairs_it_should_throw() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act - var items = new List> - { - new(1, "One"), - new(2, "Two") - }; - - Action act = () => dictionary.Should().NotContain(items, "we did not put them {0}", "there"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary to not contain key/value pairs {[1, One], [2, Two]} because we did not put them there, but found them anyhow."); - } - - [Fact] - public void When_dictionary_contains_the_key_value_pair_it_should_throw() - { - // Arrange - var dictionary = new Dictionary - { - [1] = "One", - [2] = "Two" - }; - - // Act - Action act = () => dictionary.Should().NotContain(1, "One", "we did not put it {0}", "there"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary not to contain value \"One\" at key 1 because we did not put it there, but found it anyhow."); - } - - [Fact] - public void When_asserting_dictionary_does_not_contain_value_at_specific_key_against_null_dictionary_it_should_throw() - { - // Arrange - Dictionary dictionary = null; - - // Act - Action act = () => - { - using var _ = new AssertionScope(); - dictionary.Should().NotContain(1, "One", "because we want to test the behaviour with a null subject"); - }; - - // Assert - act.Should().Throw().WithMessage( - "Expected dictionary not to contain value \"One\" at key 1 because we want to test the behaviour with a null subject, but dictionary is ."); - } - } - public class OtherDictionaryAssertions { [Theory] @@ -2840,25 +130,34 @@ public void When_comparing_dictionary_like_collections_for_inequality_it_should_ act.Should().Throw(); } - public static IEnumerable SingleDictionaryData() => - Dictionaries().Select(x => new[] { x }); + public static TheoryData SingleDictionaryData() => + new(Dictionaries()); public static object[] Dictionaries() { - return new object[] - { + return + [ new Dictionary { [1] = 42 }, new TrueReadOnlyDictionary(new Dictionary { [1] = 42 }), new List> { new(1, 42) } - }; + ]; } - public static IEnumerable DictionariesData() + public static TheoryData DictionariesData() { - return + var pairs = from x in Dictionaries() from y in Dictionaries() - select new[] { x, y }; + select (x, y); + + var data = new TheoryData(); + + foreach (var (x, y) in pairs) + { + data.Add(x, y); + } + + return data; } } @@ -2913,181 +212,216 @@ public TrueReadOnlyDictionary(IReadOnlyDictionary dictionary) IEnumerator IEnumerable.GetEnumerator() => dictionary.GetEnumerator(); } -} - -internal class TrackingTestDictionary : IDictionary -{ - private readonly IDictionary entries; - public TrackingTestDictionary(params KeyValuePair[] entries) + internal class TrackingTestDictionary : IDictionary { - this.entries = entries.ToDictionary(e => e.Key, e => e.Value); - Enumerator = new TrackingDictionaryEnumerator(entries); - } + private readonly IDictionary entries; - public TrackingDictionaryEnumerator Enumerator { get; } + public TrackingTestDictionary(params KeyValuePair[] entries) + { + this.entries = entries.ToDictionary(e => e.Key, e => e.Value); + Enumerator = new TrackingDictionaryEnumerator(entries); + } - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + public TrackingDictionaryEnumerator Enumerator { get; } - public IEnumerator> GetEnumerator() - { - Enumerator.IncreaseEnumerationCount(); - Enumerator.Reset(); - return Enumerator; - } + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } - public void Add(KeyValuePair item) - { - entries.Add(item); - } + public IEnumerator> GetEnumerator() + { + Enumerator.IncreaseEnumerationCount(); + Enumerator.Reset(); + return Enumerator; + } - public void Clear() - { - entries.Clear(); - } + public void Add(KeyValuePair item) + { + entries.Add(item); + } - public bool Contains(KeyValuePair item) - { - return entries.Contains(item); - } + public void Clear() + { + entries.Clear(); + } - public void CopyTo(KeyValuePair[] array, int arrayIndex) - { - entries.CopyTo(array, arrayIndex); - } + public bool Contains(KeyValuePair item) + { + return entries.Contains(item); + } - public bool Remove(KeyValuePair item) - { - return entries.Remove(item); - } + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + entries.CopyTo(array, arrayIndex); + } - public int Count - { - get { return entries.Count; } - } + public bool Remove(KeyValuePair item) + { + return entries.Remove(item); + } - public bool IsReadOnly - { - get { return entries.IsReadOnly; } - } + public int Count + { + get { return entries.Count; } + } - public bool ContainsKey(int key) - { - return entries.ContainsKey(key); - } + public bool IsReadOnly + { + get { return entries.IsReadOnly; } + } - public void Add(int key, string value) - { - entries.Add(key, value); - } + public bool ContainsKey(int key) + { + return entries.ContainsKey(key); + } - public bool Remove(int key) - { - return entries.Remove(key); - } + public void Add(int key, string value) + { + entries.Add(key, value); + } - public bool TryGetValue(int key, out string value) - { - return entries.TryGetValue(key, out value); - } + public bool Remove(int key) + { + return entries.Remove(key); + } - public string this[int key] - { - get { return entries[key]; } - set { entries[key] = value; } - } + public bool TryGetValue(int key, out string value) + { + return entries.TryGetValue(key, out value); + } - public ICollection Keys - { - get { return entries.Keys; } + public string this[int key] + { + get { return entries[key]; } + set { entries[key] = value; } + } + + public ICollection Keys + { + get { return entries.Keys; } + } + + public ICollection Values + { + get { return entries.Values; } + } } - public ICollection Values + internal sealed class TrackingDictionaryEnumerator : IEnumerator> { - get { return entries.Values; } - } -} + private readonly KeyValuePair[] values; + private int index; -internal sealed class TrackingDictionaryEnumerator : IEnumerator> -{ - private readonly KeyValuePair[] values; - private int index; + public TrackingDictionaryEnumerator(KeyValuePair[] values) + { + index = -1; + this.values = values; + } - public TrackingDictionaryEnumerator(KeyValuePair[] values) - { - index = -1; - this.values = values; - } + public int LoopCount { get; private set; } - public int LoopCount { get; private set; } + public void IncreaseEnumerationCount() + { + LoopCount++; + } - public void IncreaseEnumerationCount() - { - LoopCount++; - } + public void Dispose() + { + } - public void Dispose() - { - } + public bool MoveNext() + { + index++; + return index < values.Length; + } - public bool MoveNext() - { - index++; - return index < values.Length; - } + public void Reset() + { + index = -1; + } - public void Reset() - { - index = -1; - } + public KeyValuePair Current + { + get { return values[index]; } + } - public KeyValuePair Current - { - get { return values[index]; } + object IEnumerator.Current + { + get { return Current; } + } } - object IEnumerator.Current + internal class DictionaryNotImplementingIReadOnlyDictionary : IDictionary { - get { return Current; } - } -} + private readonly Dictionary dictionary = []; -internal class DictionaryNotImplementingIReadOnlyDictionary : IDictionary -{ - private readonly Dictionary dictionary = new(); + public TValue this[TKey key] { get => dictionary[key]; set => throw new NotImplementedException(); } - public TValue this[TKey key] { get => dictionary[key]; set => throw new NotImplementedException(); } + public ICollection Keys => dictionary.Keys; - public ICollection Keys => dictionary.Keys; + public ICollection Values => dictionary.Values; - public ICollection Values => dictionary.Values; + public int Count => dictionary.Count; + + public bool IsReadOnly => ((ICollection>)dictionary).IsReadOnly; + + public void Add(TKey key, TValue value) => throw new NotImplementedException(); + + public void Add(KeyValuePair item) => throw new NotImplementedException(); + + public void Clear() => throw new NotImplementedException(); + + public bool Contains(KeyValuePair item) => dictionary.Contains(item); + + public bool ContainsKey(TKey key) => dictionary.ContainsKey(key); - public int Count => dictionary.Count; + public void CopyTo(KeyValuePair[] array, int arrayIndex) => throw new NotImplementedException(); - public bool IsReadOnly => ((ICollection>)dictionary).IsReadOnly; + public IEnumerator> GetEnumerator() => dictionary.GetEnumerator(); - public void Add(TKey key, TValue value) => throw new NotImplementedException(); + public bool Remove(TKey key) => throw new NotImplementedException(); - public void Add(KeyValuePair item) => throw new NotImplementedException(); + public bool Remove(KeyValuePair item) => throw new NotImplementedException(); - public void Clear() => throw new NotImplementedException(); + public bool TryGetValue(TKey key, out TValue value) => dictionary.TryGetValue(key, out value); - public bool Contains(KeyValuePair item) => dictionary.Contains(item); + IEnumerator IEnumerable.GetEnumerator() => dictionary.GetEnumerator(); + } - public bool ContainsKey(TKey key) => dictionary.ContainsKey(key); + public class MyClass + { + public int SomeProperty { get; set; } - public void CopyTo(KeyValuePair[] array, int arrayIndex) => throw new NotImplementedException(); + protected bool Equals(MyClass other) + { + return SomeProperty == other.SomeProperty; + } - public IEnumerator> GetEnumerator() => dictionary.GetEnumerator(); + public override bool Equals(object obj) + { + if (obj is null) + { + return false; + } - public bool Remove(TKey key) => throw new NotImplementedException(); + if (ReferenceEquals(this, obj)) + { + return true; + } - public bool Remove(KeyValuePair item) => throw new NotImplementedException(); + if (obj.GetType() != GetType()) + { + return false; + } - public bool TryGetValue(TKey key, out TValue value) => dictionary.TryGetValue(key, out value); + return Equals((MyClass)obj); + } - IEnumerator IEnumerable.GetEnumerator() => dictionary.GetEnumerator(); + public override int GetHashCode() + { + return SomeProperty; + } + } } diff --git a/Tests/FluentAssertions.Specs/Common/TypeExtensionsSpecs.cs b/Tests/FluentAssertions.Specs/Common/TypeExtensionsSpecs.cs index b5e6a0f7be..72c2e297b4 100644 --- a/Tests/FluentAssertions.Specs/Common/TypeExtensionsSpecs.cs +++ b/Tests/FluentAssertions.Specs/Common/TypeExtensionsSpecs.cs @@ -5,6 +5,7 @@ using System.Runtime.CompilerServices; using System.Text; using FluentAssertions.Common; +using JetBrains.Annotations; using Xunit; namespace FluentAssertions.Specs.Common; @@ -170,7 +171,7 @@ private static MethodInfo GetFakeConversionOperator(Type type, string name, Bind return methods.SingleOrDefault(m => m.Name == name && m.ReturnType == returnType - && m.GetParameters().Select(p => p.ParameterType).SequenceEqual(new[] { type }) + && m.GetParameters().Select(p => p.ParameterType).SequenceEqual([type]) ); } @@ -182,24 +183,30 @@ private readonly struct TypeWithFakeConversionOperators { private readonly int value; + [UsedImplicitly] private TypeWithFakeConversionOperators(int value) { this.value = value; } #pragma warning disable IDE1006, SA1300 // These two functions mimic the compiler generated conversion operators + [UsedImplicitly] public static int op_Implicit(TypeWithFakeConversionOperators typeWithFakeConversionOperators) => typeWithFakeConversionOperators.value; + [UsedImplicitly] public static byte op_Explicit(TypeWithFakeConversionOperators typeWithFakeConversionOperators) => (byte)typeWithFakeConversionOperators.value; #pragma warning restore SA1300, IDE1006 } + [UsedImplicitly] private record MyRecord(int Value); + [UsedImplicitly] private record struct MyRecordStruct(int Value); + [UsedImplicitly] private record struct MyRecordStructWithCustomPrintMembers(int Value) { // ReSharper disable once RedundantNameQualifier @@ -217,15 +224,18 @@ private record struct MyRecordStructWithOverriddenEquality(int Value) public override int GetHashCode() => Value; } + [UsedImplicitly] private readonly record struct MyReadonlyRecordStruct(int Value); private struct MyStruct { + [UsedImplicitly] public int Value { get; set; } } private struct MyStructWithFakeCompilerGeneratedEquality : IEquatable { + [UsedImplicitly] public int Value { get; set; } public bool Equals(MyStructWithFakeCompilerGeneratedEquality other) => Value == other.Value; @@ -248,6 +258,7 @@ private struct MyStructWithFakeCompilerGeneratedEquality : IEquatable { + [UsedImplicitly] public int Value { get; set; } public bool Equals(MyStructWithFakeCompilerGeneratedEqualityAndPrintMembers other) => Value == other.Value; @@ -264,6 +275,7 @@ public override bool Equals(object obj) => public static bool operator !=(MyStructWithFakeCompilerGeneratedEqualityAndPrintMembers left, MyStructWithFakeCompilerGeneratedEqualityAndPrintMembers right) => !left.Equals(right); + [UsedImplicitly] private bool PrintMembers(StringBuilder builder) { builder.Append(Value); @@ -273,6 +285,7 @@ private bool PrintMembers(StringBuilder builder) private struct MyStructWithOverriddenEquality : IEquatable { + [UsedImplicitly] public int Value { get; set; } public bool Equals(MyStructWithOverriddenEquality other) => Value == other.Value; @@ -290,6 +303,7 @@ private struct MyStructWithOverriddenEquality : IEquatable AssertionConfiguration.Current.Equivalency.Modify(configureOptions: null); + + // Assert + action.Should().ThrowExactly() + .WithParameterName("configureOptions"); + } + + [Fact] + public void When_concurrently_getting_equality_strategy_it_should_not_throw() + { + // Arrange / Act + var action = () => + { +#pragma warning disable CA1859 // https://github.com/dotnet/roslyn-analyzers/issues/6704 + IEquivalencyOptions equivalencyOptions = new EquivalencyOptions(); +#pragma warning restore CA1859 + + return () => Parallel.For(0, 10_000, new ParallelOptions { MaxDegreeOfParallelism = 8 }, + _ => equivalencyOptions.GetEqualityStrategy(typeof(IEnumerable)) + ); + }; + + // Assert + action.Should().NotThrow(); + } + + [Collection("ConfigurationSpecs")] + public sealed class Given_temporary_global_assertion_options : IDisposable + { + [Fact] + public void When_modifying_global_reference_type_settings_a_previous_assertion_should_not_have_any_effect_it_should_try_to_compare_the_classes_by_member_semantics_and_thus_throw() + { + // Arrange + // Trigger a first equivalency check using the default global settings + new MyValueType { Value = 1 }.Should().BeEquivalentTo(new MyValueType { Value = 2 }); + + AssertionConfiguration.Current.Equivalency.Modify(o => o.ComparingByMembers()); + + // Act + Action act = () => new MyValueType { Value = 1 }.Should().BeEquivalentTo(new MyValueType { Value = 2 }); + + // Assert + act.Should().Throw(); + } + + internal class MyValueType + { + [UsedImplicitly] + public int Value { get; set; } + + public override bool Equals(object obj) => true; + + public override int GetHashCode() => 0; + } + + [Fact] + public void When_modifying_global_value_type_settings_a_previous_assertion_should_not_have_any_effect_it_should_try_to_compare_the_classes_by_value_semantics_and_thus_throw() + { + // Arrange + // Trigger a first equivalency check using the default global settings + new MyClass { Value = 1 }.Should().BeEquivalentTo(new MyClass { Value = 1 }); + + AssertionConfiguration.Current.Equivalency.Modify(o => o.ComparingByValue()); + + // Act + Action act = () => new MyClass() { Value = 1 }.Should().BeEquivalentTo(new MyClass { Value = 1 }); + + // Assert + act.Should().Throw(); + } + + internal class MyClass + { + [UsedImplicitly] + public int Value { get; set; } + } + + [Fact] + public void When_modifying_record_settings_globally_it_should_use_the_global_settings_for_comparing_records() + { + // Arrange + AssertionConfiguration.Current.Equivalency.Modify(o => o.ComparingByValue(typeof(Position))); + + // Act / Assert + new Position(123).Should().BeEquivalentTo(new Position(123)); + } + + private record Position + { + [UsedImplicitly] + private readonly int value; + + public Position(int value) + { + this.value = value; + } + } + + [Fact] + public void When_assertion_doubles_should_always_allow_small_deviations_then_it_should_ignore_small_differences_without_the_need_of_local_options() + { + // Arrange + AssertionConfiguration.Current.Equivalency.Modify(options => options + .Using(ctx => ctx.Subject.Should().BeApproximately(ctx.Expectation, 0.01)) + .WhenTypeIs()); + + var actual = new + { + Value = 1D / 3D + }; + + var expected = new + { + Value = 0.33D + }; + + // Act + Action act = () => actual.Should().BeEquivalentTo(expected); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_local_similar_options_are_used_then_they_should_override_the_global_options() + { + // Arrange + AssertionConfiguration.Current.Equivalency.Modify(options => options + .Using(ctx => ctx.Subject.Should().BeApproximately(ctx.Expectation, 0.01)) + .WhenTypeIs()); + + var actual = new + { + Value = 1D / 3D + }; + + var expected = new + { + Value = 0.33D + }; + + // Act + Action act = () => actual.Should().BeEquivalentTo(expected, options => options + .Using(ctx => ctx.Subject.Should().Be(ctx.Expectation)) + .WhenTypeIs()); + + // Assert + act.Should().Throw().WithMessage("Expected*"); + } + + [Fact] + public void When_local_similar_options_are_used_then_they_should_not_affect_any_other_assertions() + { + // Arrange + AssertionConfiguration.Current.Equivalency.Modify(options => options + .Using(ctx => ctx.Subject.Should().BeApproximately(ctx.Expectation, 0.01)) + .WhenTypeIs()); + + var actual = new + { + Value = 1D / 3D + }; + + var expected = new + { + Value = 0.33D + }; + + // Act + Action act = () => actual.Should().BeEquivalentTo(expected); + + // Assert + act.Should().NotThrow(); + } + + public void Dispose() => + AssertionConfiguration.Current.Equivalency.Modify(_ => new EquivalencyOptions()); + } + + [Collection("ConfigurationSpecs")] + public sealed class Given_self_resetting_equivalency_plan : IDisposable + { + private static EquivalencyPlan Plan => AssertionConfiguration.Current.Equivalency.Plan; + + [Fact] + public void When_inserting_a_step_then_it_should_precede_all_other_steps() + { + // Arrange / Act + Plan.Insert(); + + // Assert + var addedStep = Plan.LastOrDefault(s => s is MyEquivalencyStep); + + Plan.Should().StartWith(addedStep); + } + + [Fact] + public void When_inserting_a_step_before_another_then_it_should_precede_that_particular_step() + { + // Arrange / Act + Plan.InsertBefore(); + + // Assert + var addedStep = Plan.LastOrDefault(s => s is MyEquivalencyStep); + var successor = Plan.LastOrDefault(s => s is DictionaryEquivalencyStep); + + Plan.Should().HaveElementPreceding(successor, addedStep); + } + + [Fact] + public void When_appending_a_step_then_it_should_precede_the_final_builtin_step() + { + // Arrange / Act + Plan.Add(); + + // Assert + var equivalencyStep = Plan.LastOrDefault(s => s is SimpleEqualityEquivalencyStep); + var subjectStep = Plan.LastOrDefault(s => s is MyEquivalencyStep); + + Plan.Should().HaveElementPreceding(equivalencyStep, subjectStep); + } + + [Fact] + public void When_appending_a_step_after_another_then_it_should_precede_the_final_builtin_step() + { + // Arrange / Act + Plan.AddAfter(); + + // Assert + var addedStep = Plan.LastOrDefault(s => s is MyEquivalencyStep); + var predecessor = Plan.LastOrDefault(s => s is DictionaryEquivalencyStep); + + Plan.Should().HaveElementSucceeding(predecessor, addedStep); + } + + [Fact] + public void When_appending_a_step_and_no_builtin_steps_are_there_then_it_should_precede_the_simple_equality_step() + { + // Arrange / Act + Plan.Clear(); + Plan.Add(); + + // Assert + var subjectStep = Plan.LastOrDefault(s => s is MyEquivalencyStep); + Plan.Should().EndWith(subjectStep); + } + + [Fact] + public void When_removing_a_specific_step_then_it_should_precede_the_simple_equality_step() + { + // Arrange / Act + Plan.Remove(); + + // Assert + Plan.Should().NotContain(s => s is SimpleEqualityEquivalencyStep); + } + + [Fact] + public void When_removing_a_specific_step_that_doesnt_exist_Then_it_should_precede_the_simple_equality_step() + { + // Arrange / Act + var action = () => Plan.Remove(); + + // Assert + action.Should().NotThrow(); + } + + private class MyEquivalencyStep : IEquivalencyStep + { + public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, + IValidateChildNodeEquivalency valueChildNodes) + { + AssertionChain.GetOrCreate().For(context).FailWith(GetType().FullName); + + return EquivalencyResult.EquivalencyProven; + } + } + + public void Dispose() => Plan.Reset(); + } +} diff --git a/Tests/FluentAssertions.Specs/Configuration/FormattingOptionsSpecs.cs b/Tests/FluentAssertions.Specs/Configuration/FormattingOptionsSpecs.cs new file mode 100644 index 0000000000..f4d3d11e66 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Configuration/FormattingOptionsSpecs.cs @@ -0,0 +1,218 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using FluentAssertions.Common; +using FluentAssertions.Execution; +using FluentAssertions.Formatting; +using JetBrains.Annotations; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Configuration; + +[Collection("ConfigurationSpecs")] +public sealed class FormattingOptionsSpecs : IDisposable +{ + [Fact] + public void When_global_formatting_settings_are_modified() + { + AssertionConfiguration.Current.Formatting.UseLineBreaks = true; + AssertionConfiguration.Current.Formatting.MaxDepth = 123; + AssertionConfiguration.Current.Formatting.MaxLines = 33; + + AssertionScope.Current.FormattingOptions.UseLineBreaks.Should().BeTrue(); + AssertionScope.Current.FormattingOptions.MaxDepth.Should().Be(123); + AssertionScope.Current.FormattingOptions.MaxLines.Should().Be(33); + } + + [Fact] + public void When_a_custom_formatter_exists_in_any_loaded_assembly_it_should_override_the_default_formatters() + { + // Arrange + AssertionConfiguration.Current.Formatting.ValueFormatterDetectionMode = ValueFormatterDetectionMode.Scan; + + var subject = new SomeClassWithCustomFormatter + { + Property = "SomeValue" + }; + + // Act + string result = Formatter.ToString(subject); + + // Assert + result.Should().Be("Property = SomeValue", "it should use my custom formatter"); + } + + [Fact] + public void When_a_base_class_has_a_custom_formatter_it_should_override_the_default_formatters() + { + // Arrange + AssertionConfiguration.Current.Formatting.ValueFormatterDetectionMode = ValueFormatterDetectionMode.Scan; + + var subject = new SomeClassInheritedFromClassWithCustomFormatterLvl1 + { + Property = "SomeValue" + }; + + // Act + string result = Formatter.ToString(subject); + + // Assert + result.Should().Be("Property = SomeValue", "it should use my custom formatter"); + } + + [Fact] + public void When_there_are_multiple_custom_formatters_it_should_select_a_more_specific_one() + { + // Arrange + AssertionConfiguration.Current.Formatting.ValueFormatterDetectionMode = ValueFormatterDetectionMode.Scan; + + var subject = new SomeClassInheritedFromClassWithCustomFormatterLvl2 + { + Property = "SomeValue" + }; + + // Act + string result = Formatter.ToString(subject); + + // Assert + result.Should().Be("Property is SomeValue", "it should use my custom formatter"); + } + + [Fact] + public void When_a_base_class_has_multiple_custom_formatters_it_should_work_the_same_as_for_the_base_class() + { + // Arrange + AssertionConfiguration.Current.Formatting.ValueFormatterDetectionMode = ValueFormatterDetectionMode.Scan; + + var subject = new SomeClassInheritedFromClassWithCustomFormatterLvl3 + { + Property = "SomeValue" + }; + + // Act + string result = Formatter.ToString(subject); + + // Assert + result.Should().Be("Property is SomeValue", "it should use my custom formatter"); + } + + [Fact] + public void When_no_custom_formatter_exists_in_the_specified_assembly_it_should_use_the_default() + { + // Arrange + AssertionConfiguration.Current.Formatting.ValueFormatterAssembly = "FluentAssertions"; + + var subject = new SomeClassWithCustomFormatter + { + Property = "SomeValue" + }; + + // Act + string result = Formatter.ToString(subject); + + // Assert + result.Should().Be(subject.ToString()); + } + + [Fact] + public void When_formatter_scanning_is_disabled_it_should_use_the_default_formatters() + { + // Arrange + AssertionConfiguration.Current.Formatting.ValueFormatterDetectionMode = ValueFormatterDetectionMode.Disabled; + + var subject = new SomeClassWithCustomFormatter + { + Property = "SomeValue" + }; + + // Act + string result = Formatter.ToString(subject); + + // Assert + result.Should().Be(subject.ToString()); + } + + [Fact] + public void When_no_formatter_scanning_is_configured_it_should_use_the_default_formatters() + { + // Arrange + AssertionConfiguration.Current.Formatting.ValueFormatterDetectionMode = ValueFormatterDetectionMode.Disabled; + + var subject = new SomeClassWithCustomFormatter + { + Property = "SomeValue" + }; + + // Act + string result = Formatter.ToString(subject); + + // Assert + result.Should().Be(subject.ToString()); + } + + public class SomeClassWithCustomFormatter + { + public string Property { get; set; } + + public override string ToString() + { + return "The value of my property is " + Property; + } + } + + public class SomeOtherClassWithCustomFormatter + { + [UsedImplicitly] + public string Property { get; set; } + + public override string ToString() + { + return "The value of my property is " + Property; + } + } + + public class SomeClassInheritedFromClassWithCustomFormatterLvl1 : SomeClassWithCustomFormatter; + + public class SomeClassInheritedFromClassWithCustomFormatterLvl2 : SomeClassInheritedFromClassWithCustomFormatterLvl1; + + public class SomeClassInheritedFromClassWithCustomFormatterLvl3 : SomeClassInheritedFromClassWithCustomFormatterLvl2; + + public static class CustomFormatter + { + [ValueFormatter] + public static int Bar(SomeClassWithCustomFormatter _) + { + return -1; + } + + [ValueFormatter] + public static void Foo(SomeClassWithCustomFormatter value, FormattedObjectGraph output) + { + output.AddFragment("Property = " + value.Property); + } + + [ValueFormatter] + [SuppressMessage("ReSharper", "CA1801")] + public static void Foo(SomeOtherClassWithCustomFormatter _, FormattedObjectGraph output) + { + throw new XunitException("Should never be called"); + } + + [ValueFormatter] + public static void Foo(SomeClassInheritedFromClassWithCustomFormatterLvl2 value, FormattedObjectGraph output) + { + output.AddFragment("Property is " + value.Property); + } + + [ValueFormatter] + public static void Foo2(SomeClassInheritedFromClassWithCustomFormatterLvl2 value, FormattedObjectGraph output) + { + output.AddFragment("Property is " + value.Property); + } + } + + public void Dispose() + { + AssertionEngine.ResetToDefaults(); + } +} diff --git a/Tests/FluentAssertions.Specs/Configuration/GlobalConfigurationSpecs.cs b/Tests/FluentAssertions.Specs/Configuration/GlobalConfigurationSpecs.cs new file mode 100644 index 0000000000..8f0782cdcf --- /dev/null +++ b/Tests/FluentAssertions.Specs/Configuration/GlobalConfigurationSpecs.cs @@ -0,0 +1,68 @@ +using System; +using System.Threading.Tasks; +using FluentAssertions.Configuration; +using FluentAssertions.Execution; +using Xunit; + +namespace FluentAssertions.Specs.Configuration; + +[Collection("ConfigurationSpecs")] +public sealed class GlobalConfigurationSpecs : IDisposable +{ + [Fact] + public void Concurrently_accessing_the_configuration_is_safe() + { + // Act + Action act = () => Parallel.For( + 0, + 10000, + new ParallelOptions + { + MaxDegreeOfParallelism = 8 + }, + __ => + { + AssertionConfiguration.Current.Formatting.ValueFormatterAssembly = string.Empty; + _ = AssertionConfiguration.Current.Formatting.ValueFormatterDetectionMode; + } + ); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void Can_override_the_runtime_test_framework_implementation() + { + // Arrange + AssertionEngine.TestFramework = new NotImplementedTestFramework(); + + // Act + var act = () => 1.Should().Be(2); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Can_override_the_runtime_test_framework() + { + // Arrange + AssertionEngine.Configuration.TestFramework = TestFramework.NUnit; + + // Act + var act = () => 1.Should().Be(2); + + // Assert + act.Should().Throw().WithMessage("*nunit.framework*"); + } + + private class NotImplementedTestFramework : ITestFramework + { + public bool IsAvailable => true; + + public void Throw(string message) => throw new NotImplementedException(); + } + + public void Dispose() => AssertionEngine.ResetToDefaults(); +} diff --git a/Tests/FluentAssertions.Specs/Configuration/TestFrameworkFactorySpecs.cs b/Tests/FluentAssertions.Specs/Configuration/TestFrameworkFactorySpecs.cs new file mode 100644 index 0000000000..ed4c33af86 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Configuration/TestFrameworkFactorySpecs.cs @@ -0,0 +1,57 @@ +using System; +using FluentAssertions.Execution; +using Xunit; +using Xunit.Sdk; +using TestFramework = FluentAssertions.Configuration.TestFramework; + +namespace FluentAssertions.Specs.Configuration; + +public class TestFrameworkFactorySpecs +{ + [Fact] + public void When_running_xunit_test_implicitly_it_should_be_detected() + { + // Arrange + var testFramework = TestFrameworkFactory.GetFramework(null); + + // Act + Action act = () => testFramework.Throw("MyMessage"); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_running_xunit_test_explicitly_it_should_be_detected() + { + // Arrange + var testFramework = TestFrameworkFactory.GetFramework(TestFramework.XUnit2); + + // Act + Action act = () => testFramework.Throw("MyMessage"); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_running_test_with_unknown_test_framework_it_should_throw() + { + // Act + Action act = () => TestFrameworkFactory.GetFramework((TestFramework)42); + + // Assert + act.Should().Throw() + .WithMessage("*the test framework '42' but this is not supported*"); + } + + [Fact] + public void When_running_test_with_late_bound_but_unavailable_test_framework_it_should_throw() + { + // Act + Action act = () => TestFrameworkFactory.GetFramework(TestFramework.NUnit); + + act.Should().Throw() + .WithMessage("*test framework 'nunit' but the required assembly 'nunit.framework' could not be found*"); + } +} diff --git a/Tests/FluentAssertions.Specs/ConfigurationSpecs.cs b/Tests/FluentAssertions.Specs/ConfigurationSpecs.cs deleted file mode 100644 index 36cbc680ef..0000000000 --- a/Tests/FluentAssertions.Specs/ConfigurationSpecs.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Threading.Tasks; -using FluentAssertions.Common; -using Xunit; - -namespace FluentAssertions.Specs; - -[Collection("ConfigurationSpecs")] -public class ConfigurationSpecs -{ - [Fact] - public void When_concurrently_accessing_current_Configuration_no_exception_should_be_thrown() - { - // Act - Action act = () => Parallel.For( - 0, - 10000, - new ParallelOptions - { - MaxDegreeOfParallelism = 8 - }, - __ => - { - Configuration.Current.ValueFormatterAssembly = string.Empty; - _ = Configuration.Current.ValueFormatterDetectionMode; - } - ); - - // Assert - act.Should().NotThrow(); - } -} - -// Due to tests that call Configuration.Current -[CollectionDefinition("ConfigurationSpecs", DisableParallelization = true)] -public class ConfigurationSpecsDefinition; diff --git a/Tests/FluentAssertions.Specs/CultureAwareTesting/CulturedFactAttribute.cs b/Tests/FluentAssertions.Specs/CultureAwareTesting/CulturedFactAttribute.cs index c9a63b63db..6a4f16789e 100644 --- a/Tests/FluentAssertions.Specs/CultureAwareTesting/CulturedFactAttribute.cs +++ b/Tests/FluentAssertions.Specs/CultureAwareTesting/CulturedFactAttribute.cs @@ -7,6 +7,7 @@ namespace FluentAssertions.Specs.CultureAwareTesting; public sealed class CulturedFactAttribute : FactAttribute { #pragma warning disable CA1019 // Define accessors for attribute arguments + // ReSharper disable once UnusedParameter.Local public CulturedFactAttribute(params string[] _) { } #pragma warning restore CA1019 // Define accessors for attribute arguments } diff --git a/Tests/FluentAssertions.Specs/CultureAwareTesting/CulturedFactAttributeDiscoverer.cs b/Tests/FluentAssertions.Specs/CultureAwareTesting/CulturedFactAttributeDiscoverer.cs index 49182923dd..aad91bb599 100644 --- a/Tests/FluentAssertions.Specs/CultureAwareTesting/CulturedFactAttributeDiscoverer.cs +++ b/Tests/FluentAssertions.Specs/CultureAwareTesting/CulturedFactAttributeDiscoverer.cs @@ -18,11 +18,11 @@ public IEnumerable Discover(ITestFrameworkDiscoveryOptions disco IAttributeInfo factAttribute) { var ctorArgs = factAttribute.GetConstructorArguments().ToArray(); - var cultures = Reflector.ConvertArguments(ctorArgs, new[] { typeof(string[]) }).Cast().Single(); + var cultures = Reflector.ConvertArguments(ctorArgs, [typeof(string[])]).Cast().Single(); if (cultures is null || cultures.Length == 0) { - cultures = new[] { "en-US", "fr-FR" }; + cultures = ["en-US", "fr-FR"]; } TestMethodDisplay methodDisplay = discoveryOptions.MethodDisplayOrDefault(); diff --git a/Tests/FluentAssertions.Specs/CultureAwareTesting/CulturedTheoryAttribute.cs b/Tests/FluentAssertions.Specs/CultureAwareTesting/CulturedTheoryAttribute.cs index 5620288f72..ea5f0c1ea5 100644 --- a/Tests/FluentAssertions.Specs/CultureAwareTesting/CulturedTheoryAttribute.cs +++ b/Tests/FluentAssertions.Specs/CultureAwareTesting/CulturedTheoryAttribute.cs @@ -8,6 +8,7 @@ namespace FluentAssertions.Specs.CultureAwareTesting; public sealed class CulturedTheoryAttribute : TheoryAttribute { #pragma warning disable CA1019 // Define accessors for attribute arguments + // ReSharper disable once UnusedParameter.Local public CulturedTheoryAttribute(params string[] _) { } #pragma warning restore CA1019 // Define accessors for attribute arguments } diff --git a/Tests/FluentAssertions.Specs/CultureAwareTesting/CulturedTheoryAttributeDiscoverer.cs b/Tests/FluentAssertions.Specs/CultureAwareTesting/CulturedTheoryAttributeDiscoverer.cs index 4c7ce5153f..5df9b30c7a 100644 --- a/Tests/FluentAssertions.Specs/CultureAwareTesting/CulturedTheoryAttributeDiscoverer.cs +++ b/Tests/FluentAssertions.Specs/CultureAwareTesting/CulturedTheoryAttributeDiscoverer.cs @@ -35,11 +35,11 @@ protected override IEnumerable CreateTestCasesForTheory(ITestFra private static string[] GetCultures(IAttributeInfo culturedTheoryAttribute) { var ctorArgs = culturedTheoryAttribute.GetConstructorArguments().ToArray(); - var cultures = Reflector.ConvertArguments(ctorArgs, new[] { typeof(string[]) }).Cast().Single(); + var cultures = Reflector.ConvertArguments(ctorArgs, [typeof(string[])]).Cast().Single(); if (cultures is null || cultures.Length == 0) { - cultures = new[] { "en-US", "fr-FR" }; + cultures = ["en-US", "fr-FR"]; } return cultures; diff --git a/Tests/FluentAssertions.Specs/Events/EventAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Events/EventAssertionSpecs.cs index 1953f25308..8c6530f81c 100644 --- a/Tests/FluentAssertions.Specs/Events/EventAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Events/EventAssertionSpecs.cs @@ -1,16 +1,17 @@ -using System; +#if NET47 +using System.Reflection.Emit; +#endif + +using System; using System.ComponentModel; using System.Linq; +using System.Reflection; using FluentAssertions.Events; using FluentAssertions.Execution; using FluentAssertions.Extensions; using FluentAssertions.Formatting; using Xunit; using Xunit.Sdk; -#if NETFRAMEWORK -using System.Reflection; -using System.Reflection.Emit; -#endif namespace FluentAssertions.Specs.Events; @@ -408,17 +409,18 @@ public void When_constraints_are_specified_it_should_filter_the_events_based_on_ recording .Should().ContainSingle("because we were expecting a specific property change") - .Which.Parameters.Last().Should().BeOfType() + .Which.Parameters[^1].Should().BeOfType() .Which.PropertyName.Should().Be("Boo"); } [Fact] - public void When_events_are_raised_regardless_of_time_tick_it_should_return_by_invokation_order() + public void When_events_are_raised_regardless_of_time_tick_it_should_return_by_invocation_order() { // Arrange var observable = new TestEventRaisingInOrder(); - var utcNow = 11.January(2022).At(12, 00).AsUtc(); - using var monitor = observable.Monitor(() => utcNow); + + using var monitor = observable.Monitor(conf => + conf.ConfigureTimestampProvider(() => 11.January(2022).At(12, 00).AsUtc())); // Act observable.RaiseAllEvents(); @@ -538,6 +540,53 @@ public void act.Should().Throw() .WithMessage("Expected*property*SomeProperty*but*OtherProperty1*OtherProperty2*"); } + + [Fact] + public void + The_number_of_property_changed_recorded_for_a_specific_property_matches_the_number_of_times_it_was_raised_specifically() + { + // Arrange + var subject = new EventRaisingClass(); + using var monitor = subject.Monitor(); + subject.RaiseEventWithSenderAndPropertyName(nameof(EventRaisingClass.SomeProperty)); + subject.RaiseEventWithSenderAndPropertyName(nameof(EventRaisingClass.SomeProperty)); + subject.RaiseEventWithSenderAndPropertyName(nameof(EventRaisingClass.SomeOtherProperty)); + + // Act + monitor.Should().RaisePropertyChangeFor(x => x.SomeProperty).Should().HaveCount(2); + } + + [Fact] + public void + The_number_of_property_changed_recorded_for_a_specific_property_matches_the_number_of_times_it_was_raised_including_agnostic_property() + { + // Arrange + var subject = new EventRaisingClass(); + using var monitor = subject.Monitor(); + subject.RaiseEventWithSenderAndPropertyName(nameof(EventRaisingClass.SomeProperty)); + subject.RaiseEventWithSenderAndPropertyName(nameof(EventRaisingClass.SomeOtherProperty)); + subject.RaiseEventWithSenderAndPropertyName(null); + subject.RaiseEventWithSenderAndPropertyName(string.Empty); + + // Act + monitor.Should().RaisePropertyChangeFor(x => x.SomeProperty).Should().HaveCount(3); + } + + [Fact] + public void + The_number_of_property_changed_recorded_matches_the_number_of_times_it_was_raised() + { + // Arrange + var subject = new EventRaisingClass(); + using var monitor = subject.Monitor(); + subject.RaiseEventWithSenderAndPropertyName(nameof(EventRaisingClass.SomeProperty)); + subject.RaiseEventWithSenderAndPropertyName(nameof(EventRaisingClass.SomeOtherProperty)); + subject.RaiseEventWithSenderAndPropertyName(null); + subject.RaiseEventWithSenderAndPropertyName(string.Empty); + + // Act + monitor.Should().RaisePropertyChangeFor(null).Should().HaveCount(4); + } } public class ShouldNotRaisePropertyChanged @@ -588,6 +637,40 @@ public void When_a_property_changed_event_for_another_than_the_unexpected_proper // Assert act.Should().NotThrow(); } + + [Fact] + public void Throw_for_an_agnostic_property_when_any_property_changed_is_recorded() + { + // Arrange + var subject = new EventRaisingClass(); + using var monitor = subject.Monitor(); + subject.RaiseEventWithSenderAndPropertyName(nameof(EventRaisingClass.SomeOtherProperty)); + + // Act + Action act = () => monitor.Should().NotRaisePropertyChangeFor(null); + + // Assert + act.Should().Throw().WithMessage( + "Did not expect object " + Formatter.ToString(subject) + + " to raise the \"PropertyChanged\" event, but it did."); + } + + [Fact] + public void Throw_for_a_specific_property_when_an_agnostic_property_changed_is_recorded() + { + // Arrange + var subject = new EventRaisingClass(); + using var monitor = subject.Monitor(); + subject.RaiseEventWithSenderAndPropertyName(null); + + // Act + Action act = () => monitor.Should().NotRaisePropertyChangeFor(x => x.SomeProperty); + + // Assert + act.Should().Throw().WithMessage( + "Did not expect object " + Formatter.ToString(subject) + + " to raise the \"PropertyChanged\" event for property \"SomeProperty\", but it did."); + } } public class PreconditionChecks @@ -664,8 +747,8 @@ public void When_monitoring_an_object_it_should_monitor_all_the_events_it_expose EventMetadata[] metadata = eventMonitor.MonitoredEvents; // Assert - metadata.Should().BeEquivalentTo(new[] - { + metadata.Should().BeEquivalentTo( + [ new { EventName = nameof(ClassThatRaisesEventsItself.InterfaceEvent), @@ -676,7 +759,7 @@ public void When_monitoring_an_object_it_should_monitor_all_the_events_it_expose EventName = nameof(ClassThatRaisesEventsItself.PropertyChanged), HandlerType = typeof(PropertyChangedEventHandler) } - }); + ]); } [Fact] @@ -690,14 +773,14 @@ public void When_monitoring_an_object_through_an_interface_it_should_monitor_onl EventMetadata[] metadata = monitor.MonitoredEvents; // Assert - metadata.Should().BeEquivalentTo(new[] - { + metadata.Should().BeEquivalentTo( + [ new { EventName = nameof(IEventRaisingInterface.InterfaceEvent), HandlerType = typeof(EventHandler) } - }); + ]); } #if NETFRAMEWORK // DefineDynamicAssembly is obsolete in .NET Core @@ -743,7 +826,7 @@ private object CreateProxyObject() string typeName = baseType.Name + "_GeneratedForTest"; TypeBuilder typeBuilder = - moduleBuilder.DefineType(typeName, TypeAttributes.Public, baseType, new[] { interfaceType }); + moduleBuilder.DefineType(typeName, TypeAttributes.Public, baseType, [interfaceType]); MethodBuilder addHandler = EmitAddRemoveEventHandler("add"); typeBuilder.DefineMethodOverride(addHandler, interfaceType.GetMethod("add_InterfaceEvent")); @@ -793,7 +876,7 @@ public void When_an_object_raises_two_events_it_should_provide_the_data_about_th DateTime utcNow = 17.September(2017).At(21, 00).AsUtc(); var eventSource = new EventRaisingClass(); - using var monitor = eventSource.Monitor(() => utcNow); + using var monitor = eventSource.Monitor(opt => opt.ConfigureTimestampProvider(() => utcNow)); // Act eventSource.RaiseEventWithSenderAndPropertyName("theProperty"); @@ -803,8 +886,8 @@ public void When_an_object_raises_two_events_it_should_provide_the_data_about_th eventSource.RaiseNonConventionalEvent("first", 123, "third"); // Assert - monitor.OccurredEvents.Should().BeEquivalentTo(new[] - { + monitor.OccurredEvents.Should().BeEquivalentTo( + [ new { EventName = "PropertyChanged", @@ -817,7 +900,7 @@ public void When_an_object_raises_two_events_it_should_provide_the_data_about_th TimestampUtc = utcNow, Parameters = new object[] { "first", 123, "third" } } - }, o => o.WithStrictOrdering()); + ], o => o.WithStrictOrdering()); } [Fact] @@ -971,6 +1054,150 @@ public void One_matching_argument_type_with_two_or_more_parameters_matching_a_mi } } + public class MonitorDefaultBehavior + { + [Fact] + public void Broken_event_add_accessors_fails() + { + // Arrange + var sut = new TestEventBrokenEventHandlerRaising(); + + // Act / Assert + sut.Invoking(c => + { + using var monitor = c.Monitor(); + }).Should().Throw(); + } + + [Fact] + public void Broken_event_remove_accessors_fails() + { + // Arrange + var sut = new TestEventBrokenEventHandlerRaising(); + + // Act / Assert + sut.Invoking(c => + { + using var monitor = c.Monitor(); + }).Should().Throw(); + } + } + + public class IgnoreMisbehavingEventAccessors + { + [Fact] + public void Monitoring_class_with_broken_event_add_accessor_succeeds() + { + // Arrange + var classToMonitor = new TestEventBrokenEventHandlerRaising(); + + // Act / Assert + classToMonitor.Invoking(c => + { + using var monitor = c.Monitor(opt => opt.IgnoringEventAccessorExceptions()); + }).Should().NotThrow(); + } + + [Fact] + public void Class_with_broken_event_remove_accessor_succeeds() + { + // Arrange + var classToMonitor = new TestEventBrokenEventHandlerRaising(); + + // Act / Assert + classToMonitor.Invoking(c => + { + using var monitor = c.Monitor(opt => opt.IgnoringEventAccessorExceptions()); + }).Should().NotThrow(); + } + + [Fact] + public void Recording_event_with_broken_add_accessor_succeeds() + { + // Arrange + var classToMonitor = new TestEventBrokenEventHandlerRaising(); + + using var monitor = + classToMonitor.Monitor(opt => + opt.IgnoringEventAccessorExceptions().RecordingEventsWithBrokenAccessor()); + + //Act + classToMonitor.RaiseOkEvent(); + + //Assert + monitor.MonitoredEvents.Should().HaveCount(1); + } + + [Fact] + public void Ignoring_broken_event_accessor_should_also_not_record_events() + { + // Arrange + var classToMonitor = new TestEventBrokenEventHandlerRaising(); + + using var monitor = classToMonitor.Monitor(opt => opt.IgnoringEventAccessorExceptions()); + + //Act + classToMonitor.RaiseOkEvent(); + + //Assert + monitor.MonitoredEvents.Should().BeEmpty(); + } + } + + private interface IAddOkEvent + { + event EventHandler OkEvent; + } + + private interface IAddFailingRecordableEvent + { + event EventHandler AddFailingRecorableEvent; + } + + private interface IAddFailingEvent + { + event EventHandler AddFailingEvent; + } + + private interface IRemoveFailingEvent + { + event EventHandler RemoveFailingEvent; + } + + private class TestEventBrokenEventHandlerRaising + : IAddFailingEvent, IRemoveFailingEvent, IAddOkEvent, IAddFailingRecordableEvent + { + public event EventHandler AddFailingEvent + { + add => throw new InvalidOperationException("Add is failing"); + remove => OkEvent -= value; + } + + public event EventHandler AddFailingRecorableEvent + { + add + { + OkEvent += value; + throw new InvalidOperationException("Add is failing"); + } + + remove => OkEvent -= value; + } + + public event EventHandler OkEvent; + + public event EventHandler RemoveFailingEvent + { + add => OkEvent += value; + remove => throw new InvalidOperationException("Remove is failing"); + } + + public void RaiseOkEvent() + { + OkEvent?.Invoke(this, EventArgs.Empty); + } + } + public class A { #pragma warning disable MA0046 diff --git a/Tests/FluentAssertions.Specs/Exceptions/AsyncFunctionExceptionAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Exceptions/AsyncFunctionExceptionAssertionSpecs.cs index d84e353a03..6617d0b65b 100644 --- a/Tests/FluentAssertions.Specs/Exceptions/AsyncFunctionExceptionAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Exceptions/AsyncFunctionExceptionAssertionSpecs.cs @@ -1,12 +1,14 @@ -using System; +// ReSharper disable AsyncVoidLambda + +using System; using System.Threading.Tasks; using FluentAssertions.Execution; using FluentAssertions.Extensions; -using Xunit; -using Xunit.Sdk; -#if NETFRAMEWORK +#if NET47 using FluentAssertions.Specs.Common; #endif +using Xunit; +using Xunit.Sdk; namespace FluentAssertions.Specs.Exceptions; @@ -213,18 +215,18 @@ public async Task When_the_expected_exception_is_not_wrapped_on_UI_thread_async_ public static TheoryData, Exception> AggregateExceptionTestData() { - var tasks = new[] - { + Func[] tasks = + [ AggregateExceptionWithLeftNestedException, AggregateExceptionWithRightNestedException - }; + ]; - var types = new Exception[] - { + Exception[] types = + [ new AggregateException(), new ArgumentNullException(), new InvalidOperationException() - }; + ]; var data = new TheoryData, Exception>(); @@ -934,7 +936,21 @@ public async Task When_async_method_throws_aggregate_exception_containing_expect } [Fact] - public async Task When_async_method_throws_the_expected_exception_it_should_succeed() + public async Task Succeeds_for_any_exception_thrown_by_async_method() + { + // Arrange + Func task = () => Throw.Async(); + + // Act + Func action = () => task + .Should().ThrowAsync(); + + // Assert + await action.Should().NotThrowAsync(); + } + + [Fact] + public async Task Succeeds_for_expected_exception_thrown_by_async_method() { // Arrange Func task = () => Throw.Async(); @@ -947,6 +963,20 @@ public async Task When_async_method_throws_the_expected_exception_it_should_succ await action.Should().NotThrowAsync(); } + [Fact] + public async Task Succeeds_for_any_exception_thrown_within_timespan_by_async_method() + { + // Arrange + Func task = () => Throw.Async(); + + // Act + Func action = () => task.Should().ThrowWithinAsync( + 100.Milliseconds()); + + // Assert + await action.Should().NotThrowAsync(); + } + [Fact] public async Task When_async_method_does_not_throw_the_expected_inner_exception_it_should_fail() { @@ -1023,7 +1053,6 @@ public async Task When_async_method_does_not_throw_the_expected_exception_it_sho } #pragma warning disable MA0147 - [Fact] public void When_asserting_async_void_method_should_throw_it_should_fail() { @@ -1079,7 +1108,6 @@ public void When_asserting_async_void_method_should_not_throw_specific_exception // Assert action.Should().Throw("*async*void*"); } - #pragma warning restore MA0147 [Fact] diff --git a/Tests/FluentAssertions.Specs/Exceptions/ExceptionAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Exceptions/ExceptionAssertionSpecs.cs index d55e32acc4..d117ad5c45 100644 --- a/Tests/FluentAssertions.Specs/Exceptions/ExceptionAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Exceptions/ExceptionAssertionSpecs.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using Xunit; using Xunit.Sdk; @@ -45,18 +44,18 @@ public void When_the_expected_exception_is_not_wrapped_it_should_fail(Action public static TheoryData AggregateExceptionTestData() { - var tasks = new[] - { + Action[] tasks = + [ AggregateExceptionWithLeftNestedException, AggregateExceptionWithRightNestedException - }; + ]; - var types = new Exception[] - { + Exception[] types = + [ new AggregateException(), new ArgumentNullException(), new InvalidOperationException() - }; + ]; var data = new TheoryData(); @@ -150,82 +149,3 @@ public void ThrowExactly_when_subject_throws_expected_exception_it_should_not_do act.Should().ThrowExactly(); } } - -public class SomeTestClass -{ - internal const string ExceptionMessage = "someMessage"; - - public IList Strings = new List(); - - public void Throw() - { - throw new ArgumentException(ExceptionMessage); - } -} - -public abstract class Does -{ - public abstract void Do(); - - public abstract void Do(string someParam); - - public abstract int Return(); - - public static Does Throw(TException exception) - where TException : Exception - { - return new DoesThrow(exception); - } - - public static Does Throw() - where TException : Exception, new() - { - return Throw(new TException()); - } - - public static Does NotThrow() => new DoesNotThrow(); - - private class DoesThrow : Does - where TException : Exception - { - private readonly TException exception; - - public DoesThrow(TException exception) - { - this.exception = exception; - } - - public override void Do() => throw exception; - - public override void Do(string someParam) => throw exception; - - public override int Return() => throw exception; - } - - private class DoesNotThrow : Does - { - public override void Do() { } - - public override void Do(string someParam) { } - - public override int Return() => 42; - } -} - -internal class ExceptionWithProperties : Exception -{ - public ExceptionWithProperties(string propertyValue) - { - Property = propertyValue; - } - - public string Property { get; set; } -} - -internal class ExceptionWithEmptyToString : Exception -{ - public override string ToString() - { - return string.Empty; - } -} diff --git a/Tests/FluentAssertions.Specs/Exceptions/FunctionExceptionAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Exceptions/FunctionExceptionAssertionSpecs.cs index 859aabe359..69d8e077c3 100644 --- a/Tests/FluentAssertions.Specs/Exceptions/FunctionExceptionAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Exceptions/FunctionExceptionAssertionSpecs.cs @@ -1,12 +1,11 @@ using System; -using System.Threading.Tasks; using FluentAssertions.Execution; using FluentAssertions.Extensions; -using Xunit; -using Xunit.Sdk; -#if NETFRAMEWORK +#if NET47 using FluentAssertions.Specs.Common; #endif +using Xunit; +using Xunit.Sdk; namespace FluentAssertions.Specs.Exceptions; @@ -69,18 +68,18 @@ public void When_the_expected_exception_is_not_wrapped_it_should_fail(Func, Exception> AggregateExceptionTestData() { - var tasks = new[] - { + Func[] tasks = + [ AggregateExceptionWithLeftNestedException, AggregateExceptionWithRightNestedException - }; + ]; - var types = new Exception[] - { + Exception[] types = + [ new AggregateException(), new ArgumentNullException(), new InvalidOperationException() - }; + ]; var data = new TheoryData, Exception>(); @@ -436,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 @@ -595,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] @@ -620,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 @@ -669,15 +642,5 @@ public void When_function_throw_one_exception_but_other_was_not_expected_it_shou action.Should().NotThrow(); } - [Fact] - public void When_no_exception_should_be_thrown_by_sync_over_async_it_should_not_throw() - { - // Arrange - Func func = () => Task.Delay(0).Wait(0); - - // Act / Assert - func.Should().NotThrow(); - } - #endregion } diff --git a/Tests/FluentAssertions.Specs/Exceptions/InnerExceptionSpecs.cs b/Tests/FluentAssertions.Specs/Exceptions/InnerExceptionSpecs.cs index a39a633832..ec657ab94b 100644 --- a/Tests/FluentAssertions.Specs/Exceptions/InnerExceptionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Exceptions/InnerExceptionSpecs.cs @@ -1,4 +1,4 @@ -using System; +using System; using Xunit; using Xunit.Sdk; @@ -10,11 +10,10 @@ public class InnerExceptionSpecs public void When_subject_throws_an_exception_with_the_expected_inner_exception_it_should_not_do_anything() { // Arrange - Does testSubject = Does.Throw(new Exception("", new ArgumentException())); + Action testSubject = () => throw new Exception("", new ArgumentException()); // Act / Assert testSubject - .Invoking(x => x.Do()) .Should().Throw() .WithInnerException(); } @@ -23,11 +22,10 @@ public void When_subject_throws_an_exception_with_the_expected_inner_exception_i public void When_subject_throws_an_exception_with_the_expected_inner_base_exception_it_should_not_do_anything() { // Arrange - Does testSubject = Does.Throw(new Exception("", new ArgumentNullException())); + Action testSubject = () => throw new Exception("", new ArgumentNullException()); // Act / Assert testSubject - .Invoking(x => x.Do()) .Should().Throw() .WithInnerException(); } @@ -36,11 +34,10 @@ public void When_subject_throws_an_exception_with_the_expected_inner_base_except public void When_subject_throws_an_exception_with_the_expected_inner_exception_from_argument_it_should_not_do_anything() { // Arrange - Does testSubject = Does.Throw(new Exception("", new ArgumentException())); + Action testSubject = () => throw new Exception("", new ArgumentException()); // Act / Assert testSubject - .Invoking(x => x.Do()) .Should().Throw() .WithInnerException(typeof(ArgumentException)); } @@ -186,15 +183,14 @@ public void When_subject_throws_an_exception_with_an_unexpected_inner_exception_ // Arrange var innerException = new NullReferenceException("InnerExceptionMessage"); - Does testSubject = Does.Throw(new Exception("", innerException)); + Action testSubject = () => throw new Exception("", innerException); try { // Act testSubject - .Invoking(x => x.Do()) .Should().Throw() - .WithInnerException("because {0} should do just that", "Does.Do"); + .WithInnerException("because {0} should do just that", "the action"); throw new XunitException("This point should not be reached"); } @@ -202,7 +198,7 @@ public void When_subject_throws_an_exception_with_an_unexpected_inner_exception_ { // Assert exc.Message.Should().Match( - "Expected*ArgumentException*Does.Do should do just that*NullReferenceException*InnerExceptionMessage*"); + "Expected*ArgumentException*the action should do just that*NullReferenceException*InnerExceptionMessage*"); } } @@ -211,17 +207,17 @@ public void When_subject_throws_an_exception_without_expected_inner_exception_it { try { - Does testSubject = Does.Throw(); + Action testSubject = () => throw new Exception(); - testSubject.Invoking(x => x.Do()).Should().Throw() - .WithInnerException("because {0} should do that", "Does.Do"); + testSubject.Should().Throw() + .WithInnerException("because {0} should do that", "the action"); throw new XunitException("This point should not be reached"); } catch (XunitException ex) { ex.Message.Should().Be( - "Expected inner System.InvalidOperationException because Does.Do should do that, but the thrown exception has no inner exception."); + "Expected inner System.InvalidOperationException because the action should do that, but the thrown exception has no inner exception."); } } diff --git a/Tests/FluentAssertions.Specs/Exceptions/InvokingActionSpecs.cs b/Tests/FluentAssertions.Specs/Exceptions/InvokingActionSpecs.cs index 1f2a4dca8e..7f8c973171 100644 --- a/Tests/FluentAssertions.Specs/Exceptions/InvokingActionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Exceptions/InvokingActionSpecs.cs @@ -1,4 +1,4 @@ -using System; +using System; using Xunit; namespace FluentAssertions.Specs.Exceptions; @@ -9,10 +9,10 @@ public class InvokingActionSpecs public void Invoking_on_null_is_not_allowed() { // Arrange - Does someClass = null; + Action someClass = null; // Act - Action act = () => someClass.Invoking(d => d.Do()); + Action act = () => someClass.Invoking(d => d()); // Assert act.Should().ThrowExactly() @@ -23,7 +23,7 @@ public void Invoking_on_null_is_not_allowed() public void Invoking_with_null_is_not_allowed() { // Arrange - Does someClass = Does.NotThrow(); + Action someClass = () => { }; // Act Action act = () => someClass.Invoking(null); diff --git a/Tests/FluentAssertions.Specs/Exceptions/InvokingFunctionSpecs.cs b/Tests/FluentAssertions.Specs/Exceptions/InvokingFunctionSpecs.cs index 8b706ec218..54e84694d9 100644 --- a/Tests/FluentAssertions.Specs/Exceptions/InvokingFunctionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Exceptions/InvokingFunctionSpecs.cs @@ -1,4 +1,4 @@ -using System; +using System; using Xunit; namespace FluentAssertions.Specs.Exceptions; @@ -9,10 +9,10 @@ public class InvokingFunctionSpecs public void Invoking_on_null_is_not_allowed() { // Arrange - Does someClass = null; + Func someClass = null; // Act - Action act = () => someClass.Invoking(d => d.Return()); + Action act = () => someClass.Invoking(d => d()); // Assert act.Should().ThrowExactly() @@ -23,10 +23,10 @@ public void Invoking_on_null_is_not_allowed() public void Invoking_with_null_is_not_allowed() { // Arrange - Does someClass = Does.NotThrow(); + Action someClass = () => { }; // Act - Action act = () => someClass.Invoking((Func)null); + Action act = () => someClass.Invoking((Func)null); // Assert act.Should().ThrowExactly() diff --git a/Tests/FluentAssertions.Specs/Exceptions/MiscellaneousExceptionSpecs.cs b/Tests/FluentAssertions.Specs/Exceptions/MiscellaneousExceptionSpecs.cs index 430fc82bb5..ebf4a2aa4e 100644 --- a/Tests/FluentAssertions.Specs/Exceptions/MiscellaneousExceptionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Exceptions/MiscellaneousExceptionSpecs.cs @@ -1,35 +1,53 @@ -using System; +using System; using System.Collections.Generic; using Xunit; using Xunit.Sdk; namespace FluentAssertions.Specs.Exceptions; +internal class ExceptionWithEmptyToString : Exception +{ + public override string ToString() + { + return string.Empty; + } +} + +internal class ExceptionWithProperties : Exception +{ + public ExceptionWithProperties(string propertyValue) + { + Property = propertyValue; + } + + public string Property { get; set; } +} + public class MiscellaneousExceptionSpecs { [Fact] public void When_getting_value_of_property_of_thrown_exception_it_should_return_value_of_property() { // Arrange - const string SomeParamNameValue = "param"; - Does target = Does.Throw(new ExceptionWithProperties(SomeParamNameValue)); + const string someParamNameValue = "param"; + Action target = () => throw new ExceptionWithProperties(someParamNameValue); // Act - Action act = target.Do; + Action act = target; // Assert - act.Should().Throw().And.Property.Should().Be(SomeParamNameValue); + act.Should().Throw().And.Property.Should().Be(someParamNameValue); } [Fact] public void When_validating_a_subject_against_multiple_conditions_it_should_support_chaining() { // Arrange - Does testSubject = Does.Throw(new InvalidOperationException("message", new ArgumentException("inner message"))); + Action testSubject = () => throw new InvalidOperationException("message", new ArgumentException("inner message")); // Act / Assert testSubject - .Invoking(x => x.Do()) + .Should().Throw() .WithInnerException() .WithMessage("inner message"); @@ -126,8 +144,8 @@ public void When_custom_condition_is_met_it_should_not_throw() public void When_two_exceptions_are_thrown_and_the_assertion_assumes_there_can_only_be_one_it_should_fail() { // Arrange - Does testSubject = Does.Throw(new AggregateException(new Exception(), new Exception())); - Action throwingMethod = testSubject.Do; + Action testSubject = () => throw new AggregateException(new Exception(), new Exception()); + Action throwingMethod = testSubject; // Act Action action = () => throwingMethod.Should().Throw().And.Message.Should(); diff --git a/Tests/FluentAssertions.Specs/Exceptions/NotThrowSpecs.cs b/Tests/FluentAssertions.Specs/Exceptions/NotThrowSpecs.cs index 398868bff5..31ebaf51ad 100644 --- a/Tests/FluentAssertions.Specs/Exceptions/NotThrowSpecs.cs +++ b/Tests/FluentAssertions.Specs/Exceptions/NotThrowSpecs.cs @@ -1,9 +1,8 @@ -#if NETFRAMEWORK -using FluentAssertions.Specs.Common; -#endif using System; -using System.Threading.Tasks; using FluentAssertions.Execution; +#if NET47 +using FluentAssertions.Specs.Common; +#endif using Xunit; using Xunit.Sdk; using static FluentAssertions.Extensions.FluentTimeSpanExtensions; @@ -35,11 +34,11 @@ public void When_subject_is_null_when_an_exception_should_not_be_thrown_it_shoul public void When_a_specific_exception_should_not_be_thrown_but_it_was_it_should_throw() { // Arrange - Does foo = Does.Throw(new ArgumentException("An exception was forced")); + Action foo = () => throw new ArgumentException("An exception was forced"); // Act Action action = - () => foo.Invoking(f => f.Do()).Should().NotThrow("we passed valid arguments"); + () => foo.Should().NotThrow("we passed valid arguments"); // Assert action @@ -52,30 +51,20 @@ public void When_a_specific_exception_should_not_be_thrown_but_it_was_it_should_ public void When_a_specific_exception_should_not_be_thrown_but_another_was_it_should_succeed() { // Arrange - Does foo = Does.Throw(); - - // Act / Assert - foo.Invoking(f => f.Do()).Should().NotThrow(); - } - - [Fact] - public void When_no_exception_should_be_thrown_by_sync_over_async_it_should_not_throw() - { - // Arrange - Action act = () => Task.Delay(0).Wait(0); + Action foo = () => throw new ArgumentException(); // Act / Assert - act.Should().NotThrow(); + foo.Should().NotThrow(); } [Fact] public void When_no_exception_should_be_thrown_but_it_was_it_should_throw() { // Arrange - Does foo = Does.Throw(new ArgumentException("An exception was forced")); + Action foo = () => throw new ArgumentException("An exception was forced"); // Act - Action action = () => foo.Invoking(f => f.Do()).Should().NotThrow("we passed valid arguments"); + Action action = () => foo.Should().NotThrow("we passed valid arguments"); // Assert action @@ -88,10 +77,10 @@ public void When_no_exception_should_be_thrown_but_it_was_it_should_throw() public void When_no_exception_should_be_thrown_and_none_was_it_should_not_throw() { // Arrange - Does foo = Does.NotThrow(); + Action foo = () => { }; // Act / Assert - foo.Invoking(f => f.Do()).Should().NotThrow(); + foo.Should().NotThrow(); } [Fact] diff --git a/Tests/FluentAssertions.Specs/Exceptions/OuterExceptionSpecs.cs b/Tests/FluentAssertions.Specs/Exceptions/OuterExceptionSpecs.cs index f4c56646be..548460ea3f 100644 --- a/Tests/FluentAssertions.Specs/Exceptions/OuterExceptionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Exceptions/OuterExceptionSpecs.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics.CodeAnalysis; using Xunit; using Xunit.Sdk; @@ -11,23 +11,23 @@ public class OuterExceptionSpecs public void When_subject_throws_expected_exception_with_an_expected_message_it_should_not_do_anything() { // Arrange - Does testSubject = Does.Throw(new InvalidOperationException("some message")); + Action testSubject = () => throw new InvalidOperationException("some message"); // Act / Assert - testSubject.Invoking(x => x.Do()).Should().Throw().WithMessage("some message"); + testSubject.Should().Throw().WithMessage("some message"); } [Fact] public void When_subject_throws_expected_exception_but_with_unexpected_message_it_should_throw() { // Arrange - Does testSubject = Does.Throw(new InvalidOperationException("some")); + Action testSubject = () => throw new InvalidOperationException("some"); try { // Act testSubject - .Invoking(x => x.Do()) + .Should().Throw() .WithMessage("some message"); @@ -41,14 +41,112 @@ public void When_subject_throws_expected_exception_but_with_unexpected_message_i } } + [Fact] + public void Long_exception_messages_are_rendered_over_multiple_lines() + { + // Arrange + Action testSubject = () => throw new InvalidOperationException("some"); + + try + { + // Act + testSubject + + .Should().Throw() + .WithMessage(new string('#', 101)); + + throw new XunitException("This point should not be reached"); + } + catch (XunitException ex) + { + // Assert + ex.Message.Should().Match( + """ + Expected exception message to match the equivalent of + + "*", + + but + + "some" + + does not. + + """); + } + } + + [Fact] + public void Multiline_exception_messages_are_rendered_over_multiple_lines() + { + // Arrange + Action testSubject = () => throw new InvalidOperationException("some"); + + try + { + // Act + testSubject + + .Should().Throw() + .WithMessage(""" + line1* + line2 + """); + + throw new XunitException("This point should not be reached"); + } + catch (XunitException ex) + { + // Assert + ex.Message.Should().Match( + """ + Expected exception message to match the equivalent of + + "line1* + line2", + + but + + "some" + + does not. + + """); + } + } + + [Fact] + public void Short_exception_messages_are_rendered_on_a_single_line() + { + // Arrange + Action testSubject = () => throw new InvalidOperationException("some"); + + try + { + // Act + testSubject + + .Should().Throw() + .WithMessage(new string('#', 50)); + + throw new XunitException("This point should not be reached"); + } + catch (XunitException ex) + { + // Assert + ex.Message.Should().Match( + """Expected exception message to match the equivalent of "*", but "some" does not."""); + } + } + [Fact] public void When_subject_throws_expected_exception_with_message_starting_with_expected_message_it_should_not_throw() { // Arrange - Does testSubject = Does.Throw(new InvalidOperationException("expected message")); + Action testSubject = () => throw new InvalidOperationException("expected message"); // Act - Action action = testSubject.Do; + Action action = testSubject; // Assert action.Should().Throw() @@ -60,11 +158,10 @@ public void When_subject_throws_expected_exception_with_message_starting_with_ex public void When_subject_throws_expected_exception_with_message_that_does_not_start_with_expected_message_it_should_throw() { // Arrange - Does testSubject = Does.Throw(new InvalidOperationException("OxpectOd message")); + Action testSubject = () => throw new InvalidOperationException("OxpectOd message"); // Act Action action = () => testSubject - .Invoking(s => s.Do()) .Should().Throw() .WithMessage("Expected mes"); @@ -79,10 +176,10 @@ public void When_subject_throws_expected_exception_with_message_starting_with_expected_equivalent_message_it_should_not_throw() { // Arrange - Does testSubject = Does.Throw(new InvalidOperationException("Expected Message")); + Action testSubject = () => throw new InvalidOperationException("Expected Message"); // Act - Action action = testSubject.Do; + Action action = testSubject; // Assert action.Should().Throw() @@ -94,11 +191,10 @@ public void public void When_subject_throws_expected_exception_with_message_that_does_not_start_with_equivalent_message_it_should_throw() { // Arrange - Does testSubject = Does.Throw(new InvalidOperationException("OxpectOd message")); + Action testSubject = () => throw new InvalidOperationException("OxpectOd message"); // Act Action action = () => testSubject - .Invoking(s => s.Do()) .Should().Throw() .WithMessage("expected mes"); @@ -112,13 +208,13 @@ public void When_subject_throws_expected_exception_with_message_that_does_not_st public void When_subject_throws_some_exception_with_unexpected_message_it_should_throw_with_clear_description() { // Arrange - Does subjectThatThrows = Does.Throw(new InvalidOperationException("message1")); + Action subjectThatThrows = () => throw new InvalidOperationException("message1"); try { // Act subjectThatThrows - .Invoking(x => x.Do()) + .Should().Throw() .WithMessage("message2", "because we want to test the failure {0}", "message"); @@ -136,13 +232,13 @@ public void When_subject_throws_some_exception_with_unexpected_message_it_should public void When_subject_throws_some_exception_with_an_empty_message_it_should_throw_with_clear_description() { // Arrange - Does subjectThatThrows = Does.Throw(new InvalidOperationException("")); + Action subjectThatThrows = () => throw new InvalidOperationException(""); try { // Act subjectThatThrows - .Invoking(x => x.Do()) + .Should().Throw() .WithMessage("message2"); @@ -161,13 +257,12 @@ public void When_subject_throws_some_exception_with_message_which_contains_complete_expected_exception_and_more_it_should_throw() { // Arrange - Does subjectThatThrows = Does.Throw(new ArgumentNullException("someParam", "message2")); + Action subjectThatThrows = () => throw new ArgumentNullException("someParam", "message2"); try { // Act subjectThatThrows - .Invoking(x => x.Do("something")) .Should().Throw() .WithMessage("message2"); @@ -177,7 +272,7 @@ public void { // Assert ex.Message.Should().Match( - "Expected exception message to match the equivalent of*\"message2\", but*message2*someParam*"); + "Expected exception message to match the equivalent of*\"message2\",*but*message2*someParam*"); } } @@ -187,10 +282,10 @@ public void When_no_exception_was_thrown_but_one_was_expected_it_should_clearly_ try { // Arrange - Does testSubject = Does.NotThrow(); + Action testSubject = () => { }; // Act - testSubject.Invoking(x => x.Do()).Should().Throw("because {0} should do that", "Does.Do"); + testSubject.Should().Throw("because {0} should do that", "Does.Do"); throw new XunitException("This point should not be reached"); } @@ -208,13 +303,13 @@ public void When_subject_throws_another_exception_than_expected_it_should_includ // Arrange var actualException = new ArgumentException(); - Does testSubject = Does.Throw(actualException); + Action testSubject = () => throw actualException; try { // Act testSubject - .Invoking(x => x.Do()) + .Should().Throw("because {0} should throw that one", "Does.Do"); throw new XunitException("This point should not be reached"); @@ -233,13 +328,12 @@ public void When_subject_throws_another_exception_than_expected_it_should_includ public void When_subject_throws_exception_with_message_with_braces_but_a_different_message_is_expected_it_should_report_that() { // Arrange - Does subjectThatThrows = Does.Throw(new Exception("message with {}")); + Action subjectThatThrows = () => throw new Exception("message with {}"); try { // Act subjectThatThrows - .Invoking(x => x.Do("something")) .Should().Throw() .WithMessage("message without"); @@ -257,10 +351,10 @@ public void When_subject_throws_exception_with_message_with_braces_but_a_differe public void When_asserting_with_an_aggregate_exception_type_the_asserts_should_occur_against_the_aggregate_exception() { // Arrange - Does testSubject = Does.Throw(new AggregateException("Outer Message", new Exception("Inner Message"))); + Action testSubject = () => throw new AggregateException("Outer Message", new Exception("Inner Message")); // Act - Action act = testSubject.Do; + Action act = testSubject; // Assert act.Should().Throw() @@ -274,10 +368,10 @@ public void When_asserting_with_an_aggregate_exception_and_inner_exception_type_from_argument_the_asserts_should_occur_against_the_aggregate_exception() { // Arrange - Does testSubject = Does.Throw(new AggregateException("Outer Message", new Exception("Inner Message"))); + Action testSubject = () => throw new AggregateException("Outer Message", new Exception("Inner Message")); // Act - Action act = testSubject.Do; + Action act = testSubject; // Assert act.Should().Throw() diff --git a/Tests/FluentAssertions.Specs/Exceptions/ThrowAssertionsSpecs.cs b/Tests/FluentAssertions.Specs/Exceptions/ThrowAssertionsSpecs.cs index b568253735..f514193f14 100644 --- a/Tests/FluentAssertions.Specs/Exceptions/ThrowAssertionsSpecs.cs +++ b/Tests/FluentAssertions.Specs/Exceptions/ThrowAssertionsSpecs.cs @@ -1,4 +1,4 @@ -using System; +using System; using Xunit; using Xunit.Sdk; @@ -7,27 +7,57 @@ namespace FluentAssertions.Specs.Exceptions; public class ThrowAssertionsSpecs { [Fact] - public void When_subject_throws_expected_exception_it_should_not_do_anything() + public void Succeeds_for_any_exception_thrown_by_subject() { // Arrange - Does testSubject = Does.Throw(); + Action testSubject = () => throw new InvalidOperationException(); // Act / Assert - testSubject.Invoking(x => x.Do()).Should().Throw(); + testSubject.Should().Throw(); } [Fact] - public void When_func_throws_expected_exception_it_should_not_do_anything() + public void Succeeds_for_expected_exception_thrown_by_subject() { // Arrange - Does testSubject = Does.Throw(); + Action testSubject = () => throw new InvalidOperationException(); // Act / Assert - testSubject.Invoking(x => x.Return()).Should().Throw(); + testSubject.Should().Throw(); } [Fact] - public void When_action_throws_expected_exception_it_should_not_do_anything() + public void Succeeds_for_any_exception_thrown_by_func() + { + // Arrange + Func testSubject = () => throw new InvalidOperationException(); + + // Act / Assert + testSubject.Should().Throw(); + } + + [Fact] + public void Succeeds_for_expected_exception_thrown_by_func() + { + // Arrange + Func testSubject = () => throw new InvalidOperationException(); + + // Act / Assert + testSubject.Should().Throw(); + } + + [Fact] + public void Succeeds_for_any_exception_thrown_by_action() + { + // Arrange + var act = new Action(() => throw new InvalidOperationException("Some exception")); + + // Act / Assert + act.Should().Throw(); + } + + [Fact] + public void Succeeds_for_expected_exception_thrown_by_action() { // Arrange var act = new Action(() => throw new InvalidOperationException("Some exception")); @@ -41,9 +71,9 @@ public void When_subject_does_not_throw_exception_but_one_was_expected_it_should { try { - Does testSubject = Does.NotThrow(); + Action testSubject = () => { }; - testSubject.Invoking(x => x.Do()).Should().Throw(); + testSubject.Should().Throw(); throw new XunitException("Should().Throw() did not throw"); } @@ -59,9 +89,9 @@ public void When_func_does_not_throw_exception_but_one_was_expected_it_should_th { try { - Does testSubject = Does.NotThrow(); + Func testSubject = () => 42; - testSubject.Invoking(x => x.Return()).Should().Throw(); + testSubject.Should().Throw(); throw new XunitException("Should().Throw() did not throw"); } @@ -76,10 +106,10 @@ public void When_func_does_not_throw_exception_but_one_was_expected_it_should_th public void When_func_does_not_throw_it_should_be_chainable() { // Arrange - Does testSubject = Does.NotThrow(); + Func testSubject = () => 42; // Act / Assert - testSubject.Invoking(x => x.Return()).Should().NotThrow() + testSubject.Should().NotThrow() .Which.Should().Be(42); } diff --git a/Tests/FluentAssertions.Specs/Execution/AssertionChainSpecs.Chaining.cs b/Tests/FluentAssertions.Specs/Execution/AssertionChainSpecs.Chaining.cs new file mode 100644 index 0000000000..45c53e0570 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Execution/AssertionChainSpecs.Chaining.cs @@ -0,0 +1,613 @@ +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 Returns_an_empty_identification_when_neither_scope_name_nor_caller_identifier_are_available() + { + // Arrange + var assertionChain = AssertionChain.GetOrCreate(); + assertionChain.OverrideCallerIdentifier(() => null); + + // Act + string identification = assertionChain.CallerIdentifier; + + // Assert + identification.Should().BeEmpty(); + } + + // [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..8a2d24e6cf --- /dev/null +++ b/Tests/FluentAssertions.Specs/Execution/AssertionChainSpecs.MessageFormating.cs @@ -0,0 +1,423 @@ +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"); + } + + [Theory] + [InlineData("{0}{0}", "\"foo\"\"foo\"")] + [InlineData("{{0}}{0}", "{0}\"foo\"")] + [InlineData("{0}{{0}}", "\"foo\"{0}")] + [InlineData("{{{0}}}{0}", "{\"foo\"}\"foo\"")] + [InlineData("{0}{{{0}}}", "\"foo\"{\"foo\"}")] + public void Can_handle_escaped_braces(string format, string expected) + { + AssertionChain.GetOrCreate() + .Invoking(e => e.FailWith(format, "foo")) + .Should().Throw().WithMessage(expected); + } + + [Theory] + [InlineData("{")] + [InlineData("}")] + [InlineData("{}")] + [InlineData("{0}")] + [InlineData("{{0}}")] + public void Can_handle_more_braces_in_dictionary_keys(string key) + { + // Arrange + var subject = new Dictionary { [key] = "" }; + var expectation = new Dictionary { [key] = null }; + + // Act + var act = () => expectation.Should().BeEquivalentTo(subject); + + // Assert + act.Should().Throw() + .WithMessage($"Expected expectation[{key}] to be \"\", but found .*"); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Execution/AssertionScope.ChainingApiSpecs.cs b/Tests/FluentAssertions.Specs/Execution/AssertionScope.ChainingApiSpecs.cs deleted file mode 100644 index dfe06be10b..0000000000 --- a/Tests/FluentAssertions.Specs/Execution/AssertionScope.ChainingApiSpecs.cs +++ /dev/null @@ -1,429 +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 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 - using 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"); - } -} 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 53119f1f6e..0000000000 --- a/Tests/FluentAssertions.Specs/Execution/AssertionScope.MessageFormatingSpecs.cs +++ /dev/null @@ -1,514 +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; - 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 - Action act = () => 1.Should().Be(2, "use of {} is okay if there are no because arguments"); - - // 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 - Action act = () => 1.Should().Be(2, "use of {} is considered invalid in because parameter with becauseArgs", - "additional becauseArgs argument"); - - // 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/AssertionScopeSpecs.ScopedFormatters.cs b/Tests/FluentAssertions.Specs/Execution/AssertionScopeSpecs.ScopedFormatters.cs new file mode 100644 index 0000000000..2c89bca59a --- /dev/null +++ b/Tests/FluentAssertions.Specs/Execution/AssertionScopeSpecs.ScopedFormatters.cs @@ -0,0 +1,157 @@ +using System; +using FluentAssertions.Execution; +using FluentAssertions.Formatting; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Execution; + +public partial class AssertionScopeSpecs +{ + [Fact] + public void A_scoped_formatter_will_be_used() + { + // Arrange + Action act = () => + { + // Act + using var outerScope = new AssertionScope(); + + var outerFormatter = new OuterFormatter(); + outerScope.FormattingOptions.AddFormatter(outerFormatter); + 1.Should().Be(2); + }; + + // Assert + act.Should().Throw().WithMessage($"*{nameof(OuterFormatter)}*"); + } + + [Fact] + public void A_scoped_formatter_is_not_available_after_disposal() + { + // Arrange + Action act = () => + { + // Act + using var outerScope = new AssertionScope(); + + var outerFormatter = new OuterFormatter(); + outerScope.FormattingOptions.AddFormatter(outerFormatter); + + // ReSharper disable once DisposeOnUsingVariable + outerScope.Dispose(); + + 1.Should().Be(2); + }; + + // Assert + act.Should().Throw().Which.Message.Should().NotMatch($"*{nameof(OuterFormatter)}*"); + } + + [Fact] + public void Removing_a_formatter_from_scope_works() + { + // Arrange + using var outerScope = new AssertionScope(); + var outerFormatter = new OuterFormatter(); + + // Act 1 + outerScope.FormattingOptions.AddFormatter(outerFormatter); + 1.Should().Be(2); + + // Assert 1 + outerScope.Discard().Should().ContainSingle().Which.Should().Match($"*{nameof(OuterFormatter)}*"); + + // Act 2 + outerScope.FormattingOptions.RemoveFormatter(outerFormatter); + 1.Should().Be(2); + + // Assert2 + outerScope.Discard().Should().ContainSingle().Which.Should().NotMatch($"*{nameof(OuterFormatter)}*"); + outerScope.FormattingOptions.ScopedFormatters.Should().BeEmpty(); + } + + [Fact] + public void Add_a_formatter_to_nested_scope_adds_and_removes_correctly_on_dispose() + { + // Arrange + using var outerScope = new AssertionScope("outside"); + + var outerFormatter = new OuterFormatter(); + var innerFormatter = new InnerFormatter(); + + // Act 1 + outerScope.FormattingOptions.AddFormatter(outerFormatter); + 1.Should().Be(2); + + // Assert 1 / Test if outer scope contains OuterFormatter + outerScope.Discard().Should().ContainSingle().Which.Should().Match($"*{nameof(OuterFormatter)}*"); + + using (var innerScope = new AssertionScope("inside")) + { + // Act 2 + innerScope.FormattingOptions.AddFormatter(innerFormatter); + "1".Should().Be("2"); // InnerFormatter + 1.Should().Be(2); // OuterFormatter + + // Assert 2 + innerScope.Discard().Should() + .SatisfyRespectively( + failure1 => failure1.Should().Match($"*{nameof(InnerFormatter)}*"), + failure2 => failure2.Should().Match($"*{nameof(OuterFormatter)}*")); + } + + // Act 3 + 1.Should().Be(2); + + // Assert 3 + outerScope.Discard().Should().ContainSingle().Which.Should().Match($"*{nameof(OuterFormatter)}*"); + } + + [Fact] + public void Removing_a_formatter_from_outer_scope_inside_nested_scope_leaves_outer_scope_untouched() + { + // Arrange + using var outerScope = new AssertionScope(); + var outerFormatter = new OuterFormatter(); + var innerFormatter = new InnerFormatter(); + + // Act + outerScope.FormattingOptions.AddFormatter(outerFormatter); + + using var innerScope = new AssertionScope(); + innerScope.FormattingOptions.AddFormatter(innerFormatter); + innerScope.FormattingOptions.RemoveFormatter(outerFormatter); + 1.Should().Be(2); + "1".Should().Be("2"); + + // Assert + innerScope.Discard().Should().SatisfyRespectively( + failure1 => failure1.Should().Match("*2, but found 1*"), + failure2 => failure2.Should().NotMatch($"*{nameof(OuterFormatter)}*") + .And.Match($"*{nameof(InnerFormatter)}*")); + + outerScope.FormattingOptions.ScopedFormatters.Should().ContainSingle() + .Which.Should().Be(outerFormatter); + } + + private class OuterFormatter : IValueFormatter + { + public bool CanHandle(object value) => value is int; + + public void Format(object value, FormattedObjectGraph formattedGraph, FormattingContext context, FormatChild formatChild) + { + formattedGraph.AddFragment(nameof(OuterFormatter)); + } + } + + private class InnerFormatter : IValueFormatter + { + public bool CanHandle(object value) => value is string; + + public void Format(object value, FormattedObjectGraph formattedGraph, FormattingContext context, FormatChild formatChild) + { + formattedGraph.AddFragment(nameof(InnerFormatter)); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Execution/AssertionScopeSpecs.cs b/Tests/FluentAssertions.Specs/Execution/AssertionScopeSpecs.cs index 93707aa0e4..9398f2e740 100644 --- a/Tests/FluentAssertions.Specs/Execution/AssertionScopeSpecs.cs +++ b/Tests/FluentAssertions.Specs/Execution/AssertionScopeSpecs.cs @@ -6,7 +6,6 @@ using System.Threading; using System.Threading.Tasks; using FluentAssertions; -using FluentAssertions.Common; using FluentAssertions.Execution; using Xunit; using Xunit.Sdk; @@ -24,7 +23,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 +45,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 +68,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 +90,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 +114,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 +144,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 +171,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 +197,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 +241,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 +249,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,9 +292,28 @@ 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 + List nonEmptyList = [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 = new(); + private readonly List failureMessages = []; public IEnumerable FailureMessages => failureMessages; @@ -314,7 +339,7 @@ public void ThrowIfAny(IDictionary context) } } - Services.ThrowException(builder.ToString()); + AssertionEngine.TestFramework.Throw(builder.ToString()); } } @@ -326,12 +351,12 @@ public void HandleFailure(string message) internal class FailWithStupidMessageAssertionStrategy : IAssertionStrategy { - public IEnumerable FailureMessages => new string[0]; + public IEnumerable FailureMessages => []; public void HandleFailure(string message) => - Services.ThrowException("Good luck with understanding what's going on!"); + AssertionEngine.TestFramework.Throw("Good luck with understanding what's going on!"); - public IEnumerable DiscardFailures() => new string[0]; + public IEnumerable DiscardFailures() => []; public void ThrowIfAny(IDictionary context) { @@ -341,9 +366,9 @@ public void ThrowIfAny(IDictionary context) } } -#pragma warning disable RCS1110, CA1050, S3903 // Declare type inside namespace. +#pragma warning disable MA0047 public class AssertionScopeSpecsWithoutNamespace -#pragma warning restore RCS1110, CA1050, S3903 // Declare type inside namespace. +#pragma warning restore MA0047 { [Fact] public void This_class_should_not_be_inside_a_namespace() diff --git a/Tests/FluentAssertions.Specs/Execution/CallerIdentifierSpecs.cs b/Tests/FluentAssertions.Specs/Execution/CallerIdentificationSpecs.cs similarity index 84% rename from Tests/FluentAssertions.Specs/Execution/CallerIdentifierSpecs.cs rename to Tests/FluentAssertions.Specs/Execution/CallerIdentificationSpecs.cs index f5a0d05f84..f9a5e33dd7 100644 --- a/Tests/FluentAssertions.Specs/Execution/CallerIdentifierSpecs.cs +++ b/Tests/FluentAssertions.Specs/Execution/CallerIdentificationSpecs.cs @@ -2,48 +2,54 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Reflection; using System.Threading.Tasks; using FluentAssertions; +using FluentAssertions.Common; using FluentAssertions.Equivalency; using FluentAssertions.Execution; using FluentAssertions.Extensions; +using FluentAssertions.Specs.Types; using Xunit; using Xunit.Sdk; #pragma warning disable RCS1192, RCS1214, S4144 // verbatim string literals and interpolated strings - +// 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(); + // ReSharper disable once RedundantNameQualifier + 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(); + // ReSharper disable once RedundantNameQualifier + Action act = () => SystemPrefixed.SystemPrefixedNamespaceClass.AssertAgainstFailure(); // Assert - act.Should().Throw().WithMessage("Expected actualCaller to be*"); + act.Should().Throw().WithMessage("Expected actualCaller*"); } [Fact] @@ -153,6 +159,7 @@ public void When_the_caller_contains_multiple_members_it_should_include_them_all { // Arrange string test1 = "test1"; + var foo = new Foo { Field = "test3" @@ -171,6 +178,7 @@ public void When_the_caller_contains_multiple_members_across_multiple_lines_it_s { // Arrange string test1 = "test1"; + var foo = new Foo { Field = "test3" @@ -566,11 +574,67 @@ 5. Test var node = Node.From(GetSubjectId); // Assert - node.Description.Should().StartWith("node.Description"); + node.Subject.Description.Should().StartWith("node.Subject.Description"); + } + + [Fact] + public void When_a_method_is_decorated_with_an_attribute_it_should_allow_chaining_assertions_on_it() + { + // Arrange + MethodInfo methodInfo = + typeof(ClassWithAllMethodsDecoratedWithDummyAttribute).GetParameterlessMethod("PublicDoNothing"); + + // Act + Action act = () => methodInfo.Should().BeDecoratedWith().Which.Filter.Should().BeFalse(); + + // Assert + act.Should().Throw().WithMessage("Expected Filter to be False, but found True."); + } + + [Fact] + public void Can_handle_chained_assertions() + { + // Arrange + var collection = new[] + { + new + { + Parameters = new[] { "a", "b", "c" } + } + }; + + // Act + Action act = () => collection.Should().ContainSingle().Which.Parameters[0].Should().Be("d"); + + // Assert + act.Should().Throw() + .WithMessage("Expected collection[0].Parameters[0] to be \"d\", but*"); + } + + [Fact] + public void Can_handle_chained_assertions_spread_over_multiple_lines() + { + // Arrange + var collection = new[] + { + new + { + Parameters = new[] { "a", "b", "c" } + } + }; + + // Act + Action act = () => collection.Should().ContainSingle() + .Which + .Parameters[0].Should().Be("d"); + + // Assert + act.Should().Throw() + .WithMessage("Expected collection[0].Parameters[0] to be \"d\", but*"); } [CustomAssertion] - private string GetSubjectId() => AssertionScope.Current.CallerIdentity; + private string GetSubjectId() => AssertionChain.GetOrCreate().CallerIdentifier; } #pragma warning disable IDE0060, RCS1163 // Remove unused parameter @@ -605,10 +669,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 +681,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 +693,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/Execution/TestFrameworkProviderTests.cs b/Tests/FluentAssertions.Specs/Execution/TestFrameworkProviderTests.cs deleted file mode 100644 index bf934d4c12..0000000000 --- a/Tests/FluentAssertions.Specs/Execution/TestFrameworkProviderTests.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using FluentAssertions.Common; -using FluentAssertions.Execution; -using Xunit; -using Xunit.Sdk; - -namespace FluentAssertions.Specs.Execution; - -public class TestFrameworkProviderTests -{ - [Fact] - public void When_running_xunit_test_implicitly_it_should_be_detected() - { - // Arrange - var configuration = new Configuration(new TestConfigurationStore()); - var testFrameworkProvider = new TestFrameworkProvider(configuration); - - // Act - Action act = () => testFrameworkProvider.Throw("MyMessage"); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_running_xunit_test_explicitly_it_should_be_detected() - { - // Arrange - var configuration = new Configuration(new TestConfigurationStore()) - { - TestFrameworkName = "xunit2" - }; - - var testFrameworkProvider = new TestFrameworkProvider(configuration); - - // Act - Action act = () => testFrameworkProvider.Throw("MyMessage"); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_running_test_with_unknown_test_framework_it_should_throw() - { - // Arrange - var configuration = new Configuration(new TestConfigurationStore()) - { - TestFrameworkName = "foo" - }; - - var testFrameworkProvider = new TestFrameworkProvider(configuration); - - // Act - Action act = () => testFrameworkProvider.Throw("MyMessage"); - - // Assert - act.Should().Throw() - .WithMessage("*the test framework 'foo' but this is not supported*"); - } - - [Fact] - public void When_running_test_with_late_bound_but_unavailable_test_framework_it_should_throw() - { - // Arrange - var configuration = new Configuration(new TestConfigurationStore()) - { - TestFrameworkName = "nunit" - }; - - var testFrameworkProvider = new TestFrameworkProvider(configuration); - - // Act - Action act = () => testFrameworkProvider.Throw("MyMessage"); - - act.Should().Throw() - .WithMessage("*test framework 'nunit' but the required assembly 'nunit.framework' could not be found*"); - } - - private sealed class TestConfigurationStore : IConfigurationStore - { - string IConfigurationStore.GetSetting(string name) => string.Empty; - } -} diff --git a/Tests/FluentAssertions.Specs/Extensions/ObjectExtensionsSpecs.cs b/Tests/FluentAssertions.Specs/Extensions/ObjectExtensionsSpecs.cs index 8c0d4f524f..5d4a636544 100644 --- a/Tests/FluentAssertions.Specs/Extensions/ObjectExtensionsSpecs.cs +++ b/Tests/FluentAssertions.Specs/Extensions/ObjectExtensionsSpecs.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using FluentAssertions.Common; using Xunit; @@ -56,12 +55,21 @@ public void When_comparing_a_numeric_to_a_numeric_it_should_succeed(object actua success.Should().BeTrue(); } - public static IEnumerable GetNumericAndNumericData() + public static TheoryData GetNumericAndNumericData() { - return + var pairs = from x in GetNumericIConvertibles() from y in GetNumericIConvertibles() - select new[] { x, y }; + select (x, y); + + var data = new TheoryData(); + + foreach (var (x, y) in pairs) + { + data.Add(x, y); + } + + return data; } [Theory] @@ -78,12 +86,21 @@ public void When_comparing_a_non_numeric_to_a_numeric_it_should_fail(object actu success.Should().BeFalse(); } - public static IEnumerable GetNonNumericAndNumericData() + public static TheoryData GetNonNumericAndNumericData() { - return + var pairs = from x in GetNonNumericIConvertibles() from y in GetNumericIConvertibles() - select new[] { x, y }; + select (x, y); + + var data = new TheoryData(); + + foreach (var (x, y) in pairs) + { + data.Add(x, y); + } + + return data; } [Theory] @@ -100,12 +117,21 @@ public void When_comparing_a_numeric_to_a_non_numeric_it_should_fail(object actu success.Should().BeFalse(); } - public static IEnumerable GetNumericAndNonNumericData() + public static TheoryData GetNumericAndNonNumericData() { - return + var pairs = from x in GetNumericIConvertibles() from y in GetNonNumericIConvertibles() - select new[] { x, y }; + select (x, y); + + var data = new TheoryData(); + + foreach (var (x, y) in pairs) + { + data.Add(x, y); + } + + return data; } [Theory] @@ -122,21 +148,30 @@ public void When_comparing_a_non_numeric_to_a_non_numeric_it_should_fail(object success.Should().BeFalse(); } - public static IEnumerable GetNonNumericAndNonNumericData() + public static TheoryData GetNonNumericAndNonNumericData() { object[] nonNumerics = GetNonNumericIConvertibles(); - return + var pairs = from x in nonNumerics from y in nonNumerics where x != y - select new[] { x, y }; + select (x, y); + + var data = new TheoryData(); + + foreach (var (x, y) in pairs) + { + data.Add(x, y); + } + + return data; } private static object[] GetNumericIConvertibles() { - return new object[] - { + return + [ (byte)1, (sbyte)1, (short)1, @@ -148,18 +183,18 @@ private static object[] GetNumericIConvertibles() 1F, 1D, 1M, - }; + ]; } private static object[] GetNonNumericIConvertibles() { - return new object[] - { + return + [ true, '\u0001', new DateTime(1), DBNull.Value, "1" - }; + ]; } } diff --git a/Tests/FluentAssertions.Specs/FluentAssertions.Specs.csproj b/Tests/FluentAssertions.Specs/FluentAssertions.Specs.csproj index 356262741d..11a0bcabd9 100644 --- a/Tests/FluentAssertions.Specs/FluentAssertions.Specs.csproj +++ b/Tests/FluentAssertions.Specs/FluentAssertions.Specs.csproj @@ -1,4 +1,4 @@ - + net47;net6.0;net8.0 @@ -7,7 +7,6 @@ false $(NoWarn),IDE0052,1573,1591,1712,CS8002 full - 12 @@ -19,35 +18,21 @@ Upgrading to 1.6.0 gives "Could not load file or assembly 'System.Runtime.CompilerServices.Unsafe, Version=4.0.4.0" --> - - all - runtime; build; native; contentfiles; analyzers - - - - - all - runtime; build; native; contentfiles; analyzers - + + - - - + + + + all runtime; build; native; contentfiles; analyzers - - - - - - - - - + + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Tests/FluentAssertions.Specs/Formatting/EnumerableExtensionsSpecs.cs b/Tests/FluentAssertions.Specs/Formatting/EnumerableExtensionsSpecs.cs index cc73be4f0b..dcbd90d295 100644 --- a/Tests/FluentAssertions.Specs/Formatting/EnumerableExtensionsSpecs.cs +++ b/Tests/FluentAssertions.Specs/Formatting/EnumerableExtensionsSpecs.cs @@ -1,23 +1,22 @@ -using System.Collections.Generic; -using FluentAssertions.Formatting; +using FluentAssertions.Formatting; using Xunit; namespace FluentAssertions.Specs.Formatting; public class EnumerableExtensionsSpecs { - public static TheoryData, string> JoinUsingWritingStyleTestCases => new() + public static TheoryData JoinUsingWritingStyleTestCases => new() { - { new object[0], "" }, - { new object[] { "test" }, "test" }, - { new object[] { "test", "test2" }, "test and test2" }, - { new object[] { "test", "test2", "test3" }, "test, test2 and test3" }, - { new object[] { "test", "test2", "test3", "test4" }, "test, test2, test3 and test4" } + { [], "" }, + { ["test"], "test" }, + { ["test", "test2"], "test and test2" }, + { ["test", "test2", "test3"], "test, test2 and test3" }, + { ["test", "test2", "test3", "test4"], "test, test2, test3 and test4" } }; [Theory] [MemberData(nameof(JoinUsingWritingStyleTestCases))] - public void JoinUsingWritingStyle_should_format_correctly(IEnumerable input, string expectation) + public void JoinUsingWritingStyle_should_format_correctly(string[] input, string expectation) { // Act var result = input.JoinUsingWritingStyle(); diff --git a/Tests/FluentAssertions.Specs/Formatting/FormatterSpecs.cs b/Tests/FluentAssertions.Specs/Formatting/FormatterSpecs.cs index b6e1670e7c..b21bf168f6 100644 --- a/Tests/FluentAssertions.Specs/Formatting/FormatterSpecs.cs +++ b/Tests/FluentAssertions.Specs/Formatting/FormatterSpecs.cs @@ -1,10 +1,8 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using System.Threading.Tasks; -using FluentAssertions.Common; using FluentAssertions.Extensions; using FluentAssertions.Formatting; using FluentAssertions.Specs.Common; @@ -14,7 +12,7 @@ namespace FluentAssertions.Specs.Formatting; [Collection("FormatterSpecs")] -public class FormatterSpecs +public class FormatterSpecs : IDisposable { [Fact] public void When_value_contains_cyclic_reference_it_should_create_descriptive_error_message() @@ -72,7 +70,7 @@ public void When_the_subject_or_expectation_contains_reserved_symbols_it_should_ Action act = () => result.Should().Be(expectedJson); // Assert - act.Should().Throw().WithMessage("*near*index 37*"); + act.Should().Throw().WithMessage("*at*index 37*"); } [Fact] @@ -177,69 +175,36 @@ public void When_the_object_is_a_generic_type_without_custom_string_representati { StuffId = 1, Description = "Stuff_1", - Children = new List { 1, 2, 3, 4 } + Children = [1, 2, 3, 4] }, new() { StuffId = 2, Description = "Stuff_2", - Children = new List { 1, 2, 3, 4 } - } - }; - - var expectedStuff = new List> - { - new() - { - StuffId = 1, - Description = "Stuff_1", - Children = new List { 1, 2, 3, 4 } - }, - new() - { - StuffId = 2, - Description = "WRONG_DESCRIPTION", - Children = new List { 1, 2, 3, 4 } + Children = [1, 2, 3, 4] } }; // Act - Action act = () => stuff.Should().NotBeNull() - .And.Equal(expectedStuff, (t1, t2) => t1.StuffId == t2.StuffId && t1.Description == t2.Description); + var actual = Formatter.ToString(stuff); // Assert - act.Should().Throw() - .WithMessage( + actual.Should().Match( """ - Expected stuff to be equal to - { - FluentAssertions.Specs.Formatting.FormatterSpecs+Stuff`1[[System.Int32*]] - { - 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", - StuffId = 2 - } - }, 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. + } """); } @@ -247,26 +212,26 @@ Expected stuff to be equal to public void When_the_object_is_a_user_defined_type_it_should_show_the_name_on_the_initial_line() { // Arrange - var stuff = new StuffRecord(42, "description", new(24), new List { 10, 20, 30, 40 }); + var stuff = new StuffRecord(42, "description", new ChildRecord(24), [10, 20, 30, 40]); // Act Action act = () => stuff.Should().BeNull(); // Assert act.Should().Throw() - .Which.Message.Should().Be( - """ - Expected stuff to be , but found FluentAssertions.Specs.Formatting.FormatterSpecs+StuffRecord - { - RecordChildren = {10, 20, 30, 40}, - RecordDescription = "description", - RecordId = 42, - SingleChild = FluentAssertions.Specs.Formatting.FormatterSpecs+ChildRecord + .Which.Message.Should().Match( + """ + Expected stuff to be , but found FluentAssertions.Specs.Formatting.FormatterSpecs+StuffRecord { - ChildRecordId = 24 - } - }. - """); + RecordChildren = {10, 20, 30, 40}, + RecordDescription = "description", + RecordId = 42, + SingleChild = FluentAssertions.Specs.Formatting.FormatterSpecs+ChildRecord + { + ChildRecordId = 24 + } + }. + """); } [Fact] @@ -280,56 +245,29 @@ public void When_the_object_is_an_anonymous_type_it_should_show_the_properties_r Children = new[] { 1, 2, 3, 4 }, }; - var expectedStuff = new - { - SingleChild = new { ChildId = 4 }, - Children = new[] { 10, 20, 30, 40 }, - }; - // Act - Action act = () => stuff.Should().Be(expectedStuff); + string actual = Formatter.ToString(stuff); // Assert - act.Should().Throw() - .Which.Message.Should().Be( + actual.Should().Match( """ - Expected stuff to be { - Children = {10, 20, 30, 40}, - SingleChild = - { - ChildId = 4 - } - }, but found - { - Children = {1, 2, 3, 4}, - Description = "absent", - SingleChild = + Children = {1, 2, 3, 4}, + Description = "absent", + SingleChild = { ChildId = 8 } - }. + } """); } [Fact] - public void When_the_object_is_a_list_of_anonymous_type_it_should_show_the_properties_recursively_with_newlines_and_indentation() + public void + When_the_object_is_a_list_of_anonymous_type_it_should_show_the_properties_recursively_with_newlines_and_indentation() { // Arrange - var stuff = new[] - { - new - { - Description = "absent", - }, - new - { - Description = "absent", - }, - }; - - var expectedStuff = new[] - { + var expectedStuff = new { ComplexChildren = new[] @@ -337,40 +275,25 @@ public void When_the_object_is_a_list_of_anonymous_type_it_should_show_the_prope new { Property = "hello" }, new { Property = "goodbye" }, }, - }, - }; + }; // Act - Action act = () => stuff.Should().BeEquivalentTo(expectedStuff); + var actual = Formatter.ToString(expectedStuff); // Assert - act.Should().Throw() - .Which.Message.Should().StartWith( + actual.Should().Be( """ - Expected stuff to be a collection with 1 item(s), but - { - { - Description = "absent" - }, - { - Description = "absent" - } - } - contains 1 item(s) more than - { + ComplexChildren = { - ComplexChildren = { - { - Property = "hello" - }, - { - Property = "goodbye" - } + Property = "hello" + }, + { + Property = "goodbye" } } - }. + } """); } @@ -387,35 +310,26 @@ public void When_the_object_is_an_empty_anonymous_type_it_should_show_braces_on_ // Assert act.Should().Throw() - .Which.Message.Should().Match("*but found { }*"); + .Which.Message.Should().Match("*but found *{ }*"); } [Fact] public void When_the_object_is_a_tuple_it_should_show_the_properties_recursively() { // Arrange - (int TupleId, string Description, List Children) stuff = (1, "description", new() { 1, 2, 3, 4 }); - - (int, string, List) expectedStuff = (2, "WRONG_DESCRIPTION", new List { 4, 5, 6, 7 }); + (int TupleId, string Description, List Children) stuff = (1, "description", [1, 2, 3, 4]); // Act - Action act = () => stuff.Should().Be(expectedStuff); + string actual = Formatter.ToString(stuff); // Assert - act.Should().Throw() - .Which.Message.Should().Be( + actual.Should().Match( """ - Expected stuff to be equal to - { - Item1 = 2, - Item2 = "WRONG_DESCRIPTION", - Item3 = {4, 5, 6, 7} - }, but found { - Item1 = 1, - Item2 = "description", + Item1 = 1,* + Item2 = "description",* Item3 = {1, 2, 3, 4} - }. + } """); } @@ -426,34 +340,24 @@ public void When_the_object_is_a_record_it_should_show_the_properties_recursivel var stuff = new StuffRecord( RecordId: 9, RecordDescription: "descriptive", - SingleChild: new(ChildRecordId: 80), - RecordChildren: new() { 4, 5, 6, 7 }); - - var expectedStuff = new - { - RecordDescription = "WRONG_DESCRIPTION", - }; + SingleChild: new ChildRecord(ChildRecordId: 80), + RecordChildren: [4, 5, 6, 7]); - // Act - Action act = () => stuff.Should().Be(expectedStuff); + var actual = Formatter.ToString(stuff); // Assert - act.Should().Throw() - .Which.Message.Should().Be( + actual.Should().Match( """ - Expected stuff to be - { - RecordDescription = "WRONG_DESCRIPTION" - }, but found FluentAssertions.Specs.Formatting.FormatterSpecs+StuffRecord + 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 } - }. + } """); } @@ -908,13 +812,13 @@ public void When_formatting_multiple_items_with_a_custom_string_representation_using_line_breaks_it_should_end_lines_with_a_comma() { // Arrange - var subject = new[] { typeof(A), typeof(B) }; + Type[] subject = [typeof(A), typeof(B)]; // Act string result = Formatter.ToString(subject, new FormattingOptions { UseLineBreaks = true }); // Assert - result.Should().Contain($"FluentAssertions.Specs.Formatting.FormatterSpecs+A, {Environment.NewLine}"); + result.Should().Contain($"FluentAssertions.Specs.Formatting.FormatterSpecs+A,{Environment.NewLine}"); result.Should().Contain($"FluentAssertions.Specs.Formatting.FormatterSpecs+B{Environment.NewLine}"); } @@ -947,283 +851,383 @@ private record StuffRecord(int RecordId, string RecordDescription, ChildRecord S private record ChildRecord(int ChildRecordId); [Fact] - public void When_a_custom_formatter_exists_in_any_loaded_assembly_it_should_override_the_default_formatters() + public void When_defining_a_custom_value_formatter_it_should_respect_the_overrides() { // Arrange - Configuration.Current.ValueFormatterDetectionMode = ValueFormatterDetectionMode.Scan; - - var subject = new SomeClassWithCustomFormatter - { - Property = "SomeValue" - }; + var value = new CustomClass(); + var formatter = new CustomClassValueFormatter(); + using var _ = new FormatterScope(formatter); // Act - string result = Formatter.ToString(subject); + string str = Formatter.ToString(value); // Assert - result.Should().Be("Property = SomeValue", "it should use my custom formatter"); + str.Should().Match( + "*CustomClass" + Environment.NewLine + + "{" + Environment.NewLine + + " IntProperty = 0" + Environment.NewLine + + "}*"); + } + + private class CustomClass + { + public int IntProperty { get; set; } + + public string StringProperty { get; set; } + } + + private class CustomClassValueFormatter : DefaultValueFormatter + { + public override bool CanHandle(object value) => value is CustomClass; + + protected override MemberInfo[] GetMembers(Type type) + { + return base + .GetMembers(type) + .Where(e => e.GetUnderlyingType() != typeof(string)) + .ToArray(); + } + + protected override string TypeDisplayName(Type type) => type.Name; } [Fact] - public void When_a_base_class_has_a_custom_formatter_it_should_override_the_default_formatters() + public void When_defining_a_custom_enumerable_value_formatter_it_should_respect_the_overrides() { // Arrange - Configuration.Current.ValueFormatterDetectionMode = ValueFormatterDetectionMode.Scan; - - var subject = new SomeClassInheritedFromClassWithCustomFormatterLvl1 + var values = new CustomClass[] { - Property = "SomeValue" + new() { IntProperty = 1 }, + new() { IntProperty = 2 } }; + var formatter = new SingleItemValueFormatter(); + using var _ = new FormatterScope(formatter); + // Act - string result = Formatter.ToString(subject); + string str = Formatter.ToString(values); - // Assert - result.Should().Be("Property = SomeValue", "it should use my custom formatter"); + str.Should().Match( + "{*FluentAssertions*FormatterSpecs+CustomClass" + Environment.NewLine + + " {" + Environment.NewLine + + " IntProperty = 1," + Environment.NewLine + + " StringProperty = " + Environment.NewLine + + " },*…1 more…*}*"); } - [Fact] - public void When_there_are_multiple_custom_formatters_it_should_select_a_more_specific_one() + private class SingleItemValueFormatter : EnumerableValueFormatter { - // Arrange - Configuration.Current.ValueFormatterDetectionMode = ValueFormatterDetectionMode.Scan; + protected override int MaxItems => 1; + + public override bool CanHandle(object value) => value is IEnumerable; + } + + private sealed class FormatterScope : IDisposable + { + private readonly IValueFormatter formatter; - var subject = new SomeClassInheritedFromClassWithCustomFormatterLvl2 + public FormatterScope(IValueFormatter formatter) { - Property = "SomeValue" - }; + this.formatter = formatter; + Formatter.AddFormatter(formatter); + } + + public void Dispose() => Formatter.RemoveFormatter(formatter); + } + [Fact] + public void Can_render_an_array_containing_anonymous_types() + { // Act - string result = Formatter.ToString(subject); + var actual = Formatter.ToString(new[] { new { Value = 1 }, new { Value = 2 } }); // Assert - result.Should().Be("Property is SomeValue", "it should use my custom formatter"); + actual.Should().Be( + """ + { + { + Value = 1 + }, + { + Value = 2 + } + } + """); } [Fact] - public void When_a_base_class_has_multiple_custom_formatters_it_should_work_the_same_as_for_the_base_class() + public void Can_render_an_array_on_a_single_line() { - // Arrange - Configuration.Current.ValueFormatterDetectionMode = ValueFormatterDetectionMode.Scan; + // Act + var actual = Formatter.ToString(new[] { "abc", "def", "efg" }); - var subject = new SomeClassInheritedFromClassWithCustomFormatterLvl3 - { - Property = "SomeValue" - }; + // Assert + actual.Should().Be(@"{""abc"", ""def"", ""efg""}"); + } + [Fact] + public void Can_render_an_array_using_line_breaks() + { // Act - string result = Formatter.ToString(subject); + var actual = Formatter.ToString(new[] { "abc", "def", "efg" }, new FormattingOptions + { + UseLineBreaks = true + }); // Assert - result.Should().Be("Property is SomeValue", "it should use my custom formatter"); + actual.Should().Be(""" + { + "abc", + "def", + "efg" + } + """); } [Fact] - public void When_no_custom_formatter_exists_in_the_specified_assembly_it_should_use_the_default() + public void Can_render_a_single_item_array_using_line_breaks() { - // Arrange - Configuration.Current.ValueFormatterAssembly = "FluentAssertions"; - - var subject = new SomeClassWithCustomFormatter + // Act + var actual = Formatter.ToString(new[] { "abc" }, new FormattingOptions { - Property = "SomeValue" - }; + UseLineBreaks = true + }); + + // Assert + actual.Should().Be(""" + { + "abc" + } + """); + } + [Fact] + public void Can_render_a_single_item_array_on_a_single_line() + { // Act - string result = Formatter.ToString(subject); + var actual = Formatter.ToString(new[] { "abc" }); // Assert - result.Should().Be(subject.ToString()); + actual.Should().Be("""{"abc"}"""); } [Fact] - public void When_formatter_scanning_is_disabled_it_should_use_the_default_formatters() + public void Can_render_a_collection_with_anonymous_types_using_line_breaks() { - // Arrange - Configuration.Current.ValueFormatterDetectionMode = ValueFormatterDetectionMode.Disabled; - - var subject = new SomeClassWithCustomFormatter + // Act + var actual = Formatter.ToString(new[] { - Property = "SomeValue" - }; + new { Value = "abc" }, new { Value = "def" }, new { Value = "efg" } + }, new FormattingOptions { UseLineBreaks = true }); + + // Assert + actual.Should().Be( + """ + { + { + Value = + "abc" + }, + { + Value = + "def" + }, + { + Value = + "efg" + } + } + """); + } + [Fact] + public void Can_render_a_simple_anonymous_object() + { // Act - string result = Formatter.ToString(subject); + var actual = Formatter.ToString(new + { + SingleChild = new { ChildId = 4 }, + Children = new[] { 10, 20, 30, 40 }, + }); // Assert - result.Should().Be(subject.ToString()); + actual.Should().Be( + """ + { + Children = {10, 20, 30, 40}, + SingleChild = + { + ChildId = 4 + } + } + """); } [Fact] - public void When_no_formatter_scanning_is_configured_it_should_use_the_default_formatters() + public void Can_format_a_multi_dimensional_array_with_linebreaks() { // Arrange - Services.ResetToDefaults(); - Configuration.Current.ValueFormatterDetectionMode = ValueFormatterDetectionMode.Disabled; - - var subject = new SomeClassWithCustomFormatter + var points = new Point[][] { - Property = "SomeValue" + [new Point("0,0")], + [new Point("1,0")], }; // Act - string result = Formatter.ToString(subject); + var result = Formatter.ToString(points, new FormattingOptions { UseLineBreaks = true }); - // Assert - result.Should().Be(subject.ToString()); + // Arrange + result.Should().Be( + """ + { + { + P0,0 + }, + { + P1,0 + } + } + """); } - public class SomeClassWithCustomFormatter + [Fact] + public void Can_format_an_enumerable_using_line_breaks() { - public string Property { get; set; } + // Arrange + Point[] points = [new("0,0"), new("1,0")]; - public override string ToString() - { - return "The value of my property is " + Property; - } + var result = Formatter.ToString(points, new FormattingOptions { UseLineBreaks = true }); + + result.Should().Be( + """ + { + P0,0, + P1,0 + } + """); } - public class SomeOtherClassWithCustomFormatter + [Fact] + public void Can_format_an_enumerable_without_line_breaks() { - public string Property { get; set; } - - public override string ToString() - { - return "The value of my property is " + Property; - } - } + // Arrange + Point[] points = [new("0,0"), new("1,0")]; - public class SomeClassInheritedFromClassWithCustomFormatterLvl1 : SomeClassWithCustomFormatter; + // Act + var result = Formatter.ToString(points, new FormattingOptions { UseLineBreaks = false }); - public class SomeClassInheritedFromClassWithCustomFormatterLvl2 : SomeClassInheritedFromClassWithCustomFormatterLvl1; + // Assert + result.Should().Be("{P0,0, P1,0}"); + } - public class SomeClassInheritedFromClassWithCustomFormatterLvl3 : SomeClassInheritedFromClassWithCustomFormatterLvl2; + private class Point(string name) + { + public override string ToString() => "P" + name; + } - public static class CustomFormatter + [Fact] + public void A_formatter_can_force_new_line() { - [ValueFormatter] - public static int Bar(SomeClassWithCustomFormatter _) - { - return -1; - } + // Arrange + var formatter = new FormatterUsingAddLine(); + using var _ = new FormatterScope(formatter); - [ValueFormatter] - public static void Foo(SomeClassWithCustomFormatter value, FormattedObjectGraph output) - { - output.AddFragment("Property = " + value.Property); - } + // Act + string result = Formatter.ToString(null); - [ValueFormatter] - [SuppressMessage("ReSharper", "CA1801")] - public static void Foo(SomeOtherClassWithCustomFormatter _, FormattedObjectGraph output) - { - throw new XunitException("Should never be called"); - } + // Assert + result.Should().Be( + """ + first fragment + separate line + last fragment + """); + } - [ValueFormatter] - public static void Foo(SomeClassInheritedFromClassWithCustomFormatterLvl2 value, FormattedObjectGraph output) - { - output.AddFragment("Property is " + value.Property); - } + private class FormatterUsingAddLine : IValueFormatter + { + public bool CanHandle(object value) => true; - [ValueFormatter] - public static void Foo2(SomeClassInheritedFromClassWithCustomFormatterLvl2 value, FormattedObjectGraph output) + public void Format(object value, FormattedObjectGraph formattedGraph, FormattingContext context, FormatChild formatChild) { - output.AddFragment("Property is " + value.Property); + formattedGraph.AddFragment("first fragment"); + formattedGraph.AddLine("separate line"); + formattedGraph.AddFragment("last fragment"); } } [Fact] - public void When_defining_a_custom_value_formatter_it_should_respect_the_overrides() + public void A_formatter_can_insert_a_line_or_fragment() { // Arrange - var value = new CustomClass(); - var formatter = new CustomClassValueFormatter(); + var formatter = new FormatterUsingInsertLineOrFragment(); using var _ = new FormatterScope(formatter); // Act - string str = Formatter.ToString(value); + string result = Formatter.ToString(null); // Assert - str.Should().Match( - "*CustomClass" + Environment.NewLine + - "{" + Environment.NewLine + - " IntProperty = 0" + Environment.NewLine + - "}*"); + result.Should().Be("fragment"); } - private class CustomClass + private class FormatterUsingInsertLineOrFragment : IValueFormatter { - public int IntProperty { get; set; } + public bool CanHandle(object value) => true; - public string StringProperty { get; set; } + public void Format(object value, FormattedObjectGraph formattedGraph, FormattingContext context, FormatChild formatChild) + { + formattedGraph.GetAnchor().InsertLineOrFragment("fragment"); + } } - private class CustomClassValueFormatter : DefaultValueFormatter + [Fact] + public void A_formatter_can_use_an_anchor_on_an_empty_graph() { - protected override int SpacesPerIndentionLevel => 8; + using var _ = new FormatterScope(new FormatterUsingInsertFragment()); - public override bool CanHandle(object value) => value is CustomClass; + // Act + string result = Formatter.ToString(null); - protected override MemberInfo[] GetMembers(Type type) + // Assert + result.Should().Be("fragment"); + } + + private class FormatterUsingInsertFragment : IValueFormatter + { + public bool CanHandle(object value) => true; + + public void Format(object value, FormattedObjectGraph formattedGraph, FormattingContext context, FormatChild formatChild) { - return base - .GetMembers(type) - .Where(e => e.GetUnderlyingType() != typeof(string)) - .ToArray(); + formattedGraph.GetAnchor().InsertFragment("fragment"); } - - protected override string TypeDisplayName(Type type) => type.Name; } [Fact] - public void When_defining_a_custom_enumerable_value_formatter_it_should_respect_the_overrides() + public void Can_insert_a_fragment_when_using_linebreaks() { - // Arrange - var values = new CustomClass[] - { - new() { IntProperty = 1 }, - new() { IntProperty = 2 } - }; - - var formatter = new SingleItemValueFormatter(); - using var _ = new FormatterScope(formatter); + using var _ = new FormatterScope(new InsertUsingLinebreaksFormatter()); // Act - string str = Formatter.ToString(values); - - str.Should().Match(Environment.NewLine + - "{*FluentAssertions*FormatterSpecs+CustomClass" + Environment.NewLine + - " {" + Environment.NewLine + - " IntProperty = 1, " + Environment.NewLine + - " StringProperty = " + Environment.NewLine + - " },*…1 more…*}*"); - } + string result = Formatter.ToString(null); - private class SingleItemValueFormatter : EnumerableValueFormatter - { - protected override int MaxItems => 1; - - public override bool CanHandle(object value) => value is IEnumerable; + // Assert + result.Should().Be("fragment"); } - private sealed class FormatterScope : IDisposable + private class InsertUsingLinebreaksFormatter : IValueFormatter { - private readonly IValueFormatter formatter; + public bool CanHandle(object value) => true; - public FormatterScope(IValueFormatter formatter) + public void Format(object value, FormattedObjectGraph formattedGraph, FormattingContext context, FormatChild formatChild) { - this.formatter = formatter; - Formatter.AddFormatter(formatter); + Anchor anchor = formattedGraph.GetAnchor(); + anchor.UseLineBreaks = true; + anchor.InsertFragment("fragment"); } - - public void Dispose() => Formatter.RemoveFormatter(formatter); } -} -// Due to the tests that call Configuration.Current -[CollectionDefinition("FormatterSpecs", DisableParallelization = true)] -public class FormatterSpecsDefinition; + public void Dispose() => AssertionEngine.ResetToDefaults(); +} internal class ExceptionThrowingClass { @@ -1249,7 +1253,7 @@ internal class Node { public Node() { - Children = new List(); + Children = []; } public static Node Default { get; } = new(); diff --git a/Tests/FluentAssertions.Specs/Formatting/FormatterSpecsDefinition.cs b/Tests/FluentAssertions.Specs/Formatting/FormatterSpecsDefinition.cs new file mode 100644 index 0000000000..bf90d4e8f9 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Formatting/FormatterSpecsDefinition.cs @@ -0,0 +1,7 @@ +using Xunit; + +namespace FluentAssertions.Specs.Formatting; + +// Due to the tests that (temporarily) modify the active formatters collection. +[CollectionDefinition("FormatterSpecs", DisableParallelization = true)] +public class FormatterSpecsDefinition; diff --git a/Tests/FluentAssertions.Specs/Formatting/JsonFormatterSpecs.cs b/Tests/FluentAssertions.Specs/Formatting/JsonFormatterSpecs.cs new file mode 100644 index 0000000000..5a60cfc863 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Formatting/JsonFormatterSpecs.cs @@ -0,0 +1,102 @@ +#if NET6_0_OR_GREATER + +using System.Text.Json.Nodes; +using FluentAssertions.Formatting; +using Xunit; +using Formatter = FluentAssertions.Formatting.Formatter; + +namespace FluentAssertions.Specs.Formatting; + +public class JsonFormatterSpecs +{ + [Fact] + public void Can_format_a_json_node() + { + // Arrange + var node = JsonNode.Parse( + """ + { + "id": 123, + "name": "Product", + "price": 99.99, + "isAvailable": true, + "tags": ["electronics", "gadget"], + "metadata": { + "createdBy": "admin", + "createdAt": null, + "settings": { + "visible": true, + "priority": 1 + } + }, + "variants": [] + } + """); + + // Act + string result = Formatter.ToString(node); + + // Assert + result.Should().Be( + """ + {"id":123,"name":"Product","price":99.99,"isAvailable":true,"tags":["electronics","gadget"],"metadata":{"createdBy":"admin","createdAt":null,"settings":{"visible":true,"priority":1}},"variants":[]} + """); + } + + [Fact] + public void Can_format_a_json_node_using_line_breaks() + { + // Arrange + var node = JsonNode.Parse( + """ + { + "id": 123, + "name": "Product", + "price": 99.99, + "isAvailable": true, + "tags": ["electronics", "gadget"], + "metadata": { + "createdBy": "admin", + "createdAt": null, + "settings": { + "visible": true, + "priority": 1 + } + }, + "variants": [] + } + """); + + // Act + string result = Formatter.ToString(node, new FormattingOptions + { + UseLineBreaks = true + }); + + // Assert + result.Should().Be( + """ + { + "id": 123, + "name": "Product", + "price": 99.99, + "isAvailable": true, + "tags": [ + "electronics", + "gadget" + ], + "metadata": { + "createdBy": "admin", + "createdAt": null, + "settings": { + "visible": true, + "priority": 1 + } + }, + "variants": [] + } + """); + } +} + +#endif diff --git a/Tests/FluentAssertions.Specs/Formatting/MultidimensionalArrayFormatterSpecs.cs b/Tests/FluentAssertions.Specs/Formatting/MultidimensionalArrayFormatterSpecs.cs index d9ab77b8b6..a2dde8f348 100644 --- a/Tests/FluentAssertions.Specs/Formatting/MultidimensionalArrayFormatterSpecs.cs +++ b/Tests/FluentAssertions.Specs/Formatting/MultidimensionalArrayFormatterSpecs.cs @@ -4,7 +4,6 @@ namespace FluentAssertions.Specs.Formatting; -[Collection("FormatterSpecs")] public class MultidimensionalArrayFormatterSpecs { [Theory] @@ -54,8 +53,8 @@ public void When_formatting_a_multi_dimensional_array_it_should_show_structure(o public void When_formatting_a_multi_dimensional_array_with_bounds_it_should_show_structure() { // Arrange - var lengthsArray = new[] { 2, 3, 4 }; - var boundsArray = new[] { 1, 5, 7 }; + int[] lengthsArray = [2, 3, 4]; + int[] boundsArray = [1, 5, 7]; var value = Array.CreateInstance(typeof(string), lengthsArray, boundsArray); for (int i = value.GetLowerBound(0); i <= value.GetUpperBound(0); i++) @@ -64,7 +63,7 @@ public void When_formatting_a_multi_dimensional_array_with_bounds_it_should_show { for (int k = value.GetLowerBound(2); k <= value.GetUpperBound(2); k++) { - var indices = new[] { i, j, k }; + int[] indices = [i, j, k]; value.SetValue($"{i}-{j}-{k}", indices); } } @@ -76,6 +75,6 @@ public void When_formatting_a_multi_dimensional_array_with_bounds_it_should_show // Assert result.Should().Match( "{{{'1-5-7', '1-5-8', '1-5-9', '1-5-10'}, {'1-6-7', '1-6-8', '1-6-9', '1-6-10'}, {'1-7-7', '1-7-8', '1-7-9', '1-7-10'}}, {{'2-5-7', '2-5-8', '2-5-9', '2-5-10'}, {'2-6-7', '2-6-8', '2-6-9', '2-6-10'}, {'2-7-7', '2-7-8', '2-7-9', '2-7-10'}}}" - .Replace("'", "\"")); + .Replace('\'', '"')); } } diff --git a/Tests/FluentAssertions.Specs/Formatting/PredicateLambdaExpressionValueFormatterSpecs.cs b/Tests/FluentAssertions.Specs/Formatting/PredicateLambdaExpressionValueFormatterSpecs.cs index b70ce9c4e5..f8ac7d281e 100644 --- a/Tests/FluentAssertions.Specs/Formatting/PredicateLambdaExpressionValueFormatterSpecs.cs +++ b/Tests/FluentAssertions.Specs/Formatting/PredicateLambdaExpressionValueFormatterSpecs.cs @@ -92,7 +92,7 @@ public void When_condition_contains_extension_method_then_extension_method_must_ public void When_condition_contains_linq_extension_method_then_extension_method_must_be_formatted() { // Arrange - var allowed = new[] { 1, 2, 3 }; + int[] allowed = [1, 2, 3]; // Act string result = Format(a => allowed.Contains(a)); diff --git a/Tests/FluentAssertions.Specs/Numeric/ComparableSpecs.cs b/Tests/FluentAssertions.Specs/Numeric/ComparableSpecs.cs index 5fd324b210..7f02063c19 100644 --- a/Tests/FluentAssertions.Specs/Numeric/ComparableSpecs.cs +++ b/Tests/FluentAssertions.Specs/Numeric/ComparableSpecs.cs @@ -236,7 +236,7 @@ public void When_two_instances_are_not_equivalent_it_should_throw() act .Should().Throw() .WithMessage( - "Expectation has property subject.SomeOtherProperty*that the other object does not have*"); + "Expectation has property SomeOtherProperty*that the other object does not have*"); } } @@ -556,6 +556,17 @@ public void When_subject_is_less_than_another_subject_and_less_than_or_equal_is_ // Assert act.Should().NotThrow(); } + + [Fact] + public void Chaining_after_one_assertion() + { + // Arrange + var subject = new ComparableOfString("World"); + var other = new ComparableOfString("World"); + + // Act / Assert + subject.Should().BeLessThanOrEqualTo(other).And.NotBeNull(); + } } public class BeGreaterThan @@ -650,6 +661,17 @@ public void When_subject_is_greater_than_another_subject_and_greater_than_or_equ // Assert act.Should().NotThrow(); } + + [Fact] + public void Chaining_after_one_assertion() + { + // Arrange + var subject = new ComparableOfString("def"); + var other = new ComparableOfString("def"); + + // Act / Assert + subject.Should().BeGreaterThanOrEqualTo(other).And.NotBeNull(); + } } } diff --git a/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.Be.cs b/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.Be.cs new file mode 100644 index 0000000000..09b6d8f624 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.Be.cs @@ -0,0 +1,114 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Numeric; + +public partial class NullableNumericAssertionSpecs +{ + public class Be + { + [Fact] + public void Should_succeed_when_asserting_nullable_numeric_value_equals_an_equal_value() + { + // Arrange + int? nullableIntegerA = 1; + int? nullableIntegerB = 1; + + // Act / Assert + nullableIntegerA.Should().Be(nullableIntegerB); + } + + [Fact] + public void Should_succeed_when_asserting_nullable_numeric_null_value_equals_null() + { + // Arrange + int? nullableIntegerA = null; + int? nullableIntegerB = null; + + // Act / Assert + nullableIntegerA.Should().Be(nullableIntegerB); + } + + [Fact] + public void Should_fail_when_asserting_nullable_numeric_value_equals_a_different_value() + { + // Arrange + int? nullableIntegerA = 1; + int? nullableIntegerB = 2; + + // Act + Action act = () => nullableIntegerA.Should().Be(nullableIntegerB); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Should_fail_with_descriptive_message_when_asserting_nullable_numeric_value_equals_a_different_value() + { + // Arrange + int? nullableIntegerA = 1; + int? nullableIntegerB = 2; + + // Act + Action act = () => + nullableIntegerA.Should().Be(nullableIntegerB, "because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw() + .WithMessage("Expected*2 because we want to test the failure message, but found 1."); + } + + [Fact] + public void Nan_is_never_equal_to_a_normal_float() + { + // Arrange + float? value = float.NaN; + + // Act + Action act = () => value.Should().Be(3.4F); + + // Assert + act + .Should().Throw() + .WithMessage( + "Expected value to be *3.4F, but found NaN*"); + } + + [Fact] + public void NaN_can_be_compared_to_NaN_when_its_a_float() + { + // Arrange + float? value = float.NaN; + + // Act + value.Should().Be(float.NaN); + } + + [Fact] + public void Nan_is_never_equal_to_a_normal_double() + { + // Arrange + double? value = double.NaN; + + // Act + Action act = () => value.Should().Be(3.4D); + + // Assert + act + .Should().Throw() + .WithMessage("Expected value to be *3.4, but found NaN*"); + } + + [Fact] + public void NaN_can_be_compared_to_NaN_when_its_a_double() + { + // Arrange + double? value = double.NaN; + + // Act + value.Should().Be(double.NaN); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.BeApproximately.cs b/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.BeApproximately.cs new file mode 100644 index 0000000000..97026a8ac7 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.BeApproximately.cs @@ -0,0 +1,957 @@ +using System; +using FluentAssertions.Execution; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Numeric; + +public partial class NullableNumericAssertionSpecs +{ + public class BeApproximately + { + [Fact] + public void When_approximating_a_nullable_double_with_a_negative_precision_it_should_throw() + { + // Arrange + double? value = 3.1415927; + + // Act + Action act = () => value.Should().BeApproximately(3.14, -0.1); + + // Assert + act.Should().Throw() + .WithParameterName("precision") + .WithMessage("*must be non-negative*"); + } + + [Fact] + public void When_approximating_two_nullable_doubles_with_a_negative_precision_it_should_throw() + { + // Arrange + double? value = 3.1415927; + double? expected = 3.14; + + // Act + Action act = () => value.Should().BeApproximately(expected, -0.1); + + // Assert + act.Should().Throw() + .WithParameterName("precision") + .WithMessage("*must be non-negative*"); + } + + [Fact] + public void When_nullable_double_is_indeed_approximating_a_value_it_should_not_throw() + { + // Arrange + double? value = 3.1415927; + + // Act + Action act = () => value.Should().BeApproximately(3.14, 0.1); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_nullable_double_is_indeed_approximating_a_nullable_value_it_should_not_throw() + { + // Arrange + double? value = 3.1415927; + double? expected = 3.142; + + // Act + Action act = () => value.Should().BeApproximately(expected, 0.1); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_nullable_double_is_null_approximating_a_nullable_null_value_it_should_not_throw() + { + // Arrange + double? value = null; + double? expected = null; + + // Act + Action act = () => value.Should().BeApproximately(expected, 0.1); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_nullable_double_with_value_is_not_approximating_a_non_null_nullable_value_it_should_throw() + { + // Arrange + double? value = 13; + double? expected = 12; + + // Act + Action act = () => value.Should().BeApproximately(expected, 0.1); + + // Assert + act.Should().Throw().WithMessage("Expected*12.0*0.1*13.0*"); + } + + [Fact] + public void When_nullable_double_is_null_approximating_a_non_null_nullable_value_it_should_throw() + { + // Arrange + double? value = null; + double? expected = 12; + + // Act + Action act = () => value.Should().BeApproximately(expected, 0.1); + + // Assert + act.Should().Throw().WithMessage( + "Expected value to approximate 12.0 +/- 0.1, but it was ."); + } + + [Fact] + public void When_nullable_double_is_not_null_approximating_a_null_value_it_should_throw() + { + // Arrange + double? value = 12; + double? expected = null; + + // Act + Action act = () => value.Should().BeApproximately(expected, 0.1); + + // Assert + act.Should().Throw().WithMessage( + "Expected value to approximate +/- 0.1, but it was 12.0."); + } + + [Fact] + public void When_nullable_double_has_no_value_it_should_throw() + { + // Arrange + double? value = null; + + // Act + Action act = () => + { + using var _ = new AssertionScope(); + value.Should().BeApproximately(3.14, 0.001); + }; + + // Assert + act.Should().Throw().WithMessage( + "Expected value to approximate 3.14 +/- 0.001, but it was ."); + } + + [Fact] + public void When_nullable_double_is_not_approximating_a_value_it_should_throw() + { + // Arrange + double? value = 3.1415927F; + + // Act + Action act = () => value.Should().BeApproximately(1.0, 0.1); + + // Assert + act + .Should().Throw() + .WithMessage("Expected value to approximate 1.0 +/- 0.1, but 3.14* differed by*"); + } + + [Fact] + public void A_double_cannot_approximate_NaN() + { + // Arrange + double? value = 3.1415927F; + + // Act + Action act = () => value.Should().BeApproximately(double.NaN, 0.1); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void When_approximating_a_nullable_float_with_a_negative_precision_it_should_throw() + { + // Arrange + float? value = 3.1415927F; + + // Act + Action act = () => value.Should().BeApproximately(3.14F, -0.1F); + + // Assert + act.Should().Throw() + .WithParameterName("precision") + .WithMessage("*must be non-negative*"); + } + + [Fact] + public void When_approximating_two_nullable_floats_with_a_negative_precision_it_should_throw() + { + // Arrange + float? value = 3.1415927F; + float? expected = 3.14F; + + // Act + Action act = () => value.Should().BeApproximately(expected, -0.1F); + + // Assert + act.Should().Throw() + .WithParameterName("precision") + .WithMessage("*must be non-negative*"); + } + + [Fact] + public void When_nullable_float_is_indeed_approximating_a_value_it_should_not_throw() + { + // Arrange + float? value = 3.1415927F; + + // Act + Action act = () => value.Should().BeApproximately(3.14F, 0.1F); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_nullable_float_is_indeed_approximating_a_nullable_value_it_should_not_throw() + { + // Arrange + float? value = 3.1415927f; + float? expected = 3.142f; + + // Act + Action act = () => value.Should().BeApproximately(expected, 0.1f); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_nullable_float_is_null_approximating_a_nullable_null_value_it_should_not_throw() + { + // Arrange + float? value = null; + float? expected = null; + + // Act + Action act = () => value.Should().BeApproximately(expected, 0.1f); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_nullable_float_with_value_is_not_approximating_a_non_null_nullable_value_it_should_throw() + { + // Arrange + float? value = 13; + float? expected = 12; + + // Act + Action act = () => value.Should().BeApproximately(expected, 0.1f); + + // Assert + act.Should().Throw().WithMessage("Expected*12*0.1*13*"); + } + + [Fact] + public void When_nullable_float_is_null_approximating_a_non_null_nullable_value_it_should_throw() + { + // Arrange + float? value = null; + float? expected = 12; + + // Act + Action act = () => value.Should().BeApproximately(expected, 0.1f); + + // Assert + act.Should().Throw().WithMessage( + "Expected value to approximate 12F +/- 0.1F, but it was ."); + } + + [Fact] + public void When_nullable_float_is_not_null_approximating_a_null_value_it_should_throw() + { + // Arrange + float? value = 12; + float? expected = null; + + // Act + Action act = () => value.Should().BeApproximately(expected, 0.1f); + + // Assert + act.Should().Throw().WithMessage( + "Expected value to approximate +/- 0.1F, but it was 12F."); + } + + [Fact] + public void When_nullable_float_has_no_value_it_should_throw() + { + // Arrange + float? value = null; + + // Act + Action act = () => + { + using var _ = new AssertionScope(); + value.Should().BeApproximately(3.14F, 0.001F); + }; + + // Assert + act.Should().Throw().WithMessage( + "Expected value to approximate 3.14F +/- 0.001F, but it was ."); + } + + [Fact] + public void When_nullable_float_is_not_approximating_a_value_it_should_throw() + { + // Arrange + float? value = 3.1415927F; + + // Act + Action act = () => value.Should().BeApproximately(1.0F, 0.1F); + + // Assert + act + .Should().Throw() + .WithMessage( + "Expected value to approximate *1* +/- *0.1* but 3.14* differed by*"); + } + + [Fact] + public void A_float_cannot_approximate_NaN() + { + // Arrange + float? value = 3.1415927F; + + // Act + Action act = () => value.Should().BeApproximately(float.NaN, 0.1F); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void When_approximating_a_nullable_decimal_with_a_negative_precision_it_should_throw() + { + // Arrange + decimal? value = 3.1415927m; + + // Act + Action act = () => value.Should().BeApproximately(3.14m, -0.1m); + + // Assert + act.Should().Throw() + .WithParameterName("precision") + .WithMessage("*must be non-negative*"); + } + + [Fact] + public void When_approximating_two_nullable_decimals_with_a_negative_precision_it_should_throw() + { + // Arrange + decimal? value = 3.1415927m; + decimal? expected = 3.14m; + + // Act + Action act = () => value.Should().BeApproximately(expected, -0.1m); + + // Assert + act.Should().Throw() + .WithParameterName("precision") + .WithMessage("*must be non-negative*"); + } + + [Fact] + public void When_nullable_decimal_is_indeed_approximating_a_value_it_should_not_throw() + { + // Arrange + decimal? value = 3.1415927m; + + // Act + Action act = () => value.Should().BeApproximately(3.14m, 0.1m); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_nullable_decimal_is_indeed_approximating_a_nullable_value_it_should_not_throw() + { + // Arrange + decimal? value = 3.1415927m; + decimal? expected = 3.142m; + + // Act + Action act = () => value.Should().BeApproximately(expected, 0.1m); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_nullable_decimal_is_null_approximating_a_nullable_null_value_it_should_not_throw() + { + // Arrange + decimal? value = null; + decimal? expected = null; + + // Act + Action act = () => value.Should().BeApproximately(expected, 0.1m); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_nullable_decimal_with_value_is_not_approximating_a_non_null_nullable_value_it_should_throw() + { + // Arrange + decimal? value = 13; + decimal? expected = 12; + + // Act + Action act = () => value.Should().BeApproximately(expected, 0.1m); + + // Assert + act.Should().Throw().WithMessage("Expected*12*0.1*13*"); + } + + [Fact] + public void When_nullable_decimal_is_null_approximating_a_non_null_nullable_value_it_should_throw() + { + // Arrange + decimal? value = null; + decimal? expected = 12; + + // Act + Action act = () => value.Should().BeApproximately(expected, 0.1m); + + // Assert + act.Should().Throw().WithMessage( + "Expected value to approximate 12M +/- 0.1M, but it was ."); + } + + [Fact] + public void When_nullable_decimal_is_not_null_approximating_a_null_value_it_should_throw() + { + // Arrange + decimal? value = 12; + decimal? expected = null; + + // Act + Action act = () => value.Should().BeApproximately(expected, 0.1m); + + // Assert + act.Should().Throw().WithMessage( + "Expected value to approximate +/- 0.1M, but it was 12M."); + } + + [Fact] + public void When_nullable_decimal_has_no_value_it_should_throw() + { + // Arrange + decimal? value = null; + + // Act + Action act = () => + { + using var _ = new AssertionScope(); + value.Should().BeApproximately(3.14m, 0.001m); + }; + + // Assert + act.Should().Throw() + .WithMessage("Expected value to approximate*3.14* +/-*0.001*, but it was ."); + } + + [Fact] + public void When_nullable_decimal_is_not_approximating_a_value_it_should_throw() + { + // Arrange + decimal? value = 3.1415927m; + + // Act + Action act = () => value.Should().BeApproximately(1.0m, 0.1m); + + // Assert + act + .Should().Throw() + .WithMessage("Expected value to approximate*1.0* +/-*0.1*, but 3.14* differed by*"); + } + } + + public class NotBeApproximately + { + [Fact] + public void When_not_approximating_a_nullable_double_with_a_negative_precision_it_should_throw() + { + // Arrange + double? value = 3.1415927; + + // Act + Action act = () => value.Should().NotBeApproximately(3.14, -0.1); + + // Assert + act.Should().Throw() + .WithParameterName("precision") + .WithMessage("*must be non-negative*"); + } + + [Fact] + public void When_not_approximating_two_nullable_doubles_with_a_negative_precision_it_should_throw() + { + // Arrange + double? value = 3.1415927; + double? expected = 3.14; + + // Act + Action act = () => value.Should().NotBeApproximately(expected, -0.1); + + // Assert + act.Should().Throw() + .WithParameterName("precision") + .WithMessage("*must be non-negative*"); + } + + [Fact] + public void When_asserting_not_approximately_and_nullable_double_is_not_approximating_a_value_it_should_not_throw() + { + // Arrange + double? value = 3.1415927; + + // Act + Action act = () => value.Should().NotBeApproximately(1.0, 0.1); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_not_approximately_and_nullable_double_has_no_value_it_should_throw() + { + // Arrange + double? value = null; + + // Act + Action act = () => value.Should().NotBeApproximately(3.14, 0.001); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_not_approximately_and_nullable_double_is_indeed_approximating_a_value_it_should_throw() + { + // Arrange + double? value = 3.1415927; + + // Act + Action act = () => value.Should().NotBeApproximately(3.14, 0.1); + + // Assert + act + .Should().Throw() + .WithMessage("Expected value to not approximate 3.14 +/- 0.1, but 3.14*only differed by*"); + } + + [Fact] + public void + When_asserting_not_approximately_and_nullable_double_is_not_approximating_a_nullable_value_it_should_not_throw() + { + // Arrange + double? value = 3.1415927; + double? expected = 1.0; + + // Act + Action act = () => value.Should().NotBeApproximately(expected, 0.1); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_not_approximately_and_nullable_double_is_not_approximating_a_null_value_it_should_throw() + { + // Arrange + double? value = 3.1415927; + double? expected = null; + + // Act + Action act = () => value.Should().NotBeApproximately(expected, 0.1); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void + When_asserting_not_approximately_and_null_double_is_not_approximating_a_nullable_double_value_it_should_throw() + { + // Arrange + double? value = null; + double? expected = 20.0; + + // Act + Action act = () => value.Should().NotBeApproximately(expected, 0.1); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_not_approximately_and_null_double_is_not_approximating_a_null_value_it_should_not_throw() + { + // Arrange + double? value = null; + double? expected = null; + + // Act + Action act = () => value.Should().NotBeApproximately(expected, 0.1); + + // Assert + act.Should().Throw() + .WithMessage("Expected*null*0.1*but*null*"); + } + + [Fact] + public void When_asserting_not_approximately_and_nullable_double_is_approximating_a_nullable_value_it_should_throw() + { + // Arrange + double? value = 3.1415927; + double? expected = 3.1; + + // Act + Action act = () => value.Should().NotBeApproximately(expected, 0.1F); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void A_double_cannot_approximate_NaN() + { + // Arrange + double? value = 3.1415927F; + + // Act + Action act = () => value.Should().NotBeApproximately(double.NaN, 0.1); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void When_not_approximating_a_nullable_float_with_a_negative_precision_it_should_throw() + { + // Arrange + float? value = 3.1415927F; + + // Act + Action act = () => value.Should().NotBeApproximately(3.14F, -0.1F); + + // Assert + act.Should().Throw() + .WithParameterName("precision") + .WithMessage("*must be non-negative*"); + } + + [Fact] + public void When_not_approximating_two_nullable_floats_with_a_negative_precision_it_should_throw() + { + // Arrange + float? value = 3.1415927F; + float? expected = 3.14F; + + // Act + Action act = () => value.Should().NotBeApproximately(expected, -0.1F); + + // Assert + act.Should().Throw() + .WithParameterName("precision") + .WithMessage("*must be non-negative*"); + } + + [Fact] + public void When_asserting_not_approximately_and_nullable_float_is_not_approximating_a_value_it_should_not_throw() + { + // Arrange + float? value = 3.1415927F; + + // Act + Action act = () => value.Should().NotBeApproximately(1.0F, 0.1F); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_not_approximately_and_nullable_float_has_no_value_it_should_throw() + { + // Arrange + float? value = null; + + // Act + Action act = () => value.Should().NotBeApproximately(3.14F, 0.001F); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_not_approximately_and_nullable_float_is_indeed_approximating_a_value_it_should_throw() + { + // Arrange + float? value = 3.1415927F; + + // Act + Action act = () => value.Should().NotBeApproximately(3.14F, 0.1F); + + // Assert + act + .Should().Throw() + .WithMessage("Expected value to not approximate *3.14F* +/- *0.1F* but 3.14* only differed by*"); + } + + [Fact] + public void + When_asserting_not_approximately_and_nullable_float_is_not_approximating_a_nullable_value_it_should_not_throw() + { + // Arrange + float? value = 3.1415927F; + float? expected = 1.0F; + + // Act + Action act = () => value.Should().NotBeApproximately(expected, 0.1F); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_not_approximately_and_nullable_float_is_not_approximating_a_null_value_it_should_throw() + { + // Arrange + float? value = 3.1415927F; + float? expected = null; + + // Act + Action act = () => value.Should().NotBeApproximately(expected, 0.1F); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void + When_asserting_not_approximately_and_null_float_is_not_approximating_a_nullable_float_value_it_should_throw() + { + // Arrange + float? value = null; + float? expected = 20.0f; + + // Act + Action act = () => value.Should().NotBeApproximately(expected, 0.1F); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_not_approximately_and_null_float_is_not_approximating_a_null_value_it_should_not_throw() + { + // Arrange + float? value = null; + float? expected = null; + + // Act + Action act = () => value.Should().NotBeApproximately(expected, 0.1F); + + // Assert + act.Should().Throw("Expected**+/-*0.1F**"); + } + + [Fact] + public void When_asserting_not_approximately_and_nullable_float_is_approximating_a_nullable_value_it_should_throw() + { + // Arrange + float? value = 3.1415927F; + float? expected = 3.1F; + + // Act + Action act = () => value.Should().NotBeApproximately(expected, 0.1F); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void A_float_cannot_approximate_NaN() + { + // Arrange + float? value = 3.1415927F; + + // Act + Action act = () => value.Should().NotBeApproximately(float.NaN, 0.1F); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void When_not_approximating_a_nullable_decimal_with_a_negative_precision_it_should_throw() + { + // Arrange + decimal? value = 3.1415927m; + + // Act + Action act = () => value.Should().NotBeApproximately(3.14m, -0.1m); + + // Assert + act.Should().Throw() + .WithParameterName("precision") + .WithMessage("*must be non-negative*"); + } + + [Fact] + public void When_not_approximating_two_nullable_decimals_with_a_negative_precision_it_should_throw() + { + // Arrange + decimal? value = 3.1415927m; + decimal? expected = 3.14m; + + // Act + Action act = () => value.Should().NotBeApproximately(expected, -0.1m); + + // Assert + act.Should().Throw() + .WithParameterName("precision") + .WithMessage("*must be non-negative*"); + } + + [Fact] + public void When_asserting_not_approximately_and_nullable_decimal_is_not_approximating_a_value_it_should_not_throw() + { + // Arrange + decimal? value = 3.1415927m; + + // Act + Action act = () => value.Should().NotBeApproximately(1.0m, 0.1m); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_not_approximately_and_nullable_decimal_has_no_value_it_should_throw() + { + // Arrange + decimal? value = null; + + // Act + Action act = () => value.Should().NotBeApproximately(3.14m, 0.001m); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_not_approximately_and_nullable_decimal_is_indeed_approximating_a_value_it_should_throw() + { + // Arrange + decimal? value = 3.1415927m; + + // Act + Action act = () => value.Should().NotBeApproximately(3.14m, 0.1m); + + // Assert + act + .Should().Throw() + .WithMessage("Expected value to not approximate*3.14* +/-*0.1*, but*3.14*only differed by*"); + } + + [Fact] + public void + When_asserting_not_approximately_and_nullable_decimal_is_not_approximating_a_nullable_value_it_should_not_throw() + { + // Arrange + decimal? value = 3.1415927m; + decimal? expected = 1.0m; + + // Act + Action act = () => value.Should().NotBeApproximately(expected, 0.1m); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_not_approximately_and_nullable_decimal_is_not_approximating_a_null_value_it_should_throw() + { + // Arrange + decimal? value = 3.1415927m; + decimal? expected = null; + + // Act + Action act = () => value.Should().NotBeApproximately(expected, 0.1m); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void + When_asserting_not_approximately_and_null_decimal_is_not_approximating_a_nullable_decimal_value_it_should_throw() + { + // Arrange + decimal? value = null; + decimal? expected = 20.0m; + + // Act + Action act = () => value.Should().NotBeApproximately(expected, 0.1m); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_not_approximately_and_null_decimal_is_not_approximating_a_null_value_it_should_not_throw() + { + // Arrange + decimal? value = null; + decimal? expected = null; + + // Act + Action act = () => value.Should().NotBeApproximately(expected, 0.1m); + + // Assert + act.Should().Throw() + .WithMessage("Expected**0.1M**"); + } + + [Fact] + public void When_asserting_not_approximately_and_nullable_decimal_is_approximating_a_nullable_value_it_should_throw() + { + // Arrange + decimal? value = 3.1415927m; + decimal? expected = 3.1m; + + // Act + Action act = () => value.Should().NotBeApproximately(expected, 0.1m); + + // Assert + act.Should().Throw(); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.BeGreaterThan.cs b/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.BeGreaterThan.cs new file mode 100644 index 0000000000..026c01aa92 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.BeGreaterThan.cs @@ -0,0 +1,201 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Numeric; + +public partial class NullableNumericAssertionSpecs +{ + public class BeGreaterThan + { + [Fact] + public void A_float_can_never_be_greater_than_NaN() + { + // Arrange + float? value = 3.4F; + + // Act + Action act = () => value.Should().BeGreaterThan(float.NaN); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void NaN_is_never_greater_than_another_float() + { + // Arrange + float? value = float.NaN; + + // Act + Action act = () => value.Should().BeGreaterThan(0); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void A_double_can_never_be_greater_than_NaN() + { + // Arrange + double? value = 3.4F; + + // Act + Action act = () => value.Should().BeGreaterThan(double.NaN); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void NaN_is_never_greater_than_another_double() + { + // Arrange + double? value = double.NaN; + + // Act + Action act = () => value.Should().BeGreaterThan(0); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Theory] + [InlineData(5, 5)] + [InlineData(1, 10)] + [InlineData(0, 5)] + [InlineData(0, 0)] + [InlineData(-1, 5)] + [InlineData(-1, -1)] + [InlineData(10, 10)] + public void To_test_the_null_path_for_difference_on_nullable_int(int? subject, int expectation) + { + // Arrange + // Act + Action act = () => subject.Should().BeGreaterThan(expectation); + + // Assert + act + .Should().Throw() + .Which.Message.Should().NotMatch("*(difference of 0)*"); + } + + [Fact] + public void To_test_the_null_path_for_difference_on_nullable_byte() + { + // Arrange + var value = (byte?)1; + + // Act + Action act = () => value.Should().BeGreaterThan(1); + + // Assert + act + .Should().Throw() + .Which.Message.Should().NotMatch("*(difference of 0)*"); + } + + [Fact] + public void To_test_the_non_null_path_for_difference_on_nullable_byte() + { + // Arrange + var value = (byte?)1; + + // Act + Action act = () => value.Should().BeGreaterThan(2); + + // Assert + act + .Should().Throw() + .Which.Message.Should().NotMatch("*(difference of 0)*"); + } + + [Fact] + public void To_test_the_null_path_for_difference_on_nullable_decimal() + { + // Arrange + var value = (decimal?)11.0; + + // Act + Action act = () => value.Should().BeGreaterThan(11M); + + // Assert + act + .Should().Throw() + .Which.Message.Should().NotMatch("*(difference of 0)*"); + } + + [Fact] + public void To_test_the_null_path_for_difference_on_short() + { + // Arrange + var value = (short?)11; + + // Act + Action act = () => value.Should().BeGreaterThan(11); + + // Assert + act + .Should().Throw() + .Which.Message.Should().NotMatch("*(difference of 0)*"); + } + + [Fact] + public void To_test_the_null_path_for_difference_on_nullable_short() + { + // Arrange + var value = (short?)11; + + // Act + Action act = () => value.Should().BeGreaterThan(11); + + // Assert + act + .Should().Throw() + .Which.Message.Should().NotMatch("*(difference of 0)*"); + } + + [Fact] + public void To_test_the_null_path_for_difference_on_nullable_ushort() + { + // Arrange + var value = (ushort?)11; + + // Act + Action act = () => value.Should().BeGreaterThan(11); + + // Assert + act + .Should().Throw() + .Which.Message.Should().NotMatch("*(difference of 0)*"); + } + + [Theory] + [InlineData(5L, 5L)] + [InlineData(1L, 10L)] + [InlineData(0L, 5L)] + [InlineData(0L, 0L)] + [InlineData(-1L, 5L)] + [InlineData(-1L, -1L)] + [InlineData(10L, 10L)] + public void To_test_the_null_path_for_difference_on_nullable_long(long? subject, long expectation) + { + // Arrange + // Act + Action act = () => subject.Should().BeGreaterThan(expectation); + + // Assert + act + .Should().Throw() + .Which.Message.Should().NotMatch("*(difference of 0)*"); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.BeGreaterThanOrEqualTo.cs b/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.BeGreaterThanOrEqualTo.cs new file mode 100644 index 0000000000..ababf48c24 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.BeGreaterThanOrEqualTo.cs @@ -0,0 +1,71 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Numeric; + +public partial class NullableNumericAssertionSpecs +{ + public class BeGreaterThanOrEqualTo + { + [Fact] + public void A_float_can_never_be_greater_than_or_equal_to_NaN() + { + // Arrange + float? value = 3.4F; + + // Act + Action act = () => value.Should().BeGreaterThanOrEqualTo(float.NaN); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void NaN_is_never_greater_than_or_equal_to_another_float() + { + // Arrange + float? value = float.NaN; + + // Act + Action act = () => value.Should().BeGreaterThanOrEqualTo(0); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void A_double_can_never_be_greater_than_or_equal_to_NaN() + { + // Arrange + double? value = 3.4; + + // Act + Action act = () => value.Should().BeGreaterThanOrEqualTo(double.NaN); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void NaN_is_never_greater_than_or_equal_to_another_double() + { + // Arrange + double? value = double.NaN; + + // Act + Action act = () => value.Should().BeGreaterThanOrEqualTo(0); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.BeInRange.cs b/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.BeInRange.cs new file mode 100644 index 0000000000..62d1ffa151 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.BeInRange.cs @@ -0,0 +1,136 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Numeric; + +public partial class NullableNumericAssertionSpecs +{ + public class BeInRange + { + [Theory] + [InlineData(float.NaN, 5F)] + [InlineData(5F, float.NaN)] + public void A_float_can_never_be_in_a_range_containing_NaN(float minimumValue, float maximumValue) + { + // Arrange + float? value = 4.5F; + + // Act + Action act = () => value.Should().BeInRange(minimumValue, maximumValue); + + // Assert + act + .Should().Throw() + .WithMessage( + "*NaN*"); + } + + [Fact] + public void NaN_is_never_in_range_of_two_floats() + { + // Arrange + float? value = float.NaN; + + // Act + Action act = () => value.Should().BeInRange(4, 5); + + // Assert + act + .Should().Throw() + .WithMessage( + "Expected value to be between*4* and*5*, but found*NaN*"); + } + + [Theory] + [InlineData(double.NaN, 5)] + [InlineData(5, double.NaN)] + public void A_double_can_never_be_in_a_range_containing_NaN(double minimumValue, double maximumValue) + { + // Arrange + double? value = 4.5; + + // Act + Action act = () => value.Should().BeInRange(minimumValue, maximumValue); + + // Assert + act + .Should().Throw() + .WithMessage( + "*NaN*"); + } + + [Fact] + public void NaN_is_never_in_range_of_two_doubles() + { + // Arrange + double? value = double.NaN; + + // Act + Action act = () => value.Should().BeInRange(4, 5); + + // Assert + act + .Should().Throw() + .WithMessage( + "Expected value to be between*4* and*5*, but found*NaN*"); + } + } + + public class NotBeInRange + { + [Theory] + [InlineData(float.NaN, 1F)] + [InlineData(1F, float.NaN)] + public void Cannot_use_NaN_in_a_range_of_floats(float minimumValue, float maximumValue) + { + // Arrange + float? value = 4.5F; + + // Act + Action act = () => value.Should().NotBeInRange(minimumValue, maximumValue); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void NaN_is_never_inside_any_range_of_floats() + { + // Arrange + float? value = float.NaN; + + // Act / Assert + value.Should().NotBeInRange(4, 5); + } + + [Theory] + [InlineData(double.NaN, 1D)] + [InlineData(1D, double.NaN)] + public void Cannot_use_NaN_in_a_range_of_doubles(double minimumValue, double maximumValue) + { + // Arrange + double? value = 4.5D; + + // Act + Action act = () => value.Should().NotBeInRange(minimumValue, maximumValue); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void NaN_is_never_inside_any_range_of_doubles() + { + // Arrange + double? value = double.NaN; + + // Act / Assert + value.Should().NotBeInRange(4, 5); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.BeLessThan.cs b/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.BeLessThan.cs new file mode 100644 index 0000000000..5dcd7aedf9 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.BeLessThan.cs @@ -0,0 +1,103 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Numeric; + +public partial class NullableNumericAssertionSpecs +{ + public class BeLessThan + { + [Fact] + public void A_float_can_never_be_less_than_NaN() + { + // Arrange + float? value = 3.4F; + + // Act + Action act = () => value.Should().BeLessThan(float.NaN); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void NaN_is_never_less_than_another_float() + { + // Arrange + float? value = float.NaN; + + // Act + Action act = () => value.Should().BeLessThan(0); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void A_double_can_never_be_less_than_NaN() + { + // Arrange + double? value = 3.4F; + + // Act + Action act = () => value.Should().BeLessThan(double.NaN); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void NaN_is_never_less_than_another_double() + { + // Arrange + double? value = double.NaN; + + // Act + Action act = () => value.Should().BeLessThan(0); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Theory] + [InlineData(5, -1)] + [InlineData(10, 5)] + [InlineData(10, -1)] + public void To_test_the_remaining_paths_for_difference_on_nullable_int(int? subject, int expectation) + { + // Arrange + // Act + Action act = () => subject.Should().BeLessThan(expectation); + + // Assert + act + .Should().Throw() + .Which.Message.Should().NotMatch("*(difference of 0)*"); + } + + [Theory] + [InlineData(5L, -1L)] + [InlineData(10L, 5L)] + [InlineData(10L, -1L)] + public void To_test_the_remaining_paths_for_difference_on_nullable_long(long? subject, long expectation) + { + // Arrange + // Act + Action act = () => subject.Should().BeLessThan(expectation); + + // Assert + act + .Should().Throw() + .Which.Message.Should().NotMatch("*(difference of 0)*"); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.BeLessThanOrEqualTo.cs b/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.BeLessThanOrEqualTo.cs new file mode 100644 index 0000000000..9c0854369f --- /dev/null +++ b/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.BeLessThanOrEqualTo.cs @@ -0,0 +1,71 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Numeric; + +public partial class NullableNumericAssertionSpecs +{ + public class BeLessThanOrEqualTo + { + [Fact] + public void A_float_can_never_be_less_than_or_equal_to_NaN() + { + // Arrange + float? value = 3.4F; + + // Act + Action act = () => value.Should().BeLessThanOrEqualTo(float.NaN); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void NaN_is_never_less_than_or_equal_to_another_float() + { + // Arrange + float? value = float.NaN; + + // Act + Action act = () => value.Should().BeLessThanOrEqualTo(0); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void A_double_can_never_be_less_than_or_equal_to_NaN() + { + // Arrange + double? value = 3.4; + + // Act + Action act = () => value.Should().BeLessThanOrEqualTo(double.NaN); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void NaN_is_never_less_than_or_equal_to_another_double() + { + // Arrange + double? value = double.NaN; + + // Act + Action act = () => value.Should().BeLessThanOrEqualTo(0); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.BeNegative.cs b/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.BeNegative.cs new file mode 100644 index 0000000000..38ecbc9b99 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.BeNegative.cs @@ -0,0 +1,37 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Numeric; + +public partial class NullableNumericAssertionSpecs +{ + public class BeNegative + { + [Fact] + public void NaN_is_never_a_negative_float() + { + // Arrange + float? value = float.NaN; + + // Act + Action act = () => value.Should().BeNegative(); + + // Assert + act.Should().Throw().WithMessage("*but found NaN*"); + } + + [Fact] + public void NaN_is_never_a_negative_double() + { + // Arrange + double? value = double.NaN; + + // Act + Action act = () => value.Should().BeNegative(); + + // Assert + act.Should().Throw().WithMessage("*but found NaN*"); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.BeNull.cs b/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.BeNull.cs new file mode 100644 index 0000000000..abac3ec134 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.BeNull.cs @@ -0,0 +1,89 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Numeric; + +public partial class NullableNumericAssertionSpecs +{ + public class BeNull + { + [Fact] + public void Should_succeed_when_asserting_nullable_numeric_value_without_a_value_to_be_null() + { + // Arrange + int? nullableInteger = null; + + // Act / Assert + nullableInteger.Should().BeNull(); + } + + [Fact] + public void Should_fail_when_asserting_nullable_numeric_value_with_a_value_to_be_null() + { + // Arrange + int? nullableInteger = 1; + + // Act + Action act = () => nullableInteger.Should().BeNull(); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Should_fail_with_descriptive_message_when_asserting_nullable_numeric_value_with_a_value_to_be_null() + { + // Arrange + int? nullableInteger = 1; + + // Act + Action act = () => nullableInteger.Should().BeNull("because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect a value because we want to test the failure message, but found 1."); + } + } + + public class NotBeNull + { + [Fact] + public void Should_succeed_when_asserting_nullable_numeric_value_with_value_to_not_be_null() + { + // Arrange + int? nullableInteger = 1; + + // Act / Assert + nullableInteger.Should().NotBeNull(); + } + + [Fact] + public void Should_fail_when_asserting_nullable_numeric_value_without_a_value_to_not_be_null() + { + // Arrange + int? nullableInteger = null; + + // Act + Action act = () => nullableInteger.Should().NotBeNull(); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void + Should_fail_with_descriptive_message_when_asserting_nullable_numeric_value_without_a_value_to_not_be_null() + { + // Arrange + int? nullableInteger = null; + + // Act + Action act = () => nullableInteger.Should().NotBeNull("because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw() + .WithMessage("Expected a value because we want to test the failure message."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.BePositive.cs b/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.BePositive.cs new file mode 100644 index 0000000000..c5a21dd73c --- /dev/null +++ b/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.BePositive.cs @@ -0,0 +1,37 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Numeric; + +public partial class NullableNumericAssertionSpecs +{ + public class BePositive + { + [Fact] + public void NaN_is_never_a_positive_float() + { + // Arrange + float? value = float.NaN; + + // Act + Action act = () => value.Should().BePositive(); + + // Assert + act.Should().Throw().WithMessage("*but found NaN*"); + } + + [Fact] + public void NaN_is_never_a_positive_double() + { + // Arrange + double? value = double.NaN; + + // Act + Action act = () => value.Should().BePositive(); + + // Assert + act.Should().Throw().WithMessage("*but found NaN*"); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.HaveValue.cs b/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.HaveValue.cs new file mode 100644 index 0000000000..67346c8d2f --- /dev/null +++ b/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.HaveValue.cs @@ -0,0 +1,90 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Numeric; + +public partial class NullableNumericAssertionSpecs +{ + public class HaveValue + { + [Fact] + public void Should_succeed_when_asserting_nullable_numeric_value_with_value_to_have_a_value() + { + // Arrange + int? nullableInteger = 1; + + // Act / Assert + nullableInteger.Should().HaveValue(); + } + + [Fact] + public void Should_fail_when_asserting_nullable_numeric_value_without_a_value_to_have_a_value() + { + // Arrange + int? nullableInteger = null; + + // Act + Action act = () => nullableInteger.Should().HaveValue(); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void + Should_fail_with_descriptive_message_when_asserting_nullable_numeric_value_without_a_value_to_have_a_value() + { + // Arrange + int? nullableInteger = null; + + // Act + Action act = () => nullableInteger.Should().HaveValue("because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw() + .WithMessage("Expected a value because we want to test the failure message."); + } + } + + public class NotHaveValue + { + [Fact] + public void Should_succeed_when_asserting_nullable_numeric_value_without_a_value_to_not_have_a_value() + { + // Arrange + int? nullableInteger = null; + + // Act / Assert + nullableInteger.Should().NotHaveValue(); + } + + [Fact] + public void Should_fail_when_asserting_nullable_numeric_value_with_a_value_to_not_have_a_value() + { + // Arrange + int? nullableInteger = 1; + + // Act + Action act = () => nullableInteger.Should().NotHaveValue(); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_nullable_value_with_unexpected_value_is_found_it_should_throw_with_message() + { + // Arrange + int? nullableInteger = 1; + + // Act + Action action = () => nullableInteger.Should().NotHaveValue("it was {0} expected", "not"); + + // Assert + action + .Should().Throw() + .WithMessage("Did not expect a value because it was not expected, but found 1."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.Match.cs b/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.Match.cs new file mode 100644 index 0000000000..f1a545a78c --- /dev/null +++ b/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.Match.cs @@ -0,0 +1,51 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Numeric; + +public partial class NullableNumericAssertionSpecs +{ + public class Match + { + [Fact] + public void When_nullable_value_satisfies_predicate_it_should_not_throw() + { + // Arrange + int? nullableInteger = 1; + + // Act / Assert + nullableInteger.Should().Match(o => o.HasValue); + } + + [Fact] + public void When_nullable_value_does_not_match_the_predicate_it_should_throw() + { + // Arrange + int? nullableInteger = 1; + + // Act + Action act = () => + nullableInteger.Should().Match(o => !o.HasValue, "because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw() + .WithMessage( + "Expected value to match Not(o.HasValue) because we want to test the failure message, but found 1."); + } + + [Fact] + public void When_nullable_value_is_matched_against_a_null_it_should_throw() + { + // Arrange + int? nullableInteger = 1; + + // Act + Action act = () => nullableInteger.Should().Match(null); + + // Assert + act.Should().ThrowExactly() + .WithParameterName("predicate"); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.cs index a77f500164..d7dce9c4e7 100644 --- a/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Numeric/NullableNumericAssertionSpecs.cs @@ -1,1710 +1,9 @@ -using System; -using FluentAssertions.Execution; using Xunit; -using Xunit.Sdk; namespace FluentAssertions.Specs.Numeric; -public class NullableNumericAssertionSpecs +public partial class NullableNumericAssertionSpecs { - public class BePositive - { - [Fact] - public void NaN_is_never_a_positive_float() - { - // Arrange - float? value = float.NaN; - - // Act - Action act = () => value.Should().BePositive(); - - // Assert - act.Should().Throw().WithMessage("*but found NaN*"); - } - - [Fact] - public void NaN_is_never_a_positive_double() - { - // Arrange - double? value = double.NaN; - - // Act - Action act = () => value.Should().BePositive(); - - // Assert - act.Should().Throw().WithMessage("*but found NaN*"); - } - } - - public class BeNegative - { - [Fact] - public void NaN_is_never_a_negative_float() - { - // Arrange - float? value = float.NaN; - - // Act - Action act = () => value.Should().BeNegative(); - - // Assert - act.Should().Throw().WithMessage("*but found NaN*"); - } - - [Fact] - public void NaN_is_never_a_negative_double() - { - // Arrange - double? value = double.NaN; - - // Act - Action act = () => value.Should().BeNegative(); - - // Assert - act.Should().Throw().WithMessage("*but found NaN*"); - } - } - - public class BeLessThan - { - [Fact] - public void A_float_can_never_be_less_than_NaN() - { - // Arrange - float? value = 3.4F; - - // Act - Action act = () => value.Should().BeLessThan(float.NaN); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - - [Fact] - public void NaN_is_never_less_than_another_float() - { - // Arrange - float? value = float.NaN; - - // Act - Action act = () => value.Should().BeLessThan(0); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - - [Fact] - public void A_double_can_never_be_less_than_NaN() - { - // Arrange - double? value = 3.4F; - - // Act - Action act = () => value.Should().BeLessThan(double.NaN); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - - [Fact] - public void NaN_is_never_less_than_another_double() - { - // Arrange - double? value = double.NaN; - - // Act - Action act = () => value.Should().BeLessThan(0); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - } - - public class BeGreaterThan - { - [Fact] - public void A_float_can_never_be_greater_than_NaN() - { - // Arrange - float? value = 3.4F; - - // Act - Action act = () => value.Should().BeGreaterThan(float.NaN); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - - [Fact] - public void NaN_is_never_greater_than_another_float() - { - // Arrange - float? value = float.NaN; - - // Act - Action act = () => value.Should().BeGreaterThan(0); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - - [Fact] - public void A_double_can_never_be_greater_than_NaN() - { - // Arrange - double? value = 3.4F; - - // Act - Action act = () => value.Should().BeGreaterThan(double.NaN); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - - [Fact] - public void NaN_is_never_greater_than_another_double() - { - // Arrange - double? value = double.NaN; - - // Act - Action act = () => value.Should().BeGreaterThan(0); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - } - - public class BeLessThanOrEqualTo - { - [Fact] - public void A_float_can_never_be_less_than_or_equal_to_NaN() - { - // Arrange - float? value = 3.4F; - - // Act - Action act = () => value.Should().BeLessThanOrEqualTo(float.NaN); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - - [Fact] - public void NaN_is_never_less_than_or_equal_to_another_float() - { - // Arrange - float? value = float.NaN; - - // Act - Action act = () => value.Should().BeLessThanOrEqualTo(0); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - - [Fact] - public void A_double_can_never_be_less_than_or_equal_to_NaN() - { - // Arrange - double? value = 3.4; - - // Act - Action act = () => value.Should().BeLessThanOrEqualTo(double.NaN); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - - [Fact] - public void NaN_is_never_less_than_or_equal_to_another_double() - { - // Arrange - double? value = double.NaN; - - // Act - Action act = () => value.Should().BeLessThanOrEqualTo(0); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - } - - public class BeGreaterThanOrEqualTo - { - [Fact] - public void A_float_can_never_be_greater_than_or_equal_to_NaN() - { - // Arrange - float? value = 3.4F; - - // Act - Action act = () => value.Should().BeGreaterThanOrEqualTo(float.NaN); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - - [Fact] - public void NaN_is_never_greater_than_or_equal_to_another_float() - { - // Arrange - float? value = float.NaN; - - // Act - Action act = () => value.Should().BeGreaterThanOrEqualTo(0); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - - [Fact] - public void A_double_can_never_be_greater_than_or_equal_to_NaN() - { - // Arrange - double? value = 3.4; - - // Act - Action act = () => value.Should().BeGreaterThanOrEqualTo(double.NaN); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - - [Fact] - public void NaN_is_never_greater_than_or_equal_to_another_double() - { - // Arrange - double? value = double.NaN; - - // Act - Action act = () => value.Should().BeGreaterThanOrEqualTo(0); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - } - - public class BeInRange - { - [Theory] - [InlineData(float.NaN, 5F)] - [InlineData(5F, float.NaN)] - public void A_float_can_never_be_in_a_range_containing_NaN(float minimumValue, float maximumValue) - { - // Arrange - float? value = 4.5F; - - // Act - Action act = () => value.Should().BeInRange(minimumValue, maximumValue); - - // Assert - act - .Should().Throw() - .WithMessage( - "*NaN*"); - } - - [Fact] - public void NaN_is_never_in_range_of_two_floats() - { - // Arrange - float? value = float.NaN; - - // Act - Action act = () => value.Should().BeInRange(4, 5); - - // Assert - act - .Should().Throw() - .WithMessage( - "Expected value to be between*4* and*5*, but found*NaN*"); - } - - [Theory] - [InlineData(double.NaN, 5)] - [InlineData(5, double.NaN)] - public void A_double_can_never_be_in_a_range_containing_NaN(double minimumValue, double maximumValue) - { - // Arrange - double? value = 4.5; - - // Act - Action act = () => value.Should().BeInRange(minimumValue, maximumValue); - - // Assert - act - .Should().Throw() - .WithMessage( - "*NaN*"); - } - - [Fact] - public void NaN_is_never_in_range_of_two_doubles() - { - // Arrange - double? value = double.NaN; - - // Act - Action act = () => value.Should().BeInRange(4, 5); - - // Assert - act - .Should().Throw() - .WithMessage( - "Expected value to be between*4* and*5*, but found*NaN*"); - } - } - - public class NotBeInRange - { - [Theory] - [InlineData(float.NaN, 1F)] - [InlineData(1F, float.NaN)] - public void Cannot_use_NaN_in_a_range_of_floats(float minimumValue, float maximumValue) - { - // Arrange - float? value = 4.5F; - - // Act - Action act = () => value.Should().NotBeInRange(minimumValue, maximumValue); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - - [Fact] - public void NaN_is_never_inside_any_range_of_floats() - { - // Arrange - float? value = float.NaN; - - // Act / Assert - value.Should().NotBeInRange(4, 5); - } - - [Theory] - [InlineData(double.NaN, 1D)] - [InlineData(1D, double.NaN)] - public void Cannot_use_NaN_in_a_range_of_doubles(double minimumValue, double maximumValue) - { - // Arrange - double? value = 4.5D; - - // Act - Action act = () => value.Should().NotBeInRange(minimumValue, maximumValue); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - - [Fact] - public void NaN_is_never_inside_any_range_of_doubles() - { - // Arrange - double? value = double.NaN; - - // Act / Assert - value.Should().NotBeInRange(4, 5); - } - } - - public class HaveValue - { - [Fact] - public void Should_succeed_when_asserting_nullable_numeric_value_with_value_to_have_a_value() - { - // Arrange - int? nullableInteger = 1; - - // Act / Assert - nullableInteger.Should().HaveValue(); - } - - [Fact] - public void Should_fail_when_asserting_nullable_numeric_value_without_a_value_to_have_a_value() - { - // Arrange - int? nullableInteger = null; - - // Act - Action act = () => nullableInteger.Should().HaveValue(); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void - Should_fail_with_descriptive_message_when_asserting_nullable_numeric_value_without_a_value_to_have_a_value() - { - // Arrange - int? nullableInteger = null; - - // Act - Action act = () => nullableInteger.Should().HaveValue("because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw() - .WithMessage("Expected a value because we want to test the failure message."); - } - } - - public class NotHaveValue - { - [Fact] - public void Should_succeed_when_asserting_nullable_numeric_value_without_a_value_to_not_have_a_value() - { - // Arrange - int? nullableInteger = null; - - // Act / Assert - nullableInteger.Should().NotHaveValue(); - } - - [Fact] - public void Should_fail_when_asserting_nullable_numeric_value_with_a_value_to_not_have_a_value() - { - // Arrange - int? nullableInteger = 1; - - // Act - Action act = () => nullableInteger.Should().NotHaveValue(); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_nullable_value_with_unexpected_value_is_found_it_should_throw_with_message() - { - // Arrange - int? nullableInteger = 1; - - // Act - Action action = () => nullableInteger.Should().NotHaveValue("it was {0} expected", "not"); - - // Assert - action - .Should().Throw() - .WithMessage("Did not expect a value because it was not expected, but found 1."); - } - } - - public class NotBeNull - { - [Fact] - public void Should_succeed_when_asserting_nullable_numeric_value_with_value_to_not_be_null() - { - // Arrange - int? nullableInteger = 1; - - // Act / Assert - nullableInteger.Should().NotBeNull(); - } - - [Fact] - public void Should_fail_when_asserting_nullable_numeric_value_without_a_value_to_not_be_null() - { - // Arrange - int? nullableInteger = null; - - // Act - Action act = () => nullableInteger.Should().NotBeNull(); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void - Should_fail_with_descriptive_message_when_asserting_nullable_numeric_value_without_a_value_to_not_be_null() - { - // Arrange - int? nullableInteger = null; - - // Act - Action act = () => nullableInteger.Should().NotBeNull("because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw() - .WithMessage("Expected a value because we want to test the failure message."); - } - } - - public class BeNull - { - [Fact] - public void Should_succeed_when_asserting_nullable_numeric_value_without_a_value_to_be_null() - { - // Arrange - int? nullableInteger = null; - - // Act / Assert - nullableInteger.Should().BeNull(); - } - - [Fact] - public void Should_fail_when_asserting_nullable_numeric_value_with_a_value_to_be_null() - { - // Arrange - int? nullableInteger = 1; - - // Act - Action act = () => nullableInteger.Should().BeNull(); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void Should_fail_with_descriptive_message_when_asserting_nullable_numeric_value_with_a_value_to_be_null() - { - // Arrange - int? nullableInteger = 1; - - // Act - Action act = () => nullableInteger.Should().BeNull("because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect a value because we want to test the failure message, but found 1."); - } - } - - public class Be - { - [Fact] - public void Should_succeed_when_asserting_nullable_numeric_value_equals_an_equal_value() - { - // Arrange - int? nullableIntegerA = 1; - int? nullableIntegerB = 1; - - // Act / Assert - nullableIntegerA.Should().Be(nullableIntegerB); - } - - [Fact] - public void Should_succeed_when_asserting_nullable_numeric_null_value_equals_null() - { - // Arrange - int? nullableIntegerA = null; - int? nullableIntegerB = null; - - // Act / Assert - nullableIntegerA.Should().Be(nullableIntegerB); - } - - [Fact] - public void Should_fail_when_asserting_nullable_numeric_value_equals_a_different_value() - { - // Arrange - int? nullableIntegerA = 1; - int? nullableIntegerB = 2; - - // Act - Action act = () => nullableIntegerA.Should().Be(nullableIntegerB); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void Should_fail_with_descriptive_message_when_asserting_nullable_numeric_value_equals_a_different_value() - { - // Arrange - int? nullableIntegerA = 1; - int? nullableIntegerB = 2; - - // Act - Action act = () => - nullableIntegerA.Should().Be(nullableIntegerB, "because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw() - .WithMessage("Expected*2 because we want to test the failure message, but found 1."); - } - - [Fact] - public void Nan_is_never_equal_to_a_normal_float() - { - // Arrange - float? value = float.NaN; - - // Act - Action act = () => value.Should().Be(3.4F); - - // Assert - act - .Should().Throw() - .WithMessage( - "Expected value to be *3.4F, but found NaN*"); - } - - [Fact] - public void NaN_can_be_compared_to_NaN_when_its_a_float() - { - // Arrange - float? value = float.NaN; - - // Act - value.Should().Be(float.NaN); - } - - [Fact] - public void Nan_is_never_equal_to_a_normal_double() - { - // Arrange - double? value = double.NaN; - - // Act - Action act = () => value.Should().Be(3.4D); - - // Assert - act - .Should().Throw() - .WithMessage("Expected value to be *3.4, but found NaN*"); - } - - [Fact] - public void NaN_can_be_compared_to_NaN_when_its_a_double() - { - // Arrange - double? value = double.NaN; - - // Act - value.Should().Be(double.NaN); - } - } - - public class BeApproximately - { - [Fact] - public void When_approximating_a_nullable_double_with_a_negative_precision_it_should_throw() - { - // Arrange - double? value = 3.1415927; - - // Act - Action act = () => value.Should().BeApproximately(3.14, -0.1); - - // Assert - act.Should().Throw() - .WithParameterName("precision") - .WithMessage("*must be non-negative*"); - } - - [Fact] - public void When_approximating_two_nullable_doubles_with_a_negative_precision_it_should_throw() - { - // Arrange - double? value = 3.1415927; - double? expected = 3.14; - - // Act - Action act = () => value.Should().BeApproximately(expected, -0.1); - - // Assert - act.Should().Throw() - .WithParameterName("precision") - .WithMessage("*must be non-negative*"); - } - - [Fact] - public void When_nullable_double_is_indeed_approximating_a_value_it_should_not_throw() - { - // Arrange - double? value = 3.1415927; - - // Act - Action act = () => value.Should().BeApproximately(3.14, 0.1); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_nullable_double_is_indeed_approximating_a_nullable_value_it_should_not_throw() - { - // Arrange - double? value = 3.1415927; - double? expected = 3.142; - - // Act - Action act = () => value.Should().BeApproximately(expected, 0.1); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_nullable_double_is_null_approximating_a_nullable_null_value_it_should_not_throw() - { - // Arrange - double? value = null; - double? expected = null; - - // Act - Action act = () => value.Should().BeApproximately(expected, 0.1); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_nullable_double_with_value_is_not_approximating_a_non_null_nullable_value_it_should_throw() - { - // Arrange - double? value = 13; - double? expected = 12; - - // Act - Action act = () => value.Should().BeApproximately(expected, 0.1); - - // Assert - act.Should().Throw().WithMessage("Expected*12.0*0.1*13.0*"); - } - - [Fact] - public void When_nullable_double_is_null_approximating_a_non_null_nullable_value_it_should_throw() - { - // Arrange - double? value = null; - double? expected = 12; - - // Act - Action act = () => value.Should().BeApproximately(expected, 0.1); - - // Assert - act.Should().Throw().WithMessage( - "Expected value to approximate 12.0 +/- 0.1, but it was ."); - } - - [Fact] - public void When_nullable_double_is_not_null_approximating_a_null_value_it_should_throw() - { - // Arrange - double? value = 12; - double? expected = null; - - // Act - Action act = () => value.Should().BeApproximately(expected, 0.1); - - // Assert - act.Should().Throw().WithMessage( - "Expected value to approximate +/- 0.1, but it was 12.0."); - } - - [Fact] - public void When_nullable_double_has_no_value_it_should_throw() - { - // Arrange - double? value = null; - - // Act - Action act = () => - { - using var _ = new AssertionScope(); - value.Should().BeApproximately(3.14, 0.001); - }; - - // Assert - act.Should().Throw().WithMessage( - "Expected value to approximate 3.14 +/- 0.001, but it was ."); - } - - [Fact] - public void When_nullable_double_is_not_approximating_a_value_it_should_throw() - { - // Arrange - double? value = 3.1415927F; - - // Act - Action act = () => value.Should().BeApproximately(1.0, 0.1); - - // Assert - act - .Should().Throw() - .WithMessage("Expected value to approximate 1.0 +/- 0.1, but 3.14* differed by*"); - } - - [Fact] - public void A_double_cannot_approximate_NaN() - { - // Arrange - double? value = 3.1415927F; - - // Act - Action act = () => value.Should().BeApproximately(double.NaN, 0.1); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - - [Fact] - public void When_approximating_a_nullable_float_with_a_negative_precision_it_should_throw() - { - // Arrange - float? value = 3.1415927F; - - // Act - Action act = () => value.Should().BeApproximately(3.14F, -0.1F); - - // Assert - act.Should().Throw() - .WithParameterName("precision") - .WithMessage("*must be non-negative*"); - } - - [Fact] - public void When_approximating_two_nullable_floats_with_a_negative_precision_it_should_throw() - { - // Arrange - float? value = 3.1415927F; - float? expected = 3.14F; - - // Act - Action act = () => value.Should().BeApproximately(expected, -0.1F); - - // Assert - act.Should().Throw() - .WithParameterName("precision") - .WithMessage("*must be non-negative*"); - } - - [Fact] - public void When_nullable_float_is_indeed_approximating_a_value_it_should_not_throw() - { - // Arrange - float? value = 3.1415927F; - - // Act - Action act = () => value.Should().BeApproximately(3.14F, 0.1F); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_nullable_float_is_indeed_approximating_a_nullable_value_it_should_not_throw() - { - // Arrange - float? value = 3.1415927f; - float? expected = 3.142f; - - // Act - Action act = () => value.Should().BeApproximately(expected, 0.1f); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_nullable_float_is_null_approximating_a_nullable_null_value_it_should_not_throw() - { - // Arrange - float? value = null; - float? expected = null; - - // Act - Action act = () => value.Should().BeApproximately(expected, 0.1f); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_nullable_float_with_value_is_not_approximating_a_non_null_nullable_value_it_should_throw() - { - // Arrange - float? value = 13; - float? expected = 12; - - // Act - Action act = () => value.Should().BeApproximately(expected, 0.1f); - - // Assert - act.Should().Throw().WithMessage("Expected*12*0.1*13*"); - } - - [Fact] - public void When_nullable_float_is_null_approximating_a_non_null_nullable_value_it_should_throw() - { - // Arrange - float? value = null; - float? expected = 12; - - // Act - Action act = () => value.Should().BeApproximately(expected, 0.1f); - - // Assert - act.Should().Throw().WithMessage( - "Expected value to approximate 12F +/- 0.1F, but it was ."); - } - - [Fact] - public void When_nullable_float_is_not_null_approximating_a_null_value_it_should_throw() - { - // Arrange - float? value = 12; - float? expected = null; - - // Act - Action act = () => value.Should().BeApproximately(expected, 0.1f); - - // Assert - act.Should().Throw().WithMessage( - "Expected value to approximate +/- 0.1F, but it was 12F."); - } - - [Fact] - public void When_nullable_float_has_no_value_it_should_throw() - { - // Arrange - float? value = null; - - // Act - Action act = () => - { - using var _ = new AssertionScope(); - value.Should().BeApproximately(3.14F, 0.001F); - }; - - // Assert - act.Should().Throw().WithMessage( - "Expected value to approximate 3.14F +/- 0.001F, but it was ."); - } - - [Fact] - public void When_nullable_float_is_not_approximating_a_value_it_should_throw() - { - // Arrange - float? value = 3.1415927F; - - // Act - Action act = () => value.Should().BeApproximately(1.0F, 0.1F); - - // Assert - act - .Should().Throw() - .WithMessage( - "Expected value to approximate *1* +/- *0.1* but 3.14* differed by*"); - } - - [Fact] - public void A_float_cannot_approximate_NaN() - { - // Arrange - float? value = 3.1415927F; - - // Act - Action act = () => value.Should().BeApproximately(float.NaN, 0.1F); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - - [Fact] - public void When_approximating_a_nullable_decimal_with_a_negative_precision_it_should_throw() - { - // Arrange - decimal? value = 3.1415927m; - - // Act - Action act = () => value.Should().BeApproximately(3.14m, -0.1m); - - // Assert - act.Should().Throw() - .WithParameterName("precision") - .WithMessage("*must be non-negative*"); - } - - [Fact] - public void When_approximating_two_nullable_decimals_with_a_negative_precision_it_should_throw() - { - // Arrange - decimal? value = 3.1415927m; - decimal? expected = 3.14m; - - // Act - Action act = () => value.Should().BeApproximately(expected, -0.1m); - - // Assert - act.Should().Throw() - .WithParameterName("precision") - .WithMessage("*must be non-negative*"); - } - - [Fact] - public void When_nullable_decimal_is_indeed_approximating_a_value_it_should_not_throw() - { - // Arrange - decimal? value = 3.1415927m; - - // Act - Action act = () => value.Should().BeApproximately(3.14m, 0.1m); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_nullable_decimal_is_indeed_approximating_a_nullable_value_it_should_not_throw() - { - // Arrange - decimal? value = 3.1415927m; - decimal? expected = 3.142m; - - // Act - Action act = () => value.Should().BeApproximately(expected, 0.1m); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_nullable_decimal_is_null_approximating_a_nullable_null_value_it_should_not_throw() - { - // Arrange - decimal? value = null; - decimal? expected = null; - - // Act - Action act = () => value.Should().BeApproximately(expected, 0.1m); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_nullable_decimal_with_value_is_not_approximating_a_non_null_nullable_value_it_should_throw() - { - // Arrange - decimal? value = 13; - decimal? expected = 12; - - // Act - Action act = () => value.Should().BeApproximately(expected, 0.1m); - - // Assert - act.Should().Throw().WithMessage("Expected*12*0.1*13*"); - } - - [Fact] - public void When_nullable_decimal_is_null_approximating_a_non_null_nullable_value_it_should_throw() - { - // Arrange - decimal? value = null; - decimal? expected = 12; - - // Act - Action act = () => value.Should().BeApproximately(expected, 0.1m); - - // Assert - act.Should().Throw().WithMessage( - "Expected value to approximate 12M +/- 0.1M, but it was ."); - } - - [Fact] - public void When_nullable_decimal_is_not_null_approximating_a_null_value_it_should_throw() - { - // Arrange - decimal? value = 12; - decimal? expected = null; - - // Act - Action act = () => value.Should().BeApproximately(expected, 0.1m); - - // Assert - act.Should().Throw().WithMessage( - "Expected value to approximate +/- 0.1M, but it was 12M."); - } - - [Fact] - public void When_nullable_decimal_has_no_value_it_should_throw() - { - // Arrange - decimal? value = null; - - // Act - Action act = () => - { - using var _ = new AssertionScope(); - value.Should().BeApproximately(3.14m, 0.001m); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expected value to approximate*3.14* +/-*0.001*, but it was ."); - } - - [Fact] - public void When_nullable_decimal_is_not_approximating_a_value_it_should_throw() - { - // Arrange - decimal? value = 3.1415927m; - - // Act - Action act = () => value.Should().BeApproximately(1.0m, 0.1m); - - // Assert - act - .Should().Throw() - .WithMessage("Expected value to approximate*1.0* +/-*0.1*, but 3.14* differed by*"); - } - } - - public class NotBeApproximately - { - [Fact] - public void When_not_approximating_a_nullable_double_with_a_negative_precision_it_should_throw() - { - // Arrange - double? value = 3.1415927; - - // Act - Action act = () => value.Should().NotBeApproximately(3.14, -0.1); - - // Assert - act.Should().Throw() - .WithParameterName("precision") - .WithMessage("*must be non-negative*"); - } - - [Fact] - public void When_not_approximating_two_nullable_doubles_with_a_negative_precision_it_should_throw() - { - // Arrange - double? value = 3.1415927; - double? expected = 3.14; - - // Act - Action act = () => value.Should().NotBeApproximately(expected, -0.1); - - // Assert - act.Should().Throw() - .WithParameterName("precision") - .WithMessage("*must be non-negative*"); - } - - [Fact] - public void When_asserting_not_approximately_and_nullable_double_is_not_approximating_a_value_it_should_not_throw() - { - // Arrange - double? value = 3.1415927; - - // Act - Action act = () => value.Should().NotBeApproximately(1.0, 0.1); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_not_approximately_and_nullable_double_has_no_value_it_should_throw() - { - // Arrange - double? value = null; - - // Act - Action act = () => value.Should().NotBeApproximately(3.14, 0.001); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_not_approximately_and_nullable_double_is_indeed_approximating_a_value_it_should_throw() - { - // Arrange - double? value = 3.1415927; - - // Act - Action act = () => value.Should().NotBeApproximately(3.14, 0.1); - - // Assert - act - .Should().Throw() - .WithMessage("Expected value to not approximate 3.14 +/- 0.1, but 3.14*only differed by*"); - } - - [Fact] - public void - When_asserting_not_approximately_and_nullable_double_is_not_approximating_a_nullable_value_it_should_not_throw() - { - // Arrange - double? value = 3.1415927; - double? expected = 1.0; - - // Act - Action act = () => value.Should().NotBeApproximately(expected, 0.1); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_not_approximately_and_nullable_double_is_not_approximating_a_null_value_it_should_throw() - { - // Arrange - double? value = 3.1415927; - double? expected = null; - - // Act - Action act = () => value.Should().NotBeApproximately(expected, 0.1); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void - When_asserting_not_approximately_and_null_double_is_not_approximating_a_nullable_double_value_it_should_throw() - { - // Arrange - double? value = null; - double? expected = 20.0; - - // Act - Action act = () => value.Should().NotBeApproximately(expected, 0.1); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_not_approximately_and_null_double_is_not_approximating_a_null_value_it_should_not_throw() - { - // Arrange - double? value = null; - double? expected = null; - - // Act - Action act = () => value.Should().NotBeApproximately(expected, 0.1); - - // Assert - act.Should().Throw() - .WithMessage("Expected*null*0.1*but*null*"); - } - - [Fact] - public void When_asserting_not_approximately_and_nullable_double_is_approximating_a_nullable_value_it_should_throw() - { - // Arrange - double? value = 3.1415927; - double? expected = 3.1; - - // Act - Action act = () => value.Should().NotBeApproximately(expected, 0.1F); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void A_double_cannot_approximate_NaN() - { - // Arrange - double? value = 3.1415927F; - - // Act - Action act = () => value.Should().NotBeApproximately(double.NaN, 0.1); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - - [Fact] - public void When_not_approximating_a_nullable_float_with_a_negative_precision_it_should_throw() - { - // Arrange - float? value = 3.1415927F; - - // Act - Action act = () => value.Should().NotBeApproximately(3.14F, -0.1F); - - // Assert - act.Should().Throw() - .WithParameterName("precision") - .WithMessage("*must be non-negative*"); - } - - [Fact] - public void When_not_approximating_two_nullable_floats_with_a_negative_precision_it_should_throw() - { - // Arrange - float? value = 3.1415927F; - float? expected = 3.14F; - - // Act - Action act = () => value.Should().NotBeApproximately(expected, -0.1F); - - // Assert - act.Should().Throw() - .WithParameterName("precision") - .WithMessage("*must be non-negative*"); - } - - [Fact] - public void When_asserting_not_approximately_and_nullable_float_is_not_approximating_a_value_it_should_not_throw() - { - // Arrange - float? value = 3.1415927F; - - // Act - Action act = () => value.Should().NotBeApproximately(1.0F, 0.1F); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_not_approximately_and_nullable_float_has_no_value_it_should_throw() - { - // Arrange - float? value = null; - - // Act - Action act = () => value.Should().NotBeApproximately(3.14F, 0.001F); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_not_approximately_and_nullable_float_is_indeed_approximating_a_value_it_should_throw() - { - // Arrange - float? value = 3.1415927F; - - // Act - Action act = () => value.Should().NotBeApproximately(3.14F, 0.1F); - - // Assert - act - .Should().Throw() - .WithMessage("Expected value to not approximate *3.14F* +/- *0.1F* but 3.14* only differed by*"); - } - - [Fact] - public void - When_asserting_not_approximately_and_nullable_float_is_not_approximating_a_nullable_value_it_should_not_throw() - { - // Arrange - float? value = 3.1415927F; - float? expected = 1.0F; - - // Act - Action act = () => value.Should().NotBeApproximately(expected, 0.1F); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_not_approximately_and_nullable_float_is_not_approximating_a_null_value_it_should_throw() - { - // Arrange - float? value = 3.1415927F; - float? expected = null; - - // Act - Action act = () => value.Should().NotBeApproximately(expected, 0.1F); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void - When_asserting_not_approximately_and_null_float_is_not_approximating_a_nullable_float_value_it_should_throw() - { - // Arrange - float? value = null; - float? expected = 20.0f; - - // Act - Action act = () => value.Should().NotBeApproximately(expected, 0.1F); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_not_approximately_and_null_float_is_not_approximating_a_null_value_it_should_not_throw() - { - // Arrange - float? value = null; - float? expected = null; - - // Act - Action act = () => value.Should().NotBeApproximately(expected, 0.1F); - - // Assert - act.Should().Throw("Expected**+/-*0.1F**"); - } - - [Fact] - public void When_asserting_not_approximately_and_nullable_float_is_approximating_a_nullable_value_it_should_throw() - { - // Arrange - float? value = 3.1415927F; - float? expected = 3.1F; - - // Act - Action act = () => value.Should().NotBeApproximately(expected, 0.1F); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void A_float_cannot_approximate_NaN() - { - // Arrange - float? value = 3.1415927F; - - // Act - Action act = () => value.Should().NotBeApproximately(float.NaN, 0.1F); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - - [Fact] - public void When_not_approximating_a_nullable_decimal_with_a_negative_precision_it_should_throw() - { - // Arrange - decimal? value = 3.1415927m; - - // Act - Action act = () => value.Should().NotBeApproximately(3.14m, -0.1m); - - // Assert - act.Should().Throw() - .WithParameterName("precision") - .WithMessage("*must be non-negative*"); - } - - [Fact] - public void When_not_approximating_two_nullable_decimals_with_a_negative_precision_it_should_throw() - { - // Arrange - decimal? value = 3.1415927m; - decimal? expected = 3.14m; - - // Act - Action act = () => value.Should().NotBeApproximately(expected, -0.1m); - - // Assert - act.Should().Throw() - .WithParameterName("precision") - .WithMessage("*must be non-negative*"); - } - - [Fact] - public void When_asserting_not_approximately_and_nullable_decimal_is_not_approximating_a_value_it_should_not_throw() - { - // Arrange - decimal? value = 3.1415927m; - - // Act - Action act = () => value.Should().NotBeApproximately(1.0m, 0.1m); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_not_approximately_and_nullable_decimal_has_no_value_it_should_throw() - { - // Arrange - decimal? value = null; - - // Act - Action act = () => value.Should().NotBeApproximately(3.14m, 0.001m); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_not_approximately_and_nullable_decimal_is_indeed_approximating_a_value_it_should_throw() - { - // Arrange - decimal? value = 3.1415927m; - - // Act - Action act = () => value.Should().NotBeApproximately(3.14m, 0.1m); - - // Assert - act - .Should().Throw() - .WithMessage("Expected value to not approximate*3.14* +/-*0.1*, but*3.14*only differed by*"); - } - - [Fact] - public void - When_asserting_not_approximately_and_nullable_decimal_is_not_approximating_a_nullable_value_it_should_not_throw() - { - // Arrange - decimal? value = 3.1415927m; - decimal? expected = 1.0m; - - // Act - Action act = () => value.Should().NotBeApproximately(expected, 0.1m); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_not_approximately_and_nullable_decimal_is_not_approximating_a_null_value_it_should_throw() - { - // Arrange - decimal? value = 3.1415927m; - decimal? expected = null; - - // Act - Action act = () => value.Should().NotBeApproximately(expected, 0.1m); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void - When_asserting_not_approximately_and_null_decimal_is_not_approximating_a_nullable_decimal_value_it_should_throw() - { - // Arrange - decimal? value = null; - decimal? expected = 20.0m; - - // Act - Action act = () => value.Should().NotBeApproximately(expected, 0.1m); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_not_approximately_and_null_decimal_is_not_approximating_a_null_value_it_should_not_throw() - { - // Arrange - decimal? value = null; - decimal? expected = null; - - // Act - Action act = () => value.Should().NotBeApproximately(expected, 0.1m); - - // Assert - act.Should().Throw() - .WithMessage("Expected**0.1M**"); - } - - [Fact] - public void When_asserting_not_approximately_and_nullable_decimal_is_approximating_a_nullable_value_it_should_throw() - { - // Arrange - decimal? value = 3.1415927m; - decimal? expected = 3.1m; - - // Act - Action act = () => value.Should().NotBeApproximately(expected, 0.1m); - - // Assert - act.Should().Throw(); - } - } - - public class Match - { - [Fact] - public void When_nullable_value_satisfies_predicate_it_should_not_throw() - { - // Arrange - int? nullableInteger = 1; - - // Act / Assert - nullableInteger.Should().Match(o => o.HasValue); - } - - [Fact] - public void When_nullable_value_does_not_match_the_predicate_it_should_throw() - { - // Arrange - int? nullableInteger = 1; - - // Act - Action act = () => - nullableInteger.Should().Match(o => !o.HasValue, "because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw() - .WithMessage( - "Expected value to match Not(o.HasValue) because we want to test the failure message, but found 1."); - } - - [Fact] - public void When_nullable_value_is_matched_against_a_null_it_should_throw() - { - // Arrange - int? nullableInteger = 1; - - // Act - Action act = () => nullableInteger.Should().Match(null); - - // Assert - act.Should().ThrowExactly() - .WithParameterName("predicate"); - } - } - [Fact] public void Should_support_chaining_constraints_with_and() { diff --git a/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.Be.cs b/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.Be.cs new file mode 100644 index 0000000000..1d23cdabdd --- /dev/null +++ b/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.Be.cs @@ -0,0 +1,537 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Numeric; + +public partial class NumericAssertionSpecs +{ + public class Be + { + [Fact] + public void A_value_is_equal_to_the_same_value() + { + // Arrange + int value = 1; + int sameValue = 1; + + // Act + value.Should().Be(sameValue); + } + + [Fact] + public void A_value_is_not_equal_to_another_value() + { + // Arrange + int value = 1; + int differentValue = 2; + + // Act + Action act = () => value.Should().Be(differentValue, "because we want to test the failure {0}", "message"); + + // Assert + act + .Should().Throw() + .WithMessage("Expected value to be 2 because we want to test the failure message, but found 1."); + } + + [Fact] + public void A_value_is_equal_to_the_same_nullable_value() + { + // Arrange + int value = 2; + int? nullableValue = 2; + + // Act + value.Should().Be(nullableValue); + } + + [Fact] + public void A_value_is_not_equal_to_null() + { + // Arrange + int value = 2; + int? nullableValue = null; + + // Act + Action act = () => value.Should().Be(nullableValue); + + // Assert + act + .Should().Throw() + .WithMessage("Expected*, but found 2."); + } + + [Fact] + public void Null_is_not_equal_to_another_nullable_value() + { + // Arrange + int? value = 2; + + // Act + Action action = () => ((int?)null).Should().Be(value); + + // Assert + action + .Should().Throw() + .WithMessage("Expected*2, but found ."); + } + + [InlineData(0, 0)] + [InlineData(null, null)] + [Theory] + public void A_nullable_value_is_equal_to_the_same_nullable_value(int? subject, int? expected) + { + // Act / Assert + subject.Should().Be(expected); + } + + [InlineData(0, 1)] + [InlineData(0, null)] + [InlineData(null, 0)] + [Theory] + public void A_nullable_value_is_not_equal_to_another_nullable_value(int? subject, int? expected) + { + // Act + Action act = () => subject.Should().Be(expected); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Null_is_not_equal_to_another_value() + { + // Arrange + int? subject = null; + int expected = 1; + + // Act + Action act = () => subject.Should().Be(expected); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_asserting_that_a_float_value_is_equal_to_a_different_value_it_should_throw() + { + // Arrange + float value = 3.5F; + + // Act + Action act = () => value.Should().Be(3.4F, "we want to test the error message"); + + // Assert + act + .Should().Throw() + .WithMessage( + "Expected value to be *3.4* because we want to test the error message, but found *3.5*"); + } + + [Fact] + public void When_asserting_that_a_float_value_is_equal_to_the_same_value_it_should_not_throw() + { + // Arrange + float value = 3.5F; + + // Act + Action act = () => value.Should().Be(3.5F); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_that_a_null_float_value_is_equal_to_some_value_it_should_throw() + { + // Arrange + float? value = null; + + // Act + Action act = () => value.Should().Be(3.5F); + + // Assert + act + .Should().Throw() + .WithMessage("Expected value to be *3.5* but found ."); + } + + [Fact] + public void When_asserting_that_a_double_value_is_equal_to_a_different_value_it_should_throw() + { + // Arrange + double value = 3.5; + + // Act + Action act = () => value.Should().Be(3.4, "we want to test the error message"); + + // Assert + act + .Should().Throw() + .WithMessage( + "Expected value to be 3.4 because we want to test the error message, but found 3.5*."); + } + + [Fact] + public void When_asserting_that_a_double_value_is_equal_to_the_same_value_it_should_not_throw() + { + // Arrange + double value = 3.5; + + // Act + Action act = () => value.Should().Be(3.5); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_that_a_null_double_value_is_equal_to_some_value_it_should_throw() + { + // Arrange + double? value = null; + + // Act + Action act = () => value.Should().Be(3.5); + + // Assert + act + .Should().Throw() + .WithMessage("Expected value to be 3.5, but found ."); + } + + [Fact] + public void When_asserting_that_a_decimal_value_is_equal_to_a_different_value_it_should_throw() + { + // Arrange + decimal value = 3.5m; + + // Act + Action act = () => value.Should().Be(3.4m, "we want to test the error message"); + + // Assert + act.Should().Throw() + .WithMessage( + "Expected value to be*3.4* because we want to test the error message, but found*3.5*"); + } + + [Fact] + public void When_asserting_that_a_decimal_value_is_equal_to_the_same_value_it_should_not_throw() + { + // Arrange + decimal value = 3.5m; + + // Act + Action act = () => value.Should().Be(3.5m); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_that_a_null_decimal_value_is_equal_to_some_value_it_should_throw() + { + // Arrange + decimal? value = null; + decimal someValue = 3.5m; + + // Act + Action act = () => value.Should().Be(someValue); + + // Assert + act + .Should().Throw() + .WithMessage("Expected value to be*3.5*, but found ."); + } + + [Fact] + public void Nan_is_never_equal_to_a_normal_float() + { + // Arrange + float value = float.NaN; + + // Act + Action act = () => value.Should().Be(3.4F); + + // Assert + act + .Should().Throw() + .WithMessage( + "Expected value to be *3.4F, but found NaN*"); + } + + [Fact] + public void NaN_can_be_compared_to_NaN_when_its_a_float() + { + // Arrange + float value = float.NaN; + + // Act + value.Should().Be(float.NaN); + } + + [Fact] + public void Nan_is_never_equal_to_a_normal_double() + { + // Arrange + double value = double.NaN; + + // Act + Action act = () => value.Should().Be(3.4D); + + // Assert + act + .Should().Throw() + .WithMessage("Expected value to be *3.4, but found NaN*"); + } + + [Fact] + public void NaN_can_be_compared_to_NaN_when_its_a_double() + { + // Arrange + double value = double.NaN; + + // Act + value.Should().Be(double.NaN); + } + } + + public class NotBe + { + [InlineData(1, 2)] + [InlineData(null, 2)] + [Theory] + public void A_nullable_value_is_not_equal_to_another_value(int? subject, int unexpected) + { + // Act + subject.Should().NotBe(unexpected); + } + + [Fact] + public void A_value_is_not_different_from_the_same_value() + { + // Arrange + int value = 1; + int sameValue = 1; + + // Act + Action act = () => value.Should().NotBe(sameValue, "because we want to test the failure {0}", "message"); + + // Assert + act + .Should().Throw() + .WithMessage("Did not expect value to be 1 because we want to test the failure message."); + } + + [InlineData(null, null)] + [InlineData(0, 0)] + [Theory] + public void A_nullable_value_is_not_different_from_the_same_value(int? subject, int? unexpected) + { + // Act + Action act = () => subject.Should().NotBe(unexpected); + + // Assert + act.Should().Throw(); + } + + [InlineData(0, 1)] + [InlineData(0, null)] + [InlineData(null, 0)] + [Theory] + public void A_nullable_value_is_different_from_another_value(int? subject, int? unexpected) + { + // Act / Assert + subject.Should().NotBe(unexpected); + } + } + + public class Bytes + { + [Fact] + public void When_asserting_a_byte_value_it_should_treat_is_any_numeric_value() + { + // Arrange + byte value = 2; + + // Act + Action act = () => value.Should().Be(2); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_a_sbyte_value_it_should_treat_is_any_numeric_value() + { + // Arrange + sbyte value = 2; + + // Act + Action act = () => value.Should().Be(2); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_a_short_value_it_should_treat_is_any_numeric_value() + { + // Arrange + short value = 2; + + // Act + Action act = () => value.Should().Be(2); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_an_ushort_value_it_should_treat_is_any_numeric_value() + { + // Arrange + ushort value = 2; + + // Act + Action act = () => value.Should().Be(2); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_an_uint_value_it_should_treat_is_any_numeric_value() + { + // Arrange + uint value = 2; + + // Act + Action act = () => value.Should().Be(2); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_a_long_value_it_should_treat_is_any_numeric_value() + { + // Arrange + long value = 2; + + // Act + Action act = () => value.Should().Be(2); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_an_ulong_value_it_should_treat_is_any_numeric_value() + { + // Arrange + ulong value = 2; + + // Act + Action act = () => value.Should().Be(2); + + // Assert + act.Should().NotThrow(); + } + } + + public class NullableBytes + { + [Fact] + public void When_asserting_a_nullable_byte_value_it_should_treat_is_any_numeric_value() + { + // Arrange + byte? value = 2; + + // Act + Action act = () => value.Should().Be(2); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_a_nullable_sbyte_value_it_should_treat_is_any_numeric_value() + { + // Arrange + sbyte? value = 2; + + // Act + Action act = () => value.Should().Be(2); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_a_nullable_short_value_it_should_treat_is_any_numeric_value() + { + // Arrange + short? value = 2; + + // Act + Action act = () => value.Should().Be(2); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_a_nullable_ushort_value_it_should_treat_is_any_numeric_value() + { + // Arrange + ushort? value = 2; + + // Act + Action act = () => value.Should().Be(2); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_a_nullable_uint_value_it_should_treat_is_any_numeric_value() + { + // Arrange + uint? value = 2; + + // Act + Action act = () => value.Should().Be(2); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_a_nullable_long_value_it_should_treat_is_any_numeric_value() + { + // Arrange + long? value = 2; + + // Act + Action act = () => value.Should().Be(2); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_a_nullable_nullable_ulong_value_it_should_treat_is_any_numeric_value() + { + // Arrange + ulong? value = 2; + + // Act + Action act = () => value.Should().Be(2); + + // Assert + act.Should().NotThrow(); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.BeApproximately.cs b/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.BeApproximately.cs new file mode 100644 index 0000000000..e87fb13693 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.BeApproximately.cs @@ -0,0 +1,898 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Numeric; + +public partial class NumericAssertionSpecs +{ + public class BeApproximately + { + [Fact] + public void When_approximating_a_float_with_a_negative_precision_it_should_throw() + { + // Arrange + float value = 3.1415927F; + + // Act + Action act = () => value.Should().BeApproximately(3.14F, -0.1F); + + // Assert + act.Should().Throw() + .WithParameterName("precision") + .WithMessage("*must be non-negative*"); + } + + [Fact] + public void When_float_is_not_approximating_a_range_it_should_throw() + { + // Arrange + float value = 3.1415927F; + + // Act + Action act = () => value.Should().BeApproximately(3.14F, 0.001F, "rockets will crash otherwise"); + + // Assert + act + .Should().Throw() + .WithMessage( + "Expected value to approximate *3.14* +/- *0.001* because rockets will crash otherwise, but *3.1415927* differed by *0.001592*"); + } + + [Fact] + public void When_float_is_indeed_approximating_a_value_it_should_not_throw() + { + // Arrange + float value = 3.1415927F; + + // Act + Action act = () => value.Should().BeApproximately(3.14F, 0.1F); + + // Assert + act.Should().NotThrow(); + } + + [InlineData(9F)] + [InlineData(11F)] + [Theory] + public void When_float_is_approximating_a_value_on_boundaries_it_should_not_throw(float value) + { + // Act + Action act = () => value.Should().BeApproximately(10F, 1F); + + // Assert + act.Should().NotThrow(); + } + + [InlineData(9F)] + [InlineData(11F)] + [Theory] + public void When_float_is_not_approximating_a_value_on_boundaries_it_should_throw(float value) + { + // Act + Action act = () => value.Should().BeApproximately(10F, 0.9F); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_approximating_a_float_towards_nan_it_should_not_throw() + { + // Arrange + float value = float.NaN; + + // Act + Action act = () => value.Should().BeApproximately(3.14F, 0.1F); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_approximating_positive_infinity_float_towards_positive_infinity_it_should_not_throw() + { + // Arrange + float value = float.PositiveInfinity; + + // Act + Action act = () => value.Should().BeApproximately(float.PositiveInfinity, 0.1F); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_approximating_negative_infinity_float_towards_negative_infinity_it_should_not_throw() + { + // Arrange + float value = float.NegativeInfinity; + + // Act + Action act = () => value.Should().BeApproximately(float.NegativeInfinity, 0.1F); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_float_is_not_approximating_positive_infinity_it_should_throw() + { + // Arrange + float value = float.PositiveInfinity; + + // Act + Action act = () => value.Should().BeApproximately(float.MaxValue, 0.1F); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_float_is_not_approximating_negative_infinity_it_should_throw() + { + // Arrange + float value = float.NegativeInfinity; + + // Act + Action act = () => value.Should().BeApproximately(float.MinValue, 0.1F); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void NaN_can_never_be_close_to_any_float() + { + // Arrange + float value = float.NaN; + + // Act + Action act = () => value.Should().BeApproximately(float.MinValue, 0.1F); + + // Assert + act.Should().Throw().WithMessage("*NaN*"); + } + + [Fact] + public void A_float_can_never_be_close_to_NaN() + { + // Arrange + float value = float.MinValue; + + // Act + Action act = () => value.Should().BeApproximately(float.NaN, 0.1F); + + // Assert + act.Should().Throw().WithMessage("*NaN*"); + } + + [Fact] + public void When_a_nullable_float_has_no_value_it_should_throw() + { + // Arrange + float? value = null; + + // Act + Action act = () => value.Should().BeApproximately(3.14F, 0.001F); + + // Assert + act + .Should().Throw() + .WithMessage("Expected value to approximate*3.14* +/-*0.001*, but it was ."); + } + + [Fact] + public void When_approximating_a_double_with_a_negative_precision_it_should_throw() + { + // Arrange + double value = 3.1415927; + + // Act + Action act = () => value.Should().BeApproximately(3.14, -0.1); + + // Assert + act.Should().Throw() + .WithParameterName("precision") + .WithMessage("*must be non-negative*"); + } + + [Fact] + public void When_double_is_not_approximating_a_range_it_should_throw() + { + // Arrange + double value = 3.1415927; + + // Act + Action act = () => value.Should().BeApproximately(3.14, 0.001, "rockets will crash otherwise"); + + // Assert + act + .Should().Throw() + .WithMessage( + "Expected value to approximate 3.14 +/- 0.001 because rockets will crash otherwise, but 3.1415927 differed by 0.001592*"); + } + + [Fact] + public void When_double_is_indeed_approximating_a_value_it_should_not_throw() + { + // Arrange + double value = 3.1415927; + + // Act + Action act = () => value.Should().BeApproximately(3.14, 0.1); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_approximating_a_double_towards_nan_it_should_not_throw() + { + // Arrange + double value = double.NaN; + + // Act + Action act = () => value.Should().BeApproximately(3.14F, 0.1F); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_approximating_positive_infinity_double_towards_positive_infinity_it_should_not_throw() + { + // Arrange + double value = double.PositiveInfinity; + + // Act + Action act = () => value.Should().BeApproximately(double.PositiveInfinity, 0.1); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_approximating_negative_infinity_double_towards_negative_infinity_it_should_not_throw() + { + // Arrange + double value = double.NegativeInfinity; + + // Act + Action act = () => value.Should().BeApproximately(double.NegativeInfinity, 0.1); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_double_is_not_approximating_positive_infinity_it_should_throw() + { + // Arrange + double value = double.PositiveInfinity; + + // Act + Action act = () => value.Should().BeApproximately(double.MaxValue, 0.1); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_double_is_not_approximating_negative_infinity_it_should_throw() + { + // Arrange + double value = double.NegativeInfinity; + + // Act + Action act = () => value.Should().BeApproximately(double.MinValue, 0.1); + + // Assert + act.Should().Throw(); + } + + [InlineData(9D)] + [InlineData(11D)] + [Theory] + public void When_double_is_approximating_a_value_on_boundaries_it_should_not_throw(double value) + { + // Act + Action act = () => value.Should().BeApproximately(10D, 1D); + + // Assert + act.Should().NotThrow(); + } + + [InlineData(9D)] + [InlineData(11D)] + [Theory] + public void When_double_is_not_approximating_a_value_on_boundaries_it_should_throw(double value) + { + // Act + Action act = () => value.Should().BeApproximately(10D, 0.9D); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void NaN_can_never_be_close_to_any_double() + { + // Arrange + double value = double.NaN; + + // Act + Action act = () => value.Should().BeApproximately(double.MinValue, 0.1F); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void A_double_can_never_be_close_to_NaN() + { + // Arrange + double value = double.MinValue; + + // Act + Action act = () => value.Should().BeApproximately(double.NaN, 0.1F); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_approximating_a_decimal_with_a_negative_precision_it_should_throw() + { + // Arrange + decimal value = 3.1415927M; + + // Act + Action act = () => value.Should().BeApproximately(3.14m, -0.1m); + + // Assert + act.Should().Throw() + .WithParameterName("precision") + .WithMessage("*must be non-negative*"); + } + + [Fact] + public void When_decimal_is_not_approximating_a_range_it_should_throw() + { + // Arrange + decimal value = 3.5011m; + + // Act + Action act = () => value.Should().BeApproximately(3.5m, 0.001m, "rockets will crash otherwise"); + + // Assert + act.Should().Throw().WithMessage( + "Expected value to approximate*3.5* +/-*0.001* because rockets will crash otherwise, but *3.5011* differed by*0.0011*"); + } + + [Fact] + public void When_decimal_is_indeed_approximating_a_value_it_should_not_throw() + { + // Arrange + decimal value = 3.5011m; + + // Act + Action act = () => value.Should().BeApproximately(3.5m, 0.01m); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_decimal_is_approximating_a_value_on_lower_boundary_it_should_not_throw() + { + // Act + decimal value = 9m; + + // Act + Action act = () => value.Should().BeApproximately(10m, 1m); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_decimal_is_approximating_a_value_on_upper_boundary_it_should_not_throw() + { + // Act + decimal value = 11m; + + // Act + Action act = () => value.Should().BeApproximately(10m, 1m); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_decimal_is_not_approximating_a_value_on_lower_boundary_it_should_throw() + { + // Act + decimal value = 9m; + + // Act + Action act = () => value.Should().BeApproximately(10m, 0.9m); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_decimal_is_not_approximating_a_value_on_upper_boundary_it_should_throw() + { + // Act + decimal value = 11m; + + // Act + Action act = () => value.Should().BeApproximately(10m, 0.9m); + + // Assert + act.Should().Throw(); + } + } + + public class NotBeApproximately + { + [Fact] + public void When_not_approximating_a_float_with_a_negative_precision_it_should_throw() + { + // Arrange + float value = 3.1415927F; + + // Act + Action act = () => value.Should().NotBeApproximately(3.14F, -0.1F); + + // Assert + act.Should().Throw() + .WithParameterName("precision") + .WithMessage("*must be non-negative*"); + } + + [Fact] + public void When_float_is_approximating_a_range_and_should_not_approximate_it_should_throw() + { + // Arrange + float value = 3.1415927F; + + // Act + Action act = () => value.Should().NotBeApproximately(3.14F, 0.1F, "rockets will crash otherwise"); + + // Assert + act + .Should().Throw() + .WithMessage( + "Expected value to not approximate *3.14* +/- *0.1* because rockets will crash otherwise, but *3.1415927* only differed by *0.001592*"); + } + + [Fact] + public void When_float_is_not_approximating_a_value_and_should_not_approximate_it_should_not_throw() + { + // Arrange + float value = 3.1415927F; + + // Act + Action act = () => value.Should().NotBeApproximately(3.14F, 0.001F); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_approximating_a_float_towards_nan_and_should_not_approximate_it_should_throw() + { + // Arrange + float value = float.NaN; + + // Act + Action act = () => value.Should().NotBeApproximately(3.14F, 0.1F); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_not_approximating_a_float_towards_positive_infinity_and_should_not_approximate_it_should_not_throw() + { + // Arrange + float value = float.PositiveInfinity; + + // Act + Action act = () => value.Should().NotBeApproximately(float.MaxValue, 0.1F); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_not_approximating_a_float_towards_negative_infinity_and_should_not_approximate_it_should_not_throw() + { + // Arrange + float value = float.NegativeInfinity; + + // Act + Action act = () => value.Should().NotBeApproximately(float.MinValue, 0.1F); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void + When_approximating_positive_infinity_float_towards_positive_infinity_and_should_not_approximate_it_should_throw() + { + // Arrange + float value = float.PositiveInfinity; + + // Act + Action act = () => value.Should().NotBeApproximately(float.PositiveInfinity, 0.1F); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void + When_not_approximating_negative_infinity_float_towards_negative_infinity_and_should_not_approximate_it_should_throw() + { + // Arrange + float value = float.NegativeInfinity; + + // Act + Action act = () => value.Should().NotBeApproximately(float.NegativeInfinity, 0.1F); + + // Assert + act.Should().Throw(); + } + + [InlineData(9F)] + [InlineData(11F)] + [Theory] + public void When_float_is_not_approximating_a_value_on_boundaries_it_should_not_throw(float value) + { + // Act + Action act = () => value.Should().NotBeApproximately(10F, 0.9F); + + // Assert + act.Should().NotThrow(); + } + + [InlineData(9F)] + [InlineData(11F)] + [Theory] + public void When_float_is_approximating_a_value_on_boundaries_it_should_throw(float value) + { + // Act + Action act = () => value.Should().NotBeApproximately(10F, 1F); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_a_nullable_float_has_no_value_and_should_not_approximate_it_should_not_throw() + { + // Arrange + float? value = null; + + // Act + Action act = () => value.Should().NotBeApproximately(3.14F, 0.001F); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void NaN_can_never_be_close_to_any_float() + { + // Arrange + float value = float.NaN; + + // Act + Action act = () => value.Should().NotBeApproximately(float.MinValue, 0.1F); + + // Assert + act.Should().Throw().WithMessage("*NaN*"); + } + + [Fact] + public void A_float_can_never_be_close_to_NaN() + { + // Arrange + float value = float.MinValue; + + // Act + Action act = () => value.Should().NotBeApproximately(float.NaN, 0.1F); + + // Assert + act.Should().Throw().WithMessage("*NaN*"); + } + + [Fact] + public void When_not_approximating_a_double_with_a_negative_precision_it_should_throw() + { + // Arrange + double value = 3.1415927; + + // Act + Action act = () => value.Should().NotBeApproximately(3.14, -0.1); + + // Assert + act.Should().Throw() + .WithParameterName("precision") + .WithMessage("*must be non-negative*"); + } + + [Fact] + public void When_double_is_approximating_a_range_and_should_not_approximate_it_should_throw() + { + // Arrange + double value = 3.1415927; + + // Act + Action act = () => value.Should().NotBeApproximately(3.14, 0.1, "rockets will crash otherwise"); + + // Assert + act + .Should().Throw() + .WithMessage( + "Expected value to not approximate *3.14* +/- *0.1* because rockets will crash otherwise, but *3.1415927* only differed by *0.001592*"); + } + + [Fact] + public void When_double_is_not_approximating_a_value_and_should_not_approximate_it_should_not_throw() + { + // Arrange + double value = 3.1415927; + + // Act + Action act = () => value.Should().NotBeApproximately(3.14, 0.001); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_approximating_a_double_towards_nan_and_should_not_approximate_it_should_throw() + { + // Arrange + double value = double.NaN; + + // Act + Action act = () => value.Should().NotBeApproximately(3.14, 0.1); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_not_approximating_a_double_towards_positive_infinity_and_should_not_approximate_it_should_not_throw() + { + // Arrange + double value = double.PositiveInfinity; + + // Act + Action act = () => value.Should().NotBeApproximately(double.MaxValue, 0.1); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_not_approximating_a_double_towards_negative_infinity_and_should_not_approximate_it_should_not_throw() + { + // Arrange + double value = double.NegativeInfinity; + + // Act + Action act = () => value.Should().NotBeApproximately(double.MinValue, 0.1); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void + When_approximating_positive_infinity_double_towards_positive_infinity_and_should_not_approximate_it_should_throw() + { + // Arrange + double value = double.PositiveInfinity; + + // Act + Action act = () => value.Should().NotBeApproximately(double.PositiveInfinity, 0.1); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void + When_not_approximating_negative_infinity_double_towards_negative_infinity_and_should_not_approximate_it_should_throw() + { + // Arrange + double value = double.NegativeInfinity; + + // Act + Action act = () => value.Should().NotBeApproximately(double.NegativeInfinity, 0.1); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_a_nullable_double_has_no_value_and_should_not_approximate_it_should_throw() + { + // Arrange + double? value = null; + + // Act + Action act = () => value.Should().NotBeApproximately(3.14, 0.001); + + // Assert + act.Should().NotThrow(); + } + + [InlineData(9D)] + [InlineData(11D)] + [Theory] + public void When_double_is_not_approximating_a_value_on_boundaries_it_should_not_throw(double value) + { + // Act + Action act = () => value.Should().NotBeApproximately(10D, 0.9D); + + // Assert + act.Should().NotThrow(); + } + + [InlineData(9D)] + [InlineData(11D)] + [Theory] + public void When_double_is_approximating_a_value_on_boundaries_it_should_throw(double value) + { + // Act + Action act = () => value.Should().NotBeApproximately(10D, 1D); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void NaN_can_never_be_close_to_any_double() + { + // Arrange + double value = double.NaN; + + // Act + Action act = () => value.Should().NotBeApproximately(double.MinValue, 0.1F); + + // Assert + act.Should().Throw().WithMessage("*NaN*"); + } + + [Fact] + public void A_double_can_never_be_close_to_NaN() + { + // Arrange + double value = double.MinValue; + + // Act + Action act = () => value.Should().NotBeApproximately(double.NaN, 0.1F); + + // Assert + act.Should().Throw().WithMessage("*NaN*"); + } + + [Fact] + public void When_not_approximating_a_decimal_with_a_negative_precision_it_should_throw() + { + // Arrange + decimal value = 3.1415927m; + + // Act + Action act = () => value.Should().NotBeApproximately(3.14m, -0.1m); + + // Assert + act.Should().Throw() + .WithParameterName("precision") + .WithMessage("*must be non-negative*"); + } + + [Fact] + public void When_decimal_is_approximating_a_range_and_should_not_approximate_it_should_throw() + { + // Arrange + decimal value = 3.5011m; + + // Act + Action act = () => value.Should().NotBeApproximately(3.5m, 0.1m, "rockets will crash otherwise"); + + // Assert + act + .Should().Throw() + .WithMessage( + "Expected value to not approximate *3.5* +/- *0.1* because rockets will crash otherwise, but *3.5011* only differed by *0.0011*"); + } + + [Fact] + public void When_decimal_is_not_approximating_a_value_and_should_not_approximate_it_should_not_throw() + { + // Arrange + decimal value = 3.5011m; + + // Act + Action act = () => value.Should().NotBeApproximately(3.5m, 0.001m); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_a_nullable_decimal_has_no_value_and_should_not_approximate_it_should_throw() + { + // Arrange + decimal? value = null; + + // Act + Action act = () => value.Should().NotBeApproximately(3.5m, 0.001m); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_decimal_is_not_approximating_a_value_on_lower_boundary_it_should_not_throw() + { + // Act + decimal value = 9m; + + // Act + Action act = () => value.Should().NotBeApproximately(10m, 0.9m); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_decimal_is_not_approximating_a_value_on_upper_boundary_it_should_not_throw() + { + // Act + decimal value = 11m; + + // Act + Action act = () => value.Should().NotBeApproximately(10m, 0.9m); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_decimal_is_approximating_a_value_on_lower_boundary_it_should_throw() + { + // Act + decimal value = 9m; + + // Act + Action act = () => value.Should().NotBeApproximately(10m, 1m); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_decimal_is_approximating_a_value_on_upper_boundary_it_should_throw() + { + // Act + decimal value = 11m; + + // Act + Action act = () => value.Should().NotBeApproximately(10m, 1m); + + // Assert + act.Should().Throw(); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.BeCloseTo.cs b/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.BeCloseTo.cs new file mode 100644 index 0000000000..f801a0ce0f --- /dev/null +++ b/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.BeCloseTo.cs @@ -0,0 +1,1479 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Numeric; + +public partial class NumericAssertionSpecs +{ + public class BeCloseTo + { + [InlineData(sbyte.MinValue, sbyte.MinValue, 0)] + [InlineData(sbyte.MinValue, sbyte.MinValue, 1)] + [InlineData(sbyte.MinValue, sbyte.MinValue, sbyte.MaxValue)] + [InlineData(sbyte.MinValue, sbyte.MinValue + 1, 1)] + [InlineData(sbyte.MinValue, sbyte.MinValue + 1, sbyte.MaxValue)] + [InlineData(sbyte.MinValue, -1, sbyte.MaxValue)] + [InlineData(sbyte.MinValue + 1, sbyte.MinValue, 1)] + [InlineData(sbyte.MinValue + 1, sbyte.MinValue, sbyte.MaxValue)] + [InlineData(sbyte.MinValue + 1, 0, sbyte.MaxValue)] + [InlineData(-1, sbyte.MinValue, sbyte.MaxValue)] + [InlineData(-1, 0, 1)] + [InlineData(-1, 0, sbyte.MaxValue)] + [InlineData(0, 0, 0)] + [InlineData(0, 0, 1)] + [InlineData(0, -1, 1)] + [InlineData(0, -1, sbyte.MaxValue)] + [InlineData(0, 1, 1)] + [InlineData(0, 1, sbyte.MaxValue)] + [InlineData(0, sbyte.MaxValue, sbyte.MaxValue)] + [InlineData(0, sbyte.MinValue + 1, sbyte.MaxValue)] + [InlineData(1, 0, 1)] + [InlineData(1, 0, sbyte.MaxValue)] + [InlineData(1, sbyte.MaxValue, sbyte.MaxValue)] + [InlineData(sbyte.MaxValue - 1, sbyte.MaxValue, 1)] + [InlineData(sbyte.MaxValue - 1, sbyte.MaxValue, sbyte.MaxValue)] + [InlineData(sbyte.MaxValue, 0, sbyte.MaxValue)] + [InlineData(sbyte.MaxValue, 1, sbyte.MaxValue)] + [InlineData(sbyte.MaxValue, sbyte.MaxValue, 0)] + [InlineData(sbyte.MaxValue, sbyte.MaxValue, 1)] + [InlineData(sbyte.MaxValue, sbyte.MaxValue, sbyte.MaxValue)] + [InlineData(sbyte.MaxValue, sbyte.MaxValue - 1, 1)] + [InlineData(sbyte.MaxValue, sbyte.MaxValue - 1, sbyte.MaxValue)] + [Theory] + public void When_a_sbyte_value_is_close_to_expected_value_it_should_succeed(sbyte actual, sbyte nearbyValue, + byte delta) + { + // Act + Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); + + // Assert + act.Should().NotThrow(); + } + + [InlineData(sbyte.MinValue, sbyte.MaxValue, 1)] + [InlineData(sbyte.MinValue, 0, sbyte.MaxValue)] + [InlineData(sbyte.MinValue, 1, sbyte.MaxValue)] + [InlineData(-1, 0, 0)] + [InlineData(-1, 1, 1)] + [InlineData(-1, sbyte.MaxValue, sbyte.MaxValue)] + [InlineData(0, sbyte.MinValue, sbyte.MaxValue)] + [InlineData(0, -1, 0)] + [InlineData(0, 1, 0)] + [InlineData(1, -1, 1)] + [InlineData(1, 0, 0)] + [InlineData(1, sbyte.MinValue, sbyte.MaxValue)] + [InlineData(sbyte.MaxValue, sbyte.MinValue, 1)] + [InlineData(sbyte.MaxValue, -1, sbyte.MaxValue)] + [Theory] + public void When_a_sbyte_value_is_not_close_to_expected_value_it_should_fail(sbyte actual, sbyte nearbyValue, + byte delta) + { + // Act + Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_a_sbyte_value_is_not_close_to_expected_value_it_should_fail_with_a_descriptive_message() + { + // Arrange + sbyte actual = 1; + sbyte nearbyValue = 4; + byte delta = 2; + + // Act + Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); + + // Assert + act.Should().Throw() + .WithMessage("*be within*2*from*4*but found*1*"); + } + + [Fact] + public void When_a_sbyte_value_is_returned_from_BeCloseTo_it_should_chain() + { + // Arrange + sbyte actual = sbyte.MaxValue; + + // Act + Action act = () => actual.Should().BeCloseTo(actual, 0) + .And.Be(actual); + + // Assert + act.Should().NotThrow(); + } + + [InlineData(short.MinValue, short.MinValue, 0)] + [InlineData(short.MinValue, short.MinValue, 1)] + [InlineData(short.MinValue, short.MinValue, short.MaxValue)] + [InlineData(short.MinValue, short.MinValue + 1, 1)] + [InlineData(short.MinValue, short.MinValue + 1, short.MaxValue)] + [InlineData(short.MinValue, -1, short.MaxValue)] + [InlineData(short.MinValue + 1, short.MinValue, 1)] + [InlineData(short.MinValue + 1, short.MinValue, short.MaxValue)] + [InlineData(short.MinValue + 1, 0, short.MaxValue)] + [InlineData(-1, short.MinValue, short.MaxValue)] + [InlineData(-1, 0, 1)] + [InlineData(-1, 0, short.MaxValue)] + [InlineData(0, 0, 0)] + [InlineData(0, 0, 1)] + [InlineData(0, -1, 1)] + [InlineData(0, -1, short.MaxValue)] + [InlineData(0, 1, 1)] + [InlineData(0, 1, short.MaxValue)] + [InlineData(0, short.MaxValue, short.MaxValue)] + [InlineData(0, short.MinValue + 1, short.MaxValue)] + [InlineData(1, 0, 1)] + [InlineData(1, 0, short.MaxValue)] + [InlineData(1, short.MaxValue, short.MaxValue)] + [InlineData(short.MaxValue - 1, short.MaxValue, 1)] + [InlineData(short.MaxValue - 1, short.MaxValue, short.MaxValue)] + [InlineData(short.MaxValue, 0, short.MaxValue)] + [InlineData(short.MaxValue, 1, short.MaxValue)] + [InlineData(short.MaxValue, short.MaxValue, 0)] + [InlineData(short.MaxValue, short.MaxValue, 1)] + [InlineData(short.MaxValue, short.MaxValue, short.MaxValue)] + [InlineData(short.MaxValue, short.MaxValue - 1, 1)] + [InlineData(short.MaxValue, short.MaxValue - 1, short.MaxValue)] + [Theory] + public void When_a_short_value_is_close_to_expected_value_it_should_succeed(short actual, short nearbyValue, + ushort delta) + { + // Act + Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); + + // Assert + act.Should().NotThrow(); + } + + [InlineData(short.MinValue, short.MaxValue, 1)] + [InlineData(short.MinValue, 0, short.MaxValue)] + [InlineData(short.MinValue, 1, short.MaxValue)] + [InlineData(-1, 0, 0)] + [InlineData(-1, 1, 1)] + [InlineData(-1, short.MaxValue, short.MaxValue)] + [InlineData(0, short.MinValue, short.MaxValue)] + [InlineData(0, -1, 0)] + [InlineData(0, 1, 0)] + [InlineData(1, -1, 1)] + [InlineData(1, 0, 0)] + [InlineData(1, short.MinValue, short.MaxValue)] + [InlineData(short.MaxValue, short.MinValue, 1)] + [InlineData(short.MaxValue, -1, short.MaxValue)] + [Theory] + public void When_a_short_value_is_not_close_to_expected_value_it_should_fail(short actual, short nearbyValue, + ushort delta) + { + // Act + Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_a_short_value_is_not_close_to_expected_value_it_should_fail_with_a_descriptive_message() + { + // Arrange + short actual = 1; + short nearbyValue = 4; + ushort delta = 2; + + // Act + Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); + + // Assert + act.Should().Throw() + .WithMessage("*be within*2*from*4*but found*1*"); + } + + [Fact] + public void When_a_short_value_is_returned_from_BeCloseTo_it_should_chain() + { + // Arrange + short actual = short.MaxValue; + + // Act + Action act = () => actual.Should().BeCloseTo(actual, 0) + .And.Be(actual); + + // Assert + act.Should().NotThrow(); + } + + [InlineData(int.MinValue, int.MinValue, 0)] + [InlineData(int.MinValue, int.MinValue, 1)] + [InlineData(int.MinValue, int.MinValue, int.MaxValue)] + [InlineData(int.MinValue, int.MinValue + 1, 1)] + [InlineData(int.MinValue, int.MinValue + 1, int.MaxValue)] + [InlineData(int.MinValue, -1, int.MaxValue)] + [InlineData(int.MinValue + 1, int.MinValue, 1)] + [InlineData(int.MinValue + 1, int.MinValue, int.MaxValue)] + [InlineData(int.MinValue + 1, 0, int.MaxValue)] + [InlineData(-1, int.MinValue, int.MaxValue)] + [InlineData(-1, 0, 1)] + [InlineData(-1, 0, int.MaxValue)] + [InlineData(0, 0, 0)] + [InlineData(0, 0, 1)] + [InlineData(0, -1, 1)] + [InlineData(0, -1, int.MaxValue)] + [InlineData(0, 1, 1)] + [InlineData(0, 1, int.MaxValue)] + [InlineData(0, int.MaxValue, int.MaxValue)] + [InlineData(0, int.MinValue + 1, int.MaxValue)] + [InlineData(1, 0, 1)] + [InlineData(1, 0, int.MaxValue)] + [InlineData(1, int.MaxValue, int.MaxValue)] + [InlineData(int.MaxValue - 1, int.MaxValue, 1)] + [InlineData(int.MaxValue - 1, int.MaxValue, int.MaxValue)] + [InlineData(int.MaxValue, 0, int.MaxValue)] + [InlineData(int.MaxValue, 1, int.MaxValue)] + [InlineData(int.MaxValue, int.MaxValue, 0)] + [InlineData(int.MaxValue, int.MaxValue, 1)] + [InlineData(int.MaxValue, int.MaxValue, int.MaxValue)] + [InlineData(int.MaxValue, int.MaxValue - 1, 1)] + [InlineData(int.MaxValue, int.MaxValue - 1, int.MaxValue)] + [Theory] + public void When_an_int_value_is_close_to_expected_value_it_should_succeed(int actual, int nearbyValue, uint delta) + { + // Act + Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); + + // Assert + act.Should().NotThrow(); + } + + [InlineData(int.MinValue, int.MaxValue, 1)] + [InlineData(int.MinValue, 0, int.MaxValue)] + [InlineData(int.MinValue, 1, int.MaxValue)] + [InlineData(-1, 0, 0)] + [InlineData(-1, 1, 1)] + [InlineData(-1, int.MaxValue, int.MaxValue)] + [InlineData(0, int.MinValue, int.MaxValue)] + [InlineData(0, -1, 0)] + [InlineData(0, 1, 0)] + [InlineData(1, -1, 1)] + [InlineData(1, 0, 0)] + [InlineData(1, int.MinValue, int.MaxValue)] + [InlineData(int.MaxValue, int.MinValue, 1)] + [InlineData(int.MaxValue, -1, int.MaxValue)] + [Theory] + public void When_an_int_value_is_not_close_to_expected_value_it_should_fail(int actual, int nearbyValue, uint delta) + { + // Act + Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_an_int_value_is_not_close_to_expected_value_it_should_fail_with_a_descriptive_message() + { + // Arrange + int actual = 1; + int nearbyValue = 4; + uint delta = 2; + + // Act + Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); + + // Assert + act.Should().Throw() + .WithMessage("*be within*2*from*4*but found*1*"); + } + + [Fact] + public void When_an_int_value_is_returned_from_BeCloseTo_it_should_chain() + { + // Arrange + int actual = int.MaxValue; + + // Act + Action act = () => actual.Should().BeCloseTo(actual, 0) + .And.Be(actual); + + // Assert + act.Should().NotThrow(); + } + + [InlineData(long.MinValue, long.MinValue, 0)] + [InlineData(long.MinValue, long.MinValue, 1)] + [InlineData(long.MinValue, long.MinValue, (ulong.MaxValue / 2) - 1)] + [InlineData(long.MinValue, long.MinValue, ulong.MaxValue / 2)] + [InlineData(long.MinValue, long.MinValue, (ulong.MaxValue / 2) + 1)] + [InlineData(long.MinValue, long.MinValue, ulong.MaxValue)] + [InlineData(long.MinValue, long.MinValue + 1, 1)] + [InlineData(long.MinValue, long.MinValue + 1, (ulong.MaxValue / 2) - 1)] + [InlineData(long.MinValue, long.MinValue + 1, ulong.MaxValue / 2)] + [InlineData(long.MinValue, long.MinValue + 1, (ulong.MaxValue / 2) + 1)] + [InlineData(long.MinValue, long.MinValue + 1, ulong.MaxValue)] + [InlineData(long.MinValue, -1, long.MaxValue)] + [InlineData(long.MinValue + 1, long.MinValue, 1)] + [InlineData(long.MinValue + 1, long.MinValue, (ulong.MaxValue / 2) - 1)] + [InlineData(long.MinValue + 1, long.MinValue, ulong.MaxValue / 2)] + [InlineData(long.MinValue + 1, long.MinValue, (ulong.MaxValue / 2) + 1)] + [InlineData(long.MinValue + 1, long.MinValue, ulong.MaxValue)] + [InlineData(long.MinValue + 1, 0, ulong.MaxValue / 2)] + [InlineData(long.MinValue + 1, 0, (ulong.MaxValue / 2) + 1)] + [InlineData(long.MinValue + 1, 0, ulong.MaxValue)] + [InlineData(long.MinValue, long.MaxValue, ulong.MaxValue)] + [InlineData(-1, long.MinValue, ulong.MaxValue / 2)] + [InlineData(-1, long.MinValue, (ulong.MaxValue / 2) + 1)] + [InlineData(-1, long.MinValue, ulong.MaxValue)] + [InlineData(-1, 0, 1)] + [InlineData(-1, 0, (ulong.MaxValue / 2) - 1)] + [InlineData(-1, 0, ulong.MaxValue / 2)] + [InlineData(-1, 0, (ulong.MaxValue / 2) + 1)] + [InlineData(-1, 0, ulong.MaxValue)] + [InlineData(0, 0, 0)] + [InlineData(0, 0, 1)] + [InlineData(0, -1, 1)] + [InlineData(0, -1, (ulong.MaxValue / 2) - 1)] + [InlineData(0, -1, ulong.MaxValue / 2)] + [InlineData(0, -1, (ulong.MaxValue / 2) + 1)] + [InlineData(0, -1, ulong.MaxValue)] + [InlineData(0, 1, 1)] + [InlineData(0, 1, (ulong.MaxValue / 2) - 1)] + [InlineData(0, 1, ulong.MaxValue / 2)] + [InlineData(0, 1, (ulong.MaxValue / 2) + 1)] + [InlineData(0, 1, ulong.MaxValue)] + [InlineData(0, long.MaxValue, ulong.MaxValue / 2)] + [InlineData(0, long.MaxValue, (ulong.MaxValue / 2) + 1)] + [InlineData(0, long.MaxValue, ulong.MaxValue)] + [InlineData(0, long.MinValue + 1, ulong.MaxValue / 2)] + [InlineData(0, long.MinValue + 1, (ulong.MaxValue / 2) + 1)] + [InlineData(0, long.MinValue + 1, ulong.MaxValue)] + [InlineData(1, 0, 1)] + [InlineData(1, 0, (ulong.MaxValue / 2) - 1)] + [InlineData(1, 0, ulong.MaxValue / 2)] + [InlineData(1, 0, (ulong.MaxValue / 2) + 1)] + [InlineData(1, 0, ulong.MaxValue)] + [InlineData(1, long.MaxValue, (ulong.MaxValue / 2) - 1)] + [InlineData(1, long.MaxValue, ulong.MaxValue / 2)] + [InlineData(1, long.MaxValue, (ulong.MaxValue / 2) + 1)] + [InlineData(1, long.MaxValue, ulong.MaxValue)] + [InlineData(long.MaxValue - 1, long.MaxValue, 1)] + [InlineData(long.MaxValue - 1, long.MaxValue, (ulong.MaxValue / 2) - 1)] + [InlineData(long.MaxValue - 1, long.MaxValue, ulong.MaxValue / 2)] + [InlineData(long.MaxValue - 1, long.MaxValue, (ulong.MaxValue / 2) + 1)] + [InlineData(long.MaxValue - 1, long.MaxValue, ulong.MaxValue)] + [InlineData(long.MaxValue, 0, ulong.MaxValue / 2)] + [InlineData(long.MaxValue, 0, (ulong.MaxValue / 2) + 1)] + [InlineData(long.MaxValue, 0, ulong.MaxValue)] + [InlineData(long.MaxValue, 1, (ulong.MaxValue / 2) - 1)] + [InlineData(long.MaxValue, 1, ulong.MaxValue / 2)] + [InlineData(long.MaxValue, 1, (ulong.MaxValue / 2) + 1)] + [InlineData(long.MaxValue, 1, ulong.MaxValue)] + [InlineData(long.MaxValue, long.MaxValue, 0)] + [InlineData(long.MaxValue, long.MaxValue, 1)] + [InlineData(long.MaxValue, long.MaxValue, (ulong.MaxValue / 2) - 1)] + [InlineData(long.MaxValue, long.MaxValue, ulong.MaxValue / 2)] + [InlineData(long.MaxValue, long.MaxValue, (ulong.MaxValue / 2) + 1)] + [InlineData(long.MaxValue, long.MaxValue, ulong.MaxValue)] + [InlineData(long.MaxValue, long.MaxValue - 1, 1)] + [InlineData(long.MaxValue, long.MaxValue - 1, (ulong.MaxValue / 2) - 1)] + [InlineData(long.MaxValue, long.MaxValue - 1, ulong.MaxValue / 2)] + [InlineData(long.MaxValue, long.MaxValue - 1, (ulong.MaxValue / 2) + 1)] + [InlineData(long.MaxValue, long.MaxValue - 1, ulong.MaxValue)] + [Theory] + public void When_a_long_value_is_close_to_expected_value_it_should_succeed(long actual, long nearbyValue, ulong delta) + { + // Act + Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); + + // Assert + act.Should().NotThrow(); + } + + [InlineData(long.MinValue, long.MaxValue, 1)] + [InlineData(long.MinValue, 0, long.MaxValue)] + [InlineData(long.MinValue, 1, long.MaxValue)] + [InlineData(long.MinValue + 1, 0, (ulong.MaxValue / 2) - 1)] + [InlineData(long.MinValue, long.MaxValue, (ulong.MaxValue / 2) - 1)] + [InlineData(long.MinValue, long.MaxValue, ulong.MaxValue / 2)] + [InlineData(-1, 0, 0)] + [InlineData(-1, 1, 1)] + [InlineData(-1, long.MaxValue, long.MaxValue)] + [InlineData(-1, long.MinValue, (ulong.MaxValue / 2) - 1)] + [InlineData(0, long.MinValue, long.MaxValue)] + [InlineData(0, long.MinValue + 1, (ulong.MaxValue / 2) - 1)] + [InlineData(0, long.MaxValue, (ulong.MaxValue / 2) - 1)] + [InlineData(0, -1, 0)] + [InlineData(0, 1, 0)] + [InlineData(1, -1, 1)] + [InlineData(1, 0, 0)] + [InlineData(1, long.MinValue, long.MaxValue)] + [InlineData(long.MaxValue, long.MinValue, 1)] + [InlineData(long.MaxValue, -1, long.MaxValue)] + [InlineData(long.MaxValue, 0, (ulong.MaxValue / 2) - 1)] + [Theory] + public void When_a_long_value_is_not_close_to_expected_value_it_should_fail(long actual, long nearbyValue, + ulong delta) + { + // Act + Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_a_long_value_is_not_close_to_expected_value_it_should_fail_with_a_descriptive_message() + { + // Arrange + long actual = 1; + long nearbyValue = 4; + ulong delta = 2; + + // Act + Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); + + // Assert + act.Should().Throw() + .WithMessage("*be within*2*from*4*but found*1*"); + } + + [Fact] + public void When_a_long_value_is_returned_from_BeCloseTo_it_should_chain() + { + // Arrange + long actual = long.MaxValue; + + // Act + Action act = () => actual.Should().BeCloseTo(actual, 0) + .And.Be(actual); + + // Assert + act.Should().NotThrow(); + } + + [InlineData(0, 0, 0)] + [InlineData(0, 0, 1)] + [InlineData(0, 1, 1)] + [InlineData(1, 0, 1)] + [InlineData(1, byte.MaxValue, byte.MaxValue)] + [InlineData(byte.MinValue, byte.MinValue + 1, byte.MaxValue)] + [InlineData(byte.MinValue + 1, 0, byte.MaxValue)] + [InlineData(byte.MinValue + 1, byte.MinValue, 1)] + [InlineData(byte.MinValue + 1, byte.MinValue, byte.MaxValue)] + [InlineData(byte.MaxValue - 1, byte.MaxValue, 1)] + [InlineData(byte.MaxValue - 1, byte.MaxValue, byte.MaxValue)] + [InlineData(byte.MaxValue, 0, byte.MaxValue)] + [InlineData(byte.MaxValue, 1, byte.MaxValue)] + [InlineData(byte.MaxValue, byte.MaxValue - 1, 1)] + [InlineData(byte.MaxValue, byte.MaxValue - 1, byte.MaxValue)] + [InlineData(byte.MaxValue, byte.MaxValue, 0)] + [InlineData(byte.MaxValue, byte.MaxValue, 1)] + [Theory] + public void When_a_byte_value_is_close_to_expected_value_it_should_succeed(byte actual, byte nearbyValue, byte delta) + { + // Act + Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); + + // Assert + act.Should().NotThrow(); + } + + [InlineData(0, 1, 0)] + [InlineData(1, 0, 0)] + [InlineData(byte.MinValue, byte.MaxValue, 1)] + [InlineData(byte.MaxValue, byte.MinValue, 1)] + [Theory] + public void When_a_byte_value_is_not_close_to_expected_value_it_should_fail(byte actual, byte nearbyValue, byte delta) + { + // Act + Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_a_byte_value_is_not_close_to_expected_value_it_should_fail_with_a_descriptive_message() + { + // Arrange + byte actual = 1; + byte nearbyValue = 4; + byte delta = 2; + + // Act + Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); + + // Assert + act.Should().Throw() + .WithMessage("*be within*2*from*4*but found*1*"); + } + + [Fact] + public void When_a_byte_value_is_returned_from_BeCloseTo_it_should_chain() + { + // Arrange + byte actual = byte.MaxValue; + + // Act + Action act = () => actual.Should().BeCloseTo(actual, 0) + .And.Be(actual); + + // Assert + act.Should().NotThrow(); + } + + [InlineData(0, 0, 0)] + [InlineData(0, 0, 1)] + [InlineData(0, 1, 1)] + [InlineData(1, 0, 1)] + [InlineData(1, ushort.MaxValue, ushort.MaxValue)] + [InlineData(ushort.MinValue, ushort.MinValue + 1, ushort.MaxValue)] + [InlineData(ushort.MinValue + 1, 0, ushort.MaxValue)] + [InlineData(ushort.MinValue + 1, ushort.MinValue, 1)] + [InlineData(ushort.MinValue + 1, ushort.MinValue, ushort.MaxValue)] + [InlineData(ushort.MaxValue - 1, ushort.MaxValue, 1)] + [InlineData(ushort.MaxValue - 1, ushort.MaxValue, ushort.MaxValue)] + [InlineData(ushort.MaxValue, 0, ushort.MaxValue)] + [InlineData(ushort.MaxValue, 1, ushort.MaxValue)] + [InlineData(ushort.MaxValue, ushort.MaxValue - 1, 1)] + [InlineData(ushort.MaxValue, ushort.MaxValue - 1, ushort.MaxValue)] + [InlineData(ushort.MaxValue, ushort.MaxValue, 0)] + [InlineData(ushort.MaxValue, ushort.MaxValue, 1)] + [Theory] + public void When_an_ushort_value_is_close_to_expected_value_it_should_succeed(ushort actual, ushort nearbyValue, + ushort delta) + { + // Act + Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); + + // Assert + act.Should().NotThrow(); + } + + [InlineData(0, 1, 0)] + [InlineData(1, 0, 0)] + [InlineData(ushort.MinValue, ushort.MaxValue, 1)] + [InlineData(ushort.MaxValue, ushort.MinValue, 1)] + [Theory] + public void When_an_ushort_value_is_not_close_to_expected_value_it_should_fail(ushort actual, ushort nearbyValue, + ushort delta) + { + // Act + Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_an_ushort_value_is_not_close_to_expected_value_it_should_fail_with_a_descriptive_message() + { + // Arrange + ushort actual = 1; + ushort nearbyValue = 4; + ushort delta = 2; + + // Act + Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); + + // Assert + act.Should().Throw() + .WithMessage("*be within*2*from*4*but found*1*"); + } + + [Fact] + public void When_an_ushort_value_is_returned_from_BeCloseTo_it_should_chain() + { + // Arrange + ushort actual = ushort.MaxValue; + + // Act + Action act = () => actual.Should().BeCloseTo(actual, 0) + .And.Be(actual); + + // Assert + act.Should().NotThrow(); + } + + [InlineData(0, 0, 0)] + [InlineData(0, 0, 1)] + [InlineData(0, 1, 1)] + [InlineData(1, 0, 1)] + [InlineData(1, uint.MaxValue, uint.MaxValue)] + [InlineData(uint.MinValue, uint.MinValue + 1, uint.MaxValue)] + [InlineData(uint.MinValue + 1, 0, uint.MaxValue)] + [InlineData(uint.MinValue + 1, uint.MinValue, 1)] + [InlineData(uint.MinValue + 1, uint.MinValue, uint.MaxValue)] + [InlineData(uint.MaxValue - 1, uint.MaxValue, 1)] + [InlineData(uint.MaxValue - 1, uint.MaxValue, uint.MaxValue)] + [InlineData(uint.MaxValue, 0, uint.MaxValue)] + [InlineData(uint.MaxValue, 1, uint.MaxValue)] + [InlineData(uint.MaxValue, uint.MaxValue - 1, 1)] + [InlineData(uint.MaxValue, uint.MaxValue - 1, uint.MaxValue)] + [InlineData(uint.MaxValue, uint.MaxValue, 0)] + [InlineData(uint.MaxValue, uint.MaxValue, 1)] + [Theory] + public void When_an_uint_value_is_close_to_expected_value_it_should_succeed(uint actual, uint nearbyValue, uint delta) + { + // Act + Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); + + // Assert + act.Should().NotThrow(); + } + + [InlineData(0, 1, 0)] + [InlineData(1, 0, 0)] + [InlineData(uint.MinValue, uint.MaxValue, 1)] + [InlineData(uint.MaxValue, uint.MinValue, 1)] + [Theory] + public void When_an_uint_value_is_not_close_to_expected_value_it_should_fail(uint actual, uint nearbyValue, + uint delta) + { + // Act + Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_an_uint_value_is_not_close_to_expected_value_it_should_fail_with_a_descriptive_message() + { + // Arrange + uint actual = 1; + uint nearbyValue = 4; + uint delta = 2; + + // Act + Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); + + // Assert + act.Should().Throw() + .WithMessage("*be within*2*from*4*but found*1*"); + } + + [Fact] + public void When_an_uint_value_is_returned_from_BeCloseTo_it_should_chain() + { + // Arrange + uint actual = uint.MaxValue; + + // Act + Action act = () => actual.Should().BeCloseTo(actual, 0) + .And.Be(actual); + + // Assert + act.Should().NotThrow(); + } + + [InlineData(0, 0, 0)] + [InlineData(0, 0, 1)] + [InlineData(0, 1, 1)] + [InlineData(1, 0, 1)] + [InlineData(1, ulong.MaxValue, ulong.MaxValue)] + [InlineData(ulong.MinValue, ulong.MinValue + 1, ulong.MaxValue)] + [InlineData(ulong.MinValue + 1, 0, ulong.MaxValue)] + [InlineData(ulong.MinValue + 1, ulong.MinValue, 1)] + [InlineData(ulong.MinValue + 1, ulong.MinValue, ulong.MaxValue)] + [InlineData(ulong.MaxValue - 1, ulong.MaxValue, 1)] + [InlineData(ulong.MaxValue - 1, ulong.MaxValue, ulong.MaxValue)] + [InlineData(ulong.MaxValue, 0, ulong.MaxValue)] + [InlineData(ulong.MaxValue, 1, ulong.MaxValue)] + [InlineData(ulong.MaxValue, ulong.MaxValue - 1, 1)] + [InlineData(ulong.MaxValue, ulong.MaxValue - 1, ulong.MaxValue)] + [InlineData(ulong.MaxValue, ulong.MaxValue, 0)] + [InlineData(ulong.MaxValue, ulong.MaxValue, 1)] + [Theory] + public void When_an_ulong_value_is_close_to_expected_value_it_should_succeed(ulong actual, ulong nearbyValue, + ulong delta) + { + // Act + Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); + + // Assert + act.Should().NotThrow(); + } + + [InlineData(0, 1, 0)] + [InlineData(1, 0, 0)] + [InlineData(ulong.MinValue, ulong.MaxValue, 1)] + [InlineData(ulong.MaxValue, ulong.MinValue, 1)] + [Theory] + public void When_an_ulong_value_is_not_close_to_expected_value_it_should_fail(ulong actual, ulong nearbyValue, + ulong delta) + { + // Act + Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_an_ulong_value_is_not_close_to_expected_value_it_should_fail_with_a_descriptive_message() + { + // Arrange + ulong actual = 1; + ulong nearbyValue = 4; + ulong delta = 2; + + // Act + Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); + + // Assert + act.Should().Throw() + .WithMessage("*be within*2*from*4*but found*1*"); + } + + [Fact] + public void When_an_ulong_value_is_returned_from_BeCloseTo_it_should_chain() + { + // Arrange + ulong actual = ulong.MaxValue; + + // Act + Action act = () => actual.Should().BeCloseTo(actual, 0) + .And.Be(actual); + + // Assert + act.Should().NotThrow(); + } + } + + public class NotBeCloseTo + { + [InlineData(sbyte.MinValue, sbyte.MaxValue, 1)] + [InlineData(sbyte.MinValue, 0, sbyte.MaxValue)] + [InlineData(sbyte.MinValue, 1, sbyte.MaxValue)] + [InlineData(-1, 0, 0)] + [InlineData(-1, 1, 1)] + [InlineData(-1, sbyte.MaxValue, sbyte.MaxValue)] + [InlineData(0, sbyte.MinValue, sbyte.MaxValue)] + [InlineData(0, -1, 0)] + [InlineData(0, 1, 0)] + [InlineData(1, -1, 1)] + [InlineData(1, 0, 0)] + [InlineData(1, sbyte.MinValue, sbyte.MaxValue)] + [InlineData(sbyte.MaxValue, sbyte.MinValue, 1)] + [InlineData(sbyte.MaxValue, -1, sbyte.MaxValue)] + [Theory] + public void When_a_sbyte_value_is_not_close_to_expected_value_it_should_succeed(sbyte actual, sbyte distantValue, + byte delta) + { + // Act + Action act = () => actual.Should().NotBeCloseTo(distantValue, delta); + + // Assert + act.Should().NotThrow(); + } + + [InlineData(sbyte.MinValue, sbyte.MinValue, 0)] + [InlineData(sbyte.MinValue, sbyte.MinValue, 1)] + [InlineData(sbyte.MinValue, sbyte.MinValue, sbyte.MaxValue)] + [InlineData(sbyte.MinValue, sbyte.MinValue + 1, 1)] + [InlineData(sbyte.MinValue, sbyte.MinValue + 1, sbyte.MaxValue)] + [InlineData(sbyte.MinValue, -1, sbyte.MaxValue)] + [InlineData(sbyte.MinValue + 1, sbyte.MinValue, 1)] + [InlineData(sbyte.MinValue + 1, sbyte.MinValue, sbyte.MaxValue)] + [InlineData(sbyte.MinValue + 1, 0, sbyte.MaxValue)] + [InlineData(-1, sbyte.MinValue, sbyte.MaxValue)] + [InlineData(-1, 0, 1)] + [InlineData(-1, 0, sbyte.MaxValue)] + [InlineData(0, 0, 0)] + [InlineData(0, 0, 1)] + [InlineData(0, -1, 1)] + [InlineData(0, -1, sbyte.MaxValue)] + [InlineData(0, 1, 1)] + [InlineData(0, 1, sbyte.MaxValue)] + [InlineData(0, sbyte.MaxValue, sbyte.MaxValue)] + [InlineData(0, sbyte.MinValue + 1, sbyte.MaxValue)] + [InlineData(1, 0, 1)] + [InlineData(1, 0, sbyte.MaxValue)] + [InlineData(1, sbyte.MaxValue, sbyte.MaxValue)] + [InlineData(sbyte.MaxValue - 1, sbyte.MaxValue, 1)] + [InlineData(sbyte.MaxValue - 1, sbyte.MaxValue, sbyte.MaxValue)] + [InlineData(sbyte.MaxValue, 0, sbyte.MaxValue)] + [InlineData(sbyte.MaxValue, 1, sbyte.MaxValue)] + [InlineData(sbyte.MaxValue, sbyte.MaxValue, 0)] + [InlineData(sbyte.MaxValue, sbyte.MaxValue, 1)] + [InlineData(sbyte.MaxValue, sbyte.MaxValue, sbyte.MaxValue)] + [InlineData(sbyte.MaxValue, sbyte.MaxValue - 1, 1)] + [InlineData(sbyte.MaxValue, sbyte.MaxValue - 1, sbyte.MaxValue)] + [Theory] + public void When_a_sbyte_value_is_close_to_expected_value_it_should_fail(sbyte actual, sbyte distantValue, byte delta) + { + // Act + Action act = () => actual.Should().NotBeCloseTo(distantValue, delta); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_a_sbyte_value_is_close_to_expected_value_it_should_fail_with_a_descriptive_message() + { + // Arrange + sbyte actual = 1; + sbyte nearbyValue = 3; + byte delta = 2; + + // Act + Action act = () => actual.Should().NotBeCloseTo(nearbyValue, delta); + + // Assert + act.Should().Throw() + .WithMessage("*be within*2*from*3*but found*1*"); + } + + [Fact] + public void When_a_sbyte_value_is_returned_from_NotBeCloseTo_it_should_chain() + { + // Arrange + sbyte actual = sbyte.MaxValue; + + // Act + Action act = () => actual.Should().NotBeCloseTo(0, 0) + .And.Be(actual); + + // Assert + act.Should().NotThrow(); + } + + [InlineData(short.MinValue, short.MaxValue, 1)] + [InlineData(short.MinValue, 0, short.MaxValue)] + [InlineData(short.MinValue, 1, short.MaxValue)] + [InlineData(-1, 0, 0)] + [InlineData(-1, 1, 1)] + [InlineData(-1, short.MaxValue, short.MaxValue)] + [InlineData(0, short.MinValue, short.MaxValue)] + [InlineData(0, -1, 0)] + [InlineData(0, 1, 0)] + [InlineData(1, -1, 1)] + [InlineData(1, 0, 0)] + [InlineData(1, short.MinValue, short.MaxValue)] + [InlineData(short.MaxValue, short.MinValue, 1)] + [InlineData(short.MaxValue, -1, short.MaxValue)] + [Theory] + public void When_a_short_value_is_not_close_to_expected_value_it_should_succeed(short actual, short distantValue, + ushort delta) + { + // Act + Action act = () => actual.Should().NotBeCloseTo(distantValue, delta); + + // Assert + act.Should().NotThrow(); + } + + [InlineData(short.MinValue, short.MinValue, 0)] + [InlineData(short.MinValue, short.MinValue, 1)] + [InlineData(short.MinValue, short.MinValue, short.MaxValue)] + [InlineData(short.MinValue, short.MinValue + 1, 1)] + [InlineData(short.MinValue, short.MinValue + 1, short.MaxValue)] + [InlineData(short.MinValue, -1, short.MaxValue)] + [InlineData(short.MinValue + 1, short.MinValue, 1)] + [InlineData(short.MinValue + 1, short.MinValue, short.MaxValue)] + [InlineData(short.MinValue + 1, 0, short.MaxValue)] + [InlineData(-1, short.MinValue, short.MaxValue)] + [InlineData(-1, 0, 1)] + [InlineData(-1, 0, short.MaxValue)] + [InlineData(0, 0, 0)] + [InlineData(0, 0, 1)] + [InlineData(0, -1, 1)] + [InlineData(0, -1, short.MaxValue)] + [InlineData(0, 1, 1)] + [InlineData(0, 1, short.MaxValue)] + [InlineData(0, short.MaxValue, short.MaxValue)] + [InlineData(0, short.MinValue + 1, short.MaxValue)] + [InlineData(1, 0, 1)] + [InlineData(1, 0, short.MaxValue)] + [InlineData(1, short.MaxValue, short.MaxValue)] + [InlineData(short.MaxValue - 1, short.MaxValue, 1)] + [InlineData(short.MaxValue - 1, short.MaxValue, short.MaxValue)] + [InlineData(short.MaxValue, 0, short.MaxValue)] + [InlineData(short.MaxValue, 1, short.MaxValue)] + [InlineData(short.MaxValue, short.MaxValue, 0)] + [InlineData(short.MaxValue, short.MaxValue, 1)] + [InlineData(short.MaxValue, short.MaxValue, short.MaxValue)] + [InlineData(short.MaxValue, short.MaxValue - 1, 1)] + [InlineData(short.MaxValue, short.MaxValue - 1, short.MaxValue)] + [Theory] + public void When_a_short_value_is_close_to_expected_value_it_should_fail(short actual, short distantValue, + ushort delta) + { + // Act + Action act = () => actual.Should().NotBeCloseTo(distantValue, delta); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_a_short_value_is_close_to_expected_value_it_should_fail_with_a_descriptive_message() + { + // Arrange + short actual = 1; + short nearbyValue = 3; + ushort delta = 2; + + // Act + Action act = () => actual.Should().NotBeCloseTo(nearbyValue, delta); + + // Assert + act.Should().Throw() + .WithMessage("*be within*2*from*3*but found*1*"); + } + + [Fact] + public void When_a_short_value_is_returned_from_NotBeCloseTo_it_should_chain() + { + // Arrange + short actual = short.MaxValue; + + // Act + Action act = () => actual.Should().NotBeCloseTo(0, 0) + .And.Be(actual); + + // Assert + act.Should().NotThrow(); + } + + [InlineData(int.MinValue, int.MaxValue, 1)] + [InlineData(int.MinValue, 0, int.MaxValue)] + [InlineData(int.MinValue, 1, int.MaxValue)] + [InlineData(-1, 0, 0)] + [InlineData(-1, 1, 1)] + [InlineData(-1, int.MaxValue, int.MaxValue)] + [InlineData(0, int.MinValue, int.MaxValue)] + [InlineData(0, -1, 0)] + [InlineData(0, 1, 0)] + [InlineData(1, -1, 1)] + [InlineData(1, 0, 0)] + [InlineData(1, int.MinValue, int.MaxValue)] + [InlineData(int.MaxValue, int.MinValue, 1)] + [InlineData(int.MaxValue, -1, int.MaxValue)] + [Theory] + public void When_an_int_value_is_not_close_to_expected_value_it_should_succeed(int actual, int distantValue, + uint delta) + { + // Act + Action act = () => actual.Should().NotBeCloseTo(distantValue, delta); + + // Assert + act.Should().NotThrow(); + } + + [InlineData(int.MinValue, int.MinValue, 0)] + [InlineData(int.MinValue, int.MinValue, 1)] + [InlineData(int.MinValue, int.MinValue, int.MaxValue)] + [InlineData(int.MinValue, int.MinValue + 1, 1)] + [InlineData(int.MinValue, int.MinValue + 1, int.MaxValue)] + [InlineData(int.MinValue, -1, int.MaxValue)] + [InlineData(int.MinValue + 1, int.MinValue, 1)] + [InlineData(int.MinValue + 1, int.MinValue, int.MaxValue)] + [InlineData(int.MinValue + 1, 0, int.MaxValue)] + [InlineData(-1, int.MinValue, int.MaxValue)] + [InlineData(-1, 0, 1)] + [InlineData(-1, 0, int.MaxValue)] + [InlineData(0, 0, 0)] + [InlineData(0, 0, 1)] + [InlineData(0, -1, 1)] + [InlineData(0, -1, int.MaxValue)] + [InlineData(0, 1, 1)] + [InlineData(0, 1, int.MaxValue)] + [InlineData(0, int.MaxValue, int.MaxValue)] + [InlineData(0, int.MinValue + 1, int.MaxValue)] + [InlineData(1, 0, 1)] + [InlineData(1, 0, int.MaxValue)] + [InlineData(1, int.MaxValue, int.MaxValue)] + [InlineData(int.MaxValue - 1, int.MaxValue, 1)] + [InlineData(int.MaxValue - 1, int.MaxValue, int.MaxValue)] + [InlineData(int.MaxValue, 0, int.MaxValue)] + [InlineData(int.MaxValue, 1, int.MaxValue)] + [InlineData(int.MaxValue, int.MaxValue, 0)] + [InlineData(int.MaxValue, int.MaxValue, 1)] + [InlineData(int.MaxValue, int.MaxValue, int.MaxValue)] + [InlineData(int.MaxValue, int.MaxValue - 1, 1)] + [InlineData(int.MaxValue, int.MaxValue - 1, int.MaxValue)] + [Theory] + public void When_an_int_value_is_close_to_expected_value_it_should_fail(int actual, int distantValue, uint delta) + { + // Act + Action act = () => actual.Should().NotBeCloseTo(distantValue, delta); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_an_int_value_is_close_to_expected_value_it_should_fail_with_a_descriptive_message() + { + // Arrange + int actual = 1; + int nearbyValue = 3; + uint delta = 2; + + // Act + Action act = () => actual.Should().NotBeCloseTo(nearbyValue, delta); + + // Assert + act.Should().Throw() + .WithMessage("*be within*2*from*3*but found*1*"); + } + + [Fact] + public void When_an_int_value_is_returned_from_NotBeCloseTo_it_should_chain() + { + // Arrange + int actual = int.MaxValue; + + // Act + Action act = () => actual.Should().NotBeCloseTo(0, 0) + .And.Be(actual); + + // Assert + act.Should().NotThrow(); + } + + [InlineData(long.MinValue, long.MaxValue, 1)] + [InlineData(long.MinValue, 0, long.MaxValue)] + [InlineData(long.MinValue, 1, long.MaxValue)] + [InlineData(long.MinValue + 1, 0, (ulong.MaxValue / 2) - 1)] + [InlineData(long.MinValue, long.MaxValue, (ulong.MaxValue / 2) - 1)] + [InlineData(long.MinValue, long.MaxValue, ulong.MaxValue / 2)] + [InlineData(-1, 0, 0)] + [InlineData(-1, 1, 1)] + [InlineData(-1, long.MaxValue, long.MaxValue)] + [InlineData(-1, long.MinValue, (ulong.MaxValue / 2) - 1)] + [InlineData(0, long.MinValue, long.MaxValue)] + [InlineData(0, long.MinValue + 1, (ulong.MaxValue / 2) - 1)] + [InlineData(0, long.MaxValue, (ulong.MaxValue / 2) - 1)] + [InlineData(0, -1, 0)] + [InlineData(0, 1, 0)] + [InlineData(1, -1, 1)] + [InlineData(1, 0, 0)] + [InlineData(1, long.MinValue, long.MaxValue)] + [InlineData(long.MaxValue, long.MinValue, 1)] + [InlineData(long.MaxValue, -1, long.MaxValue)] + [InlineData(long.MaxValue, 0, (ulong.MaxValue / 2) - 1)] + [Theory] + public void When_a_long_value_is_not_close_to_expected_value_it_should_succeed(long actual, long distantValue, + ulong delta) + { + // Act + Action act = () => actual.Should().NotBeCloseTo(distantValue, delta); + + // Assert + act.Should().NotThrow(); + } + + [InlineData(long.MinValue, long.MinValue, 0)] + [InlineData(long.MinValue, long.MinValue, 1)] + [InlineData(long.MinValue, long.MinValue, (ulong.MaxValue / 2) - 1)] + [InlineData(long.MinValue, long.MinValue, ulong.MaxValue / 2)] + [InlineData(long.MinValue, long.MinValue, (ulong.MaxValue / 2) + 1)] + [InlineData(long.MinValue, long.MinValue, ulong.MaxValue)] + [InlineData(long.MinValue, long.MinValue + 1, 1)] + [InlineData(long.MinValue, long.MinValue + 1, (ulong.MaxValue / 2) - 1)] + [InlineData(long.MinValue, long.MinValue + 1, ulong.MaxValue / 2)] + [InlineData(long.MinValue, long.MinValue + 1, (ulong.MaxValue / 2) + 1)] + [InlineData(long.MinValue, long.MinValue + 1, ulong.MaxValue)] + [InlineData(long.MinValue, -1, long.MaxValue)] + [InlineData(long.MinValue + 1, long.MinValue, 1)] + [InlineData(long.MinValue + 1, long.MinValue, (ulong.MaxValue / 2) - 1)] + [InlineData(long.MinValue + 1, long.MinValue, ulong.MaxValue / 2)] + [InlineData(long.MinValue + 1, long.MinValue, (ulong.MaxValue / 2) + 1)] + [InlineData(long.MinValue + 1, long.MinValue, ulong.MaxValue)] + [InlineData(long.MinValue + 1, 0, ulong.MaxValue / 2)] + [InlineData(long.MinValue + 1, 0, (ulong.MaxValue / 2) + 1)] + [InlineData(long.MinValue + 1, 0, ulong.MaxValue)] + [InlineData(long.MinValue, long.MaxValue, ulong.MaxValue)] + [InlineData(-1, long.MinValue, ulong.MaxValue / 2)] + [InlineData(-1, long.MinValue, (ulong.MaxValue / 2) + 1)] + [InlineData(-1, long.MinValue, ulong.MaxValue)] + [InlineData(-1, 0, 1)] + [InlineData(-1, 0, (ulong.MaxValue / 2) - 1)] + [InlineData(-1, 0, ulong.MaxValue / 2)] + [InlineData(-1, 0, (ulong.MaxValue / 2) + 1)] + [InlineData(-1, 0, ulong.MaxValue)] + [InlineData(0, 0, 0)] + [InlineData(0, 0, 1)] + [InlineData(0, -1, 1)] + [InlineData(0, -1, (ulong.MaxValue / 2) - 1)] + [InlineData(0, -1, ulong.MaxValue / 2)] + [InlineData(0, -1, (ulong.MaxValue / 2) + 1)] + [InlineData(0, -1, ulong.MaxValue)] + [InlineData(0, 1, 1)] + [InlineData(0, 1, (ulong.MaxValue / 2) - 1)] + [InlineData(0, 1, ulong.MaxValue / 2)] + [InlineData(0, 1, (ulong.MaxValue / 2) + 1)] + [InlineData(0, 1, ulong.MaxValue)] + [InlineData(0, long.MaxValue, ulong.MaxValue / 2)] + [InlineData(0, long.MaxValue, (ulong.MaxValue / 2) + 1)] + [InlineData(0, long.MaxValue, ulong.MaxValue)] + [InlineData(0, long.MinValue + 1, ulong.MaxValue / 2)] + [InlineData(0, long.MinValue + 1, (ulong.MaxValue / 2) + 1)] + [InlineData(0, long.MinValue + 1, ulong.MaxValue)] + [InlineData(1, 0, 1)] + [InlineData(1, 0, (ulong.MaxValue / 2) - 1)] + [InlineData(1, 0, ulong.MaxValue / 2)] + [InlineData(1, 0, (ulong.MaxValue / 2) + 1)] + [InlineData(1, 0, ulong.MaxValue)] + [InlineData(1, long.MaxValue, (ulong.MaxValue / 2) - 1)] + [InlineData(1, long.MaxValue, ulong.MaxValue / 2)] + [InlineData(1, long.MaxValue, (ulong.MaxValue / 2) + 1)] + [InlineData(1, long.MaxValue, ulong.MaxValue)] + [InlineData(long.MaxValue - 1, long.MaxValue, 1)] + [InlineData(long.MaxValue - 1, long.MaxValue, (ulong.MaxValue / 2) - 1)] + [InlineData(long.MaxValue - 1, long.MaxValue, ulong.MaxValue / 2)] + [InlineData(long.MaxValue - 1, long.MaxValue, (ulong.MaxValue / 2) + 1)] + [InlineData(long.MaxValue - 1, long.MaxValue, ulong.MaxValue)] + [InlineData(long.MaxValue, 0, ulong.MaxValue / 2)] + [InlineData(long.MaxValue, 0, (ulong.MaxValue / 2) + 1)] + [InlineData(long.MaxValue, 0, ulong.MaxValue)] + [InlineData(long.MaxValue, 1, (ulong.MaxValue / 2) - 1)] + [InlineData(long.MaxValue, 1, ulong.MaxValue / 2)] + [InlineData(long.MaxValue, 1, (ulong.MaxValue / 2) + 1)] + [InlineData(long.MaxValue, 1, ulong.MaxValue)] + [InlineData(long.MaxValue, long.MaxValue, 0)] + [InlineData(long.MaxValue, long.MaxValue, 1)] + [InlineData(long.MaxValue, long.MaxValue, (ulong.MaxValue / 2) - 1)] + [InlineData(long.MaxValue, long.MaxValue, ulong.MaxValue / 2)] + [InlineData(long.MaxValue, long.MaxValue, (ulong.MaxValue / 2) + 1)] + [InlineData(long.MaxValue, long.MaxValue, ulong.MaxValue)] + [InlineData(long.MaxValue, long.MaxValue - 1, 1)] + [InlineData(long.MaxValue, long.MaxValue - 1, (ulong.MaxValue / 2) - 1)] + [InlineData(long.MaxValue, long.MaxValue - 1, ulong.MaxValue / 2)] + [InlineData(long.MaxValue, long.MaxValue - 1, (ulong.MaxValue / 2) + 1)] + [InlineData(long.MaxValue, long.MaxValue - 1, ulong.MaxValue)] + [Theory] + public void When_a_long_value_is_close_to_expected_value_it_should_fail(long actual, long distantValue, ulong delta) + { + // Act + Action act = () => actual.Should().NotBeCloseTo(distantValue, delta); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_a_long_value_is_close_to_expected_value_it_should_fail_with_a_descriptive_message() + { + // Arrange + long actual = 1; + long nearbyValue = 3; + ulong delta = 2; + + // Act + Action act = () => actual.Should().NotBeCloseTo(nearbyValue, delta); + + // Assert + act.Should().Throw() + .WithMessage("*be within*2*from*3*but found*1*"); + } + + [Fact] + public void When_a_long_value_is_returned_from_NotBeCloseTo_it_should_chain() + { + // Arrange + long actual = long.MaxValue; + + // Act + Action act = () => actual.Should().NotBeCloseTo(0, 0) + .And.Be(actual); + + // Assert + act.Should().NotThrow(); + } + + [InlineData(0, 1, 0)] + [InlineData(1, 0, 0)] + [InlineData(byte.MinValue, byte.MaxValue, 1)] + [InlineData(byte.MaxValue, byte.MinValue, 1)] + [Theory] + public void When_a_byte_value_is_not_close_to_expected_value_it_should_succeed(byte actual, byte distantValue, + byte delta) + { + // Act + Action act = () => actual.Should().NotBeCloseTo(distantValue, delta); + + // Assert + act.Should().NotThrow(); + } + + [InlineData(0, 0, 0)] + [InlineData(0, 0, 1)] + [InlineData(0, 1, 1)] + [InlineData(1, 0, 1)] + [InlineData(1, byte.MaxValue, byte.MaxValue)] + [InlineData(byte.MinValue, byte.MinValue + 1, byte.MaxValue)] + [InlineData(byte.MinValue + 1, 0, byte.MaxValue)] + [InlineData(byte.MinValue + 1, byte.MinValue, 1)] + [InlineData(byte.MinValue + 1, byte.MinValue, byte.MaxValue)] + [InlineData(byte.MaxValue - 1, byte.MaxValue, 1)] + [InlineData(byte.MaxValue - 1, byte.MaxValue, byte.MaxValue)] + [InlineData(byte.MaxValue, 0, byte.MaxValue)] + [InlineData(byte.MaxValue, 1, byte.MaxValue)] + [InlineData(byte.MaxValue, byte.MaxValue - 1, 1)] + [InlineData(byte.MaxValue, byte.MaxValue - 1, byte.MaxValue)] + [InlineData(byte.MaxValue, byte.MaxValue, 0)] + [InlineData(byte.MaxValue, byte.MaxValue, 1)] + [Theory] + public void When_a_byte_value_is_close_to_expected_value_it_should_fail(byte actual, byte distantValue, byte delta) + { + // Act + Action act = () => actual.Should().NotBeCloseTo(distantValue, delta); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_a_byte_value_is_close_to_expected_value_it_should_fail_with_a_descriptive_message() + { + // Arrange + byte actual = 1; + byte nearbyValue = 3; + byte delta = 2; + + // Act + Action act = () => actual.Should().NotBeCloseTo(nearbyValue, delta); + + // Assert + act.Should().Throw() + .WithMessage("*be within*2*from*3*but found*1*"); + } + + [Fact] + public void When_a_byte_value_is_returned_from_NotBeCloseTo_it_should_chain() + { + // Arrange + byte actual = byte.MaxValue; + + // Act + Action act = () => actual.Should().NotBeCloseTo(0, 0) + .And.Be(actual); + + // Assert + act.Should().NotThrow(); + } + + [InlineData(0, 1, 0)] + [InlineData(1, 0, 0)] + [InlineData(ushort.MinValue, ushort.MaxValue, 1)] + [InlineData(ushort.MaxValue, ushort.MinValue, 1)] + [Theory] + public void When_an_ushort_value_is_not_close_to_expected_value_it_should_succeed(ushort actual, ushort distantValue, + ushort delta) + { + // Act + Action act = () => actual.Should().NotBeCloseTo(distantValue, delta); + + // Assert + act.Should().NotThrow(); + } + + [InlineData(0, 0, 0)] + [InlineData(0, 0, 1)] + [InlineData(0, 1, 1)] + [InlineData(1, 0, 1)] + [InlineData(1, ushort.MaxValue, ushort.MaxValue)] + [InlineData(ushort.MinValue, ushort.MinValue + 1, ushort.MaxValue)] + [InlineData(ushort.MinValue + 1, 0, ushort.MaxValue)] + [InlineData(ushort.MinValue + 1, ushort.MinValue, 1)] + [InlineData(ushort.MinValue + 1, ushort.MinValue, ushort.MaxValue)] + [InlineData(ushort.MaxValue - 1, ushort.MaxValue, 1)] + [InlineData(ushort.MaxValue - 1, ushort.MaxValue, ushort.MaxValue)] + [InlineData(ushort.MaxValue, 0, ushort.MaxValue)] + [InlineData(ushort.MaxValue, 1, ushort.MaxValue)] + [InlineData(ushort.MaxValue, ushort.MaxValue - 1, 1)] + [InlineData(ushort.MaxValue, ushort.MaxValue - 1, ushort.MaxValue)] + [InlineData(ushort.MaxValue, ushort.MaxValue, 0)] + [InlineData(ushort.MaxValue, ushort.MaxValue, 1)] + [Theory] + public void When_an_ushort_value_is_close_to_expected_value_it_should_fail(ushort actual, ushort distantValue, + ushort delta) + { + // Act + Action act = () => actual.Should().NotBeCloseTo(distantValue, delta); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_an_ushort_value_is_close_to_expected_value_it_should_fail_with_a_descriptive_message() + { + // Arrange + ushort actual = 1; + ushort nearbyValue = 3; + ushort delta = 2; + + // Act + Action act = () => actual.Should().NotBeCloseTo(nearbyValue, delta); + + // Assert + act.Should().Throw() + .WithMessage("*be within*2*from*3*but found*1*"); + } + + [Fact] + public void When_an_ushort_value_is_returned_from_NotBeCloseTo_it_should_chain() + { + // Arrange + ushort actual = ushort.MaxValue; + + // Act + Action act = () => actual.Should().NotBeCloseTo(0, 0) + .And.Be(actual); + + // Assert + act.Should().NotThrow(); + } + + [InlineData(0, 1, 0)] + [InlineData(1, 0, 0)] + [InlineData(uint.MinValue, uint.MaxValue, 1)] + [InlineData(uint.MaxValue, uint.MinValue, 1)] + [Theory] + public void When_an_uint_value_is_not_close_to_expected_value_it_should_succeed(uint actual, uint distantValue, + uint delta) + { + // Act + Action act = () => actual.Should().NotBeCloseTo(distantValue, delta); + + // Assert + act.Should().NotThrow(); + } + + [InlineData(0, 0, 0)] + [InlineData(0, 0, 1)] + [InlineData(0, 1, 1)] + [InlineData(1, 0, 1)] + [InlineData(1, uint.MaxValue, uint.MaxValue)] + [InlineData(uint.MinValue, uint.MinValue + 1, uint.MaxValue)] + [InlineData(uint.MinValue + 1, 0, uint.MaxValue)] + [InlineData(uint.MinValue + 1, uint.MinValue, 1)] + [InlineData(uint.MinValue + 1, uint.MinValue, uint.MaxValue)] + [InlineData(uint.MaxValue - 1, uint.MaxValue, 1)] + [InlineData(uint.MaxValue - 1, uint.MaxValue, uint.MaxValue)] + [InlineData(uint.MaxValue, 0, uint.MaxValue)] + [InlineData(uint.MaxValue, 1, uint.MaxValue)] + [InlineData(uint.MaxValue, uint.MaxValue - 1, 1)] + [InlineData(uint.MaxValue, uint.MaxValue - 1, uint.MaxValue)] + [InlineData(uint.MaxValue, uint.MaxValue, 0)] + [InlineData(uint.MaxValue, uint.MaxValue, 1)] + [Theory] + public void When_an_uint_value_is_close_to_expected_value_it_should_fail(uint actual, uint distantValue, uint delta) + { + // Act + Action act = () => actual.Should().NotBeCloseTo(distantValue, delta); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_an_uint_value_is_close_to_expected_value_it_should_fail_with_a_descriptive_message() + { + // Arrange + uint actual = 1; + uint nearbyValue = 3; + uint delta = 2; + + // Act + Action act = () => actual.Should().NotBeCloseTo(nearbyValue, delta); + + // Assert + act.Should().Throw() + .WithMessage("*be within*2*from*3*but found*1*"); + } + + [Fact] + public void When_an_uint_value_is_returned_from_NotBeCloseTo_it_should_chain() + { + // Arrange + uint actual = uint.MaxValue; + + // Act + Action act = () => actual.Should().NotBeCloseTo(0, 0) + .And.Be(actual); + + // Assert + act.Should().NotThrow(); + } + + [InlineData(0, 1, 0)] + [InlineData(1, 0, 0)] + [InlineData(ulong.MinValue, ulong.MaxValue, 1)] + [InlineData(ulong.MaxValue, ulong.MinValue, 1)] + [Theory] + public void When_an_ulong_value_is_not_close_to_expected_value_it_should_succeed(ulong actual, ulong distantValue, + ulong delta) + { + // Act + Action act = () => actual.Should().NotBeCloseTo(distantValue, delta); + + // Assert + act.Should().NotThrow(); + } + + [InlineData(0, 0, 0)] + [InlineData(0, 0, 1)] + [InlineData(0, 1, 1)] + [InlineData(1, 0, 1)] + [InlineData(1, ulong.MaxValue, ulong.MaxValue)] + [InlineData(ulong.MinValue, ulong.MinValue + 1, ulong.MaxValue)] + [InlineData(ulong.MinValue + 1, 0, ulong.MaxValue)] + [InlineData(ulong.MinValue + 1, ulong.MinValue, 1)] + [InlineData(ulong.MinValue + 1, ulong.MinValue, ulong.MaxValue)] + [InlineData(ulong.MaxValue - 1, ulong.MaxValue, 1)] + [InlineData(ulong.MaxValue - 1, ulong.MaxValue, ulong.MaxValue)] + [InlineData(ulong.MaxValue, 0, ulong.MaxValue)] + [InlineData(ulong.MaxValue, 1, ulong.MaxValue)] + [InlineData(ulong.MaxValue, ulong.MaxValue - 1, 1)] + [InlineData(ulong.MaxValue, ulong.MaxValue - 1, ulong.MaxValue)] + [InlineData(ulong.MaxValue, ulong.MaxValue, 0)] + [InlineData(ulong.MaxValue, ulong.MaxValue, 1)] + [Theory] + public void When_an_ulong_value_is_close_to_expected_value_it_should_fail(ulong actual, ulong distantValue, + ulong delta) + { + // Act + Action act = () => actual.Should().NotBeCloseTo(distantValue, delta); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_an_ulong_value_is_close_to_expected_value_it_should_fail_with_a_descriptive_message() + { + // Arrange + ulong actual = 1; + ulong nearbyValue = 3; + ulong delta = 2; + + // Act + Action act = () => actual.Should().NotBeCloseTo(nearbyValue, delta); + + // Assert + act.Should().Throw() + .WithMessage("*be within*2*from*3*but found*1*"); + } + + [Fact] + public void When_an_ulong_value_is_returned_from_NotBeCloseTo_it_should_chain() + { + // Arrange + ulong actual = ulong.MaxValue; + + // Act + Action act = () => actual.Should().NotBeCloseTo(0, 0) + .And.Be(actual); + + // Assert + act.Should().NotThrow(); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.BeGreaterThan.cs b/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.BeGreaterThan.cs new file mode 100644 index 0000000000..0e0febda79 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.BeGreaterThan.cs @@ -0,0 +1,219 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Numeric; + +public partial class NumericAssertionSpecs +{ + public class BeGreaterThan + { + [Fact] + public void When_a_value_is_greater_than_smaller_value_it_should_not_throw() + { + // Arrange + int value = 2; + int smallerValue = 1; + + // Act + Action act = () => value.Should().BeGreaterThan(smallerValue); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_a_value_is_greater_than_greater_value_it_should_throw() + { + // Arrange + int value = 2; + int greaterValue = 3; + + // Act + Action act = () => value.Should().BeGreaterThan(greaterValue); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_a_value_is_greater_than_same_value_it_should_throw() + { + // Arrange + int value = 2; + int sameValue = 2; + + // Act + Action act = () => value.Should().BeGreaterThan(sameValue); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_a_value_is_greater_than_greater_value_it_should_throw_with_descriptive_message() + { + // Arrange + int value = 2; + int greaterValue = 3; + + // Act + Action act = () => + value.Should().BeGreaterThan(greaterValue, "because we want to test the failure {0}", "message"); + + // Assert + act + .Should().Throw() + .WithMessage("Expected value to be greater than 3 because we want to test the failure message, but found 2."); + } + + [Fact] + public void NaN_is_never_greater_than_another_float() + { + // Act + Action act = () => float.NaN.Should().BeGreaterThan(0); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void A_float_cannot_be_greater_than_NaN() + { + // Act + Action act = () => 3.4F.Should().BeGreaterThan(float.NaN); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void NaN_is_never_greater_than_another_double() + { + // Act + Action act = () => double.NaN.Should().BeGreaterThan(0); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void A_double_can_never_be_greater_than_NaN() + { + // Act + Action act = () => 3.4D.Should().BeGreaterThan(double.NaN); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void When_a_nullable_numeric_null_value_is_not_greater_than_it_should_throw() + { + // Arrange + int? value = null; + + // Act + Action act = () => value.Should().BeGreaterThan(0); + + // Assert + act + .Should().Throw() + .WithMessage("*null*"); + } + + [Fact] + public void To_test_the_null_path_for_difference_on_byte() + { + // Arrange + var value = (byte)1; + + // Act + Action act = () => value.Should().BeGreaterThan(1); + + // Assert + act + .Should().Throw() + .Which.Message.Should().NotMatch("*(difference of 0)*"); + } + + [Fact] + public void To_test_the_non_null_path_for_difference_on_byte() + { + // Arrange + var value = (byte)1; + + // Act + Action act = () => value.Should().BeGreaterThan(2); + + // Assert + act + .Should().Throw() + .Which.Message.Should().NotMatch("*(difference of 0)*"); + } + + [Theory] + [InlineData(5, 5)] + [InlineData(1, 10)] + [InlineData(0, 5)] + [InlineData(0, 0)] + [InlineData(-1, 5)] + [InlineData(-1, -1)] + [InlineData(10, 10)] + public void To_test_the_null_path_for_difference_on_int(int subject, int expectation) + { + // Arrange + // Act + Action act = () => subject.Should().BeGreaterThan(expectation); + + // Assert + act + .Should().Throw() + .Which.Message.Should().NotMatch("*(difference of 0)*"); + } + + [Theory] + [InlineData(5L, 5L)] + [InlineData(1L, 10L)] + [InlineData(0L, 5L)] + [InlineData(0L, 0L)] + [InlineData(-1L, 5L)] + [InlineData(-1L, -1L)] + [InlineData(10L, 10L)] + public void To_test_the_null_path_for_difference_on_long(long subject, long expectation) + { + // Arrange + // Act + Action act = () => subject.Should().BeGreaterThan(expectation); + + // Assert + act + .Should().Throw() + .Which.Message.Should().NotMatch("*(difference of 0)*"); + } + + [Theory] + [InlineData(1, 1)] + [InlineData(10, 10)] + [InlineData(10, 11)] + public void To_test_the_null_path_for_difference_on_ushort(ushort subject, ushort expectation) + { + // Arrange + // Act + Action act = () => subject.Should().BeGreaterThan(expectation); + + // Assert + act + .Should().Throw() + .Which.Message.Should().NotMatch("*(difference of 0)*"); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.BeGreaterThanOrEqualTo.cs b/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.BeGreaterThanOrEqualTo.cs new file mode 100644 index 0000000000..66c177d734 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.BeGreaterThanOrEqualTo.cs @@ -0,0 +1,146 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Numeric; + +public partial class NumericAssertionSpecs +{ + public class BeGreaterThanOrEqualTo + { + [Fact] + public void When_a_value_is_greater_than_or_equal_to_smaller_value_it_should_not_throw() + { + // Arrange + int value = 2; + int smallerValue = 1; + + // Act + Action act = () => value.Should().BeGreaterThanOrEqualTo(smallerValue); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_a_value_is_greater_than_or_equal_to_same_value_it_should_not_throw() + { + // Arrange + int value = 2; + int sameValue = 2; + + // Act + Action act = () => value.Should().BeGreaterThanOrEqualTo(sameValue); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_a_value_is_greater_than_or_equal_to_greater_value_it_should_throw() + { + // Arrange + int value = 2; + int greaterValue = 3; + + // Act + Action act = () => value.Should().BeGreaterThanOrEqualTo(greaterValue); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_a_value_is_greater_than_or_equal_to_greater_value_it_should_throw_with_descriptive_message() + { + // Arrange + int value = 2; + int greaterValue = 3; + + // Act + Action act = + () => value.Should() + .BeGreaterThanOrEqualTo(greaterValue, "because we want to test the failure {0}", "message"); + + // Assert + act + .Should().Throw() + .WithMessage( + "Expected value to be greater than or equal to 3 because we want to test the failure message, but found 2."); + } + + [Fact] + public void When_a_nullable_numeric_null_value_is_not_greater_than_or_equal_to_it_should_throw() + { + // Arrange + int? value = null; + + // Act + Action act = () => value.Should().BeGreaterThanOrEqualTo(0); + + // Assert + act + .Should().Throw() + .WithMessage("*null*"); + } + + [Fact] + public void NaN_is_never_greater_than_or_equal_to_another_float() + { + // Act + Action act = () => float.NaN.Should().BeGreaterThanOrEqualTo(0); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void A_float_cannot_be_greater_than_or_equal_to_NaN() + { + // Act + Action act = () => 3.4F.Should().BeGreaterThanOrEqualTo(float.NaN); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void NaN_is_never_greater_or_equal_to_another_double() + { + // Act + Action act = () => double.NaN.Should().BeGreaterThanOrEqualTo(0); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void A_double_can_never_be_greater_or_equal_to_NaN() + { + // Act + Action act = () => 3.4D.Should().BeGreaterThanOrEqualTo(double.NaN); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void Chaining_after_one_assertion() + { + // Arrange + int value = 2; + int smallerValue = 1; + + // Act / Assert + value.Should().BeGreaterThanOrEqualTo(smallerValue).And.Be(2); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.BeInRange.cs b/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.BeInRange.cs new file mode 100644 index 0000000000..a302c0eb8b --- /dev/null +++ b/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.BeInRange.cs @@ -0,0 +1,224 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Numeric; + +public partial class NumericAssertionSpecs +{ + public class BeInRange + { + [Fact] + public void When_a_value_is_outside_a_range_it_should_throw() + { + // Arrange + float value = 3.99F; + + // Act + Action act = () => value.Should().BeInRange(4, 5, "because that's the valid range"); + + // Assert + act + .Should().Throw() + .WithMessage( + "Expected value to be between*4* and*5* because that\'s the valid range, but found*3.99*"); + } + + [Fact] + public void When_a_value_is_inside_a_range_it_should_not_throw() + { + // Arrange + int value = 4; + + // Act + Action act = () => value.Should().BeInRange(3, 5); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_a_nullable_numeric_null_value_is_not_in_range_it_should_throw() + { + // Arrange + int? value = null; + + // Act + Action act = () => value.Should().BeInRange(0, 1); + + // Assert + act + .Should().Throw() + .WithMessage("*null*"); + } + + [Fact] + public void NaN_is_never_in_range_of_two_floats() + { + // Arrange + float value = float.NaN; + + // Act + Action act = () => value.Should().BeInRange(4, 5); + + // Assert + act + .Should().Throw() + .WithMessage( + "Expected value to be between*4* and*5*, but found*NaN*"); + } + + [Theory] + [InlineData(float.NaN, 5F)] + [InlineData(5F, float.NaN)] + public void A_float_can_never_be_in_a_range_containing_NaN(float minimumValue, float maximumValue) + { + // Arrange + float value = 4.5F; + + // Act + Action act = () => value.Should().BeInRange(minimumValue, maximumValue); + + // Assert + act + .Should().Throw() + .WithMessage( + "*NaN*"); + } + + [Fact] + public void A_NaN_is_never_in_range_of_two_doubles() + { + // Arrange + double value = double.NaN; + + // Act + Action act = () => value.Should().BeInRange(4, 5); + + // Assert + act + .Should().Throw() + .WithMessage( + "Expected value to be between*4* and*5*, but found*NaN*"); + } + + [Theory] + [InlineData(double.NaN, 5)] + [InlineData(5, double.NaN)] + public void A_double_can_never_be_in_a_range_containing_NaN(double minimumValue, double maximumValue) + { + // Arrange + double value = 4.5D; + + // Act + Action act = () => value.Should().BeInRange(minimumValue, maximumValue); + + // Assert + act + .Should().Throw() + .WithMessage( + "*NaN*"); + } + } + + public class NotBeInRange + { + [Fact] + public void When_a_value_is_inside_an_unexpected_range_it_should_throw() + { + // Arrange + float value = 4.99F; + + // Act + Action act = () => value.Should().NotBeInRange(4, 5, "because that's the invalid range"); + + // Assert + act + .Should().Throw() + .WithMessage( + "Expected value to not be between*4* and*5* because that\'s the invalid range, but found*4.99*"); + } + + [Fact] + public void When_a_value_is_outside_an_unexpected_range_it_should_not_throw() + { + // Arrange + float value = 3.99F; + + // Act + Action act = () => value.Should().NotBeInRange(4, 5); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_a_nullable_numeric_null_value_is_not_not_in_range_to_it_should_throw() + { + // Arrange + int? value = null; + + // Act + Action act = () => value.Should().NotBeInRange(0, 1); + + // Assert + act + .Should().Throw() + .WithMessage("*null*"); + } + + [Fact] + public void NaN_is_never_inside_any_range_of_floats() + { + // Arrange + float value = float.NaN; + + // Act / Assert + value.Should().NotBeInRange(4, 5); + } + + [Theory] + [InlineData(float.NaN, 1F)] + [InlineData(1F, float.NaN)] + public void Cannot_use_NaN_in_a_range_of_floats(float minimumValue, float maximumValue) + { + // Arrange + float value = 4.5F; + + // Act + Action act = () => value.Should().NotBeInRange(minimumValue, maximumValue); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void NaN_is_never_inside_any_range_of_doubles() + { + // Arrange + double value = double.NaN; + + // Act / Assert + value.Should().NotBeInRange(4, 5); + } + + [Theory] + [InlineData(double.NaN, 1D)] + [InlineData(1D, double.NaN)] + public void Cannot_use_NaN_in_a_range_of_doubles(double minimumValue, double maximumValue) + { + // Arrange + double value = 4.5D; + + // Act + Action act = () => value.Should().NotBeInRange(minimumValue, maximumValue); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.BeLessThan.cs b/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.BeLessThan.cs new file mode 100644 index 0000000000..2d64782c3b --- /dev/null +++ b/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.BeLessThan.cs @@ -0,0 +1,164 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Numeric; + +public partial class NumericAssertionSpecs +{ + public class BeLessThan + { + [Fact] + public void When_a_value_is_less_than_greater_value_it_should_not_throw() + { + // Arrange + int value = 1; + int greaterValue = 2; + + // Act + Action act = () => value.Should().BeLessThan(greaterValue); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_a_value_is_less_than_smaller_value_it_should_throw() + { + // Arrange + int value = 2; + int smallerValue = 1; + + // Act + Action act = () => value.Should().BeLessThan(smallerValue); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_a_value_is_less_than_same_value_it_should_throw() + { + // Arrange + int value = 2; + int sameValue = 2; + + // Act + Action act = () => value.Should().BeLessThan(sameValue); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_a_value_is_less_than_smaller_value_it_should_throw_with_descriptive_message() + { + // Arrange + int value = 2; + int smallerValue = 1; + + // Act + Action act = () => value.Should().BeLessThan(smallerValue, "because we want to test the failure {0}", "message"); + + // Assert + act + .Should().Throw() + .WithMessage("Expected value to be less than 1 because we want to test the failure message, but found 2."); + } + + [Fact] + public void NaN_is_never_less_than_another_float() + { + // Act + Action act = () => float.NaN.Should().BeLessThan(0); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void A_float_can_never_be_less_than_NaN() + { + // Act + Action act = () => 3.4F.Should().BeLessThan(float.NaN); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void NaN_is_never_less_than_another_double() + { + // Act + Action act = () => double.NaN.Should().BeLessThan(0); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void A_double_can_never_be_less_than_NaN() + { + // Act + Action act = () => 3.4D.Should().BeLessThan(double.NaN); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void When_a_nullable_numeric_null_value_is_not_less_than_it_should_throw() + { + // Arrange + int? value = null; + + // Act + Action act = () => value.Should().BeLessThan(0); + + // Assert + act + .Should().Throw() + .WithMessage("*null*"); + } + + [Theory] + [InlineData(5, -1)] + [InlineData(10, 5)] + [InlineData(10, -1)] + public void To_test_the_remaining_paths_for_difference_on_int(int subject, int expectation) + { + // Arrange + // Act + Action act = () => subject.Should().BeLessThan(expectation); + + // Assert + act + .Should().Throw() + .Which.Message.Should().NotMatch("*(difference of 0)*"); + } + + [Theory] + [InlineData(5L, -1L)] + [InlineData(10L, 5L)] + [InlineData(10L, -1L)] + public void To_test_the_remaining_paths_for_difference_on_long(long subject, long expectation) + { + // Arrange + // Act + Action act = () => subject.Should().BeLessThan(expectation); + + // Assert + act + .Should().Throw() + .Which.Message.Should().NotMatch("*(difference of 0)*"); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.BeLessThanOrEqualTo.cs b/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.BeLessThanOrEqualTo.cs new file mode 100644 index 0000000000..18ddf08d38 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.BeLessThanOrEqualTo.cs @@ -0,0 +1,145 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Numeric; + +public partial class NumericAssertionSpecs +{ + public class BeLessThanOrEqualTo + { + [Fact] + public void When_a_value_is_less_than_or_equal_to_greater_value_it_should_not_throw() + { + // Arrange + int value = 1; + int greaterValue = 2; + + // Act + Action act = () => value.Should().BeLessThanOrEqualTo(greaterValue); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_a_value_is_less_than_or_equal_to_same_value_it_should_not_throw() + { + // Arrange + int value = 2; + int sameValue = 2; + + // Act + Action act = () => value.Should().BeLessThanOrEqualTo(sameValue); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_a_value_is_less_than_or_equal_to_smaller_value_it_should_throw() + { + // Arrange + int value = 2; + int smallerValue = 1; + + // Act + Action act = () => value.Should().BeLessThanOrEqualTo(smallerValue); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_a_value_is_less_than_or_equal_to_smaller_value_it_should_throw_with_descriptive_message() + { + // Arrange + int value = 2; + int smallerValue = 1; + + // Act + Action act = () => + value.Should().BeLessThanOrEqualTo(smallerValue, "because we want to test the failure {0}", "message"); + + // Assert + act + .Should().Throw() + .WithMessage( + "Expected value to be less than or equal to 1 because we want to test the failure message, but found 2."); + } + + [Fact] + public void When_a_nullable_numeric_null_value_is_not_less_than_or_equal_to_it_should_throw() + { + // Arrange + int? value = null; + + // Act + Action act = () => value.Should().BeLessThanOrEqualTo(0); + + // Assert + act + .Should().Throw() + .WithMessage("*null*"); + } + + [Fact] + public void NaN_is_never_less_than_or_equal_to_another_float() + { + // Act + Action act = () => float.NaN.Should().BeLessThanOrEqualTo(0); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void A_float_can_never_be_less_than_or_equal_to_NaN() + { + // Act + Action act = () => 3.4F.Should().BeLessThanOrEqualTo(float.NaN); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void NaN_is_never_less_than_or_equal_to_another_double() + { + // Act + Action act = () => double.NaN.Should().BeLessThanOrEqualTo(0); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void A_double_can_never_be_less_than_or_equal_to_NaN() + { + // Act + Action act = () => 3.4D.Should().BeLessThanOrEqualTo(double.NaN); + + // Assert + act + .Should().Throw() + .WithMessage("*NaN*"); + } + + [Fact] + public void Chaining_after_one_assertion() + { + // Arrange + int value = 1; + int greaterValue = 2; + + // Act / Assert + value.Should().BeLessThanOrEqualTo(greaterValue).And.Be(1); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.BeNaN.cs b/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.BeNaN.cs new file mode 100644 index 0000000000..d5b4383b28 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.BeNaN.cs @@ -0,0 +1,442 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Numeric; + +public partial class NumericAssertionSpecs +{ + public class BeNaN + { + [Fact] + public void NaN_is_equal_to_NaN_when_its_a_float() + { + // Arrange + float actual = float.NaN; + + // Act / Assert + actual.Should().BeNaN(); + } + + [InlineData(-1f)] + [InlineData(0f)] + [InlineData(1f)] + [InlineData(float.MinValue)] + [InlineData(float.MaxValue)] + [InlineData(float.Epsilon)] + [InlineData(float.NegativeInfinity)] + [InlineData(float.PositiveInfinity)] + [Theory] + public void Should_fail_when_asserting_normal_float_value_to_be_NaN(float actual) + { + // Act + Action act = () => actual.Should().BeNaN(); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Should_fail_with_a_descriptive_message_when_asserting_normal_float_value_to_be_NaN() + { + // Arrange + float actual = 1; + + // Act + Action act = () => actual.Should().BeNaN(); + + // Assert + act.Should().Throw() + .WithMessage("Expected actual to be NaN, but found 1F."); + } + + [Fact] + public void Should_chain_when_asserting_NaN_as_float() + { + // Arrange + float actual = float.NaN; + + // Act / Assert + actual.Should().BeNaN() + .And.Be(actual); + } + + [Fact] + public void NaN_is_equal_to_NaN_when_its_a_double() + { + // Arrange + double actual = double.NaN; + + // Act / Assert + actual.Should().BeNaN(); + } + + [InlineData(-1d)] + [InlineData(0d)] + [InlineData(1d)] + [InlineData(double.MinValue)] + [InlineData(double.MaxValue)] + [InlineData(double.Epsilon)] + [InlineData(double.NegativeInfinity)] + [InlineData(double.PositiveInfinity)] + [Theory] + public void Should_fail_when_asserting_normal_double_value_to_be_NaN(double actual) + { + // Act + Action act = () => actual.Should().BeNaN(); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Should_fail_with_a_descriptive_message_when_asserting_normal_double_value_to_be_NaN() + { + // Arrange + double actual = 1; + + // Act + Action act = () => actual.Should().BeNaN(); + + // Assert + act.Should().Throw() + .WithMessage("Expected actual to be NaN, but found 1.0."); + } + + [Fact] + public void Should_chain_when_asserting_NaN_as_double() + { + // Arrange + double actual = double.NaN; + + // Act / Assert + actual.Should().BeNaN() + .And.Be(actual); + } + + [Fact] + public void NaN_is_equal_to_NaN_when_its_a_nullable_float() + { + // Arrange + float? actual = float.NaN; + + // Act / Assert + actual.Should().BeNaN(); + } + + [InlineData(null)] + [InlineData(-1f)] + [InlineData(0f)] + [InlineData(1f)] + [InlineData(float.MinValue)] + [InlineData(float.MaxValue)] + [InlineData(float.Epsilon)] + [InlineData(float.NegativeInfinity)] + [InlineData(float.PositiveInfinity)] + [Theory] + public void Should_fail_when_asserting_nullable_normal_float_value_to_be_NaN(float? actual) + { + // Act + Action act = () => actual.Should().BeNaN(); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Should_fail_with_a_descriptive_message_when_asserting_nullable_normal_float_value_to_be_NaN() + { + // Arrange + float? actual = 1; + + // Act + Action act = () => actual.Should().BeNaN(); + + // Assert + act.Should().Throw() + .WithMessage("Expected actual to be NaN, but found 1F."); + } + + [Fact] + public void Should_chain_when_asserting_NaN_as_nullable_float() + { + // Arrange + float? actual = float.NaN; + + // Act / Assert + actual.Should().BeNaN() + .And.Be(actual); + } + + [Fact] + public void NaN_is_equal_to_NaN_when_its_a_nullable_double() + { + // Arrange + double? actual = double.NaN; + + // Act / Assert + actual.Should().BeNaN(); + } + + [InlineData(null)] + [InlineData(-1d)] + [InlineData(0d)] + [InlineData(1d)] + [InlineData(double.MinValue)] + [InlineData(double.MaxValue)] + [InlineData(double.Epsilon)] + [InlineData(double.NegativeInfinity)] + [InlineData(double.PositiveInfinity)] + [Theory] + public void Should_fail_when_asserting_nullable_normal_double_value_to_be_NaN(double? actual) + { + // Act + Action act = () => actual.Should().BeNaN(); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Should_fail_with_a_descriptive_message_when_asserting_nullable_normal_double_value_to_be_NaN() + { + // Arrange + double? actual = 1; + + // Act + Action act = () => actual.Should().BeNaN(); + + // Assert + act.Should().Throw() + .WithMessage("Expected actual to be NaN, but found 1.0."); + } + + [Fact] + public void Should_chain_when_asserting_NaN_as_nullable_double() + { + // Arrange + double? actual = double.NaN; + + // Act / Assert + actual.Should().BeNaN() + .And.Be(actual); + } + } + + public class NotBeNaN + { + [InlineData(-1f)] + [InlineData(0f)] + [InlineData(1f)] + [InlineData(float.MinValue)] + [InlineData(float.MaxValue)] + [InlineData(float.Epsilon)] + [InlineData(float.NegativeInfinity)] + [InlineData(float.PositiveInfinity)] + [Theory] + public void Normal_float_is_never_equal_to_NaN(float actual) + { + // Act / Assert + actual.Should().NotBeNaN(); + } + + [Fact] + public void Should_fail_when_asserting_NaN_as_float() + { + // Arrange + float actual = float.NaN; + + // Act + Action act = () => actual.Should().NotBeNaN(); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Should_fail_with_a_descriptive_message_when_asserting_NaN_as_float() + { + // Arrange + float actual = float.NaN; + + // Act + Action act = () => actual.Should().NotBeNaN(); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect actual to be NaN."); + } + + [Fact] + public void Should_chain_when_asserting_normal_float_value() + { + // Arrange + float actual = 1; + + // Act / Assert + actual.Should().NotBeNaN() + .And.Be(actual); + } + + [InlineData(-1d)] + [InlineData(0d)] + [InlineData(1d)] + [InlineData(double.MinValue)] + [InlineData(double.MaxValue)] + [InlineData(double.Epsilon)] + [InlineData(double.NegativeInfinity)] + [InlineData(double.PositiveInfinity)] + [Theory] + public void Normal_double_is_never_equal_to_NaN(double actual) + { + // Act / Assert + actual.Should().NotBeNaN(); + } + + [Fact] + public void Should_fail_when_asserting_NaN_as_double() + { + // Arrange + double actual = double.NaN; + + // Act + Action act = () => actual.Should().NotBeNaN(); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Should_fail_with_a_descriptive_message_when_asserting_NaN_as_double() + { + // Arrange + double actual = double.NaN; + + // Act + Action act = () => actual.Should().NotBeNaN(); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect actual to be NaN."); + } + + [Fact] + public void Should_chain_when_asserting_normal_double_value() + { + // Arrange + double actual = 1; + + // Act / Assert + actual.Should().NotBeNaN() + .And.Be(actual); + } + + [InlineData(null)] + [InlineData(-1f)] + [InlineData(0f)] + [InlineData(1f)] + [InlineData(float.MinValue)] + [InlineData(float.MaxValue)] + [InlineData(float.Epsilon)] + [InlineData(float.NegativeInfinity)] + [InlineData(float.PositiveInfinity)] + [Theory] + public void Normal_nullable_float_is_never_equal_to_NaN(float? actual) + { + // Act / Assert + actual.Should().NotBeNaN(); + } + + [Fact] + public void Should_fail_when_asserting_NaN_as_nullable_float() + { + // Arrange + float? actual = float.NaN; + + // Act + Action act = () => actual.Should().NotBeNaN(); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Should_fail_with_a_descriptive_message_when_asserting_NaN_as_nullable_float() + { + // Arrange + float? actual = float.NaN; + + // Act + Action act = () => actual.Should().NotBeNaN(); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect actual to be NaN."); + } + + [Fact] + public void Should_chain_when_asserting_normal_nullable_float_value() + { + // Arrange + float? actual = 1; + + // Act / Assert + actual.Should().NotBeNaN() + .And.Be(actual); + } + + [InlineData(null)] + [InlineData(-1d)] + [InlineData(0d)] + [InlineData(1d)] + [InlineData(double.MinValue)] + [InlineData(double.MaxValue)] + [InlineData(double.Epsilon)] + [InlineData(double.NegativeInfinity)] + [InlineData(double.PositiveInfinity)] + [Theory] + public void Normal_nullable_double_is_never_equal_to_NaN(double? actual) + { + // Act / Assert + actual.Should().NotBeNaN(); + } + + [Fact] + public void Should_fail_when_asserting_NaN_as_nullable_double() + { + // Arrange + double? actual = double.NaN; + + // Act + Action act = () => actual.Should().NotBeNaN(); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Should_fail_with_a_descriptive_message_when_asserting_NaN_as_nullable_double() + { + // Arrange + double? actual = double.NaN; + + // Act + Action act = () => actual.Should().NotBeNaN(); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect actual to be NaN."); + } + + [Fact] + public void Should_chain_when_asserting_normal_nullable_double_value() + { + // Arrange + double? actual = 1; + + // Act / Assert + actual.Should().NotBeNaN() + .And.Be(actual); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.BeNegative.cs b/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.BeNegative.cs new file mode 100644 index 0000000000..0f2d9b5290 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.BeNegative.cs @@ -0,0 +1,106 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Numeric; + +public partial class NumericAssertionSpecs +{ + public class BeNegative + { + [Fact] + public void When_a_negative_value_is_negative_it_should_not_throw() + { + // Arrange + int value = -1; + + // Act + Action act = () => value.Should().BeNegative(); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_a_positive_value_is_negative_it_should_throw() + { + // Arrange + int value = 1; + + // Act + Action act = () => value.Should().BeNegative(); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_a_zero_value_is_negative_it_should_throw() + { + // Arrange + int value = 0; + + // Act + Action act = () => value.Should().BeNegative(); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_a_positive_value_is_negative_it_should_throw_with_descriptive_message() + { + // Arrange + int value = 1; + + // Act + Action act = () => value.Should().BeNegative("because we want to test the failure {0}", "message"); + + // Assert + act + .Should().Throw() + .WithMessage("Expected value to be negative because we want to test the failure message, but found 1."); + } + + [Fact] + public void When_a_nullable_numeric_null_value_is_not_negative_it_should_throw() + { + // Arrange + int? value = null; + + // Act + Action act = () => value.Should().BeNegative(); + + // Assert + act + .Should().Throw() + .WithMessage("*null*"); + } + + [Fact] + public void NaN_is_never_a_negative_float() + { + // Arrange + float value = float.NaN; + + // Act + Action act = () => value.Should().BeNegative(); + + // Assert + act.Should().Throw().WithMessage("*but found NaN*"); + } + + [Fact] + public void NaN_is_never_a_negative_double() + { + // Arrange + double value = double.NaN; + + // Act + Action act = () => value.Should().BeNegative(); + + // Assert + act.Should().Throw().WithMessage("*but found NaN*"); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.BeOneOf.cs b/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.BeOneOf.cs new file mode 100644 index 0000000000..ae02c601ad --- /dev/null +++ b/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.BeOneOf.cs @@ -0,0 +1,119 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Numeric; + +public partial class NumericAssertionSpecs +{ + public class BeOneOf + { + [Fact] + public void When_a_value_is_not_one_of_the_specified_values_it_should_throw() + { + // Arrange + int value = 3; + + // Act + Action act = () => value.Should().BeOneOf(4, 5); + + // Assert + act + .Should().Throw() + .WithMessage("Expected value to be one of {4, 5}, but found 3."); + } + + [Fact] + public void When_a_value_is_not_one_of_the_specified_values_it_should_throw_with_descriptive_message() + { + // Arrange + int value = 3; + + // Act + Action act = () => value.Should().BeOneOf([4, 5], "because those are the valid values"); + + // Assert + act + .Should().Throw() + .WithMessage("Expected value to be one of {4, 5} because those are the valid values, but found 3."); + } + + [Fact] + public void When_a_value_is_one_of_the_specified_values_it_should_succeed() + { + // Arrange + int value = 4; + + // Act + Action act = () => value.Should().BeOneOf(4, 5); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_a_nullable_numeric_null_value_is_not_one_of_to_it_should_throw() + { + // Arrange + int? value = null; + + // Act + Action act = () => value.Should().BeOneOf(0, 1); + + // Assert + act + .Should().Throw() + .WithMessage("*null*"); + } + + [Fact] + public void Two_floats_that_are_NaN_can_be_compared() + { + // Arrange + float value = float.NaN; + + // Act / Assert + value.Should().BeOneOf(float.NaN, 4.5F); + } + + [Fact] + public void Floats_are_never_equal_to_NaN() + { + // Arrange + float value = float.NaN; + + // Act + Action act = () => value.Should().BeOneOf(1.5F, 4.5F); + + // Assert + act + .Should().Throw() + .WithMessage("Expected*1.5F*found*NaN*"); + } + + [Fact] + public void Two_doubles_that_are_NaN_can_be_compared() + { + // Arrange + double value = double.NaN; + + // Act / Assert + value.Should().BeOneOf(double.NaN, 4.5F); + } + + [Fact] + public void Doubles_are_never_equal_to_NaN() + { + // Arrange + double value = double.NaN; + + // Act + Action act = () => value.Should().BeOneOf(1.5D, 4.5D); + + // Assert + act + .Should().Throw() + .WithMessage("Expected*1.5*found NaN*"); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.BePositive.cs b/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.BePositive.cs new file mode 100644 index 0000000000..e2610e445d --- /dev/null +++ b/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.BePositive.cs @@ -0,0 +1,106 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Numeric; + +public partial class NumericAssertionSpecs +{ + public class BePositive + { + [Fact] + public void When_a_positive_value_is_positive_it_should_not_throw() + { + // Arrange + float value = 1F; + + // Act + Action act = () => value.Should().BePositive(); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_a_negative_value_is_positive_it_should_throw() + { + // Arrange + double value = -1D; + + // Act + Action act = () => value.Should().BePositive(); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_a_zero_value_is_positive_it_should_throw() + { + // Arrange + int value = 0; + + // Act + Action act = () => value.Should().BePositive(); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void NaN_is_never_a_positive_float() + { + // Arrange + float value = float.NaN; + + // Act + Action act = () => value.Should().BePositive(); + + // Assert + act.Should().Throw().WithMessage("*but found NaN*"); + } + + [Fact] + public void NaN_is_never_a_positive_double() + { + // Arrange + double value = double.NaN; + + // Act + Action act = () => value.Should().BePositive(); + + // Assert + act.Should().Throw().WithMessage("*but found NaN*"); + } + + [Fact] + public void When_a_negative_value_is_positive_it_should_throw_with_descriptive_message() + { + // Arrange + int value = -1; + + // Act + Action act = () => value.Should().BePositive("because we want to test the failure {0}", "message"); + + // Assert + act + .Should().Throw() + .WithMessage("Expected value to be positive because we want to test the failure message, but found -1."); + } + + [Fact] + public void When_a_nullable_numeric_null_value_is_not_positive_it_should_throw() + { + // Arrange + int? value = null; + + // Act + Action act = () => value.Should().BePositive(); + + // Assert + act + .Should().Throw() + .WithMessage("*null*"); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.Match.cs b/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.Match.cs new file mode 100644 index 0000000000..6504165682 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.Match.cs @@ -0,0 +1,49 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Numeric; + +public partial class NumericAssertionSpecs +{ + public class Match + { + [Fact] + public void When_value_satisfies_predicate_it_should_not_throw() + { + // Arrange + int value = 1; + + // Act / Assert + value.Should().Match(o => o > 0); + } + + [Fact] + public void When_value_does_not_match_the_predicate_it_should_throw() + { + // Arrange + int value = 1; + + // Act + Action act = () => value.Should().Match(o => o == 0, "because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw() + .WithMessage("Expected value to match (o == 0) because we want to test the failure message, but found 1."); + } + + [Fact] + public void When_value_is_matched_against_a_null_it_should_throw() + { + // Arrange + int value = 1; + + // Act + Action act = () => value.Should().Match(null); + + // Assert + act.Should().ThrowExactly() + .WithParameterName("predicate"); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.cs index 71aecb389b..332cbddb6a 100644 --- a/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Numeric/NumericAssertionSpecs.cs @@ -1,3955 +1,10 @@ using System; using Xunit; -using Xunit.Sdk; namespace FluentAssertions.Specs.Numeric; -public class NumericAssertionSpecs +public partial class NumericAssertionSpecs { - public class BePositiveOrNegative - { - [Fact] - public void When_a_positive_value_is_positive_it_should_not_throw() - { - // Arrange - float value = 1F; - - // Act - Action act = () => value.Should().BePositive(); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_a_negative_value_is_positive_it_should_throw() - { - // Arrange - double value = -1D; - - // Act - Action act = () => value.Should().BePositive(); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_a_zero_value_is_positive_it_should_throw() - { - // Arrange - int value = 0; - - // Act - Action act = () => value.Should().BePositive(); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void NaN_is_never_a_positive_float() - { - // Arrange - float value = float.NaN; - - // Act - Action act = () => value.Should().BePositive(); - - // Assert - act.Should().Throw().WithMessage("*but found NaN*"); - } - - [Fact] - public void NaN_is_never_a_positive_double() - { - // Arrange - double value = double.NaN; - - // Act - Action act = () => value.Should().BePositive(); - - // Assert - act.Should().Throw().WithMessage("*but found NaN*"); - } - - [Fact] - public void When_a_negative_value_is_positive_it_should_throw_with_descriptive_message() - { - // Arrange - int value = -1; - - // Act - Action act = () => value.Should().BePositive("because we want to test the failure {0}", "message"); - - // Assert - act - .Should().Throw() - .WithMessage("Expected value to be positive because we want to test the failure message, but found -1."); - } - - [Fact] - public void When_a_nullable_numeric_null_value_is_not_positive_it_should_throw() - { - // Arrange - int? value = null; - - // Act - Action act = () => value.Should().BePositive(); - - // Assert - act - .Should().Throw() - .WithMessage("*null*"); - } - - [Fact] - public void When_a_negative_value_is_negative_it_should_not_throw() - { - // Arrange - int value = -1; - - // Act - Action act = () => value.Should().BeNegative(); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_a_positive_value_is_negative_it_should_throw() - { - // Arrange - int value = 1; - - // Act - Action act = () => value.Should().BeNegative(); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_a_zero_value_is_negative_it_should_throw() - { - // Arrange - int value = 0; - - // Act - Action act = () => value.Should().BeNegative(); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_a_positive_value_is_negative_it_should_throw_with_descriptive_message() - { - // Arrange - int value = 1; - - // Act - Action act = () => value.Should().BeNegative("because we want to test the failure {0}", "message"); - - // Assert - act - .Should().Throw() - .WithMessage("Expected value to be negative because we want to test the failure message, but found 1."); - } - - [Fact] - public void When_a_nullable_numeric_null_value_is_not_negative_it_should_throw() - { - // Arrange - int? value = null; - - // Act - Action act = () => value.Should().BeNegative(); - - // Assert - act - .Should().Throw() - .WithMessage("*null*"); - } - - [Fact] - public void NaN_is_never_a_negative_float() - { - // Arrange - float value = float.NaN; - - // Act - Action act = () => value.Should().BeNegative(); - - // Assert - act.Should().Throw().WithMessage("*but found NaN*"); - } - - [Fact] - public void NaN_is_never_a_negative_double() - { - // Arrange - double value = double.NaN; - - // Act - Action act = () => value.Should().BeNegative(); - - // Assert - act.Should().Throw().WithMessage("*but found NaN*"); - } - } - - public class Be - { - [Fact] - public void A_value_is_equal_to_the_same_value() - { - // Arrange - int value = 1; - int sameValue = 1; - - // Act - value.Should().Be(sameValue); - } - - [Fact] - public void A_value_is_not_equal_to_another_value() - { - // Arrange - int value = 1; - int differentValue = 2; - - // Act - Action act = () => value.Should().Be(differentValue, "because we want to test the failure {0}", "message"); - - // Assert - act - .Should().Throw() - .WithMessage("Expected value to be 2 because we want to test the failure message, but found 1."); - } - - [Fact] - public void A_value_is_equal_to_the_same_nullable_value() - { - // Arrange - int value = 2; - int? nullableValue = 2; - - // Act - value.Should().Be(nullableValue); - } - - [Fact] - public void A_value_is_not_equal_to_null() - { - // Arrange - int value = 2; - int? nullableValue = null; - - // Act - Action act = () => value.Should().Be(nullableValue); - - // Assert - act - .Should().Throw() - .WithMessage("Expected*, but found 2."); - } - - [Fact] - public void Null_is_not_equal_to_another_nullable_value() - { - // Arrange - int? value = 2; - - // Act - Action action = () => ((int?)null).Should().Be(value); - - // Assert - action - .Should().Throw() - .WithMessage("Expected*2, but found ."); - } - - [InlineData(1, 2)] - [InlineData(null, 2)] - [Theory] - public void A_nullable_value_is_not_equal_to_another_value(int? subject, int unexpected) - { - // Act - subject.Should().NotBe(unexpected); - } - - [Fact] - public void A_value_is_not_different_from_the_same_value() - { - // Arrange - int value = 1; - int sameValue = 1; - - // Act - Action act = () => value.Should().NotBe(sameValue, "because we want to test the failure {0}", "message"); - - // Assert - act - .Should().Throw() - .WithMessage("Did not expect value to be 1 because we want to test the failure message."); - } - - [InlineData(null, null)] - [InlineData(0, 0)] - [Theory] - public void A_nullable_value_is_not_different_from_the_same_value(int? subject, int? unexpected) - { - // Act - Action act = () => subject.Should().NotBe(unexpected); - - // Assert - act.Should().Throw(); - } - - [InlineData(0, 1)] - [InlineData(0, null)] - [InlineData(null, 0)] - [Theory] - public void A_nullable_value_is_different_from_another_value(int? subject, int? unexpected) - { - // Act / Assert - subject.Should().NotBe(unexpected); - } - - [InlineData(0, 0)] - [InlineData(null, null)] - [Theory] - public void A_nullable_value_is_equal_to_the_same_nullable_value(int? subject, int? expected) - { - // Act / Assert - subject.Should().Be(expected); - } - - [InlineData(0, 1)] - [InlineData(0, null)] - [InlineData(null, 0)] - [Theory] - public void A_nullable_value_is_not_equal_to_another_nullable_value(int? subject, int? expected) - { - // Act - Action act = () => subject.Should().Be(expected); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void Null_is_not_equal_to_another_value() - { - // Arrange - int? subject = null; - int expected = 1; - - // Act - Action act = () => subject.Should().Be(expected); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_asserting_that_a_float_value_is_equal_to_a_different_value_it_should_throw() - { - // Arrange - float value = 3.5F; - - // Act - Action act = () => value.Should().Be(3.4F, "we want to test the error message"); - - // Assert - act - .Should().Throw() - .WithMessage( - "Expected value to be *3.4* because we want to test the error message, but found *3.5*"); - } - - [Fact] - public void When_asserting_that_a_float_value_is_equal_to_the_same_value_it_should_not_throw() - { - // Arrange - float value = 3.5F; - - // Act - Action act = () => value.Should().Be(3.5F); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_that_a_null_float_value_is_equal_to_some_value_it_should_throw() - { - // Arrange - float? value = null; - - // Act - Action act = () => value.Should().Be(3.5F); - - // Assert - act - .Should().Throw() - .WithMessage("Expected value to be *3.5* but found ."); - } - - [Fact] - public void When_asserting_that_a_double_value_is_equal_to_a_different_value_it_should_throw() - { - // Arrange - double value = 3.5; - - // Act - Action act = () => value.Should().Be(3.4, "we want to test the error message"); - - // Assert - act - .Should().Throw() - .WithMessage( - "Expected value to be 3.4 because we want to test the error message, but found 3.5*."); - } - - [Fact] - public void When_asserting_that_a_double_value_is_equal_to_the_same_value_it_should_not_throw() - { - // Arrange - double value = 3.5; - - // Act - Action act = () => value.Should().Be(3.5); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_that_a_null_double_value_is_equal_to_some_value_it_should_throw() - { - // Arrange - double? value = null; - - // Act - Action act = () => value.Should().Be(3.5); - - // Assert - act - .Should().Throw() - .WithMessage("Expected value to be 3.5, but found ."); - } - - [Fact] - public void When_asserting_that_a_decimal_value_is_equal_to_a_different_value_it_should_throw() - { - // Arrange - decimal value = 3.5m; - - // Act - Action act = () => value.Should().Be(3.4m, "we want to test the error message"); - - // Assert - act.Should().Throw() - .WithMessage( - "Expected value to be*3.4* because we want to test the error message, but found*3.5*"); - } - - [Fact] - public void When_asserting_that_a_decimal_value_is_equal_to_the_same_value_it_should_not_throw() - { - // Arrange - decimal value = 3.5m; - - // Act - Action act = () => value.Should().Be(3.5m); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_that_a_null_decimal_value_is_equal_to_some_value_it_should_throw() - { - // Arrange - decimal? value = null; - decimal someValue = 3.5m; - - // Act - Action act = () => value.Should().Be(someValue); - - // Assert - act - .Should().Throw() - .WithMessage("Expected value to be*3.5*, but found ."); - } - - [Fact] - public void Nan_is_never_equal_to_a_normal_float() - { - // Arrange - float value = float.NaN; - - // Act - Action act = () => value.Should().Be(3.4F); - - // Assert - act - .Should().Throw() - .WithMessage( - "Expected value to be *3.4F, but found NaN*"); - } - - [Fact] - public void NaN_can_be_compared_to_NaN_when_its_a_float() - { - // Arrange - float value = float.NaN; - - // Act - value.Should().Be(float.NaN); - } - - [Fact] - public void Nan_is_never_equal_to_a_normal_double() - { - // Arrange - double value = double.NaN; - - // Act - Action act = () => value.Should().Be(3.4D); - - // Assert - act - .Should().Throw() - .WithMessage("Expected value to be *3.4, but found NaN*"); - } - - [Fact] - public void NaN_can_be_compared_to_NaN_when_its_a_double() - { - // Arrange - double value = double.NaN; - - // Act - value.Should().Be(double.NaN); - } - } - - public class BeGreaterThanOrEqualTo - { - [Fact] - public void When_a_value_is_greater_than_smaller_value_it_should_not_throw() - { - // Arrange - int value = 2; - int smallerValue = 1; - - // Act - Action act = () => value.Should().BeGreaterThan(smallerValue); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_a_value_is_greater_than_greater_value_it_should_throw() - { - // Arrange - int value = 2; - int greaterValue = 3; - - // Act - Action act = () => value.Should().BeGreaterThan(greaterValue); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_a_value_is_greater_than_same_value_it_should_throw() - { - // Arrange - int value = 2; - int sameValue = 2; - - // Act - Action act = () => value.Should().BeGreaterThan(sameValue); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_a_value_is_greater_than_greater_value_it_should_throw_with_descriptive_message() - { - // Arrange - int value = 2; - int greaterValue = 3; - - // Act - Action act = () => - value.Should().BeGreaterThan(greaterValue, "because we want to test the failure {0}", "message"); - - // Assert - act - .Should().Throw() - .WithMessage("Expected value to be greater than 3 because we want to test the failure message, but found 2."); - } - - [Fact] - public void NaN_is_never_greater_than_another_float() - { - // Act - Action act = () => float.NaN.Should().BeGreaterThan(0); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - - [Fact] - public void A_float_cannot_be_greater_than_NaN() - { - // Act - Action act = () => 3.4F.Should().BeGreaterThan(float.NaN); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - - [Fact] - public void NaN_is_never_greater_than_another_double() - { - // Act - Action act = () => double.NaN.Should().BeGreaterThan(0); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - - [Fact] - public void A_double_can_never_be_greater_than_NaN() - { - // Act - Action act = () => 3.4D.Should().BeGreaterThan(double.NaN); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - - [Fact] - public void When_a_value_is_greater_than_or_equal_to_smaller_value_it_should_not_throw() - { - // Arrange - int value = 2; - int smallerValue = 1; - - // Act - Action act = () => value.Should().BeGreaterThanOrEqualTo(smallerValue); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_a_value_is_greater_than_or_equal_to_same_value_it_should_not_throw() - { - // Arrange - int value = 2; - int sameValue = 2; - - // Act - Action act = () => value.Should().BeGreaterThanOrEqualTo(sameValue); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_a_value_is_greater_than_or_equal_to_greater_value_it_should_throw() - { - // Arrange - int value = 2; - int greaterValue = 3; - - // Act - Action act = () => value.Should().BeGreaterThanOrEqualTo(greaterValue); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_a_value_is_greater_than_or_equal_to_greater_value_it_should_throw_with_descriptive_message() - { - // Arrange - int value = 2; - int greaterValue = 3; - - // Act - Action act = - () => value.Should() - .BeGreaterThanOrEqualTo(greaterValue, "because we want to test the failure {0}", "message"); - - // Assert - act - .Should().Throw() - .WithMessage( - "Expected value to be greater than or equal to 3 because we want to test the failure message, but found 2."); - } - - [Fact] - public void When_a_nullable_numeric_null_value_is_not_greater_than_it_should_throw() - { - // Arrange - int? value = null; - - // Act - Action act = () => value.Should().BeGreaterThan(0); - - // Assert - act - .Should().Throw() - .WithMessage("*null*"); - } - - [Fact] - public void When_a_nullable_numeric_null_value_is_not_greater_than_or_equal_to_it_should_throw() - { - // Arrange - int? value = null; - - // Act - Action act = () => value.Should().BeGreaterThanOrEqualTo(0); - - // Assert - act - .Should().Throw() - .WithMessage("*null*"); - } - - [Fact] - public void NaN_is_never_greater_than_or_equal_to_another_float() - { - // Act - Action act = () => float.NaN.Should().BeGreaterThanOrEqualTo(0); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - - [Fact] - public void A_float_cannot_be_greater_than_or_equal_to_NaN() - { - // Act - Action act = () => 3.4F.Should().BeGreaterThanOrEqualTo(float.NaN); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - - [Fact] - public void NaN_is_never_greater_or_equal_to_another_double() - { - // Act - Action act = () => double.NaN.Should().BeGreaterThanOrEqualTo(0); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - - [Fact] - public void A_double_can_never_be_greater_or_equal_to_NaN() - { - // Act - Action act = () => 3.4D.Should().BeGreaterThanOrEqualTo(double.NaN); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - } - - public class LessThanOrEqualTo - { - [Fact] - public void When_a_value_is_less_than_greater_value_it_should_not_throw() - { - // Arrange - int value = 1; - int greaterValue = 2; - - // Act - Action act = () => value.Should().BeLessThan(greaterValue); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_a_value_is_less_than_smaller_value_it_should_throw() - { - // Arrange - int value = 2; - int smallerValue = 1; - - // Act - Action act = () => value.Should().BeLessThan(smallerValue); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_a_value_is_less_than_same_value_it_should_throw() - { - // Arrange - int value = 2; - int sameValue = 2; - - // Act - Action act = () => value.Should().BeLessThan(sameValue); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_a_value_is_less_than_smaller_value_it_should_throw_with_descriptive_message() - { - // Arrange - int value = 2; - int smallerValue = 1; - - // Act - Action act = () => value.Should().BeLessThan(smallerValue, "because we want to test the failure {0}", "message"); - - // Assert - act - .Should().Throw() - .WithMessage("Expected value to be less than 1 because we want to test the failure message, but found 2."); - } - - [Fact] - public void NaN_is_never_less_than_another_float() - { - // Act - Action act = () => float.NaN.Should().BeLessThan(0); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - - [Fact] - public void A_float_can_never_be_less_than_NaN() - { - // Act - Action act = () => 3.4F.Should().BeLessThan(float.NaN); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - - [Fact] - public void NaN_is_never_less_than_another_double() - { - // Act - Action act = () => double.NaN.Should().BeLessThan(0); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - - [Fact] - public void A_double_can_never_be_less_than_NaN() - { - // Act - Action act = () => 3.4D.Should().BeLessThan(double.NaN); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - - [Fact] - public void When_a_value_is_less_than_or_equal_to_greater_value_it_should_not_throw() - { - // Arrange - int value = 1; - int greaterValue = 2; - - // Act - Action act = () => value.Should().BeLessThanOrEqualTo(greaterValue); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_a_value_is_less_than_or_equal_to_same_value_it_should_not_throw() - { - // Arrange - int value = 2; - int sameValue = 2; - - // Act - Action act = () => value.Should().BeLessThanOrEqualTo(sameValue); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_a_value_is_less_than_or_equal_to_smaller_value_it_should_throw() - { - // Arrange - int value = 2; - int smallerValue = 1; - - // Act - Action act = () => value.Should().BeLessThanOrEqualTo(smallerValue); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_a_value_is_less_than_or_equal_to_smaller_value_it_should_throw_with_descriptive_message() - { - // Arrange - int value = 2; - int smallerValue = 1; - - // Act - Action act = () => - value.Should().BeLessThanOrEqualTo(smallerValue, "because we want to test the failure {0}", "message"); - - // Assert - act - .Should().Throw() - .WithMessage( - "Expected value to be less than or equal to 1 because we want to test the failure message, but found 2."); - } - - [Fact] - public void When_a_nullable_numeric_null_value_is_not_less_than_it_should_throw() - { - // Arrange - int? value = null; - - // Act - Action act = () => value.Should().BeLessThan(0); - - // Assert - act - .Should().Throw() - .WithMessage("*null*"); - } - - [Fact] - public void When_a_nullable_numeric_null_value_is_not_less_than_or_equal_to_it_should_throw() - { - // Arrange - int? value = null; - - // Act - Action act = () => value.Should().BeLessThanOrEqualTo(0); - - // Assert - act - .Should().Throw() - .WithMessage("*null*"); - } - - [Fact] - public void NaN_is_never_less_than_or_equal_to_another_float() - { - // Act - Action act = () => float.NaN.Should().BeLessThanOrEqualTo(0); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - - [Fact] - public void A_float_can_never_be_less_than_or_equal_to_NaN() - { - // Act - Action act = () => 3.4F.Should().BeLessThanOrEqualTo(float.NaN); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - - [Fact] - public void NaN_is_never_less_than_or_equal_to_another_double() - { - // Act - Action act = () => double.NaN.Should().BeLessThanOrEqualTo(0); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - - [Fact] - public void A_double_can_never_be_less_than_or_equal_to_NaN() - { - // Act - Action act = () => 3.4D.Should().BeLessThanOrEqualTo(double.NaN); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - } - - public class InRange - { - [Fact] - public void When_a_value_is_outside_a_range_it_should_throw() - { - // Arrange - float value = 3.99F; - - // Act - Action act = () => value.Should().BeInRange(4, 5, "because that's the valid range"); - - // Assert - act - .Should().Throw() - .WithMessage( - "Expected value to be between*4* and*5* because that\'s the valid range, but found*3.99*"); - } - - [Fact] - public void When_a_value_is_inside_a_range_it_should_not_throw() - { - // Arrange - int value = 4; - - // Act - Action act = () => value.Should().BeInRange(3, 5); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_a_nullable_numeric_null_value_is_not_in_range_it_should_throw() - { - // Arrange - int? value = null; - - // Act - Action act = () => value.Should().BeInRange(0, 1); - - // Assert - act - .Should().Throw() - .WithMessage("*null*"); - } - - [Fact] - public void NaN_is_never_in_range_of_two_floats() - { - // Arrange - float value = float.NaN; - - // Act - Action act = () => value.Should().BeInRange(4, 5); - - // Assert - act - .Should().Throw() - .WithMessage( - "Expected value to be between*4* and*5*, but found*NaN*"); - } - - [Theory] - [InlineData(float.NaN, 5F)] - [InlineData(5F, float.NaN)] - public void A_float_can_never_be_in_a_range_containing_NaN(float minimumValue, float maximumValue) - { - // Arrange - float value = 4.5F; - - // Act - Action act = () => value.Should().BeInRange(minimumValue, maximumValue); - - // Assert - act - .Should().Throw() - .WithMessage( - "*NaN*"); - } - - [Fact] - public void A_NaN_is_never_in_range_of_two_doubles() - { - // Arrange - double value = double.NaN; - - // Act - Action act = () => value.Should().BeInRange(4, 5); - - // Assert - act - .Should().Throw() - .WithMessage( - "Expected value to be between*4* and*5*, but found*NaN*"); - } - - [Theory] - [InlineData(double.NaN, 5)] - [InlineData(5, double.NaN)] - public void A_double_can_never_be_in_a_range_containing_NaN(double minimumValue, double maximumValue) - { - // Arrange - double value = 4.5D; - - // Act - Action act = () => value.Should().BeInRange(minimumValue, maximumValue); - - // Assert - act - .Should().Throw() - .WithMessage( - "*NaN*"); - } - } - - public class NotInRange - { - [Fact] - public void When_a_value_is_inside_an_unexpected_range_it_should_throw() - { - // Arrange - float value = 4.99F; - - // Act - Action act = () => value.Should().NotBeInRange(4, 5, "because that's the invalid range"); - - // Assert - act - .Should().Throw() - .WithMessage( - "Expected value to not be between*4* and*5* because that\'s the invalid range, but found*4.99*"); - } - - [Fact] - public void When_a_value_is_outside_an_unexpected_range_it_should_not_throw() - { - // Arrange - float value = 3.99F; - - // Act - Action act = () => value.Should().NotBeInRange(4, 5); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_a_nullable_numeric_null_value_is_not_not_in_range_to_it_should_throw() - { - // Arrange - int? value = null; - - // Act - Action act = () => value.Should().NotBeInRange(0, 1); - - // Assert - act - .Should().Throw() - .WithMessage("*null*"); - } - - [Fact] - public void NaN_is_never_inside_any_range_of_floats() - { - // Arrange - float value = float.NaN; - - // Act / Assert - value.Should().NotBeInRange(4, 5); - } - - [Theory] - [InlineData(float.NaN, 1F)] - [InlineData(1F, float.NaN)] - public void Cannot_use_NaN_in_a_range_of_floats(float minimumValue, float maximumValue) - { - // Arrange - float value = 4.5F; - - // Act - Action act = () => value.Should().NotBeInRange(minimumValue, maximumValue); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - - [Fact] - public void NaN_is_never_inside_any_range_of_doubles() - { - // Arrange - double value = double.NaN; - - // Act / Assert - value.Should().NotBeInRange(4, 5); - } - - [Theory] - [InlineData(double.NaN, 1D)] - [InlineData(1D, double.NaN)] - public void Cannot_use_NaN_in_a_range_of_doubles(double minimumValue, double maximumValue) - { - // Arrange - double value = 4.5D; - - // Act - Action act = () => value.Should().NotBeInRange(minimumValue, maximumValue); - - // Assert - act - .Should().Throw() - .WithMessage("*NaN*"); - } - } - - public class BeOneOf - { - [Fact] - public void When_a_value_is_not_one_of_the_specified_values_it_should_throw() - { - // Arrange - int value = 3; - - // Act - Action act = () => value.Should().BeOneOf(4, 5); - - // Assert - act - .Should().Throw() - .WithMessage("Expected value to be one of {4, 5}, but found 3."); - } - - [Fact] - public void When_a_value_is_not_one_of_the_specified_values_it_should_throw_with_descriptive_message() - { - // Arrange - int value = 3; - - // Act - Action act = () => value.Should().BeOneOf(new[] { 4, 5 }, "because those are the valid values"); - - // Assert - act - .Should().Throw() - .WithMessage("Expected value to be one of {4, 5} because those are the valid values, but found 3."); - } - - [Fact] - public void When_a_value_is_one_of_the_specified_values_it_should_succeed() - { - // Arrange - int value = 4; - - // Act - Action act = () => value.Should().BeOneOf(4, 5); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_a_nullable_numeric_null_value_is_not_one_of_to_it_should_throw() - { - // Arrange - int? value = null; - - // Act - Action act = () => value.Should().BeOneOf(0, 1); - - // Assert - act - .Should().Throw() - .WithMessage("*null*"); - } - - [Fact] - public void Two_floats_that_are_NaN_can_be_compared() - { - // Arrange - float value = float.NaN; - - // Act / Assert - value.Should().BeOneOf(float.NaN, 4.5F); - } - - [Fact] - public void Floats_are_never_equal_to_NaN() - { - // Arrange - float value = float.NaN; - - // Act - Action act = () => value.Should().BeOneOf(1.5F, 4.5F); - - // Assert - act - .Should().Throw() - .WithMessage("Expected*1.5F*found*NaN*"); - } - - [Fact] - public void Two_doubles_that_are_NaN_can_be_compared() - { - // Arrange - double value = double.NaN; - - // Act / Assert - value.Should().BeOneOf(double.NaN, 4.5F); - } - - [Fact] - public void Doubles_are_never_equal_to_NaN() - { - // Arrange - double value = double.NaN; - - // Act - Action act = () => value.Should().BeOneOf(1.5D, 4.5D); - - // Assert - act - .Should().Throw() - .WithMessage("Expected*1.5*found NaN*"); - } - } - - public class Bytes - { - [Fact] - public void When_asserting_a_byte_value_it_should_treat_is_any_numeric_value() - { - // Arrange - byte value = 2; - - // Act - Action act = () => value.Should().Be(2); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_a_sbyte_value_it_should_treat_is_any_numeric_value() - { - // Arrange - sbyte value = 2; - - // Act - Action act = () => value.Should().Be(2); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_a_short_value_it_should_treat_is_any_numeric_value() - { - // Arrange - short value = 2; - - // Act - Action act = () => value.Should().Be(2); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_an_ushort_value_it_should_treat_is_any_numeric_value() - { - // Arrange - ushort value = 2; - - // Act - Action act = () => value.Should().Be(2); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_an_uint_value_it_should_treat_is_any_numeric_value() - { - // Arrange - uint value = 2; - - // Act - Action act = () => value.Should().Be(2); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_a_long_value_it_should_treat_is_any_numeric_value() - { - // Arrange - long value = 2; - - // Act - Action act = () => value.Should().Be(2); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_an_ulong_value_it_should_treat_is_any_numeric_value() - { - // Arrange - ulong value = 2; - - // Act - Action act = () => value.Should().Be(2); - - // Assert - act.Should().NotThrow(); - } - } - - public class NullableBytes - { - [Fact] - public void When_asserting_a_nullable_byte_value_it_should_treat_is_any_numeric_value() - { - // Arrange - byte? value = 2; - - // Act - Action act = () => value.Should().Be(2); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_a_nullable_sbyte_value_it_should_treat_is_any_numeric_value() - { - // Arrange - sbyte? value = 2; - - // Act - Action act = () => value.Should().Be(2); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_a_nullable_short_value_it_should_treat_is_any_numeric_value() - { - // Arrange - short? value = 2; - - // Act - Action act = () => value.Should().Be(2); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_a_nullable_ushort_value_it_should_treat_is_any_numeric_value() - { - // Arrange - ushort? value = 2; - - // Act - Action act = () => value.Should().Be(2); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_a_nullable_uint_value_it_should_treat_is_any_numeric_value() - { - // Arrange - uint? value = 2; - - // Act - Action act = () => value.Should().Be(2); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_a_nullable_long_value_it_should_treat_is_any_numeric_value() - { - // Arrange - long? value = 2; - - // Act - Action act = () => value.Should().Be(2); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_a_nullable_nullable_ulong_value_it_should_treat_is_any_numeric_value() - { - // Arrange - ulong? value = 2; - - // Act - Action act = () => value.Should().Be(2); - - // Assert - act.Should().NotThrow(); - } - } - - public class BeApproximately - { - [Fact] - public void When_approximating_a_float_with_a_negative_precision_it_should_throw() - { - // Arrange - float value = 3.1415927F; - - // Act - Action act = () => value.Should().BeApproximately(3.14F, -0.1F); - - // Assert - act.Should().Throw() - .WithParameterName("precision") - .WithMessage("*must be non-negative*"); - } - - [Fact] - public void When_float_is_not_approximating_a_range_it_should_throw() - { - // Arrange - float value = 3.1415927F; - - // Act - Action act = () => value.Should().BeApproximately(3.14F, 0.001F, "rockets will crash otherwise"); - - // Assert - act - .Should().Throw() - .WithMessage( - "Expected value to approximate *3.14* +/- *0.001* because rockets will crash otherwise, but *3.1415927* differed by *0.001592*"); - } - - [Fact] - public void When_float_is_indeed_approximating_a_value_it_should_not_throw() - { - // Arrange - float value = 3.1415927F; - - // Act - Action act = () => value.Should().BeApproximately(3.14F, 0.1F); - - // Assert - act.Should().NotThrow(); - } - - [InlineData(9F)] - [InlineData(11F)] - [Theory] - public void When_float_is_approximating_a_value_on_boundaries_it_should_not_throw(float value) - { - // Act - Action act = () => value.Should().BeApproximately(10F, 1F); - - // Assert - act.Should().NotThrow(); - } - - [InlineData(9F)] - [InlineData(11F)] - [Theory] - public void When_float_is_not_approximating_a_value_on_boundaries_it_should_throw(float value) - { - // Act - Action act = () => value.Should().BeApproximately(10F, 0.9F); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_approximating_a_float_towards_nan_it_should_not_throw() - { - // Arrange - float value = float.NaN; - - // Act - Action act = () => value.Should().BeApproximately(3.14F, 0.1F); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_approximating_positive_infinity_float_towards_positive_infinity_it_should_not_throw() - { - // Arrange - float value = float.PositiveInfinity; - - // Act - Action act = () => value.Should().BeApproximately(float.PositiveInfinity, 0.1F); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_approximating_negative_infinity_float_towards_negative_infinity_it_should_not_throw() - { - // Arrange - float value = float.NegativeInfinity; - - // Act - Action act = () => value.Should().BeApproximately(float.NegativeInfinity, 0.1F); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_float_is_not_approximating_positive_infinity_it_should_throw() - { - // Arrange - float value = float.PositiveInfinity; - - // Act - Action act = () => value.Should().BeApproximately(float.MaxValue, 0.1F); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_float_is_not_approximating_negative_infinity_it_should_throw() - { - // Arrange - float value = float.NegativeInfinity; - - // Act - Action act = () => value.Should().BeApproximately(float.MinValue, 0.1F); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void NaN_can_never_be_close_to_any_float() - { - // Arrange - float value = float.NaN; - - // Act - Action act = () => value.Should().BeApproximately(float.MinValue, 0.1F); - - // Assert - act.Should().Throw().WithMessage("*NaN*"); - } - - [Fact] - public void A_float_can_never_be_close_to_NaN() - { - // Arrange - float value = float.MinValue; - - // Act - Action act = () => value.Should().BeApproximately(float.NaN, 0.1F); - - // Assert - act.Should().Throw().WithMessage("*NaN*"); - } - - [Fact] - public void When_a_nullable_float_has_no_value_it_should_throw() - { - // Arrange - float? value = null; - - // Act - Action act = () => value.Should().BeApproximately(3.14F, 0.001F); - - // Assert - act - .Should().Throw() - .WithMessage("Expected value to approximate*3.14* +/-*0.001*, but it was ."); - } - - [Fact] - public void When_approximating_a_double_with_a_negative_precision_it_should_throw() - { - // Arrange - double value = 3.1415927; - - // Act - Action act = () => value.Should().BeApproximately(3.14, -0.1); - - // Assert - act.Should().Throw() - .WithParameterName("precision") - .WithMessage("*must be non-negative*"); - } - - [Fact] - public void When_double_is_not_approximating_a_range_it_should_throw() - { - // Arrange - double value = 3.1415927; - - // Act - Action act = () => value.Should().BeApproximately(3.14, 0.001, "rockets will crash otherwise"); - - // Assert - act - .Should().Throw() - .WithMessage( - "Expected value to approximate 3.14 +/- 0.001 because rockets will crash otherwise, but 3.1415927 differed by 0.001592*"); - } - - [Fact] - public void When_double_is_indeed_approximating_a_value_it_should_not_throw() - { - // Arrange - double value = 3.1415927; - - // Act - Action act = () => value.Should().BeApproximately(3.14, 0.1); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_approximating_a_double_towards_nan_it_should_not_throw() - { - // Arrange - double value = double.NaN; - - // Act - Action act = () => value.Should().BeApproximately(3.14F, 0.1F); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_approximating_positive_infinity_double_towards_positive_infinity_it_should_not_throw() - { - // Arrange - double value = double.PositiveInfinity; - - // Act - Action act = () => value.Should().BeApproximately(double.PositiveInfinity, 0.1); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_approximating_negative_infinity_double_towards_negative_infinity_it_should_not_throw() - { - // Arrange - double value = double.NegativeInfinity; - - // Act - Action act = () => value.Should().BeApproximately(double.NegativeInfinity, 0.1); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_double_is_not_approximating_positive_infinity_it_should_throw() - { - // Arrange - double value = double.PositiveInfinity; - - // Act - Action act = () => value.Should().BeApproximately(double.MaxValue, 0.1); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_double_is_not_approximating_negative_infinity_it_should_throw() - { - // Arrange - double value = double.NegativeInfinity; - - // Act - Action act = () => value.Should().BeApproximately(double.MinValue, 0.1); - - // Assert - act.Should().Throw(); - } - - [InlineData(9D)] - [InlineData(11D)] - [Theory] - public void When_double_is_approximating_a_value_on_boundaries_it_should_not_throw(double value) - { - // Act - Action act = () => value.Should().BeApproximately(10D, 1D); - - // Assert - act.Should().NotThrow(); - } - - [InlineData(9D)] - [InlineData(11D)] - [Theory] - public void When_double_is_not_approximating_a_value_on_boundaries_it_should_throw(double value) - { - // Act - Action act = () => value.Should().BeApproximately(10D, 0.9D); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void NaN_can_never_be_close_to_any_double() - { - // Arrange - double value = double.NaN; - - // Act - Action act = () => value.Should().BeApproximately(double.MinValue, 0.1F); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void A_double_can_never_be_close_to_NaN() - { - // Arrange - double value = double.MinValue; - - // Act - Action act = () => value.Should().BeApproximately(double.NaN, 0.1F); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_approximating_a_decimal_with_a_negative_precision_it_should_throw() - { - // Arrange - decimal value = 3.1415927M; - - // Act - Action act = () => value.Should().BeApproximately(3.14m, -0.1m); - - // Assert - act.Should().Throw() - .WithParameterName("precision") - .WithMessage("*must be non-negative*"); - } - - [Fact] - public void When_decimal_is_not_approximating_a_range_it_should_throw() - { - // Arrange - decimal value = 3.5011m; - - // Act - Action act = () => value.Should().BeApproximately(3.5m, 0.001m, "rockets will crash otherwise"); - - // Assert - act.Should().Throw().WithMessage( - "Expected value to approximate*3.5* +/-*0.001* because rockets will crash otherwise, but *3.5011* differed by*0.0011*"); - } - - [Fact] - public void When_decimal_is_indeed_approximating_a_value_it_should_not_throw() - { - // Arrange - decimal value = 3.5011m; - - // Act - Action act = () => value.Should().BeApproximately(3.5m, 0.01m); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_decimal_is_approximating_a_value_on_lower_boundary_it_should_not_throw() - { - // Act - decimal value = 9m; - - // Act - Action act = () => value.Should().BeApproximately(10m, 1m); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_decimal_is_approximating_a_value_on_upper_boundary_it_should_not_throw() - { - // Act - decimal value = 11m; - - // Act - Action act = () => value.Should().BeApproximately(10m, 1m); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_decimal_is_not_approximating_a_value_on_lower_boundary_it_should_throw() - { - // Act - decimal value = 9m; - - // Act - Action act = () => value.Should().BeApproximately(10m, 0.9m); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_decimal_is_not_approximating_a_value_on_upper_boundary_it_should_throw() - { - // Act - decimal value = 11m; - - // Act - Action act = () => value.Should().BeApproximately(10m, 0.9m); - - // Assert - act.Should().Throw(); - } - } - - public class NotBeApproximately - { - [Fact] - public void When_not_approximating_a_float_with_a_negative_precision_it_should_throw() - { - // Arrange - float value = 3.1415927F; - - // Act - Action act = () => value.Should().NotBeApproximately(3.14F, -0.1F); - - // Assert - act.Should().Throw() - .WithParameterName("precision") - .WithMessage("*must be non-negative*"); - } - - [Fact] - public void When_float_is_approximating_a_range_and_should_not_approximate_it_should_throw() - { - // Arrange - float value = 3.1415927F; - - // Act - Action act = () => value.Should().NotBeApproximately(3.14F, 0.1F, "rockets will crash otherwise"); - - // Assert - act - .Should().Throw() - .WithMessage( - "Expected value to not approximate *3.14* +/- *0.1* because rockets will crash otherwise, but *3.1415927* only differed by *0.001592*"); - } - - [Fact] - public void When_float_is_not_approximating_a_value_and_should_not_approximate_it_should_not_throw() - { - // Arrange - float value = 3.1415927F; - - // Act - Action act = () => value.Should().NotBeApproximately(3.14F, 0.001F); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_approximating_a_float_towards_nan_and_should_not_approximate_it_should_throw() - { - // Arrange - float value = float.NaN; - - // Act - Action act = () => value.Should().NotBeApproximately(3.14F, 0.1F); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_not_approximating_a_float_towards_positive_infinity_and_should_not_approximate_it_should_not_throw() - { - // Arrange - float value = float.PositiveInfinity; - - // Act - Action act = () => value.Should().NotBeApproximately(float.MaxValue, 0.1F); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_not_approximating_a_float_towards_negative_infinity_and_should_not_approximate_it_should_not_throw() - { - // Arrange - float value = float.NegativeInfinity; - - // Act - Action act = () => value.Should().NotBeApproximately(float.MinValue, 0.1F); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void - When_approximating_positive_infinity_float_towards_positive_infinity_and_should_not_approximate_it_should_throw() - { - // Arrange - float value = float.PositiveInfinity; - - // Act - Action act = () => value.Should().NotBeApproximately(float.PositiveInfinity, 0.1F); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void - When_not_approximating_negative_infinity_float_towards_negative_infinity_and_should_not_approximate_it_should_throw() - { - // Arrange - float value = float.NegativeInfinity; - - // Act - Action act = () => value.Should().NotBeApproximately(float.NegativeInfinity, 0.1F); - - // Assert - act.Should().Throw(); - } - - [InlineData(9F)] - [InlineData(11F)] - [Theory] - public void When_float_is_not_approximating_a_value_on_boundaries_it_should_not_throw(float value) - { - // Act - Action act = () => value.Should().NotBeApproximately(10F, 0.9F); - - // Assert - act.Should().NotThrow(); - } - - [InlineData(9F)] - [InlineData(11F)] - [Theory] - public void When_float_is_approximating_a_value_on_boundaries_it_should_throw(float value) - { - // Act - Action act = () => value.Should().NotBeApproximately(10F, 1F); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_a_nullable_float_has_no_value_and_should_not_approximate_it_should_not_throw() - { - // Arrange - float? value = null; - - // Act - Action act = () => value.Should().NotBeApproximately(3.14F, 0.001F); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void NaN_can_never_be_close_to_any_float() - { - // Arrange - float value = float.NaN; - - // Act - Action act = () => value.Should().NotBeApproximately(float.MinValue, 0.1F); - - // Assert - act.Should().Throw().WithMessage("*NaN*"); - } - - [Fact] - public void A_float_can_never_be_close_to_NaN() - { - // Arrange - float value = float.MinValue; - - // Act - Action act = () => value.Should().NotBeApproximately(float.NaN, 0.1F); - - // Assert - act.Should().Throw().WithMessage("*NaN*"); - } - - [Fact] - public void When_not_approximating_a_double_with_a_negative_precision_it_should_throw() - { - // Arrange - double value = 3.1415927; - - // Act - Action act = () => value.Should().NotBeApproximately(3.14, -0.1); - - // Assert - act.Should().Throw() - .WithParameterName("precision") - .WithMessage("*must be non-negative*"); - } - - [Fact] - public void When_double_is_approximating_a_range_and_should_not_approximate_it_should_throw() - { - // Arrange - double value = 3.1415927; - - // Act - Action act = () => value.Should().NotBeApproximately(3.14, 0.1, "rockets will crash otherwise"); - - // Assert - act - .Should().Throw() - .WithMessage( - "Expected value to not approximate *3.14* +/- *0.1* because rockets will crash otherwise, but *3.1415927* only differed by *0.001592*"); - } - - [Fact] - public void When_double_is_not_approximating_a_value_and_should_not_approximate_it_should_not_throw() - { - // Arrange - double value = 3.1415927; - - // Act - Action act = () => value.Should().NotBeApproximately(3.14, 0.001); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_approximating_a_double_towards_nan_and_should_not_approximate_it_should_throw() - { - // Arrange - double value = double.NaN; - - // Act - Action act = () => value.Should().NotBeApproximately(3.14, 0.1); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_not_approximating_a_double_towards_positive_infinity_and_should_not_approximate_it_should_not_throw() - { - // Arrange - double value = double.PositiveInfinity; - - // Act - Action act = () => value.Should().NotBeApproximately(double.MaxValue, 0.1); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_not_approximating_a_double_towards_negative_infinity_and_should_not_approximate_it_should_not_throw() - { - // Arrange - double value = double.NegativeInfinity; - - // Act - Action act = () => value.Should().NotBeApproximately(double.MinValue, 0.1); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void - When_approximating_positive_infinity_double_towards_positive_infinity_and_should_not_approximate_it_should_throw() - { - // Arrange - double value = double.PositiveInfinity; - - // Act - Action act = () => value.Should().NotBeApproximately(double.PositiveInfinity, 0.1); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void - When_not_approximating_negative_infinity_double_towards_negative_infinity_and_should_not_approximate_it_should_throw() - { - // Arrange - double value = double.NegativeInfinity; - - // Act - Action act = () => value.Should().NotBeApproximately(double.NegativeInfinity, 0.1); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_a_nullable_double_has_no_value_and_should_not_approximate_it_should_throw() - { - // Arrange - double? value = null; - - // Act - Action act = () => value.Should().NotBeApproximately(3.14, 0.001); - - // Assert - act.Should().NotThrow(); - } - - [InlineData(9D)] - [InlineData(11D)] - [Theory] - public void When_double_is_not_approximating_a_value_on_boundaries_it_should_not_throw(double value) - { - // Act - Action act = () => value.Should().NotBeApproximately(10D, 0.9D); - - // Assert - act.Should().NotThrow(); - } - - [InlineData(9D)] - [InlineData(11D)] - [Theory] - public void When_double_is_approximating_a_value_on_boundaries_it_should_throw(double value) - { - // Act - Action act = () => value.Should().NotBeApproximately(10D, 1D); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void NaN_can_never_be_close_to_any_double() - { - // Arrange - double value = double.NaN; - - // Act - Action act = () => value.Should().NotBeApproximately(double.MinValue, 0.1F); - - // Assert - act.Should().Throw().WithMessage("*NaN*"); - } - - [Fact] - public void A_double_can_never_be_close_to_NaN() - { - // Arrange - double value = double.MinValue; - - // Act - Action act = () => value.Should().NotBeApproximately(double.NaN, 0.1F); - - // Assert - act.Should().Throw().WithMessage("*NaN*"); - } - - [Fact] - public void When_not_approximating_a_decimal_with_a_negative_precision_it_should_throw() - { - // Arrange - decimal value = 3.1415927m; - - // Act - Action act = () => value.Should().NotBeApproximately(3.14m, -0.1m); - - // Assert - act.Should().Throw() - .WithParameterName("precision") - .WithMessage("*must be non-negative*"); - } - - [Fact] - public void When_decimal_is_approximating_a_range_and_should_not_approximate_it_should_throw() - { - // Arrange - decimal value = 3.5011m; - - // Act - Action act = () => value.Should().NotBeApproximately(3.5m, 0.1m, "rockets will crash otherwise"); - - // Assert - act - .Should().Throw() - .WithMessage( - "Expected value to not approximate *3.5* +/- *0.1* because rockets will crash otherwise, but *3.5011* only differed by *0.0011*"); - } - - [Fact] - public void When_decimal_is_not_approximating_a_value_and_should_not_approximate_it_should_not_throw() - { - // Arrange - decimal value = 3.5011m; - - // Act - Action act = () => value.Should().NotBeApproximately(3.5m, 0.001m); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_a_nullable_decimal_has_no_value_and_should_not_approximate_it_should_throw() - { - // Arrange - decimal? value = null; - - // Act - Action act = () => value.Should().NotBeApproximately(3.5m, 0.001m); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_decimal_is_not_approximating_a_value_on_lower_boundary_it_should_not_throw() - { - // Act - decimal value = 9m; - - // Act - Action act = () => value.Should().NotBeApproximately(10m, 0.9m); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_decimal_is_not_approximating_a_value_on_upper_boundary_it_should_not_throw() - { - // Act - decimal value = 11m; - - // Act - Action act = () => value.Should().NotBeApproximately(10m, 0.9m); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_decimal_is_approximating_a_value_on_lower_boundary_it_should_throw() - { - // Act - decimal value = 9m; - - // Act - Action act = () => value.Should().NotBeApproximately(10m, 1m); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_decimal_is_approximating_a_value_on_upper_boundary_it_should_throw() - { - // Act - decimal value = 11m; - - // Act - Action act = () => value.Should().NotBeApproximately(10m, 1m); - - // Assert - act.Should().Throw(); - } - } - - public class CloseTo - { - [InlineData(sbyte.MinValue, sbyte.MinValue, 0)] - [InlineData(sbyte.MinValue, sbyte.MinValue, 1)] - [InlineData(sbyte.MinValue, sbyte.MinValue, sbyte.MaxValue)] - [InlineData(sbyte.MinValue, sbyte.MinValue + 1, 1)] - [InlineData(sbyte.MinValue, sbyte.MinValue + 1, sbyte.MaxValue)] - [InlineData(sbyte.MinValue, -1, sbyte.MaxValue)] - [InlineData(sbyte.MinValue + 1, sbyte.MinValue, 1)] - [InlineData(sbyte.MinValue + 1, sbyte.MinValue, sbyte.MaxValue)] - [InlineData(sbyte.MinValue + 1, 0, sbyte.MaxValue)] - [InlineData(-1, sbyte.MinValue, sbyte.MaxValue)] - [InlineData(-1, 0, 1)] - [InlineData(-1, 0, sbyte.MaxValue)] - [InlineData(0, 0, 0)] - [InlineData(0, 0, 1)] - [InlineData(0, -1, 1)] - [InlineData(0, -1, sbyte.MaxValue)] - [InlineData(0, 1, 1)] - [InlineData(0, 1, sbyte.MaxValue)] - [InlineData(0, sbyte.MaxValue, sbyte.MaxValue)] - [InlineData(0, sbyte.MinValue + 1, sbyte.MaxValue)] - [InlineData(1, 0, 1)] - [InlineData(1, 0, sbyte.MaxValue)] - [InlineData(1, sbyte.MaxValue, sbyte.MaxValue)] - [InlineData(sbyte.MaxValue - 1, sbyte.MaxValue, 1)] - [InlineData(sbyte.MaxValue - 1, sbyte.MaxValue, sbyte.MaxValue)] - [InlineData(sbyte.MaxValue, 0, sbyte.MaxValue)] - [InlineData(sbyte.MaxValue, 1, sbyte.MaxValue)] - [InlineData(sbyte.MaxValue, sbyte.MaxValue, 0)] - [InlineData(sbyte.MaxValue, sbyte.MaxValue, 1)] - [InlineData(sbyte.MaxValue, sbyte.MaxValue, sbyte.MaxValue)] - [InlineData(sbyte.MaxValue, sbyte.MaxValue - 1, 1)] - [InlineData(sbyte.MaxValue, sbyte.MaxValue - 1, sbyte.MaxValue)] - [Theory] - public void When_a_sbyte_value_is_close_to_expected_value_it_should_succeed(sbyte actual, sbyte nearbyValue, - byte delta) - { - // Act - Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); - - // Assert - act.Should().NotThrow(); - } - - [InlineData(sbyte.MinValue, sbyte.MaxValue, 1)] - [InlineData(sbyte.MinValue, 0, sbyte.MaxValue)] - [InlineData(sbyte.MinValue, 1, sbyte.MaxValue)] - [InlineData(-1, 0, 0)] - [InlineData(-1, 1, 1)] - [InlineData(-1, sbyte.MaxValue, sbyte.MaxValue)] - [InlineData(0, sbyte.MinValue, sbyte.MaxValue)] - [InlineData(0, -1, 0)] - [InlineData(0, 1, 0)] - [InlineData(1, -1, 1)] - [InlineData(1, 0, 0)] - [InlineData(1, sbyte.MinValue, sbyte.MaxValue)] - [InlineData(sbyte.MaxValue, sbyte.MinValue, 1)] - [InlineData(sbyte.MaxValue, -1, sbyte.MaxValue)] - [Theory] - public void When_a_sbyte_value_is_not_close_to_expected_value_it_should_fail(sbyte actual, sbyte nearbyValue, - byte delta) - { - // Act - Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_a_sbyte_value_is_not_close_to_expected_value_it_should_fail_with_a_descriptive_message() - { - // Arrange - sbyte actual = 1; - sbyte nearbyValue = 4; - byte delta = 2; - - // Act - Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); - - // Assert - act.Should().Throw() - .WithMessage("*be within*2*from*4*but found*1*"); - } - - [Fact] - public void When_a_sbyte_value_is_returned_from_BeCloseTo_it_should_chain() - { - // Arrange - sbyte actual = sbyte.MaxValue; - - // Act - Action act = () => actual.Should().BeCloseTo(actual, 0) - .And.Be(actual); - - // Assert - act.Should().NotThrow(); - } - - [InlineData(short.MinValue, short.MinValue, 0)] - [InlineData(short.MinValue, short.MinValue, 1)] - [InlineData(short.MinValue, short.MinValue, short.MaxValue)] - [InlineData(short.MinValue, short.MinValue + 1, 1)] - [InlineData(short.MinValue, short.MinValue + 1, short.MaxValue)] - [InlineData(short.MinValue, -1, short.MaxValue)] - [InlineData(short.MinValue + 1, short.MinValue, 1)] - [InlineData(short.MinValue + 1, short.MinValue, short.MaxValue)] - [InlineData(short.MinValue + 1, 0, short.MaxValue)] - [InlineData(-1, short.MinValue, short.MaxValue)] - [InlineData(-1, 0, 1)] - [InlineData(-1, 0, short.MaxValue)] - [InlineData(0, 0, 0)] - [InlineData(0, 0, 1)] - [InlineData(0, -1, 1)] - [InlineData(0, -1, short.MaxValue)] - [InlineData(0, 1, 1)] - [InlineData(0, 1, short.MaxValue)] - [InlineData(0, short.MaxValue, short.MaxValue)] - [InlineData(0, short.MinValue + 1, short.MaxValue)] - [InlineData(1, 0, 1)] - [InlineData(1, 0, short.MaxValue)] - [InlineData(1, short.MaxValue, short.MaxValue)] - [InlineData(short.MaxValue - 1, short.MaxValue, 1)] - [InlineData(short.MaxValue - 1, short.MaxValue, short.MaxValue)] - [InlineData(short.MaxValue, 0, short.MaxValue)] - [InlineData(short.MaxValue, 1, short.MaxValue)] - [InlineData(short.MaxValue, short.MaxValue, 0)] - [InlineData(short.MaxValue, short.MaxValue, 1)] - [InlineData(short.MaxValue, short.MaxValue, short.MaxValue)] - [InlineData(short.MaxValue, short.MaxValue - 1, 1)] - [InlineData(short.MaxValue, short.MaxValue - 1, short.MaxValue)] - [Theory] - public void When_a_short_value_is_close_to_expected_value_it_should_succeed(short actual, short nearbyValue, - ushort delta) - { - // Act - Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); - - // Assert - act.Should().NotThrow(); - } - - [InlineData(short.MinValue, short.MaxValue, 1)] - [InlineData(short.MinValue, 0, short.MaxValue)] - [InlineData(short.MinValue, 1, short.MaxValue)] - [InlineData(-1, 0, 0)] - [InlineData(-1, 1, 1)] - [InlineData(-1, short.MaxValue, short.MaxValue)] - [InlineData(0, short.MinValue, short.MaxValue)] - [InlineData(0, -1, 0)] - [InlineData(0, 1, 0)] - [InlineData(1, -1, 1)] - [InlineData(1, 0, 0)] - [InlineData(1, short.MinValue, short.MaxValue)] - [InlineData(short.MaxValue, short.MinValue, 1)] - [InlineData(short.MaxValue, -1, short.MaxValue)] - [Theory] - public void When_a_short_value_is_not_close_to_expected_value_it_should_fail(short actual, short nearbyValue, - ushort delta) - { - // Act - Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_a_short_value_is_not_close_to_expected_value_it_should_fail_with_a_descriptive_message() - { - // Arrange - short actual = 1; - short nearbyValue = 4; - ushort delta = 2; - - // Act - Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); - - // Assert - act.Should().Throw() - .WithMessage("*be within*2*from*4*but found*1*"); - } - - [Fact] - public void When_a_short_value_is_returned_from_BeCloseTo_it_should_chain() - { - // Arrange - short actual = short.MaxValue; - - // Act - Action act = () => actual.Should().BeCloseTo(actual, 0) - .And.Be(actual); - - // Assert - act.Should().NotThrow(); - } - - [InlineData(int.MinValue, int.MinValue, 0)] - [InlineData(int.MinValue, int.MinValue, 1)] - [InlineData(int.MinValue, int.MinValue, int.MaxValue)] - [InlineData(int.MinValue, int.MinValue + 1, 1)] - [InlineData(int.MinValue, int.MinValue + 1, int.MaxValue)] - [InlineData(int.MinValue, -1, int.MaxValue)] - [InlineData(int.MinValue + 1, int.MinValue, 1)] - [InlineData(int.MinValue + 1, int.MinValue, int.MaxValue)] - [InlineData(int.MinValue + 1, 0, int.MaxValue)] - [InlineData(-1, int.MinValue, int.MaxValue)] - [InlineData(-1, 0, 1)] - [InlineData(-1, 0, int.MaxValue)] - [InlineData(0, 0, 0)] - [InlineData(0, 0, 1)] - [InlineData(0, -1, 1)] - [InlineData(0, -1, int.MaxValue)] - [InlineData(0, 1, 1)] - [InlineData(0, 1, int.MaxValue)] - [InlineData(0, int.MaxValue, int.MaxValue)] - [InlineData(0, int.MinValue + 1, int.MaxValue)] - [InlineData(1, 0, 1)] - [InlineData(1, 0, int.MaxValue)] - [InlineData(1, int.MaxValue, int.MaxValue)] - [InlineData(int.MaxValue - 1, int.MaxValue, 1)] - [InlineData(int.MaxValue - 1, int.MaxValue, int.MaxValue)] - [InlineData(int.MaxValue, 0, int.MaxValue)] - [InlineData(int.MaxValue, 1, int.MaxValue)] - [InlineData(int.MaxValue, int.MaxValue, 0)] - [InlineData(int.MaxValue, int.MaxValue, 1)] - [InlineData(int.MaxValue, int.MaxValue, int.MaxValue)] - [InlineData(int.MaxValue, int.MaxValue - 1, 1)] - [InlineData(int.MaxValue, int.MaxValue - 1, int.MaxValue)] - [Theory] - public void When_an_int_value_is_close_to_expected_value_it_should_succeed(int actual, int nearbyValue, uint delta) - { - // Act - Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); - - // Assert - act.Should().NotThrow(); - } - - [InlineData(int.MinValue, int.MaxValue, 1)] - [InlineData(int.MinValue, 0, int.MaxValue)] - [InlineData(int.MinValue, 1, int.MaxValue)] - [InlineData(-1, 0, 0)] - [InlineData(-1, 1, 1)] - [InlineData(-1, int.MaxValue, int.MaxValue)] - [InlineData(0, int.MinValue, int.MaxValue)] - [InlineData(0, -1, 0)] - [InlineData(0, 1, 0)] - [InlineData(1, -1, 1)] - [InlineData(1, 0, 0)] - [InlineData(1, int.MinValue, int.MaxValue)] - [InlineData(int.MaxValue, int.MinValue, 1)] - [InlineData(int.MaxValue, -1, int.MaxValue)] - [Theory] - public void When_an_int_value_is_not_close_to_expected_value_it_should_fail(int actual, int nearbyValue, uint delta) - { - // Act - Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_an_int_value_is_not_close_to_expected_value_it_should_fail_with_a_descriptive_message() - { - // Arrange - int actual = 1; - int nearbyValue = 4; - uint delta = 2; - - // Act - Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); - - // Assert - act.Should().Throw() - .WithMessage("*be within*2*from*4*but found*1*"); - } - - [Fact] - public void When_an_int_value_is_returned_from_BeCloseTo_it_should_chain() - { - // Arrange - int actual = int.MaxValue; - - // Act - Action act = () => actual.Should().BeCloseTo(actual, 0) - .And.Be(actual); - - // Assert - act.Should().NotThrow(); - } - - [InlineData(long.MinValue, long.MinValue, 0)] - [InlineData(long.MinValue, long.MinValue, 1)] - [InlineData(long.MinValue, long.MinValue, (ulong.MaxValue / 2) - 1)] - [InlineData(long.MinValue, long.MinValue, ulong.MaxValue / 2)] - [InlineData(long.MinValue, long.MinValue, (ulong.MaxValue / 2) + 1)] - [InlineData(long.MinValue, long.MinValue, ulong.MaxValue)] - [InlineData(long.MinValue, long.MinValue + 1, 1)] - [InlineData(long.MinValue, long.MinValue + 1, (ulong.MaxValue / 2) - 1)] - [InlineData(long.MinValue, long.MinValue + 1, ulong.MaxValue / 2)] - [InlineData(long.MinValue, long.MinValue + 1, (ulong.MaxValue / 2) + 1)] - [InlineData(long.MinValue, long.MinValue + 1, ulong.MaxValue)] - [InlineData(long.MinValue, -1, long.MaxValue)] - [InlineData(long.MinValue + 1, long.MinValue, 1)] - [InlineData(long.MinValue + 1, long.MinValue, (ulong.MaxValue / 2) - 1)] - [InlineData(long.MinValue + 1, long.MinValue, ulong.MaxValue / 2)] - [InlineData(long.MinValue + 1, long.MinValue, (ulong.MaxValue / 2) + 1)] - [InlineData(long.MinValue + 1, long.MinValue, ulong.MaxValue)] - [InlineData(long.MinValue + 1, 0, ulong.MaxValue / 2)] - [InlineData(long.MinValue + 1, 0, (ulong.MaxValue / 2) + 1)] - [InlineData(long.MinValue + 1, 0, ulong.MaxValue)] - [InlineData(long.MinValue, long.MaxValue, ulong.MaxValue)] - [InlineData(-1, long.MinValue, ulong.MaxValue / 2)] - [InlineData(-1, long.MinValue, (ulong.MaxValue / 2) + 1)] - [InlineData(-1, long.MinValue, ulong.MaxValue)] - [InlineData(-1, 0, 1)] - [InlineData(-1, 0, (ulong.MaxValue / 2) - 1)] - [InlineData(-1, 0, ulong.MaxValue / 2)] - [InlineData(-1, 0, (ulong.MaxValue / 2) + 1)] - [InlineData(-1, 0, ulong.MaxValue)] - [InlineData(0, 0, 0)] - [InlineData(0, 0, 1)] - [InlineData(0, -1, 1)] - [InlineData(0, -1, (ulong.MaxValue / 2) - 1)] - [InlineData(0, -1, ulong.MaxValue / 2)] - [InlineData(0, -1, (ulong.MaxValue / 2) + 1)] - [InlineData(0, -1, ulong.MaxValue)] - [InlineData(0, 1, 1)] - [InlineData(0, 1, (ulong.MaxValue / 2) - 1)] - [InlineData(0, 1, ulong.MaxValue / 2)] - [InlineData(0, 1, (ulong.MaxValue / 2) + 1)] - [InlineData(0, 1, ulong.MaxValue)] - [InlineData(0, long.MaxValue, ulong.MaxValue / 2)] - [InlineData(0, long.MaxValue, (ulong.MaxValue / 2) + 1)] - [InlineData(0, long.MaxValue, ulong.MaxValue)] - [InlineData(0, long.MinValue + 1, ulong.MaxValue / 2)] - [InlineData(0, long.MinValue + 1, (ulong.MaxValue / 2) + 1)] - [InlineData(0, long.MinValue + 1, ulong.MaxValue)] - [InlineData(1, 0, 1)] - [InlineData(1, 0, (ulong.MaxValue / 2) - 1)] - [InlineData(1, 0, ulong.MaxValue / 2)] - [InlineData(1, 0, (ulong.MaxValue / 2) + 1)] - [InlineData(1, 0, ulong.MaxValue)] - [InlineData(1, long.MaxValue, (ulong.MaxValue / 2) - 1)] - [InlineData(1, long.MaxValue, ulong.MaxValue / 2)] - [InlineData(1, long.MaxValue, (ulong.MaxValue / 2) + 1)] - [InlineData(1, long.MaxValue, ulong.MaxValue)] - [InlineData(long.MaxValue - 1, long.MaxValue, 1)] - [InlineData(long.MaxValue - 1, long.MaxValue, (ulong.MaxValue / 2) - 1)] - [InlineData(long.MaxValue - 1, long.MaxValue, ulong.MaxValue / 2)] - [InlineData(long.MaxValue - 1, long.MaxValue, (ulong.MaxValue / 2) + 1)] - [InlineData(long.MaxValue - 1, long.MaxValue, ulong.MaxValue)] - [InlineData(long.MaxValue, 0, ulong.MaxValue / 2)] - [InlineData(long.MaxValue, 0, (ulong.MaxValue / 2) + 1)] - [InlineData(long.MaxValue, 0, ulong.MaxValue)] - [InlineData(long.MaxValue, 1, (ulong.MaxValue / 2) - 1)] - [InlineData(long.MaxValue, 1, ulong.MaxValue / 2)] - [InlineData(long.MaxValue, 1, (ulong.MaxValue / 2) + 1)] - [InlineData(long.MaxValue, 1, ulong.MaxValue)] - [InlineData(long.MaxValue, long.MaxValue, 0)] - [InlineData(long.MaxValue, long.MaxValue, 1)] - [InlineData(long.MaxValue, long.MaxValue, (ulong.MaxValue / 2) - 1)] - [InlineData(long.MaxValue, long.MaxValue, ulong.MaxValue / 2)] - [InlineData(long.MaxValue, long.MaxValue, (ulong.MaxValue / 2) + 1)] - [InlineData(long.MaxValue, long.MaxValue, ulong.MaxValue)] - [InlineData(long.MaxValue, long.MaxValue - 1, 1)] - [InlineData(long.MaxValue, long.MaxValue - 1, (ulong.MaxValue / 2) - 1)] - [InlineData(long.MaxValue, long.MaxValue - 1, ulong.MaxValue / 2)] - [InlineData(long.MaxValue, long.MaxValue - 1, (ulong.MaxValue / 2) + 1)] - [InlineData(long.MaxValue, long.MaxValue - 1, ulong.MaxValue)] - [Theory] - public void When_a_long_value_is_close_to_expected_value_it_should_succeed(long actual, long nearbyValue, ulong delta) - { - // Act - Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); - - // Assert - act.Should().NotThrow(); - } - - [InlineData(long.MinValue, long.MaxValue, 1)] - [InlineData(long.MinValue, 0, long.MaxValue)] - [InlineData(long.MinValue, 1, long.MaxValue)] - [InlineData(long.MinValue + 1, 0, (ulong.MaxValue / 2) - 1)] - [InlineData(long.MinValue, long.MaxValue, (ulong.MaxValue / 2) - 1)] - [InlineData(long.MinValue, long.MaxValue, ulong.MaxValue / 2)] - [InlineData(-1, 0, 0)] - [InlineData(-1, 1, 1)] - [InlineData(-1, long.MaxValue, long.MaxValue)] - [InlineData(-1, long.MinValue, (ulong.MaxValue / 2) - 1)] - [InlineData(0, long.MinValue, long.MaxValue)] - [InlineData(0, long.MinValue + 1, (ulong.MaxValue / 2) - 1)] - [InlineData(0, long.MaxValue, (ulong.MaxValue / 2) - 1)] - [InlineData(0, -1, 0)] - [InlineData(0, 1, 0)] - [InlineData(1, -1, 1)] - [InlineData(1, 0, 0)] - [InlineData(1, long.MinValue, long.MaxValue)] - [InlineData(long.MaxValue, long.MinValue, 1)] - [InlineData(long.MaxValue, -1, long.MaxValue)] - [InlineData(long.MaxValue, 0, (ulong.MaxValue / 2) - 1)] - [Theory] - public void When_a_long_value_is_not_close_to_expected_value_it_should_fail(long actual, long nearbyValue, - ulong delta) - { - // Act - Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_a_long_value_is_not_close_to_expected_value_it_should_fail_with_a_descriptive_message() - { - // Arrange - long actual = 1; - long nearbyValue = 4; - ulong delta = 2; - - // Act - Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); - - // Assert - act.Should().Throw() - .WithMessage("*be within*2*from*4*but found*1*"); - } - - [Fact] - public void When_a_long_value_is_returned_from_BeCloseTo_it_should_chain() - { - // Arrange - long actual = long.MaxValue; - - // Act - Action act = () => actual.Should().BeCloseTo(actual, 0) - .And.Be(actual); - - // Assert - act.Should().NotThrow(); - } - - [InlineData(0, 0, 0)] - [InlineData(0, 0, 1)] - [InlineData(0, 1, 1)] - [InlineData(1, 0, 1)] - [InlineData(1, byte.MaxValue, byte.MaxValue)] - [InlineData(byte.MinValue, byte.MinValue + 1, byte.MaxValue)] - [InlineData(byte.MinValue + 1, 0, byte.MaxValue)] - [InlineData(byte.MinValue + 1, byte.MinValue, 1)] - [InlineData(byte.MinValue + 1, byte.MinValue, byte.MaxValue)] - [InlineData(byte.MaxValue - 1, byte.MaxValue, 1)] - [InlineData(byte.MaxValue - 1, byte.MaxValue, byte.MaxValue)] - [InlineData(byte.MaxValue, 0, byte.MaxValue)] - [InlineData(byte.MaxValue, 1, byte.MaxValue)] - [InlineData(byte.MaxValue, byte.MaxValue - 1, 1)] - [InlineData(byte.MaxValue, byte.MaxValue - 1, byte.MaxValue)] - [InlineData(byte.MaxValue, byte.MaxValue, 0)] - [InlineData(byte.MaxValue, byte.MaxValue, 1)] - [Theory] - public void When_a_byte_value_is_close_to_expected_value_it_should_succeed(byte actual, byte nearbyValue, byte delta) - { - // Act - Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); - - // Assert - act.Should().NotThrow(); - } - - [InlineData(0, 1, 0)] - [InlineData(1, 0, 0)] - [InlineData(byte.MinValue, byte.MaxValue, 1)] - [InlineData(byte.MaxValue, byte.MinValue, 1)] - [Theory] - public void When_a_byte_value_is_not_close_to_expected_value_it_should_fail(byte actual, byte nearbyValue, byte delta) - { - // Act - Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_a_byte_value_is_not_close_to_expected_value_it_should_fail_with_a_descriptive_message() - { - // Arrange - byte actual = 1; - byte nearbyValue = 4; - byte delta = 2; - - // Act - Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); - - // Assert - act.Should().Throw() - .WithMessage("*be within*2*from*4*but found*1*"); - } - - [Fact] - public void When_a_byte_value_is_returned_from_BeCloseTo_it_should_chain() - { - // Arrange - byte actual = byte.MaxValue; - - // Act - Action act = () => actual.Should().BeCloseTo(actual, 0) - .And.Be(actual); - - // Assert - act.Should().NotThrow(); - } - - [InlineData(0, 0, 0)] - [InlineData(0, 0, 1)] - [InlineData(0, 1, 1)] - [InlineData(1, 0, 1)] - [InlineData(1, ushort.MaxValue, ushort.MaxValue)] - [InlineData(ushort.MinValue, ushort.MinValue + 1, ushort.MaxValue)] - [InlineData(ushort.MinValue + 1, 0, ushort.MaxValue)] - [InlineData(ushort.MinValue + 1, ushort.MinValue, 1)] - [InlineData(ushort.MinValue + 1, ushort.MinValue, ushort.MaxValue)] - [InlineData(ushort.MaxValue - 1, ushort.MaxValue, 1)] - [InlineData(ushort.MaxValue - 1, ushort.MaxValue, ushort.MaxValue)] - [InlineData(ushort.MaxValue, 0, ushort.MaxValue)] - [InlineData(ushort.MaxValue, 1, ushort.MaxValue)] - [InlineData(ushort.MaxValue, ushort.MaxValue - 1, 1)] - [InlineData(ushort.MaxValue, ushort.MaxValue - 1, ushort.MaxValue)] - [InlineData(ushort.MaxValue, ushort.MaxValue, 0)] - [InlineData(ushort.MaxValue, ushort.MaxValue, 1)] - [Theory] - public void When_an_ushort_value_is_close_to_expected_value_it_should_succeed(ushort actual, ushort nearbyValue, - ushort delta) - { - // Act - Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); - - // Assert - act.Should().NotThrow(); - } - - [InlineData(0, 1, 0)] - [InlineData(1, 0, 0)] - [InlineData(ushort.MinValue, ushort.MaxValue, 1)] - [InlineData(ushort.MaxValue, ushort.MinValue, 1)] - [Theory] - public void When_an_ushort_value_is_not_close_to_expected_value_it_should_fail(ushort actual, ushort nearbyValue, - ushort delta) - { - // Act - Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_an_ushort_value_is_not_close_to_expected_value_it_should_fail_with_a_descriptive_message() - { - // Arrange - ushort actual = 1; - ushort nearbyValue = 4; - ushort delta = 2; - - // Act - Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); - - // Assert - act.Should().Throw() - .WithMessage("*be within*2*from*4*but found*1*"); - } - - [Fact] - public void When_an_ushort_value_is_returned_from_BeCloseTo_it_should_chain() - { - // Arrange - ushort actual = ushort.MaxValue; - - // Act - Action act = () => actual.Should().BeCloseTo(actual, 0) - .And.Be(actual); - - // Assert - act.Should().NotThrow(); - } - - [InlineData(0, 0, 0)] - [InlineData(0, 0, 1)] - [InlineData(0, 1, 1)] - [InlineData(1, 0, 1)] - [InlineData(1, uint.MaxValue, uint.MaxValue)] - [InlineData(uint.MinValue, uint.MinValue + 1, uint.MaxValue)] - [InlineData(uint.MinValue + 1, 0, uint.MaxValue)] - [InlineData(uint.MinValue + 1, uint.MinValue, 1)] - [InlineData(uint.MinValue + 1, uint.MinValue, uint.MaxValue)] - [InlineData(uint.MaxValue - 1, uint.MaxValue, 1)] - [InlineData(uint.MaxValue - 1, uint.MaxValue, uint.MaxValue)] - [InlineData(uint.MaxValue, 0, uint.MaxValue)] - [InlineData(uint.MaxValue, 1, uint.MaxValue)] - [InlineData(uint.MaxValue, uint.MaxValue - 1, 1)] - [InlineData(uint.MaxValue, uint.MaxValue - 1, uint.MaxValue)] - [InlineData(uint.MaxValue, uint.MaxValue, 0)] - [InlineData(uint.MaxValue, uint.MaxValue, 1)] - [Theory] - public void When_an_uint_value_is_close_to_expected_value_it_should_succeed(uint actual, uint nearbyValue, uint delta) - { - // Act - Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); - - // Assert - act.Should().NotThrow(); - } - - [InlineData(0, 1, 0)] - [InlineData(1, 0, 0)] - [InlineData(uint.MinValue, uint.MaxValue, 1)] - [InlineData(uint.MaxValue, uint.MinValue, 1)] - [Theory] - public void When_an_uint_value_is_not_close_to_expected_value_it_should_fail(uint actual, uint nearbyValue, - uint delta) - { - // Act - Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_an_uint_value_is_not_close_to_expected_value_it_should_fail_with_a_descriptive_message() - { - // Arrange - uint actual = 1; - uint nearbyValue = 4; - uint delta = 2; - - // Act - Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); - - // Assert - act.Should().Throw() - .WithMessage("*be within*2*from*4*but found*1*"); - } - - [Fact] - public void When_an_uint_value_is_returned_from_BeCloseTo_it_should_chain() - { - // Arrange - uint actual = uint.MaxValue; - - // Act - Action act = () => actual.Should().BeCloseTo(actual, 0) - .And.Be(actual); - - // Assert - act.Should().NotThrow(); - } - - [InlineData(0, 0, 0)] - [InlineData(0, 0, 1)] - [InlineData(0, 1, 1)] - [InlineData(1, 0, 1)] - [InlineData(1, ulong.MaxValue, ulong.MaxValue)] - [InlineData(ulong.MinValue, ulong.MinValue + 1, ulong.MaxValue)] - [InlineData(ulong.MinValue + 1, 0, ulong.MaxValue)] - [InlineData(ulong.MinValue + 1, ulong.MinValue, 1)] - [InlineData(ulong.MinValue + 1, ulong.MinValue, ulong.MaxValue)] - [InlineData(ulong.MaxValue - 1, ulong.MaxValue, 1)] - [InlineData(ulong.MaxValue - 1, ulong.MaxValue, ulong.MaxValue)] - [InlineData(ulong.MaxValue, 0, ulong.MaxValue)] - [InlineData(ulong.MaxValue, 1, ulong.MaxValue)] - [InlineData(ulong.MaxValue, ulong.MaxValue - 1, 1)] - [InlineData(ulong.MaxValue, ulong.MaxValue - 1, ulong.MaxValue)] - [InlineData(ulong.MaxValue, ulong.MaxValue, 0)] - [InlineData(ulong.MaxValue, ulong.MaxValue, 1)] - [Theory] - public void When_an_ulong_value_is_close_to_expected_value_it_should_succeed(ulong actual, ulong nearbyValue, - ulong delta) - { - // Act - Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); - - // Assert - act.Should().NotThrow(); - } - - [InlineData(0, 1, 0)] - [InlineData(1, 0, 0)] - [InlineData(ulong.MinValue, ulong.MaxValue, 1)] - [InlineData(ulong.MaxValue, ulong.MinValue, 1)] - [Theory] - public void When_an_ulong_value_is_not_close_to_expected_value_it_should_fail(ulong actual, ulong nearbyValue, - ulong delta) - { - // Act - Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_an_ulong_value_is_not_close_to_expected_value_it_should_fail_with_a_descriptive_message() - { - // Arrange - ulong actual = 1; - ulong nearbyValue = 4; - ulong delta = 2; - - // Act - Action act = () => actual.Should().BeCloseTo(nearbyValue, delta); - - // Assert - act.Should().Throw() - .WithMessage("*be within*2*from*4*but found*1*"); - } - - [Fact] - public void When_an_ulong_value_is_returned_from_BeCloseTo_it_should_chain() - { - // Arrange - ulong actual = ulong.MaxValue; - - // Act - Action act = () => actual.Should().BeCloseTo(actual, 0) - .And.Be(actual); - - // Assert - act.Should().NotThrow(); - } - } - - public class NotBeCloseTo - { - [InlineData(sbyte.MinValue, sbyte.MaxValue, 1)] - [InlineData(sbyte.MinValue, 0, sbyte.MaxValue)] - [InlineData(sbyte.MinValue, 1, sbyte.MaxValue)] - [InlineData(-1, 0, 0)] - [InlineData(-1, 1, 1)] - [InlineData(-1, sbyte.MaxValue, sbyte.MaxValue)] - [InlineData(0, sbyte.MinValue, sbyte.MaxValue)] - [InlineData(0, -1, 0)] - [InlineData(0, 1, 0)] - [InlineData(1, -1, 1)] - [InlineData(1, 0, 0)] - [InlineData(1, sbyte.MinValue, sbyte.MaxValue)] - [InlineData(sbyte.MaxValue, sbyte.MinValue, 1)] - [InlineData(sbyte.MaxValue, -1, sbyte.MaxValue)] - [Theory] - public void When_a_sbyte_value_is_not_close_to_expected_value_it_should_succeed(sbyte actual, sbyte distantValue, - byte delta) - { - // Act - Action act = () => actual.Should().NotBeCloseTo(distantValue, delta); - - // Assert - act.Should().NotThrow(); - } - - [InlineData(sbyte.MinValue, sbyte.MinValue, 0)] - [InlineData(sbyte.MinValue, sbyte.MinValue, 1)] - [InlineData(sbyte.MinValue, sbyte.MinValue, sbyte.MaxValue)] - [InlineData(sbyte.MinValue, sbyte.MinValue + 1, 1)] - [InlineData(sbyte.MinValue, sbyte.MinValue + 1, sbyte.MaxValue)] - [InlineData(sbyte.MinValue, -1, sbyte.MaxValue)] - [InlineData(sbyte.MinValue + 1, sbyte.MinValue, 1)] - [InlineData(sbyte.MinValue + 1, sbyte.MinValue, sbyte.MaxValue)] - [InlineData(sbyte.MinValue + 1, 0, sbyte.MaxValue)] - [InlineData(-1, sbyte.MinValue, sbyte.MaxValue)] - [InlineData(-1, 0, 1)] - [InlineData(-1, 0, sbyte.MaxValue)] - [InlineData(0, 0, 0)] - [InlineData(0, 0, 1)] - [InlineData(0, -1, 1)] - [InlineData(0, -1, sbyte.MaxValue)] - [InlineData(0, 1, 1)] - [InlineData(0, 1, sbyte.MaxValue)] - [InlineData(0, sbyte.MaxValue, sbyte.MaxValue)] - [InlineData(0, sbyte.MinValue + 1, sbyte.MaxValue)] - [InlineData(1, 0, 1)] - [InlineData(1, 0, sbyte.MaxValue)] - [InlineData(1, sbyte.MaxValue, sbyte.MaxValue)] - [InlineData(sbyte.MaxValue - 1, sbyte.MaxValue, 1)] - [InlineData(sbyte.MaxValue - 1, sbyte.MaxValue, sbyte.MaxValue)] - [InlineData(sbyte.MaxValue, 0, sbyte.MaxValue)] - [InlineData(sbyte.MaxValue, 1, sbyte.MaxValue)] - [InlineData(sbyte.MaxValue, sbyte.MaxValue, 0)] - [InlineData(sbyte.MaxValue, sbyte.MaxValue, 1)] - [InlineData(sbyte.MaxValue, sbyte.MaxValue, sbyte.MaxValue)] - [InlineData(sbyte.MaxValue, sbyte.MaxValue - 1, 1)] - [InlineData(sbyte.MaxValue, sbyte.MaxValue - 1, sbyte.MaxValue)] - [Theory] - public void When_a_sbyte_value_is_close_to_expected_value_it_should_fail(sbyte actual, sbyte distantValue, byte delta) - { - // Act - Action act = () => actual.Should().NotBeCloseTo(distantValue, delta); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_a_sbyte_value_is_close_to_expected_value_it_should_fail_with_a_descriptive_message() - { - // Arrange - sbyte actual = 1; - sbyte nearbyValue = 3; - byte delta = 2; - - // Act - Action act = () => actual.Should().NotBeCloseTo(nearbyValue, delta); - - // Assert - act.Should().Throw() - .WithMessage("*be within*2*from*3*but found*1*"); - } - - [Fact] - public void When_a_sbyte_value_is_returned_from_NotBeCloseTo_it_should_chain() - { - // Arrange - sbyte actual = sbyte.MaxValue; - - // Act - Action act = () => actual.Should().NotBeCloseTo(0, 0) - .And.Be(actual); - - // Assert - act.Should().NotThrow(); - } - - [InlineData(short.MinValue, short.MaxValue, 1)] - [InlineData(short.MinValue, 0, short.MaxValue)] - [InlineData(short.MinValue, 1, short.MaxValue)] - [InlineData(-1, 0, 0)] - [InlineData(-1, 1, 1)] - [InlineData(-1, short.MaxValue, short.MaxValue)] - [InlineData(0, short.MinValue, short.MaxValue)] - [InlineData(0, -1, 0)] - [InlineData(0, 1, 0)] - [InlineData(1, -1, 1)] - [InlineData(1, 0, 0)] - [InlineData(1, short.MinValue, short.MaxValue)] - [InlineData(short.MaxValue, short.MinValue, 1)] - [InlineData(short.MaxValue, -1, short.MaxValue)] - [Theory] - public void When_a_short_value_is_not_close_to_expected_value_it_should_succeed(short actual, short distantValue, - ushort delta) - { - // Act - Action act = () => actual.Should().NotBeCloseTo(distantValue, delta); - - // Assert - act.Should().NotThrow(); - } - - [InlineData(short.MinValue, short.MinValue, 0)] - [InlineData(short.MinValue, short.MinValue, 1)] - [InlineData(short.MinValue, short.MinValue, short.MaxValue)] - [InlineData(short.MinValue, short.MinValue + 1, 1)] - [InlineData(short.MinValue, short.MinValue + 1, short.MaxValue)] - [InlineData(short.MinValue, -1, short.MaxValue)] - [InlineData(short.MinValue + 1, short.MinValue, 1)] - [InlineData(short.MinValue + 1, short.MinValue, short.MaxValue)] - [InlineData(short.MinValue + 1, 0, short.MaxValue)] - [InlineData(-1, short.MinValue, short.MaxValue)] - [InlineData(-1, 0, 1)] - [InlineData(-1, 0, short.MaxValue)] - [InlineData(0, 0, 0)] - [InlineData(0, 0, 1)] - [InlineData(0, -1, 1)] - [InlineData(0, -1, short.MaxValue)] - [InlineData(0, 1, 1)] - [InlineData(0, 1, short.MaxValue)] - [InlineData(0, short.MaxValue, short.MaxValue)] - [InlineData(0, short.MinValue + 1, short.MaxValue)] - [InlineData(1, 0, 1)] - [InlineData(1, 0, short.MaxValue)] - [InlineData(1, short.MaxValue, short.MaxValue)] - [InlineData(short.MaxValue - 1, short.MaxValue, 1)] - [InlineData(short.MaxValue - 1, short.MaxValue, short.MaxValue)] - [InlineData(short.MaxValue, 0, short.MaxValue)] - [InlineData(short.MaxValue, 1, short.MaxValue)] - [InlineData(short.MaxValue, short.MaxValue, 0)] - [InlineData(short.MaxValue, short.MaxValue, 1)] - [InlineData(short.MaxValue, short.MaxValue, short.MaxValue)] - [InlineData(short.MaxValue, short.MaxValue - 1, 1)] - [InlineData(short.MaxValue, short.MaxValue - 1, short.MaxValue)] - [Theory] - public void When_a_short_value_is_close_to_expected_value_it_should_fail(short actual, short distantValue, - ushort delta) - { - // Act - Action act = () => actual.Should().NotBeCloseTo(distantValue, delta); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_a_short_value_is_close_to_expected_value_it_should_fail_with_a_descriptive_message() - { - // Arrange - short actual = 1; - short nearbyValue = 3; - ushort delta = 2; - - // Act - Action act = () => actual.Should().NotBeCloseTo(nearbyValue, delta); - - // Assert - act.Should().Throw() - .WithMessage("*be within*2*from*3*but found*1*"); - } - - [Fact] - public void When_a_short_value_is_returned_from_NotBeCloseTo_it_should_chain() - { - // Arrange - short actual = short.MaxValue; - - // Act - Action act = () => actual.Should().NotBeCloseTo(0, 0) - .And.Be(actual); - - // Assert - act.Should().NotThrow(); - } - - [InlineData(int.MinValue, int.MaxValue, 1)] - [InlineData(int.MinValue, 0, int.MaxValue)] - [InlineData(int.MinValue, 1, int.MaxValue)] - [InlineData(-1, 0, 0)] - [InlineData(-1, 1, 1)] - [InlineData(-1, int.MaxValue, int.MaxValue)] - [InlineData(0, int.MinValue, int.MaxValue)] - [InlineData(0, -1, 0)] - [InlineData(0, 1, 0)] - [InlineData(1, -1, 1)] - [InlineData(1, 0, 0)] - [InlineData(1, int.MinValue, int.MaxValue)] - [InlineData(int.MaxValue, int.MinValue, 1)] - [InlineData(int.MaxValue, -1, int.MaxValue)] - [Theory] - public void When_an_int_value_is_not_close_to_expected_value_it_should_succeed(int actual, int distantValue, - uint delta) - { - // Act - Action act = () => actual.Should().NotBeCloseTo(distantValue, delta); - - // Assert - act.Should().NotThrow(); - } - - [InlineData(int.MinValue, int.MinValue, 0)] - [InlineData(int.MinValue, int.MinValue, 1)] - [InlineData(int.MinValue, int.MinValue, int.MaxValue)] - [InlineData(int.MinValue, int.MinValue + 1, 1)] - [InlineData(int.MinValue, int.MinValue + 1, int.MaxValue)] - [InlineData(int.MinValue, -1, int.MaxValue)] - [InlineData(int.MinValue + 1, int.MinValue, 1)] - [InlineData(int.MinValue + 1, int.MinValue, int.MaxValue)] - [InlineData(int.MinValue + 1, 0, int.MaxValue)] - [InlineData(-1, int.MinValue, int.MaxValue)] - [InlineData(-1, 0, 1)] - [InlineData(-1, 0, int.MaxValue)] - [InlineData(0, 0, 0)] - [InlineData(0, 0, 1)] - [InlineData(0, -1, 1)] - [InlineData(0, -1, int.MaxValue)] - [InlineData(0, 1, 1)] - [InlineData(0, 1, int.MaxValue)] - [InlineData(0, int.MaxValue, int.MaxValue)] - [InlineData(0, int.MinValue + 1, int.MaxValue)] - [InlineData(1, 0, 1)] - [InlineData(1, 0, int.MaxValue)] - [InlineData(1, int.MaxValue, int.MaxValue)] - [InlineData(int.MaxValue - 1, int.MaxValue, 1)] - [InlineData(int.MaxValue - 1, int.MaxValue, int.MaxValue)] - [InlineData(int.MaxValue, 0, int.MaxValue)] - [InlineData(int.MaxValue, 1, int.MaxValue)] - [InlineData(int.MaxValue, int.MaxValue, 0)] - [InlineData(int.MaxValue, int.MaxValue, 1)] - [InlineData(int.MaxValue, int.MaxValue, int.MaxValue)] - [InlineData(int.MaxValue, int.MaxValue - 1, 1)] - [InlineData(int.MaxValue, int.MaxValue - 1, int.MaxValue)] - [Theory] - public void When_an_int_value_is_close_to_expected_value_it_should_fail(int actual, int distantValue, uint delta) - { - // Act - Action act = () => actual.Should().NotBeCloseTo(distantValue, delta); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_an_int_value_is_close_to_expected_value_it_should_fail_with_a_descriptive_message() - { - // Arrange - int actual = 1; - int nearbyValue = 3; - uint delta = 2; - - // Act - Action act = () => actual.Should().NotBeCloseTo(nearbyValue, delta); - - // Assert - act.Should().Throw() - .WithMessage("*be within*2*from*3*but found*1*"); - } - - [Fact] - public void When_an_int_value_is_returned_from_NotBeCloseTo_it_should_chain() - { - // Arrange - int actual = int.MaxValue; - - // Act - Action act = () => actual.Should().NotBeCloseTo(0, 0) - .And.Be(actual); - - // Assert - act.Should().NotThrow(); - } - - [InlineData(long.MinValue, long.MaxValue, 1)] - [InlineData(long.MinValue, 0, long.MaxValue)] - [InlineData(long.MinValue, 1, long.MaxValue)] - [InlineData(long.MinValue + 1, 0, (ulong.MaxValue / 2) - 1)] - [InlineData(long.MinValue, long.MaxValue, (ulong.MaxValue / 2) - 1)] - [InlineData(long.MinValue, long.MaxValue, ulong.MaxValue / 2)] - [InlineData(-1, 0, 0)] - [InlineData(-1, 1, 1)] - [InlineData(-1, long.MaxValue, long.MaxValue)] - [InlineData(-1, long.MinValue, (ulong.MaxValue / 2) - 1)] - [InlineData(0, long.MinValue, long.MaxValue)] - [InlineData(0, long.MinValue + 1, (ulong.MaxValue / 2) - 1)] - [InlineData(0, long.MaxValue, (ulong.MaxValue / 2) - 1)] - [InlineData(0, -1, 0)] - [InlineData(0, 1, 0)] - [InlineData(1, -1, 1)] - [InlineData(1, 0, 0)] - [InlineData(1, long.MinValue, long.MaxValue)] - [InlineData(long.MaxValue, long.MinValue, 1)] - [InlineData(long.MaxValue, -1, long.MaxValue)] - [InlineData(long.MaxValue, 0, (ulong.MaxValue / 2) - 1)] - [Theory] - public void When_a_long_value_is_not_close_to_expected_value_it_should_succeed(long actual, long distantValue, - ulong delta) - { - // Act - Action act = () => actual.Should().NotBeCloseTo(distantValue, delta); - - // Assert - act.Should().NotThrow(); - } - - [InlineData(long.MinValue, long.MinValue, 0)] - [InlineData(long.MinValue, long.MinValue, 1)] - [InlineData(long.MinValue, long.MinValue, (ulong.MaxValue / 2) - 1)] - [InlineData(long.MinValue, long.MinValue, ulong.MaxValue / 2)] - [InlineData(long.MinValue, long.MinValue, (ulong.MaxValue / 2) + 1)] - [InlineData(long.MinValue, long.MinValue, ulong.MaxValue)] - [InlineData(long.MinValue, long.MinValue + 1, 1)] - [InlineData(long.MinValue, long.MinValue + 1, (ulong.MaxValue / 2) - 1)] - [InlineData(long.MinValue, long.MinValue + 1, ulong.MaxValue / 2)] - [InlineData(long.MinValue, long.MinValue + 1, (ulong.MaxValue / 2) + 1)] - [InlineData(long.MinValue, long.MinValue + 1, ulong.MaxValue)] - [InlineData(long.MinValue, -1, long.MaxValue)] - [InlineData(long.MinValue + 1, long.MinValue, 1)] - [InlineData(long.MinValue + 1, long.MinValue, (ulong.MaxValue / 2) - 1)] - [InlineData(long.MinValue + 1, long.MinValue, ulong.MaxValue / 2)] - [InlineData(long.MinValue + 1, long.MinValue, (ulong.MaxValue / 2) + 1)] - [InlineData(long.MinValue + 1, long.MinValue, ulong.MaxValue)] - [InlineData(long.MinValue + 1, 0, ulong.MaxValue / 2)] - [InlineData(long.MinValue + 1, 0, (ulong.MaxValue / 2) + 1)] - [InlineData(long.MinValue + 1, 0, ulong.MaxValue)] - [InlineData(long.MinValue, long.MaxValue, ulong.MaxValue)] - [InlineData(-1, long.MinValue, ulong.MaxValue / 2)] - [InlineData(-1, long.MinValue, (ulong.MaxValue / 2) + 1)] - [InlineData(-1, long.MinValue, ulong.MaxValue)] - [InlineData(-1, 0, 1)] - [InlineData(-1, 0, (ulong.MaxValue / 2) - 1)] - [InlineData(-1, 0, ulong.MaxValue / 2)] - [InlineData(-1, 0, (ulong.MaxValue / 2) + 1)] - [InlineData(-1, 0, ulong.MaxValue)] - [InlineData(0, 0, 0)] - [InlineData(0, 0, 1)] - [InlineData(0, -1, 1)] - [InlineData(0, -1, (ulong.MaxValue / 2) - 1)] - [InlineData(0, -1, ulong.MaxValue / 2)] - [InlineData(0, -1, (ulong.MaxValue / 2) + 1)] - [InlineData(0, -1, ulong.MaxValue)] - [InlineData(0, 1, 1)] - [InlineData(0, 1, (ulong.MaxValue / 2) - 1)] - [InlineData(0, 1, ulong.MaxValue / 2)] - [InlineData(0, 1, (ulong.MaxValue / 2) + 1)] - [InlineData(0, 1, ulong.MaxValue)] - [InlineData(0, long.MaxValue, ulong.MaxValue / 2)] - [InlineData(0, long.MaxValue, (ulong.MaxValue / 2) + 1)] - [InlineData(0, long.MaxValue, ulong.MaxValue)] - [InlineData(0, long.MinValue + 1, ulong.MaxValue / 2)] - [InlineData(0, long.MinValue + 1, (ulong.MaxValue / 2) + 1)] - [InlineData(0, long.MinValue + 1, ulong.MaxValue)] - [InlineData(1, 0, 1)] - [InlineData(1, 0, (ulong.MaxValue / 2) - 1)] - [InlineData(1, 0, ulong.MaxValue / 2)] - [InlineData(1, 0, (ulong.MaxValue / 2) + 1)] - [InlineData(1, 0, ulong.MaxValue)] - [InlineData(1, long.MaxValue, (ulong.MaxValue / 2) - 1)] - [InlineData(1, long.MaxValue, ulong.MaxValue / 2)] - [InlineData(1, long.MaxValue, (ulong.MaxValue / 2) + 1)] - [InlineData(1, long.MaxValue, ulong.MaxValue)] - [InlineData(long.MaxValue - 1, long.MaxValue, 1)] - [InlineData(long.MaxValue - 1, long.MaxValue, (ulong.MaxValue / 2) - 1)] - [InlineData(long.MaxValue - 1, long.MaxValue, ulong.MaxValue / 2)] - [InlineData(long.MaxValue - 1, long.MaxValue, (ulong.MaxValue / 2) + 1)] - [InlineData(long.MaxValue - 1, long.MaxValue, ulong.MaxValue)] - [InlineData(long.MaxValue, 0, ulong.MaxValue / 2)] - [InlineData(long.MaxValue, 0, (ulong.MaxValue / 2) + 1)] - [InlineData(long.MaxValue, 0, ulong.MaxValue)] - [InlineData(long.MaxValue, 1, (ulong.MaxValue / 2) - 1)] - [InlineData(long.MaxValue, 1, ulong.MaxValue / 2)] - [InlineData(long.MaxValue, 1, (ulong.MaxValue / 2) + 1)] - [InlineData(long.MaxValue, 1, ulong.MaxValue)] - [InlineData(long.MaxValue, long.MaxValue, 0)] - [InlineData(long.MaxValue, long.MaxValue, 1)] - [InlineData(long.MaxValue, long.MaxValue, (ulong.MaxValue / 2) - 1)] - [InlineData(long.MaxValue, long.MaxValue, ulong.MaxValue / 2)] - [InlineData(long.MaxValue, long.MaxValue, (ulong.MaxValue / 2) + 1)] - [InlineData(long.MaxValue, long.MaxValue, ulong.MaxValue)] - [InlineData(long.MaxValue, long.MaxValue - 1, 1)] - [InlineData(long.MaxValue, long.MaxValue - 1, (ulong.MaxValue / 2) - 1)] - [InlineData(long.MaxValue, long.MaxValue - 1, ulong.MaxValue / 2)] - [InlineData(long.MaxValue, long.MaxValue - 1, (ulong.MaxValue / 2) + 1)] - [InlineData(long.MaxValue, long.MaxValue - 1, ulong.MaxValue)] - [Theory] - public void When_a_long_value_is_close_to_expected_value_it_should_fail(long actual, long distantValue, ulong delta) - { - // Act - Action act = () => actual.Should().NotBeCloseTo(distantValue, delta); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_a_long_value_is_close_to_expected_value_it_should_fail_with_a_descriptive_message() - { - // Arrange - long actual = 1; - long nearbyValue = 3; - ulong delta = 2; - - // Act - Action act = () => actual.Should().NotBeCloseTo(nearbyValue, delta); - - // Assert - act.Should().Throw() - .WithMessage("*be within*2*from*3*but found*1*"); - } - - [Fact] - public void When_a_long_value_is_returned_from_NotBeCloseTo_it_should_chain() - { - // Arrange - long actual = long.MaxValue; - - // Act - Action act = () => actual.Should().NotBeCloseTo(0, 0) - .And.Be(actual); - - // Assert - act.Should().NotThrow(); - } - - [InlineData(0, 1, 0)] - [InlineData(1, 0, 0)] - [InlineData(byte.MinValue, byte.MaxValue, 1)] - [InlineData(byte.MaxValue, byte.MinValue, 1)] - [Theory] - public void When_a_byte_value_is_not_close_to_expected_value_it_should_succeed(byte actual, byte distantValue, - byte delta) - { - // Act - Action act = () => actual.Should().NotBeCloseTo(distantValue, delta); - - // Assert - act.Should().NotThrow(); - } - - [InlineData(0, 0, 0)] - [InlineData(0, 0, 1)] - [InlineData(0, 1, 1)] - [InlineData(1, 0, 1)] - [InlineData(1, byte.MaxValue, byte.MaxValue)] - [InlineData(byte.MinValue, byte.MinValue + 1, byte.MaxValue)] - [InlineData(byte.MinValue + 1, 0, byte.MaxValue)] - [InlineData(byte.MinValue + 1, byte.MinValue, 1)] - [InlineData(byte.MinValue + 1, byte.MinValue, byte.MaxValue)] - [InlineData(byte.MaxValue - 1, byte.MaxValue, 1)] - [InlineData(byte.MaxValue - 1, byte.MaxValue, byte.MaxValue)] - [InlineData(byte.MaxValue, 0, byte.MaxValue)] - [InlineData(byte.MaxValue, 1, byte.MaxValue)] - [InlineData(byte.MaxValue, byte.MaxValue - 1, 1)] - [InlineData(byte.MaxValue, byte.MaxValue - 1, byte.MaxValue)] - [InlineData(byte.MaxValue, byte.MaxValue, 0)] - [InlineData(byte.MaxValue, byte.MaxValue, 1)] - [Theory] - public void When_a_byte_value_is_close_to_expected_value_it_should_fail(byte actual, byte distantValue, byte delta) - { - // Act - Action act = () => actual.Should().NotBeCloseTo(distantValue, delta); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_a_byte_value_is_close_to_expected_value_it_should_fail_with_a_descriptive_message() - { - // Arrange - byte actual = 1; - byte nearbyValue = 3; - byte delta = 2; - - // Act - Action act = () => actual.Should().NotBeCloseTo(nearbyValue, delta); - - // Assert - act.Should().Throw() - .WithMessage("*be within*2*from*3*but found*1*"); - } - - [Fact] - public void When_a_byte_value_is_returned_from_NotBeCloseTo_it_should_chain() - { - // Arrange - byte actual = byte.MaxValue; - - // Act - Action act = () => actual.Should().NotBeCloseTo(0, 0) - .And.Be(actual); - - // Assert - act.Should().NotThrow(); - } - - [InlineData(0, 1, 0)] - [InlineData(1, 0, 0)] - [InlineData(ushort.MinValue, ushort.MaxValue, 1)] - [InlineData(ushort.MaxValue, ushort.MinValue, 1)] - [Theory] - public void When_an_ushort_value_is_not_close_to_expected_value_it_should_succeed(ushort actual, ushort distantValue, - ushort delta) - { - // Act - Action act = () => actual.Should().NotBeCloseTo(distantValue, delta); - - // Assert - act.Should().NotThrow(); - } - - [InlineData(0, 0, 0)] - [InlineData(0, 0, 1)] - [InlineData(0, 1, 1)] - [InlineData(1, 0, 1)] - [InlineData(1, ushort.MaxValue, ushort.MaxValue)] - [InlineData(ushort.MinValue, ushort.MinValue + 1, ushort.MaxValue)] - [InlineData(ushort.MinValue + 1, 0, ushort.MaxValue)] - [InlineData(ushort.MinValue + 1, ushort.MinValue, 1)] - [InlineData(ushort.MinValue + 1, ushort.MinValue, ushort.MaxValue)] - [InlineData(ushort.MaxValue - 1, ushort.MaxValue, 1)] - [InlineData(ushort.MaxValue - 1, ushort.MaxValue, ushort.MaxValue)] - [InlineData(ushort.MaxValue, 0, ushort.MaxValue)] - [InlineData(ushort.MaxValue, 1, ushort.MaxValue)] - [InlineData(ushort.MaxValue, ushort.MaxValue - 1, 1)] - [InlineData(ushort.MaxValue, ushort.MaxValue - 1, ushort.MaxValue)] - [InlineData(ushort.MaxValue, ushort.MaxValue, 0)] - [InlineData(ushort.MaxValue, ushort.MaxValue, 1)] - [Theory] - public void When_an_ushort_value_is_close_to_expected_value_it_should_fail(ushort actual, ushort distantValue, - ushort delta) - { - // Act - Action act = () => actual.Should().NotBeCloseTo(distantValue, delta); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_an_ushort_value_is_close_to_expected_value_it_should_fail_with_a_descriptive_message() - { - // Arrange - ushort actual = 1; - ushort nearbyValue = 3; - ushort delta = 2; - - // Act - Action act = () => actual.Should().NotBeCloseTo(nearbyValue, delta); - - // Assert - act.Should().Throw() - .WithMessage("*be within*2*from*3*but found*1*"); - } - - [Fact] - public void When_an_ushort_value_is_returned_from_NotBeCloseTo_it_should_chain() - { - // Arrange - ushort actual = ushort.MaxValue; - - // Act - Action act = () => actual.Should().NotBeCloseTo(0, 0) - .And.Be(actual); - - // Assert - act.Should().NotThrow(); - } - - [InlineData(0, 1, 0)] - [InlineData(1, 0, 0)] - [InlineData(uint.MinValue, uint.MaxValue, 1)] - [InlineData(uint.MaxValue, uint.MinValue, 1)] - [Theory] - public void When_an_uint_value_is_not_close_to_expected_value_it_should_succeed(uint actual, uint distantValue, - uint delta) - { - // Act - Action act = () => actual.Should().NotBeCloseTo(distantValue, delta); - - // Assert - act.Should().NotThrow(); - } - - [InlineData(0, 0, 0)] - [InlineData(0, 0, 1)] - [InlineData(0, 1, 1)] - [InlineData(1, 0, 1)] - [InlineData(1, uint.MaxValue, uint.MaxValue)] - [InlineData(uint.MinValue, uint.MinValue + 1, uint.MaxValue)] - [InlineData(uint.MinValue + 1, 0, uint.MaxValue)] - [InlineData(uint.MinValue + 1, uint.MinValue, 1)] - [InlineData(uint.MinValue + 1, uint.MinValue, uint.MaxValue)] - [InlineData(uint.MaxValue - 1, uint.MaxValue, 1)] - [InlineData(uint.MaxValue - 1, uint.MaxValue, uint.MaxValue)] - [InlineData(uint.MaxValue, 0, uint.MaxValue)] - [InlineData(uint.MaxValue, 1, uint.MaxValue)] - [InlineData(uint.MaxValue, uint.MaxValue - 1, 1)] - [InlineData(uint.MaxValue, uint.MaxValue - 1, uint.MaxValue)] - [InlineData(uint.MaxValue, uint.MaxValue, 0)] - [InlineData(uint.MaxValue, uint.MaxValue, 1)] - [Theory] - public void When_an_uint_value_is_close_to_expected_value_it_should_fail(uint actual, uint distantValue, uint delta) - { - // Act - Action act = () => actual.Should().NotBeCloseTo(distantValue, delta); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_an_uint_value_is_close_to_expected_value_it_should_fail_with_a_descriptive_message() - { - // Arrange - uint actual = 1; - uint nearbyValue = 3; - uint delta = 2; - - // Act - Action act = () => actual.Should().NotBeCloseTo(nearbyValue, delta); - - // Assert - act.Should().Throw() - .WithMessage("*be within*2*from*3*but found*1*"); - } - - [Fact] - public void When_an_uint_value_is_returned_from_NotBeCloseTo_it_should_chain() - { - // Arrange - uint actual = uint.MaxValue; - - // Act - Action act = () => actual.Should().NotBeCloseTo(0, 0) - .And.Be(actual); - - // Assert - act.Should().NotThrow(); - } - - [InlineData(0, 1, 0)] - [InlineData(1, 0, 0)] - [InlineData(ulong.MinValue, ulong.MaxValue, 1)] - [InlineData(ulong.MaxValue, ulong.MinValue, 1)] - [Theory] - public void When_an_ulong_value_is_not_close_to_expected_value_it_should_succeed(ulong actual, ulong distantValue, - ulong delta) - { - // Act - Action act = () => actual.Should().NotBeCloseTo(distantValue, delta); - - // Assert - act.Should().NotThrow(); - } - - [InlineData(0, 0, 0)] - [InlineData(0, 0, 1)] - [InlineData(0, 1, 1)] - [InlineData(1, 0, 1)] - [InlineData(1, ulong.MaxValue, ulong.MaxValue)] - [InlineData(ulong.MinValue, ulong.MinValue + 1, ulong.MaxValue)] - [InlineData(ulong.MinValue + 1, 0, ulong.MaxValue)] - [InlineData(ulong.MinValue + 1, ulong.MinValue, 1)] - [InlineData(ulong.MinValue + 1, ulong.MinValue, ulong.MaxValue)] - [InlineData(ulong.MaxValue - 1, ulong.MaxValue, 1)] - [InlineData(ulong.MaxValue - 1, ulong.MaxValue, ulong.MaxValue)] - [InlineData(ulong.MaxValue, 0, ulong.MaxValue)] - [InlineData(ulong.MaxValue, 1, ulong.MaxValue)] - [InlineData(ulong.MaxValue, ulong.MaxValue - 1, 1)] - [InlineData(ulong.MaxValue, ulong.MaxValue - 1, ulong.MaxValue)] - [InlineData(ulong.MaxValue, ulong.MaxValue, 0)] - [InlineData(ulong.MaxValue, ulong.MaxValue, 1)] - [Theory] - public void When_an_ulong_value_is_close_to_expected_value_it_should_fail(ulong actual, ulong distantValue, - ulong delta) - { - // Act - Action act = () => actual.Should().NotBeCloseTo(distantValue, delta); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_an_ulong_value_is_close_to_expected_value_it_should_fail_with_a_descriptive_message() - { - // Arrange - ulong actual = 1; - ulong nearbyValue = 3; - ulong delta = 2; - - // Act - Action act = () => actual.Should().NotBeCloseTo(nearbyValue, delta); - - // Assert - act.Should().Throw() - .WithMessage("*be within*2*from*3*but found*1*"); - } - - [Fact] - public void When_an_ulong_value_is_returned_from_NotBeCloseTo_it_should_chain() - { - // Arrange - ulong actual = ulong.MaxValue; - - // Act - Action act = () => actual.Should().NotBeCloseTo(0, 0) - .And.Be(actual); - - // Assert - act.Should().NotThrow(); - } - } - - public class Match - { - [Fact] - public void When_value_satisfies_predicate_it_should_not_throw() - { - // Arrange - int value = 1; - - // Act / Assert - value.Should().Match(o => o > 0); - } - - [Fact] - public void When_value_does_not_match_the_predicate_it_should_throw() - { - // Arrange - int value = 1; - - // Act - Action act = () => value.Should().Match(o => o == 0, "because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw() - .WithMessage("Expected value to match (o == 0) because we want to test the failure message, but found 1."); - } - - [Fact] - public void When_value_is_matched_against_a_null_it_should_throw() - { - // Arrange - int value = 1; - - // Act - Action act = () => value.Should().Match(null); - - // Assert - act.Should().ThrowExactly() - .WithParameterName("predicate"); - } - } - [Fact] public void When_chaining_constraints_with_and_should_not_throw() { 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/BooleanAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Primitives/BooleanAssertionSpecs.cs index 63e8cf8cb5..8288817edd 100644 --- a/Tests/FluentAssertions.Specs/Primitives/BooleanAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Primitives/BooleanAssertionSpecs.cs @@ -4,6 +4,7 @@ namespace FluentAssertions.Specs.Primitives; +// ReSharper disable ConditionIsAlwaysTrueOrFalse public class BooleanAssertionSpecs { public class BeTrue diff --git a/Tests/FluentAssertions.Specs/Primitives/DateOnlyAssertionSpecs.Be.cs b/Tests/FluentAssertions.Specs/Primitives/DateOnlyAssertionSpecs.Be.cs new file mode 100644 index 0000000000..eb3c050c8d --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateOnlyAssertionSpecs.Be.cs @@ -0,0 +1,197 @@ +#if NET6_0_OR_GREATER +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateOnlyAssertionSpecs +{ + public class Be + { + [Fact] + public void Should_succeed_when_asserting_dateonly_value_is_equal_to_the_same_value() + { + // Arrange + DateOnly dateOnly = new(2016, 06, 04); + DateOnly sameDateOnly = new(2016, 06, 04); + + // Act/Assert + dateOnly.Should().Be(sameDateOnly); + } + + [Fact] + public void When_dateonly_value_is_equal_to_the_same_nullable_value_be_should_succeed() + { + // Arrange + DateOnly dateOnly = new(2016, 06, 04); + DateOnly? sameDateOnly = new(2016, 06, 04); + + // Act/Assert + dateOnly.Should().Be(sameDateOnly); + } + + [Fact] + public void When_both_values_are_at_their_minimum_then_it_should_succeed() + { + // Arrange + DateOnly dateOnly = DateOnly.MinValue; + DateOnly sameDateOnly = DateOnly.MinValue; + + // Act/Assert + dateOnly.Should().Be(sameDateOnly); + } + + [Fact] + public void When_both_values_are_at_their_maximum_then_it_should_succeed() + { + // Arrange + DateOnly dateOnly = DateOnly.MaxValue; + DateOnly sameDateOnly = DateOnly.MaxValue; + + // Act/Assert + dateOnly.Should().Be(sameDateOnly); + } + + [Fact] + public void Should_fail_when_asserting_dateonly_value_is_equal_to_the_different_value() + { + // Arrange + var dateOnly = new DateOnly(2012, 03, 10); + var otherDateOnly = new DateOnly(2012, 03, 11); + + // Act + Action act = () => dateOnly.Should().Be(otherDateOnly, "because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw() + .WithMessage("Expected dateOnly to be <2012-03-11>*failure message, but found <2012-03-10>."); + } + + [Fact] + public void Should_succeed_when_asserting_nullable_numeric_value_equals_the_same_value() + { + // Arrange + DateOnly? nullableDateOnlyA = new DateOnly(2016, 06, 04); + DateOnly? nullableDateOnlyB = new DateOnly(2016, 06, 04); + + // Act/Assert + nullableDateOnlyA.Should().Be(nullableDateOnlyB); + } + + [Fact] + public void Should_succeed_when_asserting_nullable_numeric_null_value_equals_null() + { + // Arrange + DateOnly? nullableDateOnlyA = null; + DateOnly? nullableDateOnlyB = null; + + // Act/Assert + nullableDateOnlyA.Should().Be(nullableDateOnlyB); + } + + [Fact] + public void Should_fail_when_asserting_nullable_numeric_value_equals_a_different_value() + { + // Arrange + DateOnly? nullableDateOnlyA = new DateOnly(2016, 06, 04); + DateOnly? nullableDateOnlyB = new DateOnly(2016, 06, 06); + + // Act + Action action = () => + nullableDateOnlyA.Should().Be(nullableDateOnlyB); + + // Assert + action.Should().Throw(); + } + + [Fact] + public void Should_fail_with_descriptive_message_when_asserting_dateonly_null_value_is_equal_to_another_value() + { + // Arrange + DateOnly? nullableDateOnly = null; + + // Act + Action action = () => + nullableDateOnly.Should().Be(new DateOnly(2016, 06, 04), "because we want to test the failure {0}", + "message"); + + // Assert + action.Should().Throw() + .WithMessage( + "Expected nullableDateOnly to be <2016-06-04> because we want to test the failure message, but found ."); + } + + [Fact] + public void Should_succeed_when_asserting_dateonly_value_is_not_equal_to_a_different_value() + { + // Arrange + DateOnly dateOnly = new(2016, 06, 04); + DateOnly otherDateOnly = new(2016, 06, 05); + + // Act/Assert + dateOnly.Should().NotBe(otherDateOnly); + } + } + + public class NotBe + { + [Fact] + public void Different_dateonly_values_are_valid() + { + // Arrange + DateOnly date = new(2020, 06, 04); + DateOnly otherDate = new(2020, 06, 05); + + // Act & Assert + date.Should().NotBe(otherDate); + } + + [Fact] + public void Different_dateonly_values_with_different_nullability_are_valid() + { + // Arrange + DateOnly date = new(2020, 06, 04); + DateOnly? otherDate = new(2020, 07, 05); + + // Act & Assert + date.Should().NotBe(otherDate); + } + + [Fact] + public void Same_dateonly_values_are_invalid() + { + // Arrange + DateOnly date = new(2020, 06, 04); + DateOnly sameDate = new(2020, 06, 04); + + // Act + Action act = + () => date.Should().NotBe(sameDate, "because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw() + .WithMessage( + "Expected date not to be <2020-06-04> because we want to test the failure message, but it is."); + } + + [Fact] + public void Same_dateonly_values_with_different_nullability_are_invalid() + { + // Arrange + DateOnly date = new(2020, 06, 04); + DateOnly? sameDate = new(2020, 06, 04); + + // Act + Action act = + () => date.Should().NotBe(sameDate, "because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw() + .WithMessage( + "Expected date not to be <2020-06-04> because we want to test the failure message, but it is."); + } + } +} + +#endif diff --git a/Tests/FluentAssertions.Specs/Primitives/DateOnlyAssertionSpecs.BeAfter.cs b/Tests/FluentAssertions.Specs/Primitives/DateOnlyAssertionSpecs.BeAfter.cs new file mode 100644 index 0000000000..98032e49ef --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateOnlyAssertionSpecs.BeAfter.cs @@ -0,0 +1,170 @@ +#if NET6_0_OR_GREATER +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateOnlyAssertionSpecs +{ + public class BeAfter + { + [Fact] + public void When_asserting_subject_dateonly_is_after_earlier_expected_dateonly_should_succeed() + { + // Arrange + DateOnly subject = new(2016, 06, 04); + DateOnly expectation = new(2016, 06, 03); + + // Act/Assert + subject.Should().BeAfter(expectation); + } + + [Fact] + public void When_asserting_subject_dateonly_is_not_after_earlier_expected_dateonly_should_throw() + { + // Arrange + DateOnly subject = new(2016, 06, 04); + DateOnly expectation = new(2016, 06, 03); + + // Act + Action act = () => subject.Should().NotBeAfter(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be on or before <2016-06-03>, but found <2016-06-04>."); + } + + [Fact] + public void When_asserting_subject_dateonly_is_after_later_expected_dateonly_should_throw() + { + // Arrange + DateOnly subject = new(2016, 06, 04); + DateOnly expectation = new(2016, 06, 05); + + // Act + Action act = () => subject.Should().BeAfter(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be after <2016-06-05>, but found <2016-06-04>."); + } + + [Fact] + public void When_asserting_subject_dateonly_is_not_after_later_expected_dateonly_should_succeed() + { + // Arrange + DateOnly subject = new(2016, 06, 04); + DateOnly expectation = new(2016, 06, 05); + + // Act/Assert + subject.Should().NotBeAfter(expectation); + } + + [Fact] + public void When_asserting_subject_dateonly_is_after_the_same_expected_dateonly_should_throw() + { + // Arrange + DateOnly subject = new(2016, 06, 04); + DateOnly expectation = new(2016, 06, 04); + + // Act + Action act = () => subject.Should().BeAfter(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be after <2016-06-04>, but found <2016-06-04>."); + } + + [Fact] + public void When_asserting_subject_dateonly_is_not_after_the_same_expected_dateonly_should_succeed() + { + // Arrange + DateOnly subject = new(2016, 06, 04); + DateOnly expectation = new(2016, 06, 04); + + // Act/Assert + subject.Should().NotBeAfter(expectation); + } + + [Fact] + public void When_asserting_subject_dateonly_is_on_or_after_earlier_expected_dateonly_should_succeed() + { + // Arrange + DateOnly subject = new(2016, 06, 04); + DateOnly expectation = new(2016, 06, 03); + + // Act/Assert + subject.Should().BeOnOrAfter(expectation); + } + + [Fact] + public void When_asserting_subject_dateonly_is_not_on_or_after_earlier_expected_dateonly_should_throw() + { + // Arrange + DateOnly subject = new(2016, 06, 04); + DateOnly expectation = new(2016, 06, 03); + + // Act + Action act = () => subject.Should().NotBeOnOrAfter(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be before <2016-06-03>, but found <2016-06-04>."); + } + + [Fact] + public void When_asserting_subject_dateonly_is_on_or_after_the_same_expected_dateonly_should_succeed() + { + // Arrange + DateOnly subject = new(2016, 06, 04); + DateOnly expectation = new(2016, 06, 04); + + // Act/Assert + subject.Should().BeOnOrAfter(expectation); + } + + [Fact] + public void When_asserting_subject_dateonly_is_not_on_or_after_the_same_expected_dateonly_should_throw() + { + // Arrange + DateOnly subject = new(2016, 06, 04); + DateOnly expectation = new(2016, 06, 04); + + // Act + Action act = () => subject.Should().NotBeOnOrAfter(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be before <2016-06-04>, but found <2016-06-04>."); + } + + [Fact] + public void When_asserting_subject_dateonly_is_on_or_after_later_expected_dateonly_should_throw() + { + // Arrange + DateOnly subject = new(2016, 06, 04); + DateOnly expectation = new(2016, 06, 05); + + // Act + Action act = () => subject.Should().BeOnOrAfter(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be on or after <2016-06-05>, but found <2016-06-04>."); + } + + [Fact] + public void When_asserting_subject_dateonly_is_not_on_or_after_later_expected_dateonly_should_succeed() + { + // Arrange + DateOnly subject = new(2016, 06, 04); + DateOnly expectation = new(2016, 06, 05); + + // Act/Assert + subject.Should().NotBeOnOrAfter(expectation); + } + } +} + +#endif diff --git a/Tests/FluentAssertions.Specs/Primitives/DateOnlyAssertionSpecs.BeBefore.cs b/Tests/FluentAssertions.Specs/Primitives/DateOnlyAssertionSpecs.BeBefore.cs new file mode 100644 index 0000000000..7f33410013 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateOnlyAssertionSpecs.BeBefore.cs @@ -0,0 +1,130 @@ +#if NET6_0_OR_GREATER +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateOnlyAssertionSpecs +{ + public class BeBefore + { + [Fact] + public void When_asserting_subject_is_not_before_earlier_expected_dateonly_it_should_succeed() + { + // Arrange + DateOnly expected = new(2016, 06, 03); + DateOnly subject = new(2016, 06, 04); + + // Act/Assert + subject.Should().NotBeBefore(expected); + } + + [Fact] + public void When_asserting_subject_dateonly_is_before_the_same_dateonly_it_should_throw() + { + // Arrange + DateOnly expected = new(2016, 06, 04); + DateOnly subject = new(2016, 06, 04); + + // Act + Action act = () => subject.Should().BeBefore(expected); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be before <2016-06-04>, but found <2016-06-04>."); + } + + [Fact] + public void When_asserting_subject_dateonly_is_not_before_the_same_dateonly_it_should_succeed() + { + // Arrange + DateOnly expected = new(2016, 06, 04); + DateOnly subject = new(2016, 06, 04); + + // Act/Assert + subject.Should().NotBeBefore(expected); + } + + [Fact] + public void When_asserting_subject_dateonly_is_on_or_before_expected_dateonly_should_succeed() + { + // Arrange + DateOnly subject = new(2016, 06, 04); + DateOnly expectation = new(2016, 06, 05); + + // Act/Assert + subject.Should().BeOnOrBefore(expectation); + } + + [Fact] + public void When_asserting_subject_dateonly_is_on_or_before_expected_dateonly_should_throw() + { + // Arrange + DateOnly subject = new(2016, 06, 04); + DateOnly expectation = new(2016, 06, 05); + + // Act + Action act = () => subject.Should().NotBeOnOrBefore(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be after <2016-06-05>, but found <2016-06-04>."); + } + + [Fact] + public void + When_asserting_subject_dateonly_is_on_or_before_the_same_date_as_the_expected_dateonly_should_succeed() + { + // Arrange + DateOnly subject = new(2016, 06, 04); + DateOnly expectation = new(2016, 06, 04); + + // Act/Assert + subject.Should().BeOnOrBefore(expectation); + } + + [Fact] + public void When_asserting_subject_dateonly_is_on_or_before_the_same_date_as_the_expected_dateonly_should_throw() + { + // Arrange + DateOnly subject = new(2016, 06, 04); + DateOnly expectation = new(2016, 06, 04); + + // Act + Action act = () => subject.Should().NotBeOnOrBefore(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be after <2016-06-04>, but found <2016-06-04>."); + } + + [Fact] + public void When_asserting_subject_dateonly_is_not_on_or_before_earlier_expected_dateonly_should_throw() + { + // Arrange + DateOnly subject = new(2016, 06, 04); + DateOnly expectation = new(2016, 06, 03); + + // Act + Action act = () => subject.Should().BeOnOrBefore(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be on or before <2016-06-03>, but found <2016-06-04>."); + } + + [Fact] + public void When_asserting_subject_dateonly_is_not_on_or_before_earlier_expected_dateonly_should_succeed() + { + // Arrange + DateOnly subject = new(2016, 06, 04); + DateOnly expectation = new(2016, 06, 03); + + // Act/Assert + subject.Should().NotBeOnOrBefore(expectation); + } + } +} + +#endif diff --git a/Tests/FluentAssertions.Specs/Primitives/DateOnlyAssertionSpecs.BeOneOf.cs b/Tests/FluentAssertions.Specs/Primitives/DateOnlyAssertionSpecs.BeOneOf.cs new file mode 100644 index 0000000000..afffcd04de --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateOnlyAssertionSpecs.BeOneOf.cs @@ -0,0 +1,78 @@ +#if NET6_0_OR_GREATER +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateOnlyAssertionSpecs +{ + public class BeOneOf + { + [Fact] + public void When_a_value_is_not_one_of_the_specified_values_it_should_throw() + { + // Arrange + DateOnly value = new(2016, 12, 20); + + // Act + Action action = () => value.Should().BeOneOf(value.AddDays(1), value.AddMonths(-1)); + + // Assert + action.Should().Throw() + .WithMessage("Expected value to be one of {<2016-12-21>, <2016-11-20>}, but found <2016-12-20>."); + } + + [Fact] + public void When_a_value_is_not_one_of_the_specified_values_it_should_throw_with_descriptive_message() + { + // Arrange + DateOnly value = new(2016, 12, 20); + + // Act + Action action = () => + value.Should().BeOneOf(new[] { value.AddDays(1), value.AddDays(2) }, "because it's true"); + + // Assert + action.Should().Throw() + .WithMessage( + "Expected value to be one of {<2016-12-21>, <2016-12-22>} because it's true, but found <2016-12-20>."); + } + + [Fact] + public void When_a_value_is_one_of_the_specified_values_it_should_succeed() + { + // Arrange + DateOnly value = new(2016, 12, 30); + + // Act/Assert + value.Should().BeOneOf(new DateOnly(2216, 1, 30), new DateOnly(2016, 12, 30)); + } + + [Fact] + public void When_a_null_value_is_not_one_of_the_specified_values_it_should_throw() + { + // Arrange + DateOnly? value = null; + + // Act + Action action = () => value.Should().BeOneOf(new DateOnly(2216, 1, 30), new DateOnly(1116, 4, 10)); + + // Assert + action.Should().Throw() + .WithMessage("Expected value to be one of {<2216-01-30>, <1116-04-10>}, but found ."); + } + + [Fact] + public void When_a_value_is_one_of_the_specified_values_it_should_succeed_when_dateonly_is_null() + { + // Arrange + DateOnly? value = null; + + // Act/Assert + value.Should().BeOneOf(new DateOnly(2216, 1, 30), null); + } + } +} + +#endif diff --git a/Tests/FluentAssertions.Specs/Primitives/DateOnlyAssertionSpecs.HaveDay.cs b/Tests/FluentAssertions.Specs/Primitives/DateOnlyAssertionSpecs.HaveDay.cs new file mode 100644 index 0000000000..f043edb95a --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateOnlyAssertionSpecs.HaveDay.cs @@ -0,0 +1,96 @@ +#if NET6_0_OR_GREATER +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateOnlyAssertionSpecs +{ + public class HaveDay + { + [Fact] + public void When_asserting_subject_dateonly_should_have_day_with_the_same_value_it_should_succeed() + { + // Arrange + DateOnly subject = new(2009, 12, 31); + const int expectation = 31; + + // Act/Assert + subject.Should().HaveDay(expectation); + } + + [Fact] + public void When_asserting_subject_dateonly_should_not_have_day_with_the_same_value_it_should_throw() + { + // Arrange + DateOnly subject = new(2009, 12, 31); + const int expectation = 31; + + // Act + Action act = () => subject.Should().NotHaveDay(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the day part of subject to be 31, but it was."); + } + + [Fact] + public void When_asserting_subject_dateonly_should_have_day_with_a_different_value_it_should_throw() + { + // Arrange + DateOnly subject = new(2009, 12, 31); + const int expectation = 30; + + // Act + Action act = () => subject.Should().HaveDay(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the day part of subject to be 30, but found 31."); + } + + [Fact] + public void When_asserting_subject_dateonly_should_not_have_day_with_a_different_value_it_should_succeed() + { + // Arrange + DateOnly subject = new(2009, 12, 31); + const int expectation = 30; + + // Act/Assert + subject.Should().NotHaveDay(expectation); + } + + [Fact] + public void When_asserting_subject_null_dateonly_should_have_day_should_throw() + { + // Arrange + DateOnly? subject = null; + const int expectation = 22; + + // Act + Action act = () => subject.Should().HaveDay(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the day part of subject to be 22, but found a DateOnly."); + } + + [Fact] + public void When_asserting_subject_null_dateonly_should_not_have_day_should_throw() + { + // Arrange + DateOnly? subject = null; + const int expectation = 22; + + // Act + Action act = () => subject.Should().NotHaveDay(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the day part of subject to be 22, but found a DateOnly."); + } + } +} + +#endif diff --git a/Tests/FluentAssertions.Specs/Primitives/DateOnlyAssertionSpecs.HaveMonth.cs b/Tests/FluentAssertions.Specs/Primitives/DateOnlyAssertionSpecs.HaveMonth.cs new file mode 100644 index 0000000000..7747917525 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateOnlyAssertionSpecs.HaveMonth.cs @@ -0,0 +1,96 @@ +#if NET6_0_OR_GREATER +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateOnlyAssertionSpecs +{ + public class HaveMonth + { + [Fact] + public void When_asserting_subject_dateonly_should_have_month_with_the_same_value_it_should_succeed() + { + // Arrange + DateOnly subject = new(2009, 12, 31); + const int expectation = 12; + + // Act/Assert + subject.Should().HaveMonth(expectation); + } + + [Fact] + public void When_asserting_subject_dateonly_should_not_have_month_with_the_same_value_it_should_throw() + { + // Arrange + DateOnly subject = new(2009, 12, 31); + const int expectation = 12; + + // Act + Action act = () => subject.Should().NotHaveMonth(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the month part of subject to be 12, but it was."); + } + + [Fact] + public void When_asserting_subject_dateonly_should_have_a_month_with_a_different_value_it_should_throw() + { + // Arrange + DateOnly subject = new(2009, 12, 31); + const int expectation = 11; + + // Act + Action act = () => subject.Should().HaveMonth(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the month part of subject to be 11, but found 12."); + } + + [Fact] + public void When_asserting_subject_dateonly_should_not_have_a_month_with_a_different_value_it_should_succeed() + { + // Arrange + DateOnly subject = new(2009, 12, 31); + const int expectation = 11; + + // Act/Assert + subject.Should().NotHaveMonth(expectation); + } + + [Fact] + public void When_asserting_subject_null_dateonly_should_have_month_should_throw() + { + // Arrange + DateOnly? subject = null; + const int expectation = 12; + + // Act + Action act = () => subject.Should().HaveMonth(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the month part of subject to be 12, but found a DateOnly."); + } + + [Fact] + public void When_asserting_subject_null_dateonly_should_not_have_month_should_throw() + { + // Arrange + DateOnly? subject = null; + const int expectation = 12; + + // Act + Action act = () => subject.Should().NotHaveMonth(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the month part of subject to be 12, but found a DateOnly."); + } + } +} + +#endif diff --git a/Tests/FluentAssertions.Specs/Primitives/DateOnlyAssertionSpecs.HaveYear.cs b/Tests/FluentAssertions.Specs/Primitives/DateOnlyAssertionSpecs.HaveYear.cs new file mode 100644 index 0000000000..6181d47eaf --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateOnlyAssertionSpecs.HaveYear.cs @@ -0,0 +1,96 @@ +#if NET6_0_OR_GREATER +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateOnlyAssertionSpecs +{ + public class HaveYear + { + [Fact] + public void When_asserting_subject_dateonly_should_have_year_with_the_same_value_should_succeed() + { + // Arrange + DateOnly subject = new(2009, 12, 31); + const int expectation = 2009; + + // Act/Assert + subject.Should().HaveYear(expectation); + } + + [Fact] + public void When_asserting_subject_dateonly_should_not_have_year_with_the_same_value_should_throw() + { + // Arrange + DateOnly subject = new(2009, 12, 31); + const int expectation = 2009; + + // Act + Action act = () => subject.Should().NotHaveYear(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the year part of subject to be 2009, but it was."); + } + + [Fact] + public void When_asserting_subject_dateonly_should_have_year_with_a_different_value_should_throw() + { + // Arrange + DateOnly subject = new(2009, 12, 31); + const int expectation = 2008; + + // Act + Action act = () => subject.Should().HaveYear(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the year part of subject to be 2008, but found 2009."); + } + + [Fact] + public void When_asserting_subject_dateonly_should_not_have_year_with_a_different_value_should_succeed() + { + // Arrange + DateOnly subject = new(2009, 12, 31); + const int expectation = 2008; + + // Act/Assert + subject.Should().NotHaveYear(expectation); + } + + [Fact] + public void When_asserting_subject_null_dateonly_should_have_year_should_throw() + { + // Arrange + DateOnly? subject = null; + const int expectation = 2008; + + // Act + Action act = () => subject.Should().HaveYear(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the year part of subject to be 2008, but found ."); + } + + [Fact] + public void When_asserting_subject_null_dateonly_should_not_have_year_should_throw() + { + // Arrange + DateOnly? subject = null; + const int expectation = 2008; + + // Act + Action act = () => subject.Should().NotHaveYear(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the year part of subject to be 2008, but found a DateOnly."); + } + } +} + +#endif diff --git a/Tests/FluentAssertions.Specs/Primitives/DateOnlyAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Primitives/DateOnlyAssertionSpecs.cs index 962f38023a..3859ed3fb9 100644 --- a/Tests/FluentAssertions.Specs/Primitives/DateOnlyAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Primitives/DateOnlyAssertionSpecs.cs @@ -1,11 +1,11 @@ +#if NET6_0_OR_GREATER + using System; using Xunit; -using Xunit.Sdk; -#if NET6_0_OR_GREATER namespace FluentAssertions.Specs.Primitives; -public class DateOnlyAssertionSpecs +public partial class DateOnlyAssertionSpecs { [Fact] public void Should_succeed_when_asserting_nullable_dateonly_value_with_value_to_have_a_value() @@ -37,824 +37,32 @@ public void Should_succeed_when_asserting_nullable_dateonly_value_with_null_to_b dateOnly.Should().BeNull(); } - public class Be - { - [Fact] - public void Should_succeed_when_asserting_dateonly_value_is_equal_to_the_same_value() - { - // Arrange - DateOnly dateOnly = new(2016, 06, 04); - DateOnly sameDateOnly = new(2016, 06, 04); - - // Act/Assert - dateOnly.Should().Be(sameDateOnly); - } - - [Fact] - public void When_dateonly_value_is_equal_to_the_same_nullable_value_be_should_succeed() - { - // Arrange - DateOnly dateOnly = new(2016, 06, 04); - DateOnly? sameDateOnly = new(2016, 06, 04); - - // Act/Assert - dateOnly.Should().Be(sameDateOnly); - } - - [Fact] - public void When_both_values_are_at_their_minimum_then_it_should_succeed() - { - // Arrange - DateOnly dateOnly = DateOnly.MinValue; - DateOnly sameDateOnly = DateOnly.MinValue; - - // Act/Assert - dateOnly.Should().Be(sameDateOnly); - } - - [Fact] - public void When_both_values_are_at_their_maximum_then_it_should_succeed() - { - // Arrange - DateOnly dateOnly = DateOnly.MaxValue; - DateOnly sameDateOnly = DateOnly.MaxValue; - - // Act/Assert - dateOnly.Should().Be(sameDateOnly); - } - - [Fact] - public void Should_fail_when_asserting_dateonly_value_is_equal_to_the_different_value() - { - // Arrange - var dateOnly = new DateOnly(2012, 03, 10); - var otherDateOnly = new DateOnly(2012, 03, 11); - - // Act - Action act = () => dateOnly.Should().Be(otherDateOnly, "because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw() - .WithMessage("Expected dateOnly to be <2012-03-11>*failure message, but found <2012-03-10>."); - } - - [Fact] - public void Should_succeed_when_asserting_nullable_numeric_value_equals_the_same_value() - { - // Arrange - DateOnly? nullableDateOnlyA = new DateOnly(2016, 06, 04); - DateOnly? nullableDateOnlyB = new DateOnly(2016, 06, 04); - - // Act/Assert - nullableDateOnlyA.Should().Be(nullableDateOnlyB); - } - - [Fact] - public void Should_succeed_when_asserting_nullable_numeric_null_value_equals_null() - { - // Arrange - DateOnly? nullableDateOnlyA = null; - DateOnly? nullableDateOnlyB = null; - - // Act/Assert - nullableDateOnlyA.Should().Be(nullableDateOnlyB); - } - - [Fact] - public void Should_fail_when_asserting_nullable_numeric_value_equals_a_different_value() - { - // Arrange - DateOnly? nullableDateOnlyA = new DateOnly(2016, 06, 04); - DateOnly? nullableDateOnlyB = new DateOnly(2016, 06, 06); - - // Act - Action action = () => - nullableDateOnlyA.Should().Be(nullableDateOnlyB); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void Should_fail_with_descriptive_message_when_asserting_dateonly_null_value_is_equal_to_another_value() - { - // Arrange - DateOnly? nullableDateOnly = null; - - // Act - Action action = () => - nullableDateOnly.Should().Be(new DateOnly(2016, 06, 04), "because we want to test the failure {0}", - "message"); - - // Assert - action.Should().Throw() - .WithMessage( - "Expected nullableDateOnly to be <2016-06-04> because we want to test the failure message, but found ."); - } - - [Fact] - public void Should_succeed_when_asserting_dateonly_value_is_not_equal_to_a_different_value() - { - // Arrange - DateOnly dateOnly = new(2016, 06, 04); - DateOnly otherDateOnly = new(2016, 06, 05); - - // Act/Assert - dateOnly.Should().NotBe(otherDateOnly); - } - } - - public class BeBefore - { - [Fact] - public void When_asserting_subject_is_not_before_earlier_expected_dateonly_it_should_succeed() - { - // Arrange - DateOnly expected = new(2016, 06, 03); - DateOnly subject = new(2016, 06, 04); - - // Act/Assert - subject.Should().NotBeBefore(expected); - } - - [Fact] - public void When_asserting_subject_dateonly_is_before_the_same_dateonly_it_should_throw() - { - // Arrange - DateOnly expected = new(2016, 06, 04); - DateOnly subject = new(2016, 06, 04); - - // Act - Action act = () => subject.Should().BeBefore(expected); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be before <2016-06-04>, but found <2016-06-04>."); - } - - [Fact] - public void When_asserting_subject_dateonly_is_not_before_the_same_dateonly_it_should_succeed() - { - // Arrange - DateOnly expected = new(2016, 06, 04); - DateOnly subject = new(2016, 06, 04); - - // Act/Assert - subject.Should().NotBeBefore(expected); - } - - [Fact] - public void When_asserting_subject_dateonly_is_on_or_before_expected_dateonly_should_succeed() - { - // Arrange - DateOnly subject = new(2016, 06, 04); - DateOnly expectation = new(2016, 06, 05); - - // Act/Assert - subject.Should().BeOnOrBefore(expectation); - } - - [Fact] - public void When_asserting_subject_dateonly_is_on_or_before_expected_dateonly_should_throw() - { - // Arrange - DateOnly subject = new(2016, 06, 04); - DateOnly expectation = new(2016, 06, 05); - - // Act - Action act = () => subject.Should().NotBeOnOrBefore(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be after <2016-06-05>, but found <2016-06-04>."); - } - - [Fact] - public void - When_asserting_subject_dateonly_is_on_or_before_the_same_date_as_the_expected_dateonly_should_succeed() - { - // Arrange - DateOnly subject = new(2016, 06, 04); - DateOnly expectation = new(2016, 06, 04); - - // Act/Assert - subject.Should().BeOnOrBefore(expectation); - } - - [Fact] - public void When_asserting_subject_dateonly_is_on_or_before_the_same_date_as_the_expected_dateonly_should_throw() - { - // Arrange - DateOnly subject = new(2016, 06, 04); - DateOnly expectation = new(2016, 06, 04); - - // Act - Action act = () => subject.Should().NotBeOnOrBefore(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be after <2016-06-04>, but found <2016-06-04>."); - } - - [Fact] - public void When_asserting_subject_dateonly_is_not_on_or_before_earlier_expected_dateonly_should_throw() - { - // Arrange - DateOnly subject = new(2016, 06, 04); - DateOnly expectation = new(2016, 06, 03); - - // Act - Action act = () => subject.Should().BeOnOrBefore(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be on or before <2016-06-03>, but found <2016-06-04>."); - } - - [Fact] - public void When_asserting_subject_dateonly_is_not_on_or_before_earlier_expected_dateonly_should_succeed() - { - // Arrange - DateOnly subject = new(2016, 06, 04); - DateOnly expectation = new(2016, 06, 03); - - // Act/Assert - subject.Should().NotBeOnOrBefore(expectation); - } - } - - public class BeAfter - { - [Fact] - public void When_asserting_subject_dateonly_is_after_earlier_expected_dateonly_should_succeed() - { - // Arrange - DateOnly subject = new(2016, 06, 04); - DateOnly expectation = new(2016, 06, 03); - - // Act/Assert - subject.Should().BeAfter(expectation); - } - - [Fact] - public void When_asserting_subject_dateonly_is_not_after_earlier_expected_dateonly_should_throw() - { - // Arrange - DateOnly subject = new(2016, 06, 04); - DateOnly expectation = new(2016, 06, 03); - - // Act - Action act = () => subject.Should().NotBeAfter(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be on or before <2016-06-03>, but found <2016-06-04>."); - } - - [Fact] - public void When_asserting_subject_dateonly_is_after_later_expected_dateonly_should_throw() - { - // Arrange - DateOnly subject = new(2016, 06, 04); - DateOnly expectation = new(2016, 06, 05); - - // Act - Action act = () => subject.Should().BeAfter(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be after <2016-06-05>, but found <2016-06-04>."); - } - - [Fact] - public void When_asserting_subject_dateonly_is_not_after_later_expected_dateonly_should_succeed() - { - // Arrange - DateOnly subject = new(2016, 06, 04); - DateOnly expectation = new(2016, 06, 05); - - // Act/Assert - subject.Should().NotBeAfter(expectation); - } - - [Fact] - public void When_asserting_subject_dateonly_is_after_the_same_expected_dateonly_should_throw() - { - // Arrange - DateOnly subject = new(2016, 06, 04); - DateOnly expectation = new(2016, 06, 04); - - // Act - Action act = () => subject.Should().BeAfter(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be after <2016-06-04>, but found <2016-06-04>."); - } - - [Fact] - public void When_asserting_subject_dateonly_is_not_after_the_same_expected_dateonly_should_succeed() - { - // Arrange - DateOnly subject = new(2016, 06, 04); - DateOnly expectation = new(2016, 06, 04); - - // Act/Assert - subject.Should().NotBeAfter(expectation); - } - - [Fact] - public void When_asserting_subject_dateonly_is_on_or_after_earlier_expected_dateonly_should_succeed() - { - // Arrange - DateOnly subject = new(2016, 06, 04); - DateOnly expectation = new(2016, 06, 03); - - // Act/Assert - subject.Should().BeOnOrAfter(expectation); - } - - [Fact] - public void When_asserting_subject_dateonly_is_not_on_or_after_earlier_expected_dateonly_should_throw() - { - // Arrange - DateOnly subject = new(2016, 06, 04); - DateOnly expectation = new(2016, 06, 03); - - // Act - Action act = () => subject.Should().NotBeOnOrAfter(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be before <2016-06-03>, but found <2016-06-04>."); - } - - [Fact] - public void When_asserting_subject_dateonly_is_on_or_after_the_same_expected_dateonly_should_succeed() - { - // Arrange - DateOnly subject = new(2016, 06, 04); - DateOnly expectation = new(2016, 06, 04); - - // Act/Assert - subject.Should().BeOnOrAfter(expectation); - } - - [Fact] - public void When_asserting_subject_dateonly_is_not_on_or_after_the_same_expected_dateonly_should_throw() - { - // Arrange - DateOnly subject = new(2016, 06, 04); - DateOnly expectation = new(2016, 06, 04); - - // Act - Action act = () => subject.Should().NotBeOnOrAfter(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be before <2016-06-04>, but found <2016-06-04>."); - } - - [Fact] - public void When_asserting_subject_dateonly_is_on_or_after_later_expected_dateonly_should_throw() - { - // Arrange - DateOnly subject = new(2016, 06, 04); - DateOnly expectation = new(2016, 06, 05); - - // Act - Action act = () => subject.Should().BeOnOrAfter(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be on or after <2016-06-05>, but found <2016-06-04>."); - } - - [Fact] - public void When_asserting_subject_dateonly_is_not_on_or_after_later_expected_dateonly_should_succeed() - { - // Arrange - DateOnly subject = new(2016, 06, 04); - DateOnly expectation = new(2016, 06, 05); - - // Act/Assert - subject.Should().NotBeOnOrAfter(expectation); - } - } - - public class HaveYear - { - [Fact] - public void When_asserting_subject_dateonly_should_have_year_with_the_same_value_should_succeed() - { - // Arrange - DateOnly subject = new(2009, 12, 31); - const int expectation = 2009; - - // Act/Assert - subject.Should().HaveYear(expectation); - } - - [Fact] - public void When_asserting_subject_dateonly_should_not_have_year_with_the_same_value_should_throw() - { - // Arrange - DateOnly subject = new(2009, 12, 31); - const int expectation = 2009; - - // Act - Action act = () => subject.Should().NotHaveYear(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the year part of subject to be 2009, but it was."); - } - - [Fact] - public void When_asserting_subject_dateonly_should_have_year_with_a_different_value_should_throw() - { - // Arrange - DateOnly subject = new(2009, 12, 31); - const int expectation = 2008; - - // Act - Action act = () => subject.Should().HaveYear(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the year part of subject to be 2008, but found 2009."); - } - - [Fact] - public void When_asserting_subject_dateonly_should_not_have_year_with_a_different_value_should_succeed() - { - // Arrange - DateOnly subject = new(2009, 12, 31); - const int expectation = 2008; - - // Act/Assert - subject.Should().NotHaveYear(expectation); - } - - [Fact] - public void When_asserting_subject_null_dateonly_should_have_year_should_throw() - { - // Arrange - DateOnly? subject = null; - const int expectation = 2008; - - // Act - Action act = () => subject.Should().HaveYear(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the year part of subject to be 2008, but found ."); - } - - [Fact] - public void When_asserting_subject_null_dateonly_should_not_have_year_should_throw() - { - // Arrange - DateOnly? subject = null; - const int expectation = 2008; - - // Act - Action act = () => subject.Should().NotHaveYear(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the year part of subject to be 2008, but found a DateOnly."); - } - } - - public class HaveMonth - { - [Fact] - public void When_asserting_subject_dateonly_should_have_month_with_the_same_value_it_should_succeed() - { - // Arrange - DateOnly subject = new(2009, 12, 31); - const int expectation = 12; - - // Act/Assert - subject.Should().HaveMonth(expectation); - } - - [Fact] - public void When_asserting_subject_dateonly_should_not_have_month_with_the_same_value_it_should_throw() - { - // Arrange - DateOnly subject = new(2009, 12, 31); - const int expectation = 12; - - // Act - Action act = () => subject.Should().NotHaveMonth(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the month part of subject to be 12, but it was."); - } - - [Fact] - public void When_asserting_subject_dateonly_should_have_a_month_with_a_different_value_it_should_throw() - { - // Arrange - DateOnly subject = new(2009, 12, 31); - const int expectation = 11; - - // Act - Action act = () => subject.Should().HaveMonth(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the month part of subject to be 11, but found 12."); - } - - [Fact] - public void When_asserting_subject_dateonly_should_not_have_a_month_with_a_different_value_it_should_succeed() - { - // Arrange - DateOnly subject = new(2009, 12, 31); - const int expectation = 11; - - // Act/Assert - subject.Should().NotHaveMonth(expectation); - } - - [Fact] - public void When_asserting_subject_null_dateonly_should_have_month_should_throw() - { - // Arrange - DateOnly? subject = null; - const int expectation = 12; - - // Act - Action act = () => subject.Should().HaveMonth(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the month part of subject to be 12, but found a DateOnly."); - } - - [Fact] - public void When_asserting_subject_null_dateonly_should_not_have_month_should_throw() - { - // Arrange - DateOnly? subject = null; - const int expectation = 12; - - // Act - Action act = () => subject.Should().NotHaveMonth(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the month part of subject to be 12, but found a DateOnly."); - } - } - - public class NotBe - { - [Fact] - public void Different_dateonly_values_are_valid() - { - // Arrange - DateOnly date = new(2020, 06, 04); - DateOnly otherDate = new(2020, 06, 05); - - // Act & Assert - date.Should().NotBe(otherDate); - } - - [Fact] - public void Different_dateonly_values_with_different_nullability_are_valid() - { - // Arrange - DateOnly date = new(2020, 06, 04); - DateOnly? otherDate = new(2020, 07, 05); - - // Act & Assert - date.Should().NotBe(otherDate); - } - - [Fact] - public void Same_dateonly_values_are_invalid() - { - // Arrange - DateOnly date = new(2020, 06, 04); - DateOnly sameDate = new(2020, 06, 04); - - // Act - Action act = - () => date.Should().NotBe(sameDate, "because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw() - .WithMessage( - "Expected date not to be <2020-06-04> because we want to test the failure message, but it is."); - } - - [Fact] - public void Same_dateonly_values_with_different_nullability_are_invalid() - { - // Arrange - DateOnly date = new(2020, 06, 04); - DateOnly? sameDate = new(2020, 06, 04); - - // Act - Action act = - () => date.Should().NotBe(sameDate, "because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw() - .WithMessage( - "Expected date not to be <2020-06-04> because we want to test the failure message, but it is."); - } - } - - public class HaveDay - { - [Fact] - public void When_asserting_subject_dateonly_should_have_day_with_the_same_value_it_should_succeed() - { - // Arrange - DateOnly subject = new(2009, 12, 31); - const int expectation = 31; - - // Act/Assert - subject.Should().HaveDay(expectation); - } - - [Fact] - public void When_asserting_subject_dateonly_should_not_have_day_with_the_same_value_it_should_throw() - { - // Arrange - DateOnly subject = new(2009, 12, 31); - const int expectation = 31; - - // Act - Action act = () => subject.Should().NotHaveDay(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the day part of subject to be 31, but it was."); - } - - [Fact] - public void When_asserting_subject_dateonly_should_have_day_with_a_different_value_it_should_throw() - { - // Arrange - DateOnly subject = new(2009, 12, 31); - const int expectation = 30; - - // Act - Action act = () => subject.Should().HaveDay(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the day part of subject to be 30, but found 31."); - } - - [Fact] - public void When_asserting_subject_dateonly_should_not_have_day_with_a_different_value_it_should_succeed() - { - // Arrange - DateOnly subject = new(2009, 12, 31); - const int expectation = 30; - - // Act/Assert - subject.Should().NotHaveDay(expectation); - } - - [Fact] - public void When_asserting_subject_null_dateonly_should_have_day_should_throw() - { - // Arrange - DateOnly? subject = null; - const int expectation = 22; - - // Act - Action act = () => subject.Should().HaveDay(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the day part of subject to be 22, but found a DateOnly."); - } - - [Fact] - public void When_asserting_subject_null_dateonly_should_not_have_day_should_throw() - { - // Arrange - DateOnly? subject = null; - const int expectation = 22; - - // Act - Action act = () => subject.Should().NotHaveDay(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the day part of subject to be 22, but found a DateOnly."); - } - } - - public class BeOneOf - { - [Fact] - public void When_a_value_is_not_one_of_the_specified_values_it_should_throw() - { - // Arrange - DateOnly value = new(2016, 12, 20); - - // Act - Action action = () => value.Should().BeOneOf(value.AddDays(1), value.AddMonths(-1)); - - // Assert - action.Should().Throw() - .WithMessage("Expected value to be one of {<2016-12-21>, <2016-11-20>}, but found <2016-12-20>."); - } - - [Fact] - public void When_a_value_is_not_one_of_the_specified_values_it_should_throw_with_descriptive_message() - { - // Arrange - DateOnly value = new(2016, 12, 20); - - // Act - Action action = () => - value.Should().BeOneOf(new[] { value.AddDays(1), value.AddDays(2) }, "because it's true"); - - // Assert - action.Should().Throw() - .WithMessage( - "Expected value to be one of {<2016-12-21>, <2016-12-22>} because it's true, but found <2016-12-20>."); - } - - [Fact] - public void When_a_value_is_one_of_the_specified_values_it_should_succeed() - { - // Arrange - DateOnly value = new(2016, 12, 30); - - // Act/Assert - value.Should().BeOneOf(new DateOnly(2216, 1, 30), new DateOnly(2016, 12, 30)); - } - - [Fact] - public void When_a_null_value_is_not_one_of_the_specified_values_it_should_throw() - { - // Arrange - DateOnly? value = null; - - // Act - Action action = () => value.Should().BeOneOf(new DateOnly(2216, 1, 30), new DateOnly(1116, 4, 10)); - - // Assert - action.Should().Throw() - .WithMessage("Expected value to be one of {<2216-01-30>, <1116-04-10>}, but found ."); - } - - [Fact] - public void When_a_value_is_one_of_the_specified_values_it_should_succeed_when_dateonly_is_null() - { - // Arrange - DateOnly? value = null; - - // Act/Assert - value.Should().BeOneOf(new DateOnly(2216, 1, 30), null); - } - } - - public class AndChaining + [Fact] + public void Should_support_chaining_constraints_with_and() { - [Fact] - public void Should_support_chaining_constraints_with_and() - { - // Arrange - DateOnly earlierDateOnly = new(2016, 06, 03); - DateOnly? nullableDateOnly = new(2016, 06, 04); + // Arrange + DateOnly earlierDateOnly = new(2016, 06, 03); + DateOnly? nullableDateOnly = new(2016, 06, 04); - // Act/Assert - nullableDateOnly.Should() - .HaveValue() - .And - .BeAfter(earlierDateOnly); - } + // Act/Assert + nullableDateOnly.Should() + .HaveValue() + .And + .BeAfter(earlierDateOnly); } - public class Miscellaneous + [Fact] + public void Should_throw_a_helpful_error_when_accidentally_using_equals() { - [Fact] - public void Should_throw_a_helpful_error_when_accidentally_using_equals() - { - // Arrange - DateOnly someDateOnly = new(2022, 9, 25); + // Arrange + DateOnly someDateOnly = new(2022, 9, 25); - // Act - Action action = () => someDateOnly.Should().Equals(someDateOnly); + // Act + Action action = () => someDateOnly.Should().Equals(someDateOnly); - // Assert - action.Should().Throw() - .WithMessage("Equals is not part of Fluent Assertions. Did you mean Be() instead?"); - } + // Assert + action.Should().Throw() + .WithMessage("Equals is not part of Fluent Assertions. Did you mean Be() instead?"); } } diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.Be.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.Be.cs new file mode 100644 index 0000000000..f9668fc0f9 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.Be.cs @@ -0,0 +1,254 @@ +using System; +using FluentAssertions.Extensions; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeAssertionSpecs +{ + public class Be + { + [Fact] + public void Should_succeed_when_asserting_datetime_value_is_equal_to_the_same_value() + { + // Arrange + DateTime dateTime = new(2016, 06, 04); + DateTime sameDateTime = new(2016, 06, 04); + + // Act + Action act = () => dateTime.Should().Be(sameDateTime); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_datetime_value_is_equal_to_the_same_nullable_value_be_should_succeed() + { + // Arrange + DateTime dateTime = 4.June(2016); + DateTime? sameDateTime = 4.June(2016); + + // Act + Action act = () => dateTime.Should().Be(sameDateTime); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_both_values_are_at_their_minimum_then_it_should_succeed() + { + // Arrange + DateTime dateTime = DateTime.MinValue; + DateTime sameDateTime = DateTime.MinValue; + + // Act + Action act = () => dateTime.Should().Be(sameDateTime); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_both_values_are_at_their_maximum_then_it_should_succeed() + { + // Arrange + DateTime dateTime = DateTime.MaxValue; + DateTime sameDateTime = DateTime.MaxValue; + + // Act + Action act = () => dateTime.Should().Be(sameDateTime); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void Should_fail_when_asserting_datetime_value_is_equal_to_the_different_value() + { + // Arrange + var dateTime = new DateTime(2012, 03, 10); + var otherDateTime = new DateTime(2012, 03, 11); + + // Act + Action act = () => dateTime.Should().Be(otherDateTime, "because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw() + .WithMessage("Expected dateTime to be <2012-03-11>*failure message, but found <2012-03-10>."); + } + + [Fact] + public void When_datetime_value_is_equal_to_the_different_nullable_value_be_should_failed() + { + // Arrange + DateTime dateTime = 10.March(2012); + DateTime? otherDateTime = 11.March(2012); + + // Act + Action act = () => dateTime.Should().Be(otherDateTime, "because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw() + .WithMessage("Expected dateTime to be <2012-03-11>*failure message, but found <2012-03-10>."); + } + + [Fact] + public void Should_succeed_when_asserting_nullable_numeric_value_equals_the_same_value() + { + // Arrange + DateTime? nullableDateTimeA = new DateTime(2016, 06, 04); + DateTime? nullableDateTimeB = new DateTime(2016, 06, 04); + + // Act + Action action = () => + nullableDateTimeA.Should().Be(nullableDateTimeB); + + // Assert + action.Should().NotThrow(); + } + + [Fact] + public void When_a_nullable_date_time_is_equal_to_a_normal_date_time_but_the_kinds_differ_it_should_still_succeed() + { + // Arrange + DateTime? nullableDateTime = new DateTime(2014, 4, 20, 9, 11, 0, DateTimeKind.Unspecified); + DateTime normalDateTime = new(2014, 4, 20, 9, 11, 0, DateTimeKind.Utc); + + // Act + Action action = () => + nullableDateTime.Should().Be(normalDateTime); + + // Assert + action.Should().NotThrow(); + } + + [Fact] + public void When_two_date_times_are_equal_but_the_kinds_differ_it_should_still_succeed() + { + // Arrange + DateTime dateTimeA = new(2014, 4, 20, 9, 11, 0, DateTimeKind.Unspecified); + DateTime dateTimeB = new(2014, 4, 20, 9, 11, 0, DateTimeKind.Utc); + + // Act + Action action = () => + dateTimeA.Should().Be(dateTimeB); + + // Assert + action.Should().NotThrow(); + } + + [Fact] + public void Should_succeed_when_asserting_nullable_numeric_null_value_equals_null() + { + // Arrange + DateTime? nullableDateTimeA = null; + DateTime? nullableDateTimeB = null; + + // Act + Action action = () => + nullableDateTimeA.Should().Be(nullableDateTimeB); + + // Assert + action.Should().NotThrow(); + } + + [Fact] + public void Should_fail_when_asserting_nullable_numeric_value_equals_a_different_value() + { + // Arrange + DateTime? nullableDateTimeA = new DateTime(2016, 06, 04); + DateTime? nullableDateTimeB = new DateTime(2016, 06, 06); + + // Act + Action action = () => + nullableDateTimeA.Should().Be(nullableDateTimeB); + + // Assert + action.Should().Throw(); + } + + [Fact] + public void Should_fail_with_descriptive_message_when_asserting_datetime_null_value_is_equal_to_another_value() + { + // Arrange + DateTime? nullableDateTime = null; + + // Act + Action action = () => + nullableDateTime.Should().Be(new DateTime(2016, 06, 04), "because we want to test the failure {0}", "message"); + + // Assert + action.Should().Throw() + .WithMessage( + "Expected nullableDateTime to be <2016-06-04> because we want to test the failure message, but found ."); + } + } + + public class NotBe + { + [Fact] + public void Should_succeed_when_asserting_datetime_value_is_not_equal_to_a_different_value() + { + // Arrange + DateTime dateTime = new(2016, 06, 04); + DateTime otherDateTime = new(2016, 06, 05); + + // Act + Action act = () => dateTime.Should().NotBe(otherDateTime); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_datetime_value_is_not_equal_to_a_different_nullable_value_notbe_should_succeed() + { + // Arrange + DateTime dateTime = 4.June(2016); + DateTime? otherDateTime = 5.June(2016); + + // Act + Action act = () => dateTime.Should().NotBe(otherDateTime); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void Should_fail_when_asserting_datetime_value_is_not_equal_to_the_same_value() + { + // Arrange + var dateTime = DateTime.SpecifyKind(10.March(2012).At(10, 00), DateTimeKind.Local); + var sameDateTime = DateTime.SpecifyKind(10.March(2012).At(10, 00), DateTimeKind.Utc); + + // Act + Action act = + () => dateTime.Should().NotBe(sameDateTime, "because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw() + .WithMessage( + "Expected dateTime not to be <2012-03-10 10:00:00> because we want to test the failure message, but it is."); + } + + [Fact] + public void When_datetime_value_is_not_equal_to_the_same_nullable_value_notbe_should_failed() + { + // Arrange + DateTime dateTime = DateTime.SpecifyKind(10.March(2012).At(10, 00), DateTimeKind.Local); + DateTime? sameDateTime = DateTime.SpecifyKind(10.March(2012).At(10, 00), DateTimeKind.Utc); + + // Act + Action act = + () => dateTime.Should().NotBe(sameDateTime, "because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw() + .WithMessage( + "Expected dateTime not to be <2012-03-10 10:00:00> because we want to test the failure message, but it is."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeAfter.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeAfter.cs new file mode 100644 index 0000000000..4518e28545 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeAfter.cs @@ -0,0 +1,101 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeAssertionSpecs +{ + public class BeAfter + { + [Fact] + public void When_asserting_subject_datetime_is_after_earlier_expected_datetime_should_succeed() + { + // Arrange + DateTime subject = new(2016, 06, 04); + DateTime expectation = new(2016, 06, 03); + + // Act + Action act = () => subject.Should().BeAfter(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_datetime_is_after_later_expected_datetime_should_throw() + { + // Arrange + DateTime subject = new(2016, 06, 04); + DateTime expectation = new(2016, 06, 05); + + // Act + Action act = () => subject.Should().BeAfter(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be after <2016-06-05>, but found <2016-06-04>."); + } + + [Fact] + public void When_asserting_subject_datetime_is_after_the_same_expected_datetime_should_throw() + { + // Arrange + DateTime subject = new(2016, 06, 04); + DateTime expectation = new(2016, 06, 04); + + // Act + Action act = () => subject.Should().BeAfter(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be after <2016-06-04>, but found <2016-06-04>."); + } + } + + public class NotBeAfter + { + [Fact] + public void When_asserting_subject_datetime_is_not_after_earlier_expected_datetime_should_throw() + { + // Arrange + DateTime subject = new(2016, 06, 04); + DateTime expectation = new(2016, 06, 03); + + // Act + Action act = () => subject.Should().NotBeAfter(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be on or before <2016-06-03>, but found <2016-06-04>."); + } + + [Fact] + public void When_asserting_subject_datetime_is_not_after_later_expected_datetime_should_succeed() + { + // Arrange + DateTime subject = new(2016, 06, 04); + DateTime expectation = new(2016, 06, 05); + + // Act + Action act = () => subject.Should().NotBeAfter(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_datetime_is_not_after_the_same_expected_datetime_should_succeed() + { + // Arrange + DateTime subject = new(2016, 06, 04); + DateTime expectation = new(2016, 06, 04); + + // Act + Action act = () => subject.Should().NotBeAfter(expectation); + + // Assert + act.Should().NotThrow(); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeAtLeast.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeAtLeast.cs new file mode 100644 index 0000000000..8ad538c2e3 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeAtLeast.cs @@ -0,0 +1,76 @@ +using System; +using FluentAssertions.Extensions; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeAssertionSpecs +{ + public class BeAtLeast + { + [Fact] + public void When_date_is_not_at_least_one_day_before_another_it_should_throw() + { + // Arrange + var target = new DateTime(2009, 10, 2); + DateTime subject = target - 23.Hours(); + + // Act + Action act = () => subject.Should().BeAtLeast(TimeSpan.FromDays(1)).Before(target, "we like {0}", "that"); + + // Assert + act.Should().Throw().WithMessage( + "Expected subject <2009-10-01 01:00:00> to be at least 1d before <2009-10-02> because we like that, but it is behind by 23h."); + } + + [Fact] + public void When_date_is_at_least_one_day_before_another_it_should_not_throw() + { + // Arrange + var target = new DateTime(2009, 10, 2); + DateTime subject = target - 24.Hours(); + + // Act / Assert + subject.Should().BeAtLeast(TimeSpan.FromDays(1)).Before(target); + } + + [Theory] + [InlineData(30, 20)] // edge case + [InlineData(30, 15)] + public void When_asserting_subject_be_at_least_10_seconds_after_target_but_subject_is_before_target_it_should_throw( + int targetSeconds, int subjectSeconds) + { + // Arrange + var expectation = 1.January(0001).At(0, 0, targetSeconds); + var subject = 1.January(0001).At(0, 0, subjectSeconds); + + // Act + Action action = () => subject.Should().BeAtLeast(10.Seconds()).After(expectation); + + // Assert + action.Should().Throw() + .WithMessage( + $"Expected subject <00:00:{subjectSeconds}> to be at least 10s after <00:00:30>, but it is behind by {Math.Abs(subjectSeconds - targetSeconds)}s."); + } + + [Theory] + [InlineData(30, 40)] // edge case + [InlineData(30, 45)] + public void When_asserting_subject_be_at_least_10_seconds_before_target_but_subject_is_after_target_it_should_throw( + int targetSeconds, int subjectSeconds) + { + // Arrange + var expectation = 1.January(0001).At(0, 0, targetSeconds); + var subject = 1.January(0001).At(0, 0, subjectSeconds); + + // Act + Action action = () => subject.Should().BeAtLeast(10.Seconds()).Before(expectation); + + // Assert + action.Should().Throw() + .WithMessage( + $"Expected subject <00:00:{subjectSeconds}> to be at least 10s before <00:00:30>, but it is ahead by {Math.Abs(subjectSeconds - targetSeconds)}s."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeBefore.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeBefore.cs new file mode 100644 index 0000000000..a4f47066d0 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeBefore.cs @@ -0,0 +1,101 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeAssertionSpecs +{ + public class BeBefore + { + [Fact] + public void When_asserting_a_point_of_time_is_before_a_later_point_it_should_succeed() + { + // Arrange + DateTime earlierDate = DateTime.SpecifyKind(new DateTime(2016, 06, 04), DateTimeKind.Unspecified); + DateTime laterDate = DateTime.SpecifyKind(new DateTime(2016, 06, 04, 0, 5, 0), DateTimeKind.Utc); + + // Act + Action act = () => earlierDate.Should().BeBefore(laterDate); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_is_before_earlier_expected_datetime_it_should_throw() + { + // Arrange + DateTime expected = new(2016, 06, 03); + DateTime subject = new(2016, 06, 04); + + // Act + Action act = () => subject.Should().BeBefore(expected); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be before <2016-06-03>, but found <2016-06-04>."); + } + + [Fact] + public void When_asserting_subject_datetime_is_before_the_same_datetime_it_should_throw() + { + // Arrange + DateTime expected = new(2016, 06, 04); + DateTime subject = new(2016, 06, 04); + + // Act + Action act = () => subject.Should().BeBefore(expected); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be before <2016-06-04>, but found <2016-06-04>."); + } + } + + public class NotBeBefore + { + [Fact] + public void When_asserting_a_point_of_time_is_not_before_another_it_should_throw() + { + // Arrange + DateTime earlierDate = DateTime.SpecifyKind(new DateTime(2016, 06, 04), DateTimeKind.Unspecified); + DateTime laterDate = DateTime.SpecifyKind(new DateTime(2016, 06, 04, 0, 5, 0), DateTimeKind.Utc); + + // Act + Action act = () => earlierDate.Should().NotBeBefore(laterDate); + + // Assert + act.Should().Throw() + .WithMessage("Expected earlierDate to be on or after <2016-06-04 00:05:00>, but found <2016-06-04>."); + } + + [Fact] + public void When_asserting_subject_is_not_before_earlier_expected_datetime_it_should_succeed() + { + // Arrange + DateTime expected = new(2016, 06, 03); + DateTime subject = new(2016, 06, 04); + + // Act + Action act = () => subject.Should().NotBeBefore(expected); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_datetime_is_not_before_the_same_datetime_it_should_succeed() + { + // Arrange + DateTime expected = new(2016, 06, 04); + DateTime subject = new(2016, 06, 04); + + // Act + Action act = () => subject.Should().NotBeBefore(expected); + + // Assert + act.Should().NotThrow(); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeCloseTo.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeCloseTo.cs new file mode 100644 index 0000000000..217a7d84d3 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeCloseTo.cs @@ -0,0 +1,433 @@ +using System; +using FluentAssertions.Extensions; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeAssertionSpecs +{ + public class BeCloseTo + { + [Fact] + public void When_asserting_that_time_is_close_to_a_negative_precision_it_should_throw() + { + // Arrange + var dateTime = DateTime.UtcNow; + var actual = new DateTime(dateTime.Ticks - 1); + + // Act + Action act = () => actual.Should().BeCloseTo(dateTime, -1.Ticks()); + + // Assert + act.Should().Throw() + .WithParameterName("precision") + .WithMessage("*must be non-negative*"); + } + + [Fact] + public void When_a_datetime_is_close_to_a_later_datetime_by_one_tick_it_should_succeed() + { + // Arrange + var dateTime = DateTime.UtcNow; + var actual = new DateTime(dateTime.Ticks - 1); + + // Act + Action act = () => actual.Should().BeCloseTo(dateTime, TimeSpan.FromTicks(1)); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_a_datetime_is_close_to_an_earlier_datetime_by_one_tick_it_should_succeed() + { + // Arrange + var dateTime = DateTime.UtcNow; + var actual = new DateTime(dateTime.Ticks + 1); + + // Act + Action act = () => actual.Should().BeCloseTo(dateTime, TimeSpan.FromTicks(1)); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_a_datetime_is_close_to_a_MinValue_by_one_tick_it_should_succeed() + { + // Arrange + var dateTime = DateTime.MinValue; + var actual = new DateTime(dateTime.Ticks + 1); + + // Act + Action act = () => actual.Should().BeCloseTo(dateTime, TimeSpan.FromTicks(1)); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_a_datetime_is_close_to_a_MaxValue_by_one_tick_it_should_succeed() + { + // Arrange + var dateTime = DateTime.MaxValue; + var actual = new DateTime(dateTime.Ticks - 1); + + // Act + Action act = () => actual.Should().BeCloseTo(dateTime, TimeSpan.FromTicks(1)); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_datetime_is_close_to_a_later_datetime_it_should_succeed() + { + // Arrange + DateTime time = DateTime.SpecifyKind(new DateTime(2016, 06, 04).At(12, 15, 30, 980), DateTimeKind.Unspecified); + DateTime nearbyTime = DateTime.SpecifyKind(new DateTime(2016, 06, 04).At(12, 15, 31), DateTimeKind.Utc); + + // Act + Action act = () => time.Should().BeCloseTo(nearbyTime, 20.Milliseconds()); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_datetime_is_close_to_an_earlier_datetime_it_should_succeed() + { + // Arrange + DateTime time = new DateTime(2016, 06, 04).At(12, 15, 31, 020); + DateTime nearbyTime = new DateTime(2016, 06, 04).At(12, 15, 31); + + // Act + Action act = () => time.Should().BeCloseTo(nearbyTime, 20.Milliseconds()); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_datetime_is_close_to_another_value_that_is_later_by_more_than_20ms_it_should_throw() + { + // Arrange + DateTime time = 13.March(2012).At(12, 15, 30, 979); + DateTime nearbyTime = 13.March(2012).At(12, 15, 31); + + // Act + Action act = () => time.Should().BeCloseTo(nearbyTime, 20.Milliseconds()); + + // Assert + act.Should().Throw() + .WithMessage( + "Expected time to be within 20ms from <2012-03-13 12:15:31>, but <2012-03-13 12:15:30.979> was off by 21ms."); + } + + [Fact] + public void + When_asserting_subject_datetime_is_close_to_another_value_that_is_later_by_more_than_a_20ms_timespan_it_should_throw() + { + // Arrange + DateTime time = 13.March(2012).At(12, 15, 30, 979); + DateTime nearbyTime = 13.March(2012).At(12, 15, 31); + + // Act + Action act = () => time.Should().BeCloseTo(nearbyTime, TimeSpan.FromMilliseconds(20)); + + // Assert + act.Should().Throw() + .WithMessage( + "Expected time to be within 20ms from <2012-03-13 12:15:31>, but <2012-03-13 12:15:30.979> was off by 21ms."); + } + + [Fact] + public void When_asserting_subject_datetime_is_close_to_another_value_that_is_earlier_by_more_than_20ms_it_should_throw() + { + // Arrange + DateTime time = 13.March(2012).At(12, 15, 31, 021); + DateTime nearbyTime = 13.March(2012).At(12, 15, 31); + + // Act + Action act = () => time.Should().BeCloseTo(nearbyTime, 20.Milliseconds()); + + // Assert + act.Should().Throw() + .WithMessage( + "Expected time to be within 20ms from <2012-03-13 12:15:31>, but <2012-03-13 12:15:31.021> was off by 21ms."); + } + + [Fact] + public void When_asserting_subject_datetime_is_close_to_an_earlier_datetime_by_35ms_it_should_succeed() + { + // Arrange + DateTime time = new DateTime(2016, 06, 04).At(12, 15, 31, 035); + DateTime nearbyTime = new DateTime(2016, 06, 04).At(12, 15, 31); + + // Act + Action act = () => time.Should().BeCloseTo(nearbyTime, 35.Milliseconds()); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_null_datetime_is_close_to_another_it_should_throw() + { + // Arrange + DateTime? time = null; + DateTime nearbyTime = new DateTime(2016, 06, 04).At(12, 15, 31); + + // Act + Action act = () => time.Should().BeCloseTo(nearbyTime, 35.Milliseconds()); + + // Assert + act.Should().Throw() + .WithMessage("Expected*, but found ."); + } + + [Fact] + public void When_asserting_subject_datetime_is_close_to_the_minimum_datetime_it_should_succeed() + { + // Arrange + DateTime time = DateTime.MinValue + 50.Milliseconds(); + DateTime nearbyTime = DateTime.MinValue; + + // Act + Action act = () => time.Should().BeCloseTo(nearbyTime, 100.Milliseconds()); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_datetime_is_close_to_the_maximum_datetime_it_should_succeed() + { + // Arrange + DateTime time = DateTime.MaxValue - 50.Milliseconds(); + DateTime nearbyTime = DateTime.MaxValue; + + // Act + Action act = () => time.Should().BeCloseTo(nearbyTime, 100.Milliseconds()); + + // Assert + act.Should().NotThrow(); + } + } + + public class NotBeCloseTo + { + [Fact] + public void When_asserting_that_time_is_not_close_to_a_negative_precision_it_should_throw() + { + // Arrange + var dateTime = DateTime.UtcNow; + var actual = new DateTime(dateTime.Ticks - 1); + + // Act + Action act = () => actual.Should().NotBeCloseTo(dateTime, -1.Ticks()); + + // Assert + act.Should().Throw() + .WithParameterName("precision") + .WithMessage("*must be non-negative*"); + } + + [Fact] + public void When_a_datetime_is_close_to_a_later_datetime_by_one_tick_it_should_fail() + { + // Arrange + var dateTime = DateTime.UtcNow; + var actual = new DateTime(dateTime.Ticks - 1); + + // Act + Action act = () => actual.Should().NotBeCloseTo(dateTime, TimeSpan.FromTicks(1)); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_a_datetime_is_close_to_an_earlier_datetime_by_one_tick_it_should_fail() + { + // Arrange + var dateTime = DateTime.UtcNow; + var actual = new DateTime(dateTime.Ticks + 1); + + // Act + Action act = () => actual.Should().NotBeCloseTo(dateTime, TimeSpan.FromTicks(1)); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_a_datetime_is_close_to_a_MinValue_by_one_tick_it_should_fail() + { + // Arrange + var dateTime = DateTime.MinValue; + var actual = new DateTime(dateTime.Ticks + 1); + + // Act + Action act = () => actual.Should().NotBeCloseTo(dateTime, TimeSpan.FromTicks(1)); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_a_datetime_is_close_to_a_MaxValue_by_one_tick_it_should_fail() + { + // Arrange + var dateTime = DateTime.MaxValue; + var actual = new DateTime(dateTime.Ticks - 1); + + // Act + Action act = () => actual.Should().NotBeCloseTo(dateTime, TimeSpan.FromTicks(1)); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_asserting_subject_datetime_is_not_close_to_a_later_datetime_it_should_throw() + { + // Arrange + DateTime time = DateTime.SpecifyKind(new DateTime(2016, 06, 04).At(12, 15, 30, 980), DateTimeKind.Unspecified); + DateTime nearbyTime = DateTime.SpecifyKind(new DateTime(2016, 06, 04).At(12, 15, 31), DateTimeKind.Utc); + + // Act + Action act = () => time.Should().NotBeCloseTo(nearbyTime, 20.Milliseconds()); + + // Assert + act.Should().Throw() + .WithMessage( + "Did not expect time to be within 20ms from <2016-06-04 12:15:31>, but it was <2016-06-04 12:15:30.980>."); + } + + [Fact] + public void When_asserting_subject_datetime_is_not_close_to_an_earlier_datetime_it_should_throw() + { + // Arrange + DateTime time = new DateTime(2016, 06, 04).At(12, 15, 31, 020); + DateTime nearbyTime = new DateTime(2016, 06, 04).At(12, 15, 31); + + // Act + Action act = () => time.Should().NotBeCloseTo(nearbyTime, 20.Milliseconds()); + + // Assert + act.Should().Throw() + .WithMessage( + "Did not expect time to be within 20ms from <2016-06-04 12:15:31>, but it was <2016-06-04 12:15:31.020>."); + } + + [Fact] + public void When_asserting_subject_datetime_is_not_close_to_an_earlier_datetime_by_a_20ms_timespan_it_should_throw() + { + // Arrange + DateTime time = new DateTime(2016, 06, 04).At(12, 15, 31, 020); + DateTime nearbyTime = new DateTime(2016, 06, 04).At(12, 15, 31); + + // Act + Action act = () => time.Should().NotBeCloseTo(nearbyTime, TimeSpan.FromMilliseconds(20)); + + // Assert + act.Should().Throw() + .WithMessage( + "Did not expect time to be within 20ms from <2016-06-04 12:15:31>, but it was <2016-06-04 12:15:31.020>."); + } + + [Fact] + public void + When_asserting_subject_datetime_is_not_close_to_another_value_that_is_later_by_more_than_20ms_it_should_succeed() + { + // Arrange + DateTime time = 13.March(2012).At(12, 15, 30, 979); + DateTime nearbyTime = 13.March(2012).At(12, 15, 31); + + // Act + Action act = () => time.Should().NotBeCloseTo(nearbyTime, 20.Milliseconds()); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void + When_asserting_subject_datetime_is_not_close_to_another_value_that_is_earlier_by_more_than_20ms_it_should_succeed() + { + // Arrange + DateTime time = 13.March(2012).At(12, 15, 31, 021); + DateTime nearbyTime = 13.March(2012).At(12, 15, 31); + + // Act + Action act = () => time.Should().NotBeCloseTo(nearbyTime, 20.Milliseconds()); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_datetime_is_not_close_to_an_earlier_datetime_by_35ms_it_should_throw() + { + // Arrange + DateTime time = new DateTime(2016, 06, 04).At(12, 15, 31, 035); + DateTime nearbyTime = new DateTime(2016, 06, 04).At(12, 15, 31); + + // Act + Action act = () => time.Should().NotBeCloseTo(nearbyTime, 35.Milliseconds()); + + // Assert + act.Should().Throw() + .WithMessage( + "Did not expect time to be within 35ms from <2016-06-04 12:15:31>, but it was <2016-06-04 12:15:31.035>."); + } + + [Fact] + public void When_asserting_subject_null_datetime_is_not_close_to_another_it_should_throw() + { + // Arrange + DateTime? time = null; + DateTime nearbyTime = new DateTime(2016, 06, 04).At(12, 15, 31); + + // Act + Action act = () => time.Should().NotBeCloseTo(nearbyTime, 35.Milliseconds()); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect*, but it was ."); + } + + [Fact] + public void When_asserting_subject_datetime_is_not_close_to_the_minimum_datetime_it_should_throw() + { + // Arrange + DateTime time = DateTime.MinValue + 50.Milliseconds(); + DateTime nearbyTime = DateTime.MinValue; + + // Act + Action act = () => time.Should().NotBeCloseTo(nearbyTime, 100.Milliseconds()); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect time to be within 100ms from <0001-01-01 00:00:00.000>, but it was <00:00:00.050>."); + } + + [Fact] + public void When_asserting_subject_datetime_is_not_close_to_the_maximum_datetime_it_should_throw() + { + // Arrange + DateTime time = DateTime.MaxValue - 50.Milliseconds(); + DateTime nearbyTime = DateTime.MaxValue; + + // Act + Action act = () => time.Should().NotBeCloseTo(nearbyTime, 100.Milliseconds()); + + // Assert + act.Should().Throw() + .WithMessage( + "Did not expect time to be within 100ms from <9999-12-31 23:59:59.9999999>, but it was <9999-12-31 23:59:59.9499999>."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeExactly.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeExactly.cs new file mode 100644 index 0000000000..6b5ceb3740 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeExactly.cs @@ -0,0 +1,69 @@ +using System; +using FluentAssertions.Extensions; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeAssertionSpecs +{ + public class BeExactly + { + [Fact] + public void When_time_is_not_at_exactly_20_minutes_before_another_time_it_should_throw() + { + // Arrange + DateTime target = 1.January(0001).At(12, 55); + DateTime subject = 1.January(0001).At(12, 36); + + // Act + Action act = + () => subject.Should().BeExactly(TimeSpan.FromMinutes(20)).Before(target, "{0} minutes is enough", 20); + + // Assert + act.Should().Throw().WithMessage( + "Expected subject <12:36:00> to be exactly 20m before <12:55:00> because 20 minutes is enough, but it is behind by 19m."); + } + + [Fact] + public void When_time_is_exactly_90_seconds_before_another_time_it_should_not_throw() + { + // Arrange + DateTime target = 1.January(0001).At(12, 55); + DateTime subject = 1.January(0001).At(12, 53, 30); + + // Act / Assert + subject.Should().BeExactly(TimeSpan.FromSeconds(90)).Before(target); + } + + [Fact] + public void When_asserting_subject_be_exactly_10_seconds_after_target_but_subject_is_before_target_it_should_throw() + { + // Arrange + var expectation = 1.January(0001).At(0, 0, 30); + var subject = 1.January(0001).At(0, 0, 20); + + // Ac + Action action = () => subject.Should().BeExactly(10.Seconds()).After(expectation); + + // Assert + action.Should().Throw() + .WithMessage("Expected subject <00:00:20> to be exactly 10s after <00:00:30>, but it is behind by 10s."); + } + + [Fact] + public void When_asserting_subject_be_exactly_10_seconds_before_target_but_subject_is_after_target_it_should_throw() + { + // Arrange + var expectation = 1.January(0001).At(0, 0, 30); + var subject = 1.January(0001).At(0, 0, 40); + + // Act + Action action = () => subject.Should().BeExactly(10.Seconds()).Before(expectation); + + // Assert + action.Should().Throw() + .WithMessage("Expected subject <00:00:40> to be exactly 10s before <00:00:30>, but it is ahead by 10s."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeIn.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeIn.cs new file mode 100644 index 0000000000..0d7fdc16cb --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeIn.cs @@ -0,0 +1,99 @@ +using System; +using FluentAssertions.Execution; +using FluentAssertions.Extensions; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeAssertionSpecs +{ + public class BeIn + { + [Fact] + public void When_asserting_subject_datetime_represents_its_own_kind_it_should_succeed() + { + // Arrange + DateTime subject = new(2009, 12, 31, 23, 59, 00, DateTimeKind.Local); + + // Act + Action act = () => subject.Should().BeIn(DateTimeKind.Local); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_datetime_represents_a_different_kind_it_should_throw() + { + // Arrange + DateTime subject = new(2009, 12, 31, 23, 59, 00, DateTimeKind.Local); + + // Act + Action act = () => subject.Should().BeIn(DateTimeKind.Utc); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be in Utc, but found Local."); + } + + [Fact] + public void When_asserting_subject_null_datetime_represents_a_specific_kind_it_should_throw() + { + // Arrange + DateTime? subject = null; + + // Act + Action act = () => subject.Should().BeIn(DateTimeKind.Utc); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be in Utc, but found a DateTime."); + } + } + + public class NotBeIn + { + [Fact] + public void Date_is_not_in_kind() + { + // Arrange + DateTime subject = 5.January(2024).AsLocal(); + + // Act / Assert + subject.Should().NotBeIn(DateTimeKind.Utc); + } + + [Fact] + public void Date_is_in_kind_but_should_not() + { + // Arrange + DateTime subject = 5.January(2024).AsLocal(); + + // Act + Action act = () => subject.Should().NotBeIn(DateTimeKind.Local); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect subject to be in Local, but it was."); + } + + [Fact] + public void Date_is_null_on_kind_check() + { + // Arrange + DateTime? subject = null; + + // Act + Action act = () => + { + using var _ = new AssertionScope(); + subject.Should().NotBeIn(DateTimeKind.Utc); + }; + + // Assert + act.Should().Throw() + .WithMessage("Did not**"); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeLessThan.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeLessThan.cs new file mode 100644 index 0000000000..dd94faea31 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeLessThan.cs @@ -0,0 +1,69 @@ +using System; +using FluentAssertions.Extensions; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeAssertionSpecs +{ + public class BeLessThan + { + [Fact] + public void When_time_is_not_less_than_30s_after_another_time_it_should_throw() + { + // Arrange + var target = new DateTime(1, 1, 1, 12, 0, 30); + DateTime subject = target + 30.Seconds(); + + // Act + Action act = + () => subject.Should().BeLessThan(TimeSpan.FromSeconds(30)).After(target, "{0}s is the max", 30); + + // Assert + act.Should().Throw().WithMessage( + "Expected subject <12:01:00> to be less than 30s after <12:00:30> because 30s is the max, but it is ahead by 30s."); + } + + [Fact] + public void When_time_is_less_than_30s_after_another_time_it_should_not_throw() + { + // Arrange + var target = new DateTime(1, 1, 1, 12, 0, 30); + DateTime subject = target + 20.Seconds(); + + // Act / Assert + subject.Should().BeLessThan(TimeSpan.FromSeconds(30)).After(target); + } + + [Fact] + public void When_asserting_subject_be_less_than_10_seconds_after_target_but_subject_is_before_target_it_should_throw() + { + // Arrange + var expectation = 1.January(0001).At(0, 0, 30); + var subject = 1.January(0001).At(0, 0, 25); + + // Act + Action action = () => subject.Should().BeLessThan(10.Seconds()).After(expectation); + + // Assert + action.Should().Throw() + .WithMessage("Expected subject <00:00:25> to be less than 10s after <00:00:30>, but it is behind by 5s."); + } + + [Fact] + public void When_asserting_subject_be_less_than_10_seconds_before_target_but_subject_is_after_target_it_should_throw() + { + // Arrange + var expectation = 1.January(0001).At(0, 0, 30); + var subject = 1.January(0001).At(0, 0, 45); + + // Act + Action action = () => subject.Should().BeLessThan(10.Seconds()).Before(expectation); + + // Assert + action.Should().Throw() + .WithMessage("Expected subject <00:00:45> to be less than 10s before <00:00:30>, but it is ahead by 15s."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeMoreThan.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeMoreThan.cs new file mode 100644 index 0000000000..8de4d2ba23 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeMoreThan.cs @@ -0,0 +1,68 @@ +using System; +using FluentAssertions.Extensions; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeAssertionSpecs +{ + public class BeMoreThan + { + [Fact] + public void When_date_is_not_more_than_the_required_one_day_before_another_it_should_throw() + { + // Arrange + var target = new DateTime(2009, 10, 2); + DateTime subject = target - 1.Days(); + + // Act + Action act = () => subject.Should().BeMoreThan(TimeSpan.FromDays(1)).Before(target, "we like {0}", "that"); + + // Assert + act.Should().Throw().WithMessage( + "Expected subject <2009-10-01> to be more than 1d before <2009-10-02> because we like that, but it is behind by 1d."); + } + + [Fact] + public void When_date_is_more_than_the_required_one_day_before_another_it_should_not_throw() + { + // Arrange + var target = new DateTime(2009, 10, 2); + DateTime subject = target - 25.Hours(); + + // Act / Assert + subject.Should().BeMoreThan(TimeSpan.FromDays(1)).Before(target); + } + + [Fact] + public void When_asserting_subject_be_more_than_10_seconds_after_target_but_subject_is_before_target_it_should_throw() + { + // Arrange + var expectation = 1.January(0001).At(0, 0, 30); + var subject = 1.January(0001).At(0, 0, 15); + + // Act + Action action = () => subject.Should().BeMoreThan(10.Seconds()).After(expectation); + + // Assert + action.Should().Throw() + .WithMessage("Expected subject <00:00:15> to be more than 10s after <00:00:30>, but it is behind by 15s."); + } + + [Fact] + public void When_asserting_subject_be_more_than_10_seconds_before_target_but_subject_is_after_target_it_should_throw() + { + // Arrange + var expectation = 1.January(0001).At(0, 0, 30); + var subject = 1.January(0001).At(0, 0, 45); + + // Act + Action action = () => subject.Should().BeMoreThan(10.Seconds()).Before(expectation); + + // Assert + action.Should().Throw() + .WithMessage("Expected subject <00:00:45> to be more than 10s before <00:00:30>, but it is ahead by 15s."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeNull.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeNull.cs new file mode 100644 index 0000000000..637a3ebd47 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeNull.cs @@ -0,0 +1,68 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeAssertionSpecs +{ + public class BeNull + { + [Fact] + public void Should_succeed_when_asserting_nullable_datetime_value_without_a_value_to_be_null() + { + // Arrange + DateTime? nullableDateTime = null; + + // Act + Action action = () => + nullableDateTime.Should().BeNull(); + + // Assert + action.Should().NotThrow(); + } + + [Fact] + public void Should_fail_when_asserting_nullable_datetime_value_with_a_value_to_be_null() + { + // Arrange + DateTime? nullableDateTime = new DateTime(2016, 06, 04); + + // Act + Action action = () => + nullableDateTime.Should().BeNull(); + + // Assert + action.Should().Throw(); + } + } + + public class NotBeNull + { + [Fact] + public void Should_succeed_when_asserting_nullable_datetime_value_with_a_value_to_not_be_null() + { + // Arrange + DateTime? nullableDateTime = new DateTime(2016, 06, 04); + + // Act + Action action = () => nullableDateTime.Should().NotBeNull(); + + // Assert + action.Should().NotThrow(); + } + + [Fact] + public void Should_fail_when_asserting_nullable_datetime_value_without_a_value_to_not_be_null() + { + // Arrange + DateTime? nullableDateTime = null; + + // Act + Action action = () => nullableDateTime.Should().NotBeNull(); + + // Assert + action.Should().Throw(); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeOnOrAfter.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeOnOrAfter.cs new file mode 100644 index 0000000000..83a826f815 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeOnOrAfter.cs @@ -0,0 +1,101 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeAssertionSpecs +{ + public class BeOnOrAfter + { + [Fact] + public void When_asserting_subject_datetime_is_on_or_after_earlier_expected_datetime_should_succeed() + { + // Arrange + DateTime subject = new(2016, 06, 04); + DateTime expectation = new(2016, 06, 03); + + // Act + Action act = () => subject.Should().BeOnOrAfter(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_datetime_is_on_or_after_the_same_expected_datetime_should_succeed() + { + // Arrange + DateTime subject = new(2016, 06, 04); + DateTime expectation = new(2016, 06, 04); + + // Act + Action act = () => subject.Should().BeOnOrAfter(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_datetime_is_on_or_after_later_expected_datetime_should_throw() + { + // Arrange + DateTime subject = new(2016, 06, 04); + DateTime expectation = new(2016, 06, 05); + + // Act + Action act = () => subject.Should().BeOnOrAfter(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be on or after <2016-06-05>, but found <2016-06-04>."); + } + } + + public class NotBeOnOrAfter + { + [Fact] + public void When_asserting_subject_datetime_is_not_on_or_after_earlier_expected_datetime_should_throw() + { + // Arrange + DateTime subject = new(2016, 06, 04); + DateTime expectation = new(2016, 06, 03); + + // Act + Action act = () => subject.Should().NotBeOnOrAfter(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be before <2016-06-03>, but found <2016-06-04>."); + } + + [Fact] + public void When_asserting_subject_datetime_is_not_on_or_after_the_same_expected_datetime_should_throw() + { + // Arrange + DateTime subject = new(2016, 06, 04); + DateTime expectation = new(2016, 06, 04); + + // Act + Action act = () => subject.Should().NotBeOnOrAfter(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be before <2016-06-04>, but found <2016-06-04>."); + } + + [Fact] + public void When_asserting_subject_datetime_is_not_on_or_after_later_expected_datetime_should_succeed() + { + // Arrange + DateTime subject = new(2016, 06, 04); + DateTime expectation = new(2016, 06, 05); + + // Act + Action act = () => subject.Should().NotBeOnOrAfter(expectation); + + // Assert + act.Should().NotThrow(); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeOnOrBefore.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeOnOrBefore.cs new file mode 100644 index 0000000000..10a02b0259 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeOnOrBefore.cs @@ -0,0 +1,101 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeAssertionSpecs +{ + public class BeOnOrBefore + { + [Fact] + public void When_asserting_subject_datetime_is_on_or_before_expected_datetime_should_succeed() + { + // Arrange + DateTime subject = new(2016, 06, 04); + DateTime expectation = new(2016, 06, 05); + + // Act + Action act = () => subject.Should().BeOnOrBefore(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_datetime_is_on_or_before_the_same_date_as_the_expected_datetime_should_succeed() + { + // Arrange + DateTime subject = new(2016, 06, 04); + DateTime expectation = new(2016, 06, 04); + + // Act + Action act = () => subject.Should().BeOnOrBefore(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_datetime_is_not_on_or_before_earlier_expected_datetime_should_throw() + { + // Arrange + DateTime subject = new(2016, 06, 04); + DateTime expectation = new(2016, 06, 03); + + // Act + Action act = () => subject.Should().BeOnOrBefore(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be on or before <2016-06-03>, but found <2016-06-04>."); + } + } + + public class NotBeOnOrBefore + { + [Fact] + public void When_asserting_subject_datetime_is_on_or_before_expected_datetime_should_throw() + { + // Arrange + DateTime subject = new(2016, 06, 04); + DateTime expectation = new(2016, 06, 05); + + // Act + Action act = () => subject.Should().NotBeOnOrBefore(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be after <2016-06-05>, but found <2016-06-04>."); + } + + [Fact] + public void When_asserting_subject_datetime_is_on_or_before_the_same_date_as_the_expected_datetime_should_throw() + { + // Arrange + DateTime subject = new(2016, 06, 04); + DateTime expectation = new(2016, 06, 04); + + // Act + Action act = () => subject.Should().NotBeOnOrBefore(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be after <2016-06-04>, but found <2016-06-04>."); + } + + [Fact] + public void When_asserting_subject_datetime_is_not_on_or_before_earlier_expected_datetime_should_succeed() + { + // Arrange + DateTime subject = new(2016, 06, 04); + DateTime expectation = new(2016, 06, 03); + + // Act + Action act = () => subject.Should().NotBeOnOrBefore(expectation); + + // Assert + act.Should().NotThrow(); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeOneOf.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeOneOf.cs new file mode 100644 index 0000000000..b40921b8e4 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeOneOf.cs @@ -0,0 +1,84 @@ +using System; +using FluentAssertions.Extensions; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeAssertionSpecs +{ + public class BeOneOf + { + [Fact] + public void When_a_value_is_not_one_of_the_specified_values_it_should_throw() + { + // Arrange + DateTime value = new(2016, 12, 30, 23, 58, 57); + + // Act + Action action = () => value.Should().BeOneOf(value + 1.Days(), value + 1.Milliseconds()); + + // Assert + action.Should().Throw() + .WithMessage( + "Expected value to be one of {<2016-12-31 23:58:57>, <2016-12-30 23:58:57.001>}, but found <2016-12-30 23:58:57>."); + } + + [Fact] + public void When_a_value_is_not_one_of_the_specified_values_it_should_throw_with_descriptive_message() + { + // Arrange + DateTime value = new(2016, 12, 30, 23, 58, 57); + + // Act + Action action = () => + value.Should().BeOneOf(new[] { value + 1.Days(), value + 1.Milliseconds() }, "because it's true"); + + // Assert + action.Should().Throw() + .WithMessage( + "Expected value to be one of {<2016-12-31 23:58:57>, <2016-12-30 23:58:57.001>} because it's true, but found <2016-12-30 23:58:57>."); + } + + [Fact] + public void When_a_value_is_one_of_the_specified_values_it_should_succeed() + { + // Arrange + DateTime value = new(2016, 12, 30, 23, 58, 57); + + // Act + Action action = () => value.Should().BeOneOf(new DateTime(2216, 1, 30, 0, 5, 7), + new DateTime(2016, 12, 30, 23, 58, 57), new DateTime(2012, 3, 3)); + + // Assert + action.Should().NotThrow(); + } + + [Fact] + public void When_a_null_value_is_not_one_of_the_specified_values_it_should_throw() + { + // Arrange + DateTime? value = null; + + // Act + Action action = () => value.Should().BeOneOf(new DateTime(2216, 1, 30, 0, 5, 7), new DateTime(1116, 4, 10, 2, 45, 7)); + + // Assert + action.Should().Throw() + .WithMessage("Expected value to be one of {<2216-01-30 00:05:07>, <1116-04-10 02:45:07>}, but found ."); + } + + [Fact] + public void When_a_value_is_one_of_the_specified_values_it_should_succeed_when_datetime_is_null() + { + // Arrange + DateTime? value = null; + + // Act + Action action = () => value.Should().BeOneOf(new DateTime(2216, 1, 30, 0, 5, 7), null); + + // Assert + action.Should().NotThrow(); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeSameDateAs.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeSameDateAs.cs new file mode 100644 index 0000000000..b4d2d6a6ca --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeSameDateAs.cs @@ -0,0 +1,125 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeAssertionSpecs +{ + public class BeSameDateAs + { + [Fact] + public void When_asserting_subject_datetime_should_be_same_date_as_another_with_the_same_date_it_should_succeed() + { + // Arrange + var subject = new DateTime(2009, 12, 31, 4, 5, 6); + + // Act + Action act = () => subject.Should().BeSameDateAs(new DateTime(2009, 12, 31)); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void + When_asserting_subject_datetime_should_be_same_as_another_with_same_date_but_different_time_it_should_succeed() + { + // Arrange + var subject = new DateTime(2009, 12, 31, 4, 5, 6); + + // Act + Action act = () => subject.Should().BeSameDateAs(new DateTime(2009, 12, 31, 11, 15, 11)); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_null_datetime_to_be_same_date_as_another_datetime_it_should_throw() + { + // Arrange + DateTime? subject = null; + + // Act + Action act = () => subject.Should().BeSameDateAs(new DateTime(2009, 12, 31)); + + // Assert + act.Should().Throw().WithMessage( + "Expected the date part of subject to be <2009-12-31>, but found a DateTime."); + } + + [Fact] + public void When_asserting_subject_datetime_should_have_same_date_as_another_but_it_doesnt_it_should_throw() + { + // Arrange + var subject = new DateTime(2009, 12, 31); + + // Act + Action act = () => subject.Should().BeSameDateAs(new DateTime(2009, 12, 30)); + + // Assert + act.Should().Throw().WithMessage( + "Expected the date part of subject to be <2009-12-30>, but found <2009-12-31>."); + } + } + + public class NotBeSameDateAs + { + [Fact] + public void When_asserting_subject_datetime_should_not_be_same_date_as_another_with_the_same_date_it_should_throw() + { + // Arrange + var subject = new DateTime(2009, 12, 31, 4, 5, 6); + + // Act + Action act = () => subject.Should().NotBeSameDateAs(new DateTime(2009, 12, 31)); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the date part of subject to be <2009-12-31>, but it was."); + } + + [Fact] + public void + When_asserting_subject_datetime_should_not_be_same_as_another_with_same_date_but_different_time_it_should_throw() + { + // Arrange + var subject = new DateTime(2009, 12, 31, 4, 5, 6); + + // Act + Action act = () => subject.Should().NotBeSameDateAs(new DateTime(2009, 12, 31, 11, 15, 11)); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the date part of subject to be <2009-12-31>, but it was."); + } + + [Fact] + public void When_asserting_subject_null_datetime_to_not_be_same_date_as_another_datetime_it_should_throw() + { + // Arrange + DateTime? subject = null; + + // Act + Action act = () => subject.Should().NotBeSameDateAs(new DateTime(2009, 12, 31)); + + // Assert + act.Should().Throw().WithMessage( + "Did not expect the date part of subject to be <2009-12-31>, but found a DateTime."); + } + + [Fact] + public void When_asserting_subject_datetime_should_not_have_same_date_as_another_but_it_doesnt_it_should_succeed() + { + // Arrange + var subject = new DateTime(2009, 12, 31); + + // Act + Action act = () => subject.Should().NotBeSameDateAs(new DateTime(2009, 12, 30)); + + // Assert + act.Should().NotThrow(); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeWithin.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeWithin.cs new file mode 100644 index 0000000000..2535f16383 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.BeWithin.cs @@ -0,0 +1,148 @@ +using System; +using FluentAssertions.Extensions; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeAssertionSpecs +{ + public class BeWithin + { + [Fact] + public void When_date_is_not_within_50_hours_before_another_date_it_should_throw() + { + // Arrange + var target = new DateTime(2010, 4, 10, 12, 0, 0); + DateTime subject = target - 50.Hours() - 1.Seconds(); + + // Act + Action act = + () => subject.Should().BeWithin(TimeSpan.FromHours(50)).Before(target, "{0} hours is enough", 50); + + // Assert + act.Should().Throw().WithMessage( + "Expected subject <2010-04-08 09:59:59> to be within 2d and 2h before <2010-04-10 12:00:00> because 50 hours is enough, but it is behind by 2d, 2h and 1s."); + } + + [Fact] + public void When_date_is_exactly_within_1d_before_another_date_it_should_not_throw() + { + // Arrange + var target = new DateTime(2010, 4, 10); + DateTime subject = target - 1.Days(); + + // Act / Assert + subject.Should().BeWithin(TimeSpan.FromHours(24)).Before(target); + } + + [Fact] + public void When_date_is_within_1d_before_another_date_it_should_not_throw() + { + // Arrange + var target = new DateTime(2010, 4, 10); + DateTime subject = target - 23.Hours(); + + // Act / Assert + subject.Should().BeWithin(TimeSpan.FromHours(24)).Before(target); + } + + [Fact] + public void When_a_utc_date_is_within_0s_before_itself_it_should_not_throw() + { + // Arrange + var date = DateTime.UtcNow; // local timezone differs from UTC + + // Act / Assert + date.Should().BeWithin(TimeSpan.Zero).Before(date); + } + + [Fact] + public void When_a_utc_date_is_within_0s_after_itself_it_should_not_throw() + { + // Arrange + var date = DateTime.UtcNow; // local timezone differs from UTC + + // Act / Assert + date.Should().BeWithin(TimeSpan.Zero).After(date); + } + + [Theory] + [InlineData(30, 20)] // edge case + [InlineData(30, 25)] + public void When_asserting_subject_be_within_10_seconds_after_target_but_subject_is_before_target_it_should_throw( + int targetSeconds, int subjectSeconds) + { + // Arrange + var expectation = 1.January(0001).At(0, 0, targetSeconds); + var subject = 1.January(0001).At(0, 0, subjectSeconds); + + // Act + Action action = () => subject.Should().BeWithin(10.Seconds()).After(expectation); + + // Assert + action.Should().Throw() + .WithMessage( + $"Expected subject <00:00:{subjectSeconds}> to be within 10s after <00:00:30>, but it is behind by {Math.Abs(subjectSeconds - targetSeconds)}s."); + } + + [Theory] + [InlineData(30, 40)] // edge case + [InlineData(30, 35)] + public void When_asserting_subject_be_within_10_seconds_before_target_but_subject_is_after_target_it_should_throw( + int targetSeconds, int subjectSeconds) + { + // Arrange + var expectation = 1.January(0001).At(0, 0, targetSeconds); + var subject = 1.January(0001).At(0, 0, subjectSeconds); + + // Act + Action action = () => subject.Should().BeWithin(10.Seconds()).Before(expectation); + + // Assert + action.Should().Throw() + .WithMessage( + $"Expected subject <00:00:{subjectSeconds}> to be within 10s before <00:00:30>, but it is ahead by {Math.Abs(subjectSeconds - targetSeconds)}s."); + } + + [Fact] + public void Should_throw_because_of_assertion_failure_when_asserting_null_is_within_second_before_specific_date() + { + // Arrange + DateTimeOffset? nullDateTime = null; + DateTimeOffset target = new(2000, 1, 1, 12, 0, 0, TimeSpan.Zero); + + // Act + Action action = () => + nullDateTime.Should() + .BeWithin(TimeSpan.FromSeconds(1)) + .Before(target); + + // Assert + action.Should().Throw() + .Which.Message + .Should().StartWith( + "Expected nullDateTime to be within 1s before <2000-01-01 12:00:00 +0h>, but found a DateTime"); + } + + [Fact] + public void Should_throw_because_of_assertion_failure_when_asserting_null_is_within_second_after_specific_date() + { + // Arrange + DateTimeOffset? nullDateTime = null; + DateTimeOffset target = new(2000, 1, 1, 12, 0, 0, TimeSpan.Zero); + + // Act + Action action = () => + nullDateTime.Should() + .BeWithin(TimeSpan.FromSeconds(1)) + .After(target); + + // Assert + action.Should().Throw() + .Which.Message + .Should().StartWith( + "Expected nullDateTime to be within 1s after <2000-01-01 12:00:00 +0h>, but found a DateTime"); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.HaveDay.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.HaveDay.cs new file mode 100644 index 0000000000..30603317df --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.HaveDay.cs @@ -0,0 +1,102 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeAssertionSpecs +{ + public class HaveDay + { + [Fact] + public void When_asserting_subject_datetime_should_have_day_with_the_same_value_it_should_succeed() + { + // Arrange + DateTime subject = new(2009, 12, 31); + int expectation = 31; + + // Act + Action act = () => subject.Should().HaveDay(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_datetime_should_have_day_with_a_different_value_it_should_throw() + { + // Arrange + DateTime subject = new(2009, 12, 31); + int expectation = 30; + + // Act + Action act = () => subject.Should().HaveDay(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the day part of subject to be 30, but found 31."); + } + + [Fact] + public void When_asserting_subject_null_datetime_should_have_day_should_throw() + { + // Arrange + DateTime? subject = null; + int expectation = 22; + + // Act + Action act = () => subject.Should().HaveDay(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the day part of subject to be 22, but found a DateTime."); + } + } + + public class NotHaveDay + { + [Fact] + public void When_asserting_subject_datetime_should_not_have_day_with_the_same_value_it_should_throw() + { + // Arrange + DateTime subject = new(2009, 12, 31); + int expectation = 31; + + // Act + Action act = () => subject.Should().NotHaveDay(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the day part of subject to be 31, but it was."); + } + + [Fact] + public void When_asserting_subject_datetime_should_not_have_day_with_a_different_value_it_should_succeed() + { + // Arrange + DateTime subject = new(2009, 12, 31); + int expectation = 30; + + // Act + Action act = () => subject.Should().NotHaveDay(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_null_datetime_should_not_have_day_should_throw() + { + // Arrange + DateTime? subject = null; + int expectation = 22; + + // Act + Action act = () => subject.Should().NotHaveDay(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the day part of subject to be 22, but found a DateTime."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.HaveHour.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.HaveHour.cs new file mode 100644 index 0000000000..0274c5c742 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.HaveHour.cs @@ -0,0 +1,102 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeAssertionSpecs +{ + public class HaveHour + { + [Fact] + public void When_asserting_subject_datetime_should_have_hour_with_the_same_value_it_should_succeed() + { + // Arrange + DateTime subject = new(2009, 12, 31, 23, 59, 00); + int expectation = 23; + + // Act + Action act = () => subject.Should().HaveHour(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_datetime_should_have_hour_with_different_value_it_should_throw() + { + // Arrange + DateTime subject = new(2009, 12, 31, 23, 59, 00); + int expectation = 22; + + // Act + Action act = () => subject.Should().HaveHour(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the hour part of subject to be 22, but found 23."); + } + + [Fact] + public void When_asserting_subject_null_datetime_should_have_hour_should_throw() + { + // Arrange + DateTime? subject = null; + int expectation = 22; + + // Act + Action act = () => subject.Should().HaveHour(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the hour part of subject to be 22, but found a DateTime."); + } + } + + public class NotHaveHour + { + [Fact] + public void When_asserting_subject_datetime_should_not_have_hour_with_the_same_value_it_should_throw() + { + // Arrange + DateTime subject = new(2009, 12, 31, 23, 59, 00); + int expectation = 23; + + // Act + Action act = () => subject.Should().NotHaveHour(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the hour part of subject to be 23, but it was."); + } + + [Fact] + public void When_asserting_subject_datetime_should_not_have_hour_with_different_value_it_should_succeed() + { + // Arrange + DateTime subject = new(2009, 12, 31, 23, 59, 00); + int expectation = 22; + + // Act + Action act = () => subject.Should().NotHaveHour(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_null_datetime_should_not_have_hour_should_throw() + { + // Arrange + DateTime? subject = null; + int expectation = 22; + + // Act + Action act = () => subject.Should().NotHaveHour(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the hour part of subject to be 22, but found a DateTime."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.HaveMinute.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.HaveMinute.cs new file mode 100644 index 0000000000..7773e521e1 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.HaveMinute.cs @@ -0,0 +1,102 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeAssertionSpecs +{ + public class HaveMinute + { + [Fact] + public void When_asserting_subject_datetime_should_have_minutes_with_the_same_value_it_should_succeed() + { + // Arrange + DateTime subject = new(2009, 12, 31, 23, 59, 00); + int expectation = 59; + + // Act + Action act = () => subject.Should().HaveMinute(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_datetime_should_have_minutes_with_different_value_it_should_throw() + { + // Arrange + DateTime subject = new(2009, 12, 31, 23, 59, 00); + int expectation = 58; + + // Act + Action act = () => subject.Should().HaveMinute(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the minute part of subject to be 58, but found 59."); + } + + [Fact] + public void When_asserting_subject_null_datetime_should_have_minute_should_throw() + { + // Arrange + DateTime? subject = null; + int expectation = 22; + + // Act + Action act = () => subject.Should().HaveMinute(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the minute part of subject to be 22, but found a DateTime."); + } + } + + public class NotHaveMinute + { + [Fact] + public void When_asserting_subject_datetime_should_not_have_minutes_with_the_same_value_it_should_throw() + { + // Arrange + DateTime subject = new(2009, 12, 31, 23, 59, 00); + int expectation = 59; + + // Act + Action act = () => subject.Should().NotHaveMinute(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the minute part of subject to be 59, but it was."); + } + + [Fact] + public void When_asserting_subject_datetime_should_not_have_minutes_with_different_value_it_should_succeed() + { + // Arrange + DateTime subject = new(2009, 12, 31, 23, 59, 00); + int expectation = 58; + + // Act + Action act = () => subject.Should().NotHaveMinute(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_null_datetime_should_not_have_minute_should_throw() + { + // Arrange + DateTime? subject = null; + int expectation = 22; + + // Act + Action act = () => subject.Should().NotHaveMinute(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the minute part of subject to be 22, but found a DateTime."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.HaveMonth.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.HaveMonth.cs new file mode 100644 index 0000000000..a8f34933f4 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.HaveMonth.cs @@ -0,0 +1,102 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeAssertionSpecs +{ + public class HaveMonth + { + [Fact] + public void When_asserting_subject_datetime_should_have_month_with_the_same_value_it_should_succeed() + { + // Arrange + DateTime subject = new(2009, 12, 31); + int expectation = 12; + + // Act + Action act = () => subject.Should().HaveMonth(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_datetime_should_have_a_month_with_a_different_value_it_should_throw() + { + // Arrange + DateTime subject = new(2009, 12, 31); + int expectation = 11; + + // Act + Action act = () => subject.Should().HaveMonth(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the month part of subject to be 11, but found 12."); + } + + [Fact] + public void When_asserting_subject_null_datetime_should_have_month_should_throw() + { + // Arrange + DateTime? subject = null; + int expectation = 12; + + // Act + Action act = () => subject.Should().HaveMonth(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the month part of subject to be 12, but found a DateTime."); + } + } + + public class NotHaveMonth + { + [Fact] + public void When_asserting_subject_datetime_should_not_have_month_with_the_same_value_it_should_throw() + { + // Arrange + DateTime subject = new(2009, 12, 31); + int expectation = 12; + + // Act + Action act = () => subject.Should().NotHaveMonth(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the month part of subject to be 12, but it was."); + } + + [Fact] + public void When_asserting_subject_datetime_should_not_have_a_month_with_a_different_value_it_should_succeed() + { + // Arrange + DateTime subject = new(2009, 12, 31); + int expectation = 11; + + // Act + Action act = () => subject.Should().NotHaveMonth(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_null_datetime_should_not_have_month_should_throw() + { + // Arrange + DateTime? subject = null; + int expectation = 12; + + // Act + Action act = () => subject.Should().NotHaveMonth(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the month part of subject to be 12, but found a DateTime."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.HaveSecond.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.HaveSecond.cs new file mode 100644 index 0000000000..547896c9c0 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.HaveSecond.cs @@ -0,0 +1,102 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeAssertionSpecs +{ + public class HaveSecond + { + [Fact] + public void When_asserting_subject_datetime_should_have_seconds_with_the_same_value_it_should_succeed() + { + // Arrange + DateTime subject = new(2009, 12, 31, 23, 59, 00); + int expectation = 0; + + // Act + Action act = () => subject.Should().HaveSecond(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_datetime_should_have_seconds_with_different_value_it_should_throw() + { + // Arrange + DateTime subject = new(2009, 12, 31, 23, 59, 00); + int expectation = 1; + + // Act + Action act = () => subject.Should().HaveSecond(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the seconds part of subject to be 1, but found 0."); + } + + [Fact] + public void When_asserting_subject_null_datetime_should_have_second_should_throw() + { + // Arrange + DateTime? subject = null; + int expectation = 22; + + // Act + Action act = () => subject.Should().HaveSecond(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the seconds part of subject to be 22, but found a DateTime."); + } + } + + public class NotHaveSecond + { + [Fact] + public void When_asserting_subject_datetime_should_not_have_seconds_with_the_same_value_it_should_throw() + { + // Arrange + DateTime subject = new(2009, 12, 31, 23, 59, 00); + int expectation = 0; + + // Act + Action act = () => subject.Should().NotHaveSecond(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the seconds part of subject to be 0, but it was."); + } + + [Fact] + public void When_asserting_subject_datetime_should_not_have_seconds_with_different_value_it_should_succeed() + { + // Arrange + DateTime subject = new(2009, 12, 31, 23, 59, 00); + int expectation = 1; + + // Act + Action act = () => subject.Should().NotHaveSecond(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_null_datetime_should_not_have_second_should_throw() + { + // Arrange + DateTime? subject = null; + int expectation = 22; + + // Act + Action act = () => subject.Should().NotHaveSecond(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the seconds part of subject to be 22, but found a DateTime."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.HaveValue.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.HaveValue.cs new file mode 100644 index 0000000000..1e017562b1 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.HaveValue.cs @@ -0,0 +1,68 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeAssertionSpecs +{ + public class HaveValue + { + [Fact] + public void Should_succeed_when_asserting_nullable_datetime_value_with_a_value_to_have_a_value() + { + // Arrange + DateTime? nullableDateTime = new DateTime(2016, 06, 04); + + // Act + Action action = () => nullableDateTime.Should().HaveValue(); + + // Assert + action.Should().NotThrow(); + } + + [Fact] + public void Should_fail_when_asserting_nullable_datetime_value_without_a_value_to_have_a_value() + { + // Arrange + DateTime? nullableDateTime = null; + + // Act + Action action = () => nullableDateTime.Should().HaveValue(); + + // Assert + action.Should().Throw(); + } + } + + public class NotHaveValue + { + [Fact] + public void Should_succeed_when_asserting_nullable_datetime_value_without_a_value_to_not_have_a_value() + { + // Arrange + DateTime? nullableDateTime = null; + + // Act + Action action = () => + nullableDateTime.Should().NotHaveValue(); + + // Assert + action.Should().NotThrow(); + } + + [Fact] + public void Should_fail_when_asserting_nullable_datetime_value_with_a_value_to_not_have_a_value() + { + // Arrange + DateTime? nullableDateTime = new DateTime(2016, 06, 04); + + // Act + Action action = () => + nullableDateTime.Should().NotHaveValue(); + + // Assert + action.Should().Throw(); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.HaveYear.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.HaveYear.cs new file mode 100644 index 0000000000..93743a3909 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.HaveYear.cs @@ -0,0 +1,102 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeAssertionSpecs +{ + public class HaveYear + { + [Fact] + public void When_asserting_subject_datetime_should_have_year_with_the_same_value_should_succeed() + { + // Arrange + DateTime subject = new(2009, 12, 31); + int expectation = 2009; + + // Act + Action act = () => subject.Should().HaveYear(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_datetime_should_have_year_with_a_different_value_should_throw() + { + // Arrange + DateTime subject = new(2009, 12, 31); + int expectation = 2008; + + // Act + Action act = () => subject.Should().HaveYear(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the year part of subject to be 2008, but found 2009."); + } + + [Fact] + public void When_asserting_subject_null_datetime_should_have_year_should_throw() + { + // Arrange + DateTime? subject = null; + int expectation = 2008; + + // Act + Action act = () => subject.Should().HaveYear(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the year part of subject to be 2008, but found ."); + } + } + + public class NotHaveYear + { + [Fact] + public void When_asserting_subject_datetime_should_not_have_year_with_the_same_value_should_throw() + { + // Arrange + DateTime subject = new(2009, 12, 31); + int expectation = 2009; + + // Act + Action act = () => subject.Should().NotHaveYear(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the year part of subject to be 2009, but it was."); + } + + [Fact] + public void When_asserting_subject_datetime_should_not_have_year_with_a_different_value_should_succeed() + { + // Arrange + DateTime subject = new(2009, 12, 31); + int expectation = 2008; + + // Act + Action act = () => subject.Should().NotHaveYear(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_null_datetime_should_not_have_year_should_throw() + { + // Arrange + DateTime? subject = null; + int expectation = 2008; + + // Act + Action act = () => subject.Should().NotHaveYear(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the year part of subject to be 2008, but found a DateTime."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.cs index e45d43156c..dcc8e89521 100644 --- a/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeAssertionSpecs.cs @@ -1,2231 +1,11 @@ using System; using FluentAssertions.Extensions; using Xunit; -using Xunit.Sdk; namespace FluentAssertions.Specs.Primitives; -public class DateTimeAssertionSpecs +public partial class DateTimeAssertionSpecs { - public class HaveValue - { - [Fact] - public void Should_succeed_when_asserting_nullable_datetime_value_with_a_value_to_have_a_value() - { - // Arrange - DateTime? nullableDateTime = new DateTime(2016, 06, 04); - - // Act - Action action = () => nullableDateTime.Should().HaveValue(); - - // Assert - action.Should().NotThrow(); - } - - [Fact] - public void Should_fail_when_asserting_nullable_datetime_value_without_a_value_to_have_a_value() - { - // Arrange - DateTime? nullableDateTime = null; - - // Act - Action action = () => nullableDateTime.Should().HaveValue(); - - // Assert - action.Should().Throw(); - } - } - - public class NotHaveValue - { - [Fact] - public void Should_succeed_when_asserting_nullable_datetime_value_without_a_value_to_not_have_a_value() - { - // Arrange - DateTime? nullableDateTime = null; - - // Act - Action action = () => - nullableDateTime.Should().NotHaveValue(); - - // Assert - action.Should().NotThrow(); - } - - [Fact] - public void Should_fail_when_asserting_nullable_datetime_value_with_a_value_to_not_have_a_value() - { - // Arrange - DateTime? nullableDateTime = new DateTime(2016, 06, 04); - - // Act - Action action = () => - nullableDateTime.Should().NotHaveValue(); - - // Assert - action.Should().Throw(); - } - } - - public class NotBeNull - { - [Fact] - public void Should_succeed_when_asserting_nullable_datetime_value_with_a_value_to_not_be_null() - { - // Arrange - DateTime? nullableDateTime = new DateTime(2016, 06, 04); - - // Act - Action action = () => nullableDateTime.Should().NotBeNull(); - - // Assert - action.Should().NotThrow(); - } - - [Fact] - public void Should_fail_when_asserting_nullable_datetime_value_without_a_value_to_not_be_null() - { - // Arrange - DateTime? nullableDateTime = null; - - // Act - Action action = () => nullableDateTime.Should().NotBeNull(); - - // Assert - action.Should().Throw(); - } - } - - public class BeNull - { - [Fact] - public void Should_succeed_when_asserting_nullable_datetime_value_without_a_value_to_be_null() - { - // Arrange - DateTime? nullableDateTime = null; - - // Act - Action action = () => - nullableDateTime.Should().BeNull(); - - // Assert - action.Should().NotThrow(); - } - - [Fact] - public void Should_fail_when_asserting_nullable_datetime_value_with_a_value_to_be_null() - { - // Arrange - DateTime? nullableDateTime = new DateTime(2016, 06, 04); - - // Act - Action action = () => - nullableDateTime.Should().BeNull(); - - // Assert - action.Should().Throw(); - } - } - - public class Be - { - [Fact] - public void Should_succeed_when_asserting_datetime_value_is_equal_to_the_same_value() - { - // Arrange - DateTime dateTime = new(2016, 06, 04); - DateTime sameDateTime = new(2016, 06, 04); - - // Act - Action act = () => dateTime.Should().Be(sameDateTime); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_datetime_value_is_equal_to_the_same_nullable_value_be_should_succeed() - { - // Arrange - DateTime dateTime = 4.June(2016); - DateTime? sameDateTime = 4.June(2016); - - // Act - Action act = () => dateTime.Should().Be(sameDateTime); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_both_values_are_at_their_minimum_then_it_should_succeed() - { - // Arrange - DateTime dateTime = DateTime.MinValue; - DateTime sameDateTime = DateTime.MinValue; - - // Act - Action act = () => dateTime.Should().Be(sameDateTime); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_both_values_are_at_their_maximum_then_it_should_succeed() - { - // Arrange - DateTime dateTime = DateTime.MaxValue; - DateTime sameDateTime = DateTime.MaxValue; - - // Act - Action act = () => dateTime.Should().Be(sameDateTime); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void Should_fail_when_asserting_datetime_value_is_equal_to_the_different_value() - { - // Arrange - var dateTime = new DateTime(2012, 03, 10); - var otherDateTime = new DateTime(2012, 03, 11); - - // Act - Action act = () => dateTime.Should().Be(otherDateTime, "because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw() - .WithMessage("Expected dateTime to be <2012-03-11>*failure message, but found <2012-03-10>."); - } - - [Fact] - public void When_datetime_value_is_equal_to_the_different_nullable_value_be_should_failed() - { - // Arrange - DateTime dateTime = 10.March(2012); - DateTime? otherDateTime = 11.March(2012); - - // Act - Action act = () => dateTime.Should().Be(otherDateTime, "because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw() - .WithMessage("Expected dateTime to be <2012-03-11>*failure message, but found <2012-03-10>."); - } - - [Fact] - public void Should_succeed_when_asserting_nullable_numeric_value_equals_the_same_value() - { - // Arrange - DateTime? nullableDateTimeA = new DateTime(2016, 06, 04); - DateTime? nullableDateTimeB = new DateTime(2016, 06, 04); - - // Act - Action action = () => - nullableDateTimeA.Should().Be(nullableDateTimeB); - - // Assert - action.Should().NotThrow(); - } - - [Fact] - public void When_a_nullable_date_time_is_equal_to_a_normal_date_time_but_the_kinds_differ_it_should_still_succeed() - { - // Arrange - DateTime? nullableDateTime = new DateTime(2014, 4, 20, 9, 11, 0, DateTimeKind.Unspecified); - DateTime normalDateTime = new(2014, 4, 20, 9, 11, 0, DateTimeKind.Utc); - - // Act - Action action = () => - nullableDateTime.Should().Be(normalDateTime); - - // Assert - action.Should().NotThrow(); - } - - [Fact] - public void When_two_date_times_are_equal_but_the_kinds_differ_it_should_still_succeed() - { - // Arrange - DateTime dateTimeA = new(2014, 4, 20, 9, 11, 0, DateTimeKind.Unspecified); - DateTime dateTimeB = new(2014, 4, 20, 9, 11, 0, DateTimeKind.Utc); - - // Act - Action action = () => - dateTimeA.Should().Be(dateTimeB); - - // Assert - action.Should().NotThrow(); - } - - [Fact] - public void Should_succeed_when_asserting_nullable_numeric_null_value_equals_null() - { - // Arrange - DateTime? nullableDateTimeA = null; - DateTime? nullableDateTimeB = null; - - // Act - Action action = () => - nullableDateTimeA.Should().Be(nullableDateTimeB); - - // Assert - action.Should().NotThrow(); - } - - [Fact] - public void Should_fail_when_asserting_nullable_numeric_value_equals_a_different_value() - { - // Arrange - DateTime? nullableDateTimeA = new DateTime(2016, 06, 04); - DateTime? nullableDateTimeB = new DateTime(2016, 06, 06); - - // Act - Action action = () => - nullableDateTimeA.Should().Be(nullableDateTimeB); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void Should_fail_with_descriptive_message_when_asserting_datetime_null_value_is_equal_to_another_value() - { - // Arrange - DateTime? nullableDateTime = null; - - // Act - Action action = () => - nullableDateTime.Should().Be(new DateTime(2016, 06, 04), "because we want to test the failure {0}", "message"); - - // Assert - action.Should().Throw() - .WithMessage( - "Expected nullableDateTime to be <2016-06-04> because we want to test the failure message, but found ."); - } - } - - public class NotBe - { - [Fact] - public void Should_succeed_when_asserting_datetime_value_is_not_equal_to_a_different_value() - { - // Arrange - DateTime dateTime = new(2016, 06, 04); - DateTime otherDateTime = new(2016, 06, 05); - - // Act - Action act = () => dateTime.Should().NotBe(otherDateTime); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_datetime_value_is_not_equal_to_a_different_nullable_value_notbe_should_succeed() - { - // Arrange - DateTime dateTime = 4.June(2016); - DateTime? otherDateTime = 5.June(2016); - - // Act - Action act = () => dateTime.Should().NotBe(otherDateTime); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void Should_fail_when_asserting_datetime_value_is_not_equal_to_the_same_value() - { - // Arrange - var dateTime = DateTime.SpecifyKind(10.March(2012).At(10, 00), DateTimeKind.Local); - var sameDateTime = DateTime.SpecifyKind(10.March(2012).At(10, 00), DateTimeKind.Utc); - - // Act - Action act = - () => dateTime.Should().NotBe(sameDateTime, "because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw() - .WithMessage( - "Expected dateTime not to be <2012-03-10 10:00:00> because we want to test the failure message, but it is."); - } - - [Fact] - public void When_datetime_value_is_not_equal_to_the_same_nullable_value_notbe_should_failed() - { - // Arrange - DateTime dateTime = DateTime.SpecifyKind(10.March(2012).At(10, 00), DateTimeKind.Local); - DateTime? sameDateTime = DateTime.SpecifyKind(10.March(2012).At(10, 00), DateTimeKind.Utc); - - // Act - Action act = - () => dateTime.Should().NotBe(sameDateTime, "because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw() - .WithMessage( - "Expected dateTime not to be <2012-03-10 10:00:00> because we want to test the failure message, but it is."); - } - } - - public class BeCloseTo - { - [Fact] - public void When_asserting_that_time_is_close_to_a_negative_precision_it_should_throw() - { - // Arrange - var dateTime = DateTime.UtcNow; - var actual = new DateTime(dateTime.Ticks - 1); - - // Act - Action act = () => actual.Should().BeCloseTo(dateTime, -1.Ticks()); - - // Assert - act.Should().Throw() - .WithParameterName("precision") - .WithMessage("*must be non-negative*"); - } - - [Fact] - public void When_a_datetime_is_close_to_a_later_datetime_by_one_tick_it_should_succeed() - { - // Arrange - var dateTime = DateTime.UtcNow; - var actual = new DateTime(dateTime.Ticks - 1); - - // Act - Action act = () => actual.Should().BeCloseTo(dateTime, TimeSpan.FromTicks(1)); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_a_datetime_is_close_to_an_earlier_datetime_by_one_tick_it_should_succeed() - { - // Arrange - var dateTime = DateTime.UtcNow; - var actual = new DateTime(dateTime.Ticks + 1); - - // Act - Action act = () => actual.Should().BeCloseTo(dateTime, TimeSpan.FromTicks(1)); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_a_datetime_is_close_to_a_MinValue_by_one_tick_it_should_succeed() - { - // Arrange - var dateTime = DateTime.MinValue; - var actual = new DateTime(dateTime.Ticks + 1); - - // Act - Action act = () => actual.Should().BeCloseTo(dateTime, TimeSpan.FromTicks(1)); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_a_datetime_is_close_to_a_MaxValue_by_one_tick_it_should_succeed() - { - // Arrange - var dateTime = DateTime.MaxValue; - var actual = new DateTime(dateTime.Ticks - 1); - - // Act - Action act = () => actual.Should().BeCloseTo(dateTime, TimeSpan.FromTicks(1)); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_datetime_is_close_to_a_later_datetime_it_should_succeed() - { - // Arrange - DateTime time = DateTime.SpecifyKind(new DateTime(2016, 06, 04).At(12, 15, 30, 980), DateTimeKind.Unspecified); - DateTime nearbyTime = DateTime.SpecifyKind(new DateTime(2016, 06, 04).At(12, 15, 31), DateTimeKind.Utc); - - // Act - Action act = () => time.Should().BeCloseTo(nearbyTime, 20.Milliseconds()); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_datetime_is_close_to_an_earlier_datetime_it_should_succeed() - { - // Arrange - DateTime time = new DateTime(2016, 06, 04).At(12, 15, 31, 020); - DateTime nearbyTime = new DateTime(2016, 06, 04).At(12, 15, 31); - - // Act - Action act = () => time.Should().BeCloseTo(nearbyTime, 20.Milliseconds()); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_datetime_is_close_to_another_value_that_is_later_by_more_than_20ms_it_should_throw() - { - // Arrange - DateTime time = 13.March(2012).At(12, 15, 30, 979); - DateTime nearbyTime = 13.March(2012).At(12, 15, 31); - - // Act - Action act = () => time.Should().BeCloseTo(nearbyTime, 20.Milliseconds()); - - // Assert - act.Should().Throw() - .WithMessage( - "Expected time to be within 20ms from <2012-03-13 12:15:31>, but <2012-03-13 12:15:30.979> was off by 21ms."); - } - - [Fact] - public void - When_asserting_subject_datetime_is_close_to_another_value_that_is_later_by_more_than_a_20ms_timespan_it_should_throw() - { - // Arrange - DateTime time = 13.March(2012).At(12, 15, 30, 979); - DateTime nearbyTime = 13.March(2012).At(12, 15, 31); - - // Act - Action act = () => time.Should().BeCloseTo(nearbyTime, TimeSpan.FromMilliseconds(20)); - - // Assert - act.Should().Throw() - .WithMessage( - "Expected time to be within 20ms from <2012-03-13 12:15:31>, but <2012-03-13 12:15:30.979> was off by 21ms."); - } - - [Fact] - public void When_asserting_subject_datetime_is_close_to_another_value_that_is_earlier_by_more_than_20ms_it_should_throw() - { - // Arrange - DateTime time = 13.March(2012).At(12, 15, 31, 021); - DateTime nearbyTime = 13.March(2012).At(12, 15, 31); - - // Act - Action act = () => time.Should().BeCloseTo(nearbyTime, 20.Milliseconds()); - - // Assert - act.Should().Throw() - .WithMessage( - "Expected time to be within 20ms from <2012-03-13 12:15:31>, but <2012-03-13 12:15:31.021> was off by 21ms."); - } - - [Fact] - public void When_asserting_subject_datetime_is_close_to_an_earlier_datetime_by_35ms_it_should_succeed() - { - // Arrange - DateTime time = new DateTime(2016, 06, 04).At(12, 15, 31, 035); - DateTime nearbyTime = new DateTime(2016, 06, 04).At(12, 15, 31); - - // Act - Action act = () => time.Should().BeCloseTo(nearbyTime, 35.Milliseconds()); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_null_datetime_is_close_to_another_it_should_throw() - { - // Arrange - DateTime? time = null; - DateTime nearbyTime = new DateTime(2016, 06, 04).At(12, 15, 31); - - // Act - Action act = () => time.Should().BeCloseTo(nearbyTime, 35.Milliseconds()); - - // Assert - act.Should().Throw() - .WithMessage("Expected*, but found ."); - } - - [Fact] - public void When_asserting_subject_datetime_is_close_to_the_minimum_datetime_it_should_succeed() - { - // Arrange - DateTime time = DateTime.MinValue + 50.Milliseconds(); - DateTime nearbyTime = DateTime.MinValue; - - // Act - Action act = () => time.Should().BeCloseTo(nearbyTime, 100.Milliseconds()); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_datetime_is_close_to_the_maximum_datetime_it_should_succeed() - { - // Arrange - DateTime time = DateTime.MaxValue - 50.Milliseconds(); - DateTime nearbyTime = DateTime.MaxValue; - - // Act - Action act = () => time.Should().BeCloseTo(nearbyTime, 100.Milliseconds()); - - // Assert - act.Should().NotThrow(); - } - } - - public class NotBeCloseTo - { - [Fact] - public void When_asserting_that_time_is_not_close_to_a_negative_precision_it_should_throw() - { - // Arrange - var dateTime = DateTime.UtcNow; - var actual = new DateTime(dateTime.Ticks - 1); - - // Act - Action act = () => actual.Should().NotBeCloseTo(dateTime, -1.Ticks()); - - // Assert - act.Should().Throw() - .WithParameterName("precision") - .WithMessage("*must be non-negative*"); - } - - [Fact] - public void When_a_datetime_is_close_to_a_later_datetime_by_one_tick_it_should_fail() - { - // Arrange - var dateTime = DateTime.UtcNow; - var actual = new DateTime(dateTime.Ticks - 1); - - // Act - Action act = () => actual.Should().NotBeCloseTo(dateTime, TimeSpan.FromTicks(1)); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_a_datetime_is_close_to_an_earlier_datetime_by_one_tick_it_should_fail() - { - // Arrange - var dateTime = DateTime.UtcNow; - var actual = new DateTime(dateTime.Ticks + 1); - - // Act - Action act = () => actual.Should().NotBeCloseTo(dateTime, TimeSpan.FromTicks(1)); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_a_datetime_is_close_to_a_MinValue_by_one_tick_it_should_fail() - { - // Arrange - var dateTime = DateTime.MinValue; - var actual = new DateTime(dateTime.Ticks + 1); - - // Act - Action act = () => actual.Should().NotBeCloseTo(dateTime, TimeSpan.FromTicks(1)); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_a_datetime_is_close_to_a_MaxValue_by_one_tick_it_should_fail() - { - // Arrange - var dateTime = DateTime.MaxValue; - var actual = new DateTime(dateTime.Ticks - 1); - - // Act - Action act = () => actual.Should().NotBeCloseTo(dateTime, TimeSpan.FromTicks(1)); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_asserting_subject_datetime_is_not_close_to_a_later_datetime_it_should_throw() - { - // Arrange - DateTime time = DateTime.SpecifyKind(new DateTime(2016, 06, 04).At(12, 15, 30, 980), DateTimeKind.Unspecified); - DateTime nearbyTime = DateTime.SpecifyKind(new DateTime(2016, 06, 04).At(12, 15, 31), DateTimeKind.Utc); - - // Act - Action act = () => time.Should().NotBeCloseTo(nearbyTime, 20.Milliseconds()); - - // Assert - act.Should().Throw() - .WithMessage( - "Did not expect time to be within 20ms from <2016-06-04 12:15:31>, but it was <2016-06-04 12:15:30.980>."); - } - - [Fact] - public void When_asserting_subject_datetime_is_not_close_to_an_earlier_datetime_it_should_throw() - { - // Arrange - DateTime time = new DateTime(2016, 06, 04).At(12, 15, 31, 020); - DateTime nearbyTime = new DateTime(2016, 06, 04).At(12, 15, 31); - - // Act - Action act = () => time.Should().NotBeCloseTo(nearbyTime, 20.Milliseconds()); - - // Assert - act.Should().Throw() - .WithMessage( - "Did not expect time to be within 20ms from <2016-06-04 12:15:31>, but it was <2016-06-04 12:15:31.020>."); - } - - [Fact] - public void When_asserting_subject_datetime_is_not_close_to_an_earlier_datetime_by_a_20ms_timespan_it_should_throw() - { - // Arrange - DateTime time = new DateTime(2016, 06, 04).At(12, 15, 31, 020); - DateTime nearbyTime = new DateTime(2016, 06, 04).At(12, 15, 31); - - // Act - Action act = () => time.Should().NotBeCloseTo(nearbyTime, TimeSpan.FromMilliseconds(20)); - - // Assert - act.Should().Throw() - .WithMessage( - "Did not expect time to be within 20ms from <2016-06-04 12:15:31>, but it was <2016-06-04 12:15:31.020>."); - } - - [Fact] - public void - When_asserting_subject_datetime_is_not_close_to_another_value_that_is_later_by_more_than_20ms_it_should_succeed() - { - // Arrange - DateTime time = 13.March(2012).At(12, 15, 30, 979); - DateTime nearbyTime = 13.March(2012).At(12, 15, 31); - - // Act - Action act = () => time.Should().NotBeCloseTo(nearbyTime, 20.Milliseconds()); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void - When_asserting_subject_datetime_is_not_close_to_another_value_that_is_earlier_by_more_than_20ms_it_should_succeed() - { - // Arrange - DateTime time = 13.March(2012).At(12, 15, 31, 021); - DateTime nearbyTime = 13.March(2012).At(12, 15, 31); - - // Act - Action act = () => time.Should().NotBeCloseTo(nearbyTime, 20.Milliseconds()); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_datetime_is_not_close_to_an_earlier_datetime_by_35ms_it_should_throw() - { - // Arrange - DateTime time = new DateTime(2016, 06, 04).At(12, 15, 31, 035); - DateTime nearbyTime = new DateTime(2016, 06, 04).At(12, 15, 31); - - // Act - Action act = () => time.Should().NotBeCloseTo(nearbyTime, 35.Milliseconds()); - - // Assert - act.Should().Throw() - .WithMessage( - "Did not expect time to be within 35ms from <2016-06-04 12:15:31>, but it was <2016-06-04 12:15:31.035>."); - } - - [Fact] - public void When_asserting_subject_null_datetime_is_not_close_to_another_it_should_throw() - { - // Arrange - DateTime? time = null; - DateTime nearbyTime = new DateTime(2016, 06, 04).At(12, 15, 31); - - // Act - Action act = () => time.Should().NotBeCloseTo(nearbyTime, 35.Milliseconds()); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect*, but it was ."); - } - - [Fact] - public void When_asserting_subject_datetime_is_not_close_to_the_minimum_datetime_it_should_throw() - { - // Arrange - DateTime time = DateTime.MinValue + 50.Milliseconds(); - DateTime nearbyTime = DateTime.MinValue; - - // Act - Action act = () => time.Should().NotBeCloseTo(nearbyTime, 100.Milliseconds()); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect time to be within 100ms from <0001-01-01 00:00:00.000>, but it was <00:00:00.050>."); - } - - [Fact] - public void When_asserting_subject_datetime_is_not_close_to_the_maximum_datetime_it_should_throw() - { - // Arrange - DateTime time = DateTime.MaxValue - 50.Milliseconds(); - DateTime nearbyTime = DateTime.MaxValue; - - // Act - Action act = () => time.Should().NotBeCloseTo(nearbyTime, 100.Milliseconds()); - - // Assert - act.Should().Throw() - .WithMessage( - "Did not expect time to be within 100ms from <9999-12-31 23:59:59.9999999>, but it was <9999-12-31 23:59:59.9499999>."); - } - } - - public class BeBefore - { - [Fact] - public void When_asserting_a_point_of_time_is_before_a_later_point_it_should_succeed() - { - // Arrange - DateTime earlierDate = DateTime.SpecifyKind(new DateTime(2016, 06, 04), DateTimeKind.Unspecified); - DateTime laterDate = DateTime.SpecifyKind(new DateTime(2016, 06, 04, 0, 5, 0), DateTimeKind.Utc); - - // Act - Action act = () => earlierDate.Should().BeBefore(laterDate); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_is_before_earlier_expected_datetime_it_should_throw() - { - // Arrange - DateTime expected = new(2016, 06, 03); - DateTime subject = new(2016, 06, 04); - - // Act - Action act = () => subject.Should().BeBefore(expected); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be before <2016-06-03>, but found <2016-06-04>."); - } - - [Fact] - public void When_asserting_subject_datetime_is_before_the_same_datetime_it_should_throw() - { - // Arrange - DateTime expected = new(2016, 06, 04); - DateTime subject = new(2016, 06, 04); - - // Act - Action act = () => subject.Should().BeBefore(expected); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be before <2016-06-04>, but found <2016-06-04>."); - } - } - - public class NotBeBefore - { - [Fact] - public void When_asserting_a_point_of_time_is_not_before_another_it_should_throw() - { - // Arrange - DateTime earlierDate = DateTime.SpecifyKind(new DateTime(2016, 06, 04), DateTimeKind.Unspecified); - DateTime laterDate = DateTime.SpecifyKind(new DateTime(2016, 06, 04, 0, 5, 0), DateTimeKind.Utc); - - // Act - Action act = () => earlierDate.Should().NotBeBefore(laterDate); - - // Assert - act.Should().Throw() - .WithMessage("Expected earlierDate to be on or after <2016-06-04 00:05:00>, but found <2016-06-04>."); - } - - [Fact] - public void When_asserting_subject_is_not_before_earlier_expected_datetime_it_should_succeed() - { - // Arrange - DateTime expected = new(2016, 06, 03); - DateTime subject = new(2016, 06, 04); - - // Act - Action act = () => subject.Should().NotBeBefore(expected); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_datetime_is_not_before_the_same_datetime_it_should_succeed() - { - // Arrange - DateTime expected = new(2016, 06, 04); - DateTime subject = new(2016, 06, 04); - - // Act - Action act = () => subject.Should().NotBeBefore(expected); - - // Assert - act.Should().NotThrow(); - } - } - - public class BeOnOrBefore - { - [Fact] - public void When_asserting_subject_datetime_is_on_or_before_expected_datetime_should_succeed() - { - // Arrange - DateTime subject = new(2016, 06, 04); - DateTime expectation = new(2016, 06, 05); - - // Act - Action act = () => subject.Should().BeOnOrBefore(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_datetime_is_on_or_before_the_same_date_as_the_expected_datetime_should_succeed() - { - // Arrange - DateTime subject = new(2016, 06, 04); - DateTime expectation = new(2016, 06, 04); - - // Act - Action act = () => subject.Should().BeOnOrBefore(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_datetime_is_not_on_or_before_earlier_expected_datetime_should_throw() - { - // Arrange - DateTime subject = new(2016, 06, 04); - DateTime expectation = new(2016, 06, 03); - - // Act - Action act = () => subject.Should().BeOnOrBefore(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be on or before <2016-06-03>, but found <2016-06-04>."); - } - } - - public class NotBeOnOrBefore - { - [Fact] - public void When_asserting_subject_datetime_is_on_or_before_expected_datetime_should_throw() - { - // Arrange - DateTime subject = new(2016, 06, 04); - DateTime expectation = new(2016, 06, 05); - - // Act - Action act = () => subject.Should().NotBeOnOrBefore(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be after <2016-06-05>, but found <2016-06-04>."); - } - - [Fact] - public void When_asserting_subject_datetime_is_on_or_before_the_same_date_as_the_expected_datetime_should_throw() - { - // Arrange - DateTime subject = new(2016, 06, 04); - DateTime expectation = new(2016, 06, 04); - - // Act - Action act = () => subject.Should().NotBeOnOrBefore(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be after <2016-06-04>, but found <2016-06-04>."); - } - - [Fact] - public void When_asserting_subject_datetime_is_not_on_or_before_earlier_expected_datetime_should_succeed() - { - // Arrange - DateTime subject = new(2016, 06, 04); - DateTime expectation = new(2016, 06, 03); - - // Act - Action act = () => subject.Should().NotBeOnOrBefore(expectation); - - // Assert - act.Should().NotThrow(); - } - } - - public class BeAfter - { - [Fact] - public void When_asserting_subject_datetime_is_after_earlier_expected_datetime_should_succeed() - { - // Arrange - DateTime subject = new(2016, 06, 04); - DateTime expectation = new(2016, 06, 03); - - // Act - Action act = () => subject.Should().BeAfter(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_datetime_is_after_later_expected_datetime_should_throw() - { - // Arrange - DateTime subject = new(2016, 06, 04); - DateTime expectation = new(2016, 06, 05); - - // Act - Action act = () => subject.Should().BeAfter(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be after <2016-06-05>, but found <2016-06-04>."); - } - - [Fact] - public void When_asserting_subject_datetime_is_after_the_same_expected_datetime_should_throw() - { - // Arrange - DateTime subject = new(2016, 06, 04); - DateTime expectation = new(2016, 06, 04); - - // Act - Action act = () => subject.Should().BeAfter(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be after <2016-06-04>, but found <2016-06-04>."); - } - } - - public class NotBeAfter - { - [Fact] - public void When_asserting_subject_datetime_is_not_after_earlier_expected_datetime_should_throw() - { - // Arrange - DateTime subject = new(2016, 06, 04); - DateTime expectation = new(2016, 06, 03); - - // Act - Action act = () => subject.Should().NotBeAfter(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be on or before <2016-06-03>, but found <2016-06-04>."); - } - - [Fact] - public void When_asserting_subject_datetime_is_not_after_later_expected_datetime_should_succeed() - { - // Arrange - DateTime subject = new(2016, 06, 04); - DateTime expectation = new(2016, 06, 05); - - // Act - Action act = () => subject.Should().NotBeAfter(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_datetime_is_not_after_the_same_expected_datetime_should_succeed() - { - // Arrange - DateTime subject = new(2016, 06, 04); - DateTime expectation = new(2016, 06, 04); - - // Act - Action act = () => subject.Should().NotBeAfter(expectation); - - // Assert - act.Should().NotThrow(); - } - } - - public class BeOnOrAfter - { - [Fact] - public void When_asserting_subject_datetime_is_on_or_after_earlier_expected_datetime_should_succeed() - { - // Arrange - DateTime subject = new(2016, 06, 04); - DateTime expectation = new(2016, 06, 03); - - // Act - Action act = () => subject.Should().BeOnOrAfter(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_datetime_is_on_or_after_the_same_expected_datetime_should_succeed() - { - // Arrange - DateTime subject = new(2016, 06, 04); - DateTime expectation = new(2016, 06, 04); - - // Act - Action act = () => subject.Should().BeOnOrAfter(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_datetime_is_on_or_after_later_expected_datetime_should_throw() - { - // Arrange - DateTime subject = new(2016, 06, 04); - DateTime expectation = new(2016, 06, 05); - - // Act - Action act = () => subject.Should().BeOnOrAfter(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be on or after <2016-06-05>, but found <2016-06-04>."); - } - } - - public class NotBeOnOrAfter - { - [Fact] - public void When_asserting_subject_datetime_is_not_on_or_after_earlier_expected_datetime_should_throw() - { - // Arrange - DateTime subject = new(2016, 06, 04); - DateTime expectation = new(2016, 06, 03); - - // Act - Action act = () => subject.Should().NotBeOnOrAfter(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be before <2016-06-03>, but found <2016-06-04>."); - } - - [Fact] - public void When_asserting_subject_datetime_is_not_on_or_after_the_same_expected_datetime_should_throw() - { - // Arrange - DateTime subject = new(2016, 06, 04); - DateTime expectation = new(2016, 06, 04); - - // Act - Action act = () => subject.Should().NotBeOnOrAfter(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be before <2016-06-04>, but found <2016-06-04>."); - } - - [Fact] - public void When_asserting_subject_datetime_is_not_on_or_after_later_expected_datetime_should_succeed() - { - // Arrange - DateTime subject = new(2016, 06, 04); - DateTime expectation = new(2016, 06, 05); - - // Act - Action act = () => subject.Should().NotBeOnOrAfter(expectation); - - // Assert - act.Should().NotThrow(); - } - } - - public class HaveYear - { - [Fact] - public void When_asserting_subject_datetime_should_have_year_with_the_same_value_should_succeed() - { - // Arrange - DateTime subject = new(2009, 12, 31); - int expectation = 2009; - - // Act - Action act = () => subject.Should().HaveYear(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_datetime_should_have_year_with_a_different_value_should_throw() - { - // Arrange - DateTime subject = new(2009, 12, 31); - int expectation = 2008; - - // Act - Action act = () => subject.Should().HaveYear(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the year part of subject to be 2008, but found 2009."); - } - - [Fact] - public void When_asserting_subject_null_datetime_should_have_year_should_throw() - { - // Arrange - DateTime? subject = null; - int expectation = 2008; - - // Act - Action act = () => subject.Should().HaveYear(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the year part of subject to be 2008, but found ."); - } - } - - public class NotHaveYear - { - [Fact] - public void When_asserting_subject_datetime_should_not_have_year_with_the_same_value_should_throw() - { - // Arrange - DateTime subject = new(2009, 12, 31); - int expectation = 2009; - - // Act - Action act = () => subject.Should().NotHaveYear(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the year part of subject to be 2009, but it was."); - } - - [Fact] - public void When_asserting_subject_datetime_should_not_have_year_with_a_different_value_should_succeed() - { - // Arrange - DateTime subject = new(2009, 12, 31); - int expectation = 2008; - - // Act - Action act = () => subject.Should().NotHaveYear(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_null_datetime_should_not_have_year_should_throw() - { - // Arrange - DateTime? subject = null; - int expectation = 2008; - - // Act - Action act = () => subject.Should().NotHaveYear(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the year part of subject to be 2008, but found a DateTime."); - } - } - - public class HaveMonth - { - [Fact] - public void When_asserting_subject_datetime_should_have_month_with_the_same_value_it_should_succeed() - { - // Arrange - DateTime subject = new(2009, 12, 31); - int expectation = 12; - - // Act - Action act = () => subject.Should().HaveMonth(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_datetime_should_have_a_month_with_a_different_value_it_should_throw() - { - // Arrange - DateTime subject = new(2009, 12, 31); - int expectation = 11; - - // Act - Action act = () => subject.Should().HaveMonth(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the month part of subject to be 11, but found 12."); - } - - [Fact] - public void When_asserting_subject_null_datetime_should_have_month_should_throw() - { - // Arrange - DateTime? subject = null; - int expectation = 12; - - // Act - Action act = () => subject.Should().HaveMonth(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the month part of subject to be 12, but found a DateTime."); - } - } - - public class NotHaveMonth - { - [Fact] - public void When_asserting_subject_datetime_should_not_have_month_with_the_same_value_it_should_throw() - { - // Arrange - DateTime subject = new(2009, 12, 31); - int expectation = 12; - - // Act - Action act = () => subject.Should().NotHaveMonth(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the month part of subject to be 12, but it was."); - } - - [Fact] - public void When_asserting_subject_datetime_should_not_have_a_month_with_a_different_value_it_should_succeed() - { - // Arrange - DateTime subject = new(2009, 12, 31); - int expectation = 11; - - // Act - Action act = () => subject.Should().NotHaveMonth(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_null_datetime_should_not_have_month_should_throw() - { - // Arrange - DateTime? subject = null; - int expectation = 12; - - // Act - Action act = () => subject.Should().NotHaveMonth(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the month part of subject to be 12, but found a DateTime."); - } - } - - public class HaveDay - { - [Fact] - public void When_asserting_subject_datetime_should_have_day_with_the_same_value_it_should_succeed() - { - // Arrange - DateTime subject = new(2009, 12, 31); - int expectation = 31; - - // Act - Action act = () => subject.Should().HaveDay(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_datetime_should_have_day_with_a_different_value_it_should_throw() - { - // Arrange - DateTime subject = new(2009, 12, 31); - int expectation = 30; - - // Act - Action act = () => subject.Should().HaveDay(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the day part of subject to be 30, but found 31."); - } - - [Fact] - public void When_asserting_subject_null_datetime_should_have_day_should_throw() - { - // Arrange - DateTime? subject = null; - int expectation = 22; - - // Act - Action act = () => subject.Should().HaveDay(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the day part of subject to be 22, but found a DateTime."); - } - } - - public class NotHaveDay - { - [Fact] - public void When_asserting_subject_datetime_should_not_have_day_with_the_same_value_it_should_throw() - { - // Arrange - DateTime subject = new(2009, 12, 31); - int expectation = 31; - - // Act - Action act = () => subject.Should().NotHaveDay(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the day part of subject to be 31, but it was."); - } - - [Fact] - public void When_asserting_subject_datetime_should_not_have_day_with_a_different_value_it_should_succeed() - { - // Arrange - DateTime subject = new(2009, 12, 31); - int expectation = 30; - - // Act - Action act = () => subject.Should().NotHaveDay(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_null_datetime_should_not_have_day_should_throw() - { - // Arrange - DateTime? subject = null; - int expectation = 22; - - // Act - Action act = () => subject.Should().NotHaveDay(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the day part of subject to be 22, but found a DateTime."); - } - } - - public class HaveHour - { - [Fact] - public void When_asserting_subject_datetime_should_have_hour_with_the_same_value_it_should_succeed() - { - // Arrange - DateTime subject = new(2009, 12, 31, 23, 59, 00); - int expectation = 23; - - // Act - Action act = () => subject.Should().HaveHour(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_datetime_should_have_hour_with_different_value_it_should_throw() - { - // Arrange - DateTime subject = new(2009, 12, 31, 23, 59, 00); - int expectation = 22; - - // Act - Action act = () => subject.Should().HaveHour(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the hour part of subject to be 22, but found 23."); - } - - [Fact] - public void When_asserting_subject_null_datetime_should_have_hour_should_throw() - { - // Arrange - DateTime? subject = null; - int expectation = 22; - - // Act - Action act = () => subject.Should().HaveHour(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the hour part of subject to be 22, but found a DateTime."); - } - } - - public class NotHaveHour - { - [Fact] - public void When_asserting_subject_datetime_should_not_have_hour_with_the_same_value_it_should_throw() - { - // Arrange - DateTime subject = new(2009, 12, 31, 23, 59, 00); - int expectation = 23; - - // Act - Action act = () => subject.Should().NotHaveHour(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the hour part of subject to be 23, but it was."); - } - - [Fact] - public void When_asserting_subject_datetime_should_not_have_hour_with_different_value_it_should_succeed() - { - // Arrange - DateTime subject = new(2009, 12, 31, 23, 59, 00); - int expectation = 22; - - // Act - Action act = () => subject.Should().NotHaveHour(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_null_datetime_should_not_have_hour_should_throw() - { - // Arrange - DateTime? subject = null; - int expectation = 22; - - // Act - Action act = () => subject.Should().NotHaveHour(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the hour part of subject to be 22, but found a DateTime."); - } - } - - public class HaveMinute - { - [Fact] - public void When_asserting_subject_datetime_should_have_minutes_with_the_same_value_it_should_succeed() - { - // Arrange - DateTime subject = new(2009, 12, 31, 23, 59, 00); - int expectation = 59; - - // Act - Action act = () => subject.Should().HaveMinute(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_datetime_should_have_minutes_with_different_value_it_should_throw() - { - // Arrange - DateTime subject = new(2009, 12, 31, 23, 59, 00); - int expectation = 58; - - // Act - Action act = () => subject.Should().HaveMinute(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the minute part of subject to be 58, but found 59."); - } - - [Fact] - public void When_asserting_subject_null_datetime_should_have_minute_should_throw() - { - // Arrange - DateTime? subject = null; - int expectation = 22; - - // Act - Action act = () => subject.Should().HaveMinute(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the minute part of subject to be 22, but found a DateTime."); - } - } - - public class NotHaveMinute - { - [Fact] - public void When_asserting_subject_datetime_should_not_have_minutes_with_the_same_value_it_should_throw() - { - // Arrange - DateTime subject = new(2009, 12, 31, 23, 59, 00); - int expectation = 59; - - // Act - Action act = () => subject.Should().NotHaveMinute(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the minute part of subject to be 59, but it was."); - } - - [Fact] - public void When_asserting_subject_datetime_should_not_have_minutes_with_different_value_it_should_succeed() - { - // Arrange - DateTime subject = new(2009, 12, 31, 23, 59, 00); - int expectation = 58; - - // Act - Action act = () => subject.Should().NotHaveMinute(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_null_datetime_should_not_have_minute_should_throw() - { - // Arrange - DateTime? subject = null; - int expectation = 22; - - // Act - Action act = () => subject.Should().NotHaveMinute(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the minute part of subject to be 22, but found a DateTime."); - } - } - - public class HaveSecond - { - [Fact] - public void When_asserting_subject_datetime_should_have_seconds_with_the_same_value_it_should_succeed() - { - // Arrange - DateTime subject = new(2009, 12, 31, 23, 59, 00); - int expectation = 0; - - // Act - Action act = () => subject.Should().HaveSecond(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_datetime_should_have_seconds_with_different_value_it_should_throw() - { - // Arrange - DateTime subject = new(2009, 12, 31, 23, 59, 00); - int expectation = 1; - - // Act - Action act = () => subject.Should().HaveSecond(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the seconds part of subject to be 1, but found 0."); - } - - [Fact] - public void When_asserting_subject_null_datetime_should_have_second_should_throw() - { - // Arrange - DateTime? subject = null; - int expectation = 22; - - // Act - Action act = () => subject.Should().HaveSecond(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the seconds part of subject to be 22, but found a DateTime."); - } - } - - public class NotHaveSecond - { - [Fact] - public void When_asserting_subject_datetime_should_not_have_seconds_with_the_same_value_it_should_throw() - { - // Arrange - DateTime subject = new(2009, 12, 31, 23, 59, 00); - int expectation = 0; - - // Act - Action act = () => subject.Should().NotHaveSecond(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the seconds part of subject to be 0, but it was."); - } - - [Fact] - public void When_asserting_subject_datetime_should_not_have_seconds_with_different_value_it_should_succeed() - { - // Arrange - DateTime subject = new(2009, 12, 31, 23, 59, 00); - int expectation = 1; - - // Act - Action act = () => subject.Should().NotHaveSecond(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_null_datetime_should_not_have_second_should_throw() - { - // Arrange - DateTime? subject = null; - int expectation = 22; - - // Act - Action act = () => subject.Should().NotHaveSecond(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the seconds part of subject to be 22, but found a DateTime."); - } - } - - public class BeInUtcOrLocal - { - [Fact] - public void When_asserting_subject_datetime_represents_its_own_kind_it_should_succeed() - { - // Arrange - DateTime subject = new(2009, 12, 31, 23, 59, 00, DateTimeKind.Local); - - // Act - Action act = () => subject.Should().BeIn(DateTimeKind.Local); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_datetime_represents_a_different_kind_it_should_throw() - { - // Arrange - DateTime subject = new(2009, 12, 31, 23, 59, 00, DateTimeKind.Local); - - // Act - Action act = () => subject.Should().BeIn(DateTimeKind.Utc); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be in Utc, but found Local."); - } - - [Fact] - public void When_asserting_subject_null_datetime_represents_a_specific_kind_it_should_throw() - { - // Arrange - DateTime? subject = null; - - // Act - Action act = () => subject.Should().BeIn(DateTimeKind.Utc); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be in Utc, but found a DateTime."); - } - } - - public class BeSameDateAs - { - [Fact] - public void When_asserting_subject_datetime_should_be_same_date_as_another_with_the_same_date_it_should_succeed() - { - // Arrange - var subject = new DateTime(2009, 12, 31, 4, 5, 6); - - // Act - Action act = () => subject.Should().BeSameDateAs(new DateTime(2009, 12, 31)); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void - When_asserting_subject_datetime_should_be_same_as_another_with_same_date_but_different_time_it_should_succeed() - { - // Arrange - var subject = new DateTime(2009, 12, 31, 4, 5, 6); - - // Act - Action act = () => subject.Should().BeSameDateAs(new DateTime(2009, 12, 31, 11, 15, 11)); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_null_datetime_to_be_same_date_as_another_datetime_it_should_throw() - { - // Arrange - DateTime? subject = null; - - // Act - Action act = () => subject.Should().BeSameDateAs(new DateTime(2009, 12, 31)); - - // Assert - act.Should().Throw().WithMessage( - "Expected the date part of subject to be <2009-12-31>, but found a DateTime."); - } - - [Fact] - public void When_asserting_subject_datetime_should_have_same_date_as_another_but_it_doesnt_it_should_throw() - { - // Arrange - var subject = new DateTime(2009, 12, 31); - - // Act - Action act = () => subject.Should().BeSameDateAs(new DateTime(2009, 12, 30)); - - // Assert - act.Should().Throw().WithMessage( - "Expected the date part of subject to be <2009-12-30>, but found <2009-12-31>."); - } - } - - public class NotBeSameDateAs - { - [Fact] - public void When_asserting_subject_datetime_should_not_be_same_date_as_another_with_the_same_date_it_should_throw() - { - // Arrange - var subject = new DateTime(2009, 12, 31, 4, 5, 6); - - // Act - Action act = () => subject.Should().NotBeSameDateAs(new DateTime(2009, 12, 31)); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the date part of subject to be <2009-12-31>, but it was."); - } - - [Fact] - public void - When_asserting_subject_datetime_should_not_be_same_as_another_with_same_date_but_different_time_it_should_throw() - { - // Arrange - var subject = new DateTime(2009, 12, 31, 4, 5, 6); - - // Act - Action act = () => subject.Should().NotBeSameDateAs(new DateTime(2009, 12, 31, 11, 15, 11)); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the date part of subject to be <2009-12-31>, but it was."); - } - - [Fact] - public void When_asserting_subject_null_datetime_to_not_be_same_date_as_another_datetime_it_should_throw() - { - // Arrange - DateTime? subject = null; - - // Act - Action act = () => subject.Should().NotBeSameDateAs(new DateTime(2009, 12, 31)); - - // Assert - act.Should().Throw().WithMessage( - "Did not expect the date part of subject to be <2009-12-31>, but found a DateTime."); - } - - [Fact] - public void When_asserting_subject_datetime_should_not_have_same_date_as_another_but_it_doesnt_it_should_succeed() - { - // Arrange - var subject = new DateTime(2009, 12, 31); - - // Act - Action act = () => subject.Should().NotBeSameDateAs(new DateTime(2009, 12, 30)); - - // Assert - act.Should().NotThrow(); - } - } - - public class TimespanComparison - { - [Fact] - public void When_date_is_not_more_than_the_required_one_day_before_another_it_should_throw() - { - // Arrange - var target = new DateTime(2009, 10, 2); - DateTime subject = target - 1.Days(); - - // Act - Action act = () => subject.Should().BeMoreThan(TimeSpan.FromDays(1)).Before(target, "we like {0}", "that"); - - // Assert - act.Should().Throw().WithMessage( - "Expected subject <2009-10-01> to be more than 1d before <2009-10-02> because we like that, but it is behind by 1d."); - } - - [Fact] - public void When_date_is_more_than_the_required_one_day_before_another_it_should_not_throw() - { - // Arrange - var target = new DateTime(2009, 10, 2); - DateTime subject = target - 25.Hours(); - - // Act / Assert - subject.Should().BeMoreThan(TimeSpan.FromDays(1)).Before(target); - } - - [Fact] - public void When_date_is_not_at_least_one_day_before_another_it_should_throw() - { - // Arrange - var target = new DateTime(2009, 10, 2); - DateTime subject = target - 23.Hours(); - - // Act - Action act = () => subject.Should().BeAtLeast(TimeSpan.FromDays(1)).Before(target, "we like {0}", "that"); - - // Assert - act.Should().Throw().WithMessage( - "Expected subject <2009-10-01 01:00:00> to be at least 1d before <2009-10-02> because we like that, but it is behind by 23h."); - } - - [Fact] - public void When_date_is_at_least_one_day_before_another_it_should_not_throw() - { - // Arrange - var target = new DateTime(2009, 10, 2); - DateTime subject = target - 24.Hours(); - - // Act / Assert - subject.Should().BeAtLeast(TimeSpan.FromDays(1)).Before(target); - } - - [Fact] - public void When_time_is_not_at_exactly_20_minutes_before_another_time_it_should_throw() - { - // Arrange - DateTime target = 1.January(0001).At(12, 55); - DateTime subject = 1.January(0001).At(12, 36); - - // Act - Action act = - () => subject.Should().BeExactly(TimeSpan.FromMinutes(20)).Before(target, "{0} minutes is enough", 20); - - // Assert - act.Should().Throw().WithMessage( - "Expected subject <12:36:00> to be exactly 20m before <12:55:00> because 20 minutes is enough, but it is behind by 19m."); - } - - [Fact] - public void When_time_is_exactly_90_seconds_before_another_time_it_should_not_throw() - { - // Arrange - DateTime target = 1.January(0001).At(12, 55); - DateTime subject = 1.January(0001).At(12, 53, 30); - - // Act / Assert - subject.Should().BeExactly(TimeSpan.FromSeconds(90)).Before(target); - } - - [Fact] - public void When_date_is_not_within_50_hours_before_another_date_it_should_throw() - { - // Arrange - var target = new DateTime(2010, 4, 10, 12, 0, 0); - DateTime subject = target - 50.Hours() - 1.Seconds(); - - // Act - Action act = - () => subject.Should().BeWithin(TimeSpan.FromHours(50)).Before(target, "{0} hours is enough", 50); - - // Assert - act.Should().Throw().WithMessage( - "Expected subject <2010-04-08 09:59:59> to be within 2d and 2h before <2010-04-10 12:00:00> because 50 hours is enough, but it is behind by 2d, 2h and 1s."); - } - - [Fact] - public void When_date_is_exactly_within_1d_before_another_date_it_should_not_throw() - { - // Arrange - var target = new DateTime(2010, 4, 10); - DateTime subject = target - 1.Days(); - - // Act / Assert - subject.Should().BeWithin(TimeSpan.FromHours(24)).Before(target); - } - - [Fact] - public void When_date_is_within_1d_before_another_date_it_should_not_throw() - { - // Arrange - var target = new DateTime(2010, 4, 10); - DateTime subject = target - 23.Hours(); - - // Act / Assert - subject.Should().BeWithin(TimeSpan.FromHours(24)).Before(target); - } - - [Fact] - public void When_a_utc_date_is_within_0s_before_itself_it_should_not_throw() - { - // Arrange - var date = DateTime.UtcNow; // local timezone differs from UTC - - // Act / Assert - date.Should().BeWithin(TimeSpan.Zero).Before(date); - } - - [Fact] - public void When_a_utc_date_is_within_0s_after_itself_it_should_not_throw() - { - // Arrange - var date = DateTime.UtcNow; // local timezone differs from UTC - - // Act / Assert - date.Should().BeWithin(TimeSpan.Zero).After(date); - } - - [Fact] - public void When_time_is_not_less_than_30s_after_another_time_it_should_throw() - { - // Arrange - var target = new DateTime(1, 1, 1, 12, 0, 30); - DateTime subject = target + 30.Seconds(); - - // Act - Action act = - () => subject.Should().BeLessThan(TimeSpan.FromSeconds(30)).After(target, "{0}s is the max", 30); - - // Assert - act.Should().Throw().WithMessage( - "Expected subject <12:01:00> to be less than 30s after <12:00:30> because 30s is the max, but it is ahead by 30s."); - } - - [Fact] - public void When_time_is_less_than_30s_after_another_time_it_should_not_throw() - { - // Arrange - var target = new DateTime(1, 1, 1, 12, 0, 30); - DateTime subject = target + 20.Seconds(); - - // Act / Assert - subject.Should().BeLessThan(TimeSpan.FromSeconds(30)).After(target); - } - - [Fact] - public void When_asserting_subject_be_more_than_10_seconds_after_target_but_subject_is_before_target_it_should_throw() - { - // Arrange - var expectation = 1.January(0001).At(0, 0, 30); - var subject = 1.January(0001).At(0, 0, 15); - - // Act - Action action = () => subject.Should().BeMoreThan(10.Seconds()).After(expectation); - - // Assert - action.Should().Throw() - .WithMessage("Expected subject <00:00:15> to be more than 10s after <00:00:30>, but it is behind by 15s."); - } - - [Theory] - [InlineData(30, 20)] // edge case - [InlineData(30, 15)] - public void When_asserting_subject_be_at_least_10_seconds_after_target_but_subject_is_before_target_it_should_throw( - int targetSeconds, int subjectSeconds) - { - // Arrange - var expectation = 1.January(0001).At(0, 0, targetSeconds); - var subject = 1.January(0001).At(0, 0, subjectSeconds); - - // Act - Action action = () => subject.Should().BeAtLeast(10.Seconds()).After(expectation); - - // Assert - action.Should().Throw() - .WithMessage( - $"Expected subject <00:00:{subjectSeconds}> to be at least 10s after <00:00:30>, but it is behind by {Math.Abs(subjectSeconds - targetSeconds)}s."); - } - - [Fact] - public void When_asserting_subject_be_exactly_10_seconds_after_target_but_subject_is_before_target_it_should_throw() - { - // Arrange - var expectation = 1.January(0001).At(0, 0, 30); - var subject = 1.January(0001).At(0, 0, 20); - - // Ac - Action action = () => subject.Should().BeExactly(10.Seconds()).After(expectation); - - // Assert - action.Should().Throw() - .WithMessage("Expected subject <00:00:20> to be exactly 10s after <00:00:30>, but it is behind by 10s."); - } - - [Theory] - [InlineData(30, 20)] // edge case - [InlineData(30, 25)] - public void When_asserting_subject_be_within_10_seconds_after_target_but_subject_is_before_target_it_should_throw( - int targetSeconds, int subjectSeconds) - { - // Arrange - var expectation = 1.January(0001).At(0, 0, targetSeconds); - var subject = 1.January(0001).At(0, 0, subjectSeconds); - - // Act - Action action = () => subject.Should().BeWithin(10.Seconds()).After(expectation); - - // Assert - action.Should().Throw() - .WithMessage( - $"Expected subject <00:00:{subjectSeconds}> to be within 10s after <00:00:30>, but it is behind by {Math.Abs(subjectSeconds - targetSeconds)}s."); - } - - [Fact] - public void When_asserting_subject_be_less_than_10_seconds_after_target_but_subject_is_before_target_it_should_throw() - { - // Arrange - var expectation = 1.January(0001).At(0, 0, 30); - var subject = 1.January(0001).At(0, 0, 25); - - // Act - Action action = () => subject.Should().BeLessThan(10.Seconds()).After(expectation); - - // Assert - action.Should().Throw() - .WithMessage("Expected subject <00:00:25> to be less than 10s after <00:00:30>, but it is behind by 5s."); - } - - [Fact] - public void When_asserting_subject_be_more_than_10_seconds_before_target_but_subject_is_after_target_it_should_throw() - { - // Arrange - var expectation = 1.January(0001).At(0, 0, 30); - var subject = 1.January(0001).At(0, 0, 45); - - // Act - Action action = () => subject.Should().BeMoreThan(10.Seconds()).Before(expectation); - - // Assert - action.Should().Throw() - .WithMessage("Expected subject <00:00:45> to be more than 10s before <00:00:30>, but it is ahead by 15s."); - } - - [Theory] - [InlineData(30, 40)] // edge case - [InlineData(30, 45)] - public void When_asserting_subject_be_at_least_10_seconds_before_target_but_subject_is_after_target_it_should_throw( - int targetSeconds, int subjectSeconds) - { - // Arrange - var expectation = 1.January(0001).At(0, 0, targetSeconds); - var subject = 1.January(0001).At(0, 0, subjectSeconds); - - // Act - Action action = () => subject.Should().BeAtLeast(10.Seconds()).Before(expectation); - - // Assert - action.Should().Throw() - .WithMessage( - $"Expected subject <00:00:{subjectSeconds}> to be at least 10s before <00:00:30>, but it is ahead by {Math.Abs(subjectSeconds - targetSeconds)}s."); - } - - [Fact] - public void When_asserting_subject_be_exactly_10_seconds_before_target_but_subject_is_after_target_it_should_throw() - { - // Arrange - var expectation = 1.January(0001).At(0, 0, 30); - var subject = 1.January(0001).At(0, 0, 40); - - // Act - Action action = () => subject.Should().BeExactly(10.Seconds()).Before(expectation); - - // Assert - action.Should().Throw() - .WithMessage("Expected subject <00:00:40> to be exactly 10s before <00:00:30>, but it is ahead by 10s."); - } - - [Theory] - [InlineData(30, 40)] // edge case - [InlineData(30, 35)] - public void When_asserting_subject_be_within_10_seconds_before_target_but_subject_is_after_target_it_should_throw( - int targetSeconds, int subjectSeconds) - { - // Arrange - var expectation = 1.January(0001).At(0, 0, targetSeconds); - var subject = 1.January(0001).At(0, 0, subjectSeconds); - - // Act - Action action = () => subject.Should().BeWithin(10.Seconds()).Before(expectation); - - // Assert - action.Should().Throw() - .WithMessage( - $"Expected subject <00:00:{subjectSeconds}> to be within 10s before <00:00:30>, but it is ahead by {Math.Abs(subjectSeconds - targetSeconds)}s."); - } - - [Fact] - public void When_asserting_subject_be_less_than_10_seconds_before_target_but_subject_is_after_target_it_should_throw() - { - // Arrange - var expectation = 1.January(0001).At(0, 0, 30); - var subject = 1.January(0001).At(0, 0, 45); - - // Act - Action action = () => subject.Should().BeLessThan(10.Seconds()).Before(expectation); - - // Assert - action.Should().Throw() - .WithMessage("Expected subject <00:00:45> to be less than 10s before <00:00:30>, but it is ahead by 15s."); - } - } - public class ChainingConstraint { [Fact] @@ -2245,119 +25,6 @@ public void Should_support_chaining_constraints_with_and() // Assert action.Should().NotThrow(); } - - [Fact] - public void Should_throw_because_of_assertion_failure_when_asserting_null_is_within_second_before_specific_date() - { - // Arrange - DateTimeOffset? nullDateTime = null; - DateTimeOffset target = new DateTimeOffset(2000, 1, 1, 12, 0, 0, TimeSpan.Zero); - - // Act - Action action = () => - nullDateTime.Should() - .BeWithin(TimeSpan.FromSeconds(1)) - .Before(target); - - // Assert - action.Should().Throw() - .Which.Message - .Should().StartWith("Expected nullDateTime to be within 1s before <2000-01-01 12:00:00 +0h>, but found a DateTime"); - } - - [Fact] - public void Should_throw_because_of_assertion_failure_when_asserting_null_is_within_second_after_specific_date() - { - // Arrange - DateTimeOffset? nullDateTime = null; - DateTimeOffset target = new DateTimeOffset(2000, 1, 1, 12, 0, 0, TimeSpan.Zero); - - // Act - Action action = () => - nullDateTime.Should() - .BeWithin(TimeSpan.FromSeconds(1)) - .After(target); - - // Assert - action.Should().Throw() - .Which.Message - .Should().StartWith("Expected nullDateTime to be within 1s after <2000-01-01 12:00:00 +0h>, but found a DateTime"); - } - } - - public class BeOneOf - { - [Fact] - public void When_a_value_is_not_one_of_the_specified_values_it_should_throw() - { - // Arrange - DateTime value = new(2016, 12, 30, 23, 58, 57); - - // Act - Action action = () => value.Should().BeOneOf(value + 1.Days(), value + 1.Milliseconds()); - - // Assert - action.Should().Throw() - .WithMessage( - "Expected value to be one of {<2016-12-31 23:58:57>, <2016-12-30 23:58:57.001>}, but found <2016-12-30 23:58:57>."); - } - - [Fact] - public void When_a_value_is_not_one_of_the_specified_values_it_should_throw_with_descriptive_message() - { - // Arrange - DateTime value = new(2016, 12, 30, 23, 58, 57); - - // Act - Action action = () => - value.Should().BeOneOf(new[] { value + 1.Days(), value + 1.Milliseconds() }, "because it's true"); - - // Assert - action.Should().Throw() - .WithMessage( - "Expected value to be one of {<2016-12-31 23:58:57>, <2016-12-30 23:58:57.001>} because it's true, but found <2016-12-30 23:58:57>."); - } - - [Fact] - public void When_a_value_is_one_of_the_specified_values_it_should_succeed() - { - // Arrange - DateTime value = new(2016, 12, 30, 23, 58, 57); - - // Act - Action action = () => value.Should().BeOneOf(new DateTime(2216, 1, 30, 0, 5, 7), - new DateTime(2016, 12, 30, 23, 58, 57), new DateTime(2012, 3, 3)); - - // Assert - action.Should().NotThrow(); - } - - [Fact] - public void When_a_null_value_is_not_one_of_the_specified_values_it_should_throw() - { - // Arrange - DateTime? value = null; - - // Act - Action action = () => value.Should().BeOneOf(new DateTime(2216, 1, 30, 0, 5, 7), new DateTime(1116, 4, 10, 2, 45, 7)); - - // Assert - action.Should().Throw() - .WithMessage("Expected value to be one of {<2216-01-30 00:05:07>, <1116-04-10 02:45:07>}, but found ."); - } - - [Fact] - public void When_a_value_is_one_of_the_specified_values_it_should_succeed_when_datetime_is_null() - { - // Arrange - DateTime? value = null; - - // Act - Action action = () => value.Should().BeOneOf(new DateTime(2216, 1, 30, 0, 5, 7), null); - - // Assert - action.Should().NotThrow(); - } } public class Miscellaneous diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.Be.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.Be.cs new file mode 100644 index 0000000000..3ee00fe022 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.Be.cs @@ -0,0 +1,266 @@ +using System; +using FluentAssertions.Common; +using FluentAssertions.Extensions; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeOffsetAssertionSpecs +{ + public class Be + { + [Fact] + public void Should_succeed_when_asserting_datetimeoffset_value_is_equal_to_the_same_value() + { + // Arrange + DateTimeOffset dateTime = new DateTime(2016, 06, 04).ToDateTimeOffset(); + DateTimeOffset sameDateTime = new DateTime(2016, 06, 04).ToDateTimeOffset(); + + // Act + Action act = () => dateTime.Should().Be(sameDateTime); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_datetimeoffset_value_is_equal_to_the_same_nullable_value_be_should_succeed() + { + // Arrange + DateTimeOffset dateTime = 4.June(2016).ToDateTimeOffset(); + DateTimeOffset? sameDateTime = 4.June(2016).ToDateTimeOffset(); + + // Act + Action act = () => dateTime.Should().Be(sameDateTime); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_both_values_are_at_their_minimum_then_it_should_succeed() + { + // Arrange + DateTimeOffset dateTime = DateTimeOffset.MinValue; + DateTimeOffset sameDateTime = DateTimeOffset.MinValue; + + // Act + Action act = () => dateTime.Should().Be(sameDateTime); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_both_values_are_at_their_maximum_then_it_should_succeed() + { + // Arrange + DateTimeOffset dateTime = DateTimeOffset.MaxValue; + DateTimeOffset sameDateTime = DateTimeOffset.MaxValue; + + // Act + Action act = () => dateTime.Should().Be(sameDateTime); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void Should_fail_when_asserting_datetimeoffset_value_is_equal_to_the_different_value() + { + // Arrange + var dateTime = 10.March(2012).WithOffset(1.Hours()); + var otherDateTime = 11.March(2012).WithOffset(1.Hours()); + + // Act + Action act = () => dateTime.Should().Be(otherDateTime, "because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dateTime to represent the same point in time as <2012-03-11 +1h>*failure message, but <2012-03-10 +1h> does not."); + } + + [Fact] + public void When_datetimeoffset_value_is_equal_to_the_different_nullable_value_be_should_failed() + { + // Arrange + DateTimeOffset dateTime = 10.March(2012).WithOffset(1.Hours()); + DateTimeOffset? otherDateTime = 11.March(2012).WithOffset(1.Hours()); + + // Act + Action act = () => dateTime.Should().Be(otherDateTime, "because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dateTime to represent the same point in time as <2012-03-11 +1h>*failure message, but <2012-03-10 +1h> does not."); + } + + [Fact] + public void Should_succeed_when_asserting_nullable_datetimeoffset_value_equals_the_same_value() + { + // Arrange + DateTimeOffset? nullableDateTimeA = new DateTime(2016, 06, 04).ToDateTimeOffset(); + DateTimeOffset? nullableDateTimeB = new DateTime(2016, 06, 04).ToDateTimeOffset(); + + // Act + Action action = () => + nullableDateTimeA.Should().Be(nullableDateTimeB); + + // Assert + action.Should().NotThrow(); + } + + [Fact] + public void Should_succeed_when_asserting_nullable_datetimeoffset_null_value_equals_null() + { + // Arrange + DateTimeOffset? nullableDateTimeA = null; + DateTimeOffset? nullableDateTimeB = null; + + // Act / Assert + nullableDateTimeA.Should().Be(nullableDateTimeB); + } + + [Fact] + public void Should_fail_when_asserting_nullable_datetimeoffset_value_equals_a_different_value() + { + // Arrange + DateTimeOffset? nullableDateTimeA = new DateTime(2016, 06, 04).ToDateTimeOffset(); + DateTimeOffset? nullableDateTimeB = new DateTime(2016, 06, 06).ToDateTimeOffset(); + + // Act + Action action = () => + nullableDateTimeA.Should().Be(nullableDateTimeB); + + // Assert + action.Should().Throw(); + } + + [Fact] + public void Should_fail_with_descriptive_message_when_asserting_datetimeoffset_null_value_is_equal_to_another_value() + { + // Arrange + DateTimeOffset? nullableDateTime = null; + DateTimeOffset expectation = 27.March(2016).ToDateTimeOffset(1.Hours()); + + // Act + Action action = () => + nullableDateTime.Should().Be(expectation, "because we want to test the failure {0}", "message"); + + // Assert + action.Should().Throw() + .WithMessage( + "Expected nullableDateTime to represent the same point in time as <2016-03-27 +1h> because we want to test the failure message, but found a DateTimeOffset."); + } + + [Fact] + public void Should_fail_with_descriptive_message_when_asserting_non_null_value_is_equal_to_null_value() + { + // Arrange + DateTimeOffset? nullableDateTime = 27.March(2016).ToDateTimeOffset(1.Hours()); + DateTimeOffset? expectation = null; + + // Act + Action action = () => + nullableDateTime.Should().Be(expectation, "because we want to test the failure {0}", "message"); + + // Assert + action.Should().Throw() + .WithMessage( + "Expected nullableDateTime to be because we want to test the failure message, but it was <2016-03-27 +1h>."); + } + + [Fact] + public void + When_asserting_different_date_time_offsets_representing_the_same_world_time_it_should_succeed() + { + // Arrange + var specificDate = 1.May(2008).At(6, 32); + + var dateWithFiveHourOffset = new DateTimeOffset(specificDate - 5.Hours(), -5.Hours()); + + var dateWithSixHourOffset = new DateTimeOffset(specificDate - 6.Hours(), -6.Hours()); + + // Act / Assert + dateWithFiveHourOffset.Should().Be(dateWithSixHourOffset); + } + } + + public class NotBe + { + [Fact] + public void Should_succeed_when_asserting_datetimeoffset_value_is_not_equal_to_a_different_value() + { + // Arrange + DateTimeOffset dateTime = new DateTime(2016, 06, 04).ToDateTimeOffset(); + DateTimeOffset otherDateTime = new DateTime(2016, 06, 05).ToDateTimeOffset(); + + // Act + Action act = () => dateTime.Should().NotBe(otherDateTime); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_datetimeoffset_value_is_not_equal_to_a_nullable_different_value_notbe_should_succeed() + { + // Arrange + DateTimeOffset dateTime = 4.June(2016).ToDateTimeOffset(); + DateTimeOffset? otherDateTime = 5.June(2016).ToDateTimeOffset(); + + // Act + Action act = () => dateTime.Should().NotBe(otherDateTime); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void Should_fail_when_asserting_datetimeoffset_value_is_not_equal_to_the_same_value() + { + // Arrange + var dateTime = new DateTimeOffset(10.March(2012), 1.Hours()); + var sameDateTime = new DateTimeOffset(10.March(2012), 1.Hours()); + + // Act + Action act = + () => dateTime.Should().NotBe(sameDateTime, "because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw().WithMessage( + "Did not expect dateTime to represent the same point in time as <2012-03-10 +1h> because we want to test the failure message, but it did."); + } + + [Fact] + public void When_datetimeoffset_value_is_not_equal_to_the_same_nullable_value_notbe_should_failed() + { + // Arrange + DateTimeOffset dateTime = new(10.March(2012), 1.Hours()); + DateTimeOffset? sameDateTime = new DateTimeOffset(10.March(2012), 1.Hours()); + + // Act + Action act = + () => dateTime.Should().NotBe(sameDateTime, "because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw().WithMessage( + "Did not expect dateTime to represent the same point in time as <2012-03-10 +1h> because we want to test the failure message, but it did."); + } + + [Fact] + public void + When_asserting_different_date_time_offsets_representing_different_world_times_it_should_not_succeed() + { + // Arrange + var specificDate = 1.May(2008).At(6, 32); + + var dateWithZeroHourOffset = new DateTimeOffset(specificDate, TimeSpan.Zero); + var dateWithOneHourOffset = new DateTimeOffset(specificDate, 1.Hours()); + + // Act / Assert + dateWithZeroHourOffset.Should().NotBe(dateWithOneHourOffset); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.BeAfter.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.BeAfter.cs new file mode 100644 index 0000000000..acb0b57e2f --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.BeAfter.cs @@ -0,0 +1,101 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeOffsetAssertionSpecs +{ + public class BeAfter + { + [Fact] + public void When_asserting_subject_datetimeoffset_is_after_earlier_expected_datetimeoffset_should_succeed() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); + DateTimeOffset expectation = new(new DateTime(2016, 06, 03), TimeSpan.Zero); + + // Act + Action act = () => subject.Should().BeAfter(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_datetimeoffset_is_after_later_expected_datetimeoffset_should_throw() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); + DateTimeOffset expectation = new(new DateTime(2016, 06, 05), TimeSpan.Zero); + + // Act + Action act = () => subject.Should().BeAfter(expectation); + + // Assert + act.Should().Throw().WithMessage( + "Expected subject to be after <2016-06-05 +0h>, but it was <2016-06-04 +0h>."); + } + + [Fact] + public void When_asserting_subject_datetimeoffset_is_after_the_same_expected_datetimeoffset_should_throw() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); + DateTimeOffset expectation = new(new DateTime(2016, 06, 04), TimeSpan.Zero); + + // Act + Action act = () => subject.Should().BeAfter(expectation); + + // Assert + act.Should().Throw().WithMessage( + "Expected subject to be after <2016-06-04 +0h>, but it was <2016-06-04 +0h>."); + } + } + + public class NotBeAfter + { + [Fact] + public void When_asserting_subject_datetimeoffset_is_not_after_earlier_expected_datetimeoffset_should_throw() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); + DateTimeOffset expectation = new(new DateTime(2016, 06, 03), TimeSpan.Zero); + + // Act + Action act = () => subject.Should().NotBeAfter(expectation); + + // Assert + act.Should().Throw().WithMessage( + "Expected subject to be on or before <2016-06-03 +0h>, but it was <2016-06-04 +0h>."); + } + + [Fact] + public void When_asserting_subject_datetimeoffset_is_not_after_later_expected_datetimeoffset_should_succeed() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); + DateTimeOffset expectation = new(new DateTime(2016, 06, 05), TimeSpan.Zero); + + // Act + Action act = () => subject.Should().NotBeAfter(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_datetimeoffset_is_not_after_the_same_expected_datetimeoffset_should_succeed() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); + DateTimeOffset expectation = new(new DateTime(2016, 06, 04), TimeSpan.Zero); + + // Act + Action act = () => subject.Should().NotBeAfter(expectation); + + // Assert + act.Should().NotThrow(); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.BeAtLeast.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.BeAtLeast.cs new file mode 100644 index 0000000000..84d0d68168 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.BeAtLeast.cs @@ -0,0 +1,76 @@ +using System; +using FluentAssertions.Extensions; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeOffsetAssertionSpecs +{ + public class BeAtLeast + { + [Fact] + public void When_date_is_not_at_least_one_day_before_another_it_should_throw() + { + // Arrange + var target = new DateTimeOffset(2.October(2009), 0.Hours()); + DateTimeOffset subject = target - 23.Hours(); + + // Act + Action act = () => subject.Should().BeAtLeast(TimeSpan.FromDays(1)).Before(target, "we like {0}", "that"); + + // Assert + act.Should().Throw().WithMessage( + "Expected subject <2009-10-01 01:00:00 +0h> to be at least 1d before <2009-10-02 +0h> because we like that, but it is behind by 23h."); + } + + [Fact] + public void When_date_is_at_least_one_day_before_another_it_should_not_throw() + { + // Arrange + var target = new DateTimeOffset(2.October(2009)); + DateTimeOffset subject = target - 24.Hours(); + + // Act / Assert + subject.Should().BeAtLeast(TimeSpan.FromDays(1)).Before(target); + } + + [Theory] + [InlineData(30, 20)] // edge case + [InlineData(30, 15)] + public void When_asserting_subject_be_at_least_10_seconds_after_target_but_subject_is_before_target_it_should_throw( + int targetSeconds, int subjectSeconds) + { + // Arrange + var expectation = 1.January(0001).At(0, 0, targetSeconds).WithOffset(0.Hours()); + var subject = 1.January(0001).At(0, 0, subjectSeconds).WithOffset(0.Hours()); + + // Act + Action action = () => subject.Should().BeAtLeast(10.Seconds()).After(expectation); + + // Assert + action.Should().Throw() + .WithMessage( + $"Expected subject <00:00:{subjectSeconds} +0h> to be at least 10s after <00:00:30 +0h>, but it is behind by {Math.Abs(subjectSeconds - targetSeconds)}s."); + } + + [Theory] + [InlineData(30, 40)] // edge case + [InlineData(30, 45)] + public void When_asserting_subject_be_at_least_10_seconds_before_target_but_subject_is_after_target_it_should_throw( + int targetSeconds, int subjectSeconds) + { + // Arrange + var expectation = 1.January(0001).At(0, 0, targetSeconds).WithOffset(0.Hours()); + var subject = 1.January(0001).At(0, 0, subjectSeconds).WithOffset(0.Hours()); + + // Act + Action action = () => subject.Should().BeAtLeast(10.Seconds()).Before(expectation); + + // Assert + action.Should().Throw() + .WithMessage( + $"Expected subject <00:00:{subjectSeconds} +0h> to be at least 10s before <00:00:30 +0h>, but it is ahead by {Math.Abs(subjectSeconds - targetSeconds)}s."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.BeBefore.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.BeBefore.cs new file mode 100644 index 0000000000..f1bd84fa24 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.BeBefore.cs @@ -0,0 +1,101 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeOffsetAssertionSpecs +{ + public class BeBefore + { + [Fact] + public void When_asserting_a_point_of_time_is_before_a_later_point_it_should_succeed() + { + // Arrange + DateTimeOffset earlierDate = new(new DateTime(2016, 06, 04), TimeSpan.Zero); + DateTimeOffset laterDate = new(new DateTime(2016, 06, 04, 0, 5, 0), TimeSpan.Zero); + + // Act + Action act = () => earlierDate.Should().BeBefore(laterDate); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_is_before_earlier_expected_datetimeoffset_it_should_throw() + { + // Arrange + DateTimeOffset expected = new(new DateTime(2016, 06, 03), TimeSpan.Zero); + DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); + + // Act + Action act = () => subject.Should().BeBefore(expected); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be before <2016-06-03 +0h>, but it was <2016-06-04 +0h>."); + } + + [Fact] + public void When_asserting_subject_datetimeoffset_is_before_the_same_datetimeoffset_it_should_throw() + { + // Arrange + DateTimeOffset expected = new(new DateTime(2016, 06, 04), TimeSpan.Zero); + DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); + + // Act + Action act = () => subject.Should().BeBefore(expected); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be before <2016-06-04 +0h>, but it was <2016-06-04 +0h>."); + } + } + + public class NotBeBefore + { + [Fact] + public void When_asserting_a_point_of_time_is_not_before_another_it_should_throw() + { + // Arrange + DateTimeOffset earlierDate = new(new DateTime(2016, 06, 04), TimeSpan.Zero); + DateTimeOffset laterDate = new(new DateTime(2016, 06, 04, 0, 5, 0), TimeSpan.Zero); + + // Act + Action act = () => earlierDate.Should().NotBeBefore(laterDate); + + // Assert + act.Should().Throw().WithMessage( + "Expected earlierDate to be on or after <2016-06-04 00:05:00 +0h>, but it was <2016-06-04 +0h>."); + } + + [Fact] + public void When_asserting_subject_is_not_before_earlier_expected_datetimeoffset_it_should_succeed() + { + // Arrange + DateTimeOffset expected = new(new DateTime(2016, 06, 03), TimeSpan.Zero); + DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); + + // Act + Action act = () => subject.Should().NotBeBefore(expected); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_datetimeoffset_is_not_before_the_same_datetimeoffset_it_should_succeed() + { + // Arrange + DateTimeOffset expected = new(new DateTime(2016, 06, 04), TimeSpan.Zero); + DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); + + // Act + Action act = () => subject.Should().NotBeBefore(expected); + + // Assert + act.Should().NotThrow(); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.BeCloseTo.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.BeCloseTo.cs new file mode 100644 index 0000000000..74de68e3ea --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.BeCloseTo.cs @@ -0,0 +1,437 @@ +using System; +using FluentAssertions.Common; +using FluentAssertions.Extensions; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeOffsetAssertionSpecs +{ + public class BeCloseTo + { + [Fact] + public void When_asserting_that_time_is_close_to_a_negative_precision_it_should_throw() + { + // Arrange + var dateTime = DateTimeOffset.UtcNow; + var actual = new DateTimeOffset(dateTime.Ticks - 1, TimeSpan.Zero); + + // Act + Action act = () => actual.Should().BeCloseTo(dateTime, -1.Ticks()); + + // Assert + act.Should().Throw() + .WithParameterName("precision") + .WithMessage("*must be non-negative*"); + } + + [Fact] + public void When_a_datetimeoffset_is_close_to_a_later_datetimeoffset_by_one_tick_it_should_succeed() + { + // Arrange + var dateTime = DateTimeOffset.UtcNow; + var actual = new DateTimeOffset(dateTime.Ticks - 1, TimeSpan.Zero); + + // Act + Action act = () => actual.Should().BeCloseTo(dateTime, TimeSpan.FromTicks(1)); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_a_datetimeoffset_is_close_to_an_earlier_datetimeoffset_by_one_tick_it_should_succeed() + { + // Arrange + var dateTime = DateTimeOffset.UtcNow; + var actual = new DateTimeOffset(dateTime.Ticks + 1, TimeSpan.Zero); + + // Act + Action act = () => actual.Should().BeCloseTo(dateTime, TimeSpan.FromTicks(1)); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_a_datetimeoffset_is_close_to_a_MinValue_by_one_tick_it_should_succeed() + { + // Arrange + var dateTime = DateTimeOffset.MinValue; + var actual = new DateTimeOffset(dateTime.Ticks + 1, TimeSpan.Zero); + + // Act + Action act = () => actual.Should().BeCloseTo(dateTime, TimeSpan.FromTicks(1)); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_a_datetimeoffset_is_close_to_a_MaxValue_by_one_tick_it_should_succeed() + { + // Arrange + var dateTime = DateTimeOffset.MaxValue; + var actual = new DateTimeOffset(dateTime.Ticks - 1, TimeSpan.Zero); + + // Act + Action act = () => actual.Should().BeCloseTo(dateTime, TimeSpan.FromTicks(1)); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_datetimeoffset_is_close_to_a_later_datetimeoffset_it_should_succeed() + { + // Arrange + DateTimeOffset time = new(2016, 06, 04, 12, 15, 30, 980, TimeSpan.Zero); + DateTimeOffset nearbyTime = new(2016, 06, 04, 12, 15, 31, 0, TimeSpan.Zero); + + // Act + Action act = () => time.Should().BeCloseTo(nearbyTime, 20.Milliseconds()); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_datetimeoffset_is_close_to_an_earlier_datetimeoffset_it_should_succeed() + { + // Arrange + DateTimeOffset time = new(2016, 06, 04, 12, 15, 31, 020, TimeSpan.Zero); + DateTimeOffset nearbyTime = new(2016, 06, 04, 12, 15, 31, 0, TimeSpan.Zero); + + // Act + Action act = () => time.Should().BeCloseTo(nearbyTime, 20.Milliseconds()); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void + When_asserting_subject_datetimeoffset_is_close_to_another_value_that_is_later_by_more_than_20ms_it_should_throw() + { + // Arrange + DateTimeOffset time = 13.March(2012).At(12, 15, 30, 979).ToDateTimeOffset(1.Hours()); + DateTimeOffset nearbyTime = 13.March(2012).At(12, 15, 31).ToDateTimeOffset(1.Hours()); + + // Act + Action act = () => time.Should().BeCloseTo(nearbyTime, 20.Milliseconds()); + + // Assert + act.Should().Throw() + .WithMessage( + "Expected time to be within 20ms from <2012-03-13 12:15:31 +1H>, but <2012-03-13 12:15:30.979 +1H> was off by 21ms."); + } + + [Fact] + public void + When_asserting_subject_datetimeoffset_is_close_to_another_value_that_is_earlier_by_more_than_20ms_it_should_throw() + { + // Arrange + DateTimeOffset time = 13.March(2012).At(12, 15, 31, 021).ToDateTimeOffset(1.Hours()); + DateTimeOffset nearbyTime = 13.March(2012).At(12, 15, 31).ToDateTimeOffset(1.Hours()); + + // Act + Action act = () => time.Should().BeCloseTo(nearbyTime, 20.Milliseconds()); + + // Assert + act.Should().Throw() + .WithMessage( + "Expected time to be within 20ms from <2012-03-13 12:15:31 +1h>, but <2012-03-13 12:15:31.021 +1h> was off by 21ms."); + } + + [Fact] + public void + When_asserting_subject_datetimeoffset_is_close_to_another_value_that_is_earlier_by_more_than_a_35ms_timespan_it_should_throw() + { + // Arrange + DateTimeOffset time = 13.March(2012).At(12, 15, 31, 036).WithOffset(1.Hours()); + DateTimeOffset nearbyTime = 13.March(2012).At(12, 15, 31).WithOffset(1.Hours()); + + // Act + Action act = () => time.Should().BeCloseTo(nearbyTime, TimeSpan.FromMilliseconds(35)); + + // Assert + act.Should().Throw() + .WithMessage( + "Expected time to be within 35ms from <2012-03-13 12:15:31 +1h>, but <2012-03-13 12:15:31.036 +1h> was off by 36ms."); + } + + [Fact] + public void When_asserting_subject_datetimeoffset_is_close_to_an_earlier_datetimeoffset_by_35ms_it_should_succeed() + { + // Arrange + DateTimeOffset time = 13.March(2012).At(12, 15, 31, 035).ToDateTimeOffset(1.Hours()); + DateTimeOffset nearbyTime = 13.March(2012).At(12, 15, 31).ToDateTimeOffset(1.Hours()); + + // Act + Action act = () => time.Should().BeCloseTo(nearbyTime, 35.Milliseconds()); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_null_datetimeoffset_is_close_to_another_it_should_throw() + { + // Arrange + DateTimeOffset? time = null; + DateTimeOffset nearbyTime = 13.March(2012).At(12, 15, 31).ToDateTimeOffset(5.Hours()); + + // Act + Action act = () => time.Should().BeCloseTo(nearbyTime, 35.Milliseconds()); + + // Assert + act.Should().Throw() + .WithMessage("Expected*, but found ."); + } + + [Fact] + public void When_asserting_subject_datetimeoffset_is_close_to_the_maximum_datetimeoffset_it_should_succeed() + { + // Arrange + DateTimeOffset time = DateTimeOffset.MaxValue - 50.Milliseconds(); + DateTimeOffset nearbyTime = DateTimeOffset.MaxValue; + + // Act + Action act = () => time.Should().BeCloseTo(nearbyTime, 100.Milliseconds()); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_datetimeoffset_is_close_to_the_minimum_datetimeoffset_it_should_succeed() + { + // Arrange + DateTimeOffset time = DateTimeOffset.MinValue + 50.Milliseconds(); + DateTimeOffset nearbyTime = DateTimeOffset.MinValue; + + // Act + Action act = () => time.Should().BeCloseTo(nearbyTime, 100.Milliseconds()); + + // Assert + act.Should().NotThrow(); + } + } + + public class NotBeCloseTo + { + [Fact] + public void When_asserting_that_time_is_not_close_to_a_negative_precision_it_should_throw() + { + // Arrange + var dateTime = DateTimeOffset.UtcNow; + var actual = new DateTimeOffset(dateTime.Ticks - 1, TimeSpan.Zero); + + // Act + Action act = () => actual.Should().NotBeCloseTo(dateTime, -1.Ticks()); + + // Assert + act.Should().Throw() + .WithParameterName("precision") + .WithMessage("*must be non-negative*"); + } + + [Fact] + public void When_a_datetimeoffset_is_close_to_a_later_datetimeoffset_by_one_tick_it_should_fail() + { + // Arrange + var dateTime = DateTimeOffset.UtcNow; + var actual = new DateTimeOffset(dateTime.Ticks - 1, TimeSpan.Zero); + + // Act + Action act = () => actual.Should().NotBeCloseTo(dateTime, TimeSpan.FromTicks(1)); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_a_datetimeoffset_is_close_to_an_earlier_datetimeoffset_by_one_tick_it_should_fail() + { + // Arrange + var dateTime = DateTimeOffset.UtcNow; + var actual = new DateTimeOffset(dateTime.Ticks + 1, TimeSpan.Zero); + + // Act + Action act = () => actual.Should().NotBeCloseTo(dateTime, TimeSpan.FromTicks(1)); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_a_datetimeoffset_is_close_to_a_MinValue_by_one_tick_it_should_fail() + { + // Arrange + var dateTime = DateTimeOffset.MinValue; + var actual = new DateTimeOffset(dateTime.Ticks + 1, TimeSpan.Zero); + + // Act + Action act = () => actual.Should().NotBeCloseTo(dateTime, TimeSpan.FromTicks(1)); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_a_datetimeoffset_is_close_to_a_MaxValue_by_one_tick_it_should_fail() + { + // Arrange + var dateTime = DateTimeOffset.MaxValue; + var actual = new DateTimeOffset(dateTime.Ticks - 1, TimeSpan.Zero); + + // Act + Action act = () => actual.Should().NotBeCloseTo(dateTime, TimeSpan.FromTicks(1)); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_asserting_subject_datetimeoffset_is_not_close_to_a_later_datetimeoffset_it_should_throw() + { + // Arrange + DateTimeOffset time = new(2016, 06, 04, 12, 15, 30, 980, TimeSpan.Zero); + DateTimeOffset nearbyTime = new(2016, 06, 04, 12, 15, 31, 0, TimeSpan.Zero); + + // Act + Action act = () => time.Should().NotBeCloseTo(nearbyTime, 20.Milliseconds()); + + // Assert + act.Should().Throw() + .WithMessage( + "Did not expect time to be within 20ms from <2016-06-04 12:15:31 +0h>, but it was <2016-06-04 12:15:30.980 +0h>."); + } + + [Fact] + public void + When_asserting_subject_datetimeoffset_is_not_close_to_a_later_datetimeoffset_by_a_20ms_timespan_it_should_throw() + { + // Arrange + DateTimeOffset time = new(2016, 06, 04, 12, 15, 30, 980, TimeSpan.Zero); + DateTimeOffset nearbyTime = new(2016, 06, 04, 12, 15, 31, 0, TimeSpan.Zero); + + // Act + Action act = () => time.Should().NotBeCloseTo(nearbyTime, TimeSpan.FromMilliseconds(20)); + + // Assert + act.Should().Throw().WithMessage( + "Did not expect time to be within 20ms from <2016-06-04 12:15:31 +0h>, but it was <2016-06-04 12:15:30.980 +0h>."); + } + + [Fact] + public void When_asserting_subject_datetimeoffset_is_not_close_to_an_earlier_datetimeoffset_it_should_throw() + { + // Arrange + DateTimeOffset time = new(2016, 06, 04, 12, 15, 31, 020, TimeSpan.Zero); + DateTimeOffset nearbyTime = new(2016, 06, 04, 12, 15, 31, 0, TimeSpan.Zero); + + // Act + Action act = () => time.Should().NotBeCloseTo(nearbyTime, 20.Milliseconds()); + + // Assert + act.Should().Throw() + .WithMessage( + "Did not expect time to be within 20ms from <2016-06-04 12:15:31 +0h>, but it was <2016-06-04 12:15:31.020 +0h>."); + } + + [Fact] + public void + When_asserting_subject_datetimeoffset_is_not_close_to_another_value_that_is_later_by_more_than_20ms_it_should_succeed() + { + // Arrange + DateTimeOffset time = 13.March(2012).At(12, 15, 30, 979).ToDateTimeOffset(1.Hours()); + DateTimeOffset nearbyTime = 13.March(2012).At(12, 15, 31).ToDateTimeOffset(1.Hours()); + + // Act + Action act = () => time.Should().NotBeCloseTo(nearbyTime, 20.Milliseconds()); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void + When_asserting_subject_datetimeoffset_is_not_close_to_another_value_that_is_earlier_by_more_than_20ms_it_should_succeed() + { + // Arrange + DateTimeOffset time = 13.March(2012).At(12, 15, 31, 021).ToDateTimeOffset(1.Hours()); + DateTimeOffset nearbyTime = 13.March(2012).At(12, 15, 31).ToDateTimeOffset(1.Hours()); + + // Act + Action act = () => time.Should().NotBeCloseTo(nearbyTime, 20.Milliseconds()); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_datetimeoffset_is_not_close_to_an_earlier_datetimeoffset_by_35ms_it_should_throw() + { + // Arrange + DateTimeOffset time = 13.March(2012).At(12, 15, 31, 035).ToDateTimeOffset(1.Hours()); + DateTimeOffset nearbyTime = 13.March(2012).At(12, 15, 31).ToDateTimeOffset(1.Hours()); + + // Act + Action act = () => time.Should().NotBeCloseTo(nearbyTime, 35.Milliseconds()); + + // Assert + act.Should().Throw() + .WithMessage( + "Did not expect time to be within 35ms from <2012-03-13 12:15:31 +1h>, but it was <2012-03-13 12:15:31.035 +1h>."); + } + + [Fact] + public void When_asserting_subject_null_datetimeoffset_is_not_close_to_another_it_should_throw() + { + // Arrange + DateTimeOffset? time = null; + DateTimeOffset nearbyTime = 13.March(2012).At(12, 15, 31).ToDateTimeOffset(5.Hours()); + + // Act + Action act = () => time.Should().NotBeCloseTo(nearbyTime, 35.Milliseconds()); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect*, but it was ."); + } + + [Fact] + public void When_asserting_subject_datetimeoffset_is_not_close_to_the_minimum_datetimeoffset_it_should_throw() + { + // Arrange + DateTimeOffset time = DateTimeOffset.MinValue + 50.Milliseconds(); + DateTimeOffset nearbyTime = DateTimeOffset.MinValue; + + // Act + Action act = () => time.Should().NotBeCloseTo(nearbyTime, 100.Milliseconds()); + + // Assert + act.Should().Throw() + .WithMessage( + "Did not expect time to be within 100ms from <0001-01-01 00:00:00.000>, but it was <00:00:00.050 +0h>."); + } + + [Fact] + public void When_asserting_subject_datetimeoffset_is_not_close_to_the_maximum_datetimeoffset_it_should_throw() + { + // Arrange + DateTimeOffset time = DateTimeOffset.MaxValue - 50.Milliseconds(); + DateTimeOffset nearbyTime = DateTimeOffset.MaxValue; + + // Act + Action act = () => time.Should().NotBeCloseTo(nearbyTime, 100.Milliseconds()); + + // Assert + act.Should().Throw() + .WithMessage( + "Did not expect time to be within 100ms from <9999-12-31 23:59:59.9999999 +0h>, but it was <9999-12-31 23:59:59.9499999 +0h>."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.BeExactly.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.BeExactly.cs new file mode 100644 index 0000000000..f0fa0a9c35 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.BeExactly.cs @@ -0,0 +1,233 @@ +using System; +using FluentAssertions.Common; +using FluentAssertions.Extensions; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeOffsetAssertionSpecs +{ + public class BeExactly + { + [Fact] + public void Should_succeed_when_asserting_value_is_exactly_equal_to_the_same_value() + { + // Arrange + DateTimeOffset dateTime = new DateTime(2016, 06, 04).ToDateTimeOffset(); + DateTimeOffset sameDateTime = new DateTime(2016, 06, 04).ToDateTimeOffset(); + + // Act / Assert + dateTime.Should().BeExactly(sameDateTime); + } + + [Fact] + public void Should_succeed_when_asserting_value_is_exactly_equal_to_the_same_nullable_value() + { + // Arrange + DateTimeOffset dateTime = 4.June(2016).ToDateTimeOffset(); + DateTimeOffset? sameDateTime = 4.June(2016).ToDateTimeOffset(); + + // Act / Assert + dateTime.Should().BeExactly(sameDateTime); + } + + [Fact] + public void Should_fail_when_asserting_value_is_exactly_equal_to_a_different_value() + { + // Arrange + var dateTime = 10.March(2012).WithOffset(1.Hours()); + var otherDateTime = dateTime.ToUniversalTime(); + + // Act + Action act = () => dateTime.Should().BeExactly(otherDateTime, "because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dateTime to be exactly <2012-03-09 23:00:00 +0h>*failure message, but it was <2012-03-10 +1h>."); + } + + [Fact] + public void Should_fail_when_asserting_value_is_exactly_equal_to_a_different_nullable_value() + { + // Arrange + DateTimeOffset dateTime = 10.March(2012).WithOffset(1.Hours()); + DateTimeOffset? otherDateTime = dateTime.ToUniversalTime(); + + // Act + Action act = () => dateTime.Should().BeExactly(otherDateTime, "because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw().WithMessage( + "Expected dateTime to be exactly <2012-03-09 23:00:00 +0h>*failure message, but it was <2012-03-10 +1h>."); + } + + [Fact] + public void Should_succeed_when_asserting_nullable_value_is_exactly_equal_to_the_same_nullable_value() + { + // Arrange + DateTimeOffset? nullableDateTimeA = new DateTime(2016, 06, 04).ToDateTimeOffset(); + DateTimeOffset? nullableDateTimeB = new DateTime(2016, 06, 04).ToDateTimeOffset(); + + // Act / Assert + nullableDateTimeA.Should().BeExactly(nullableDateTimeB); + } + + [Fact] + public void Should_succeed_when_asserting_nullable_null_value_exactly_equals_null() + { + // Arrange + DateTimeOffset? nullableDateTimeA = null; + DateTimeOffset? nullableDateTimeB = null; + + // Act / Assert + nullableDateTimeA.Should().BeExactly(nullableDateTimeB); + } + + [Fact] + public void Should_fail_when_asserting_nullable_value_exactly_equals_a_different_value() + { + // Arrange + DateTimeOffset? nullableDateTimeA = new DateTime(2016, 06, 04).ToDateTimeOffset(); + DateTimeOffset? nullableDateTimeB = new DateTime(2016, 06, 06).ToDateTimeOffset(); + + // Act + Action action = () => + nullableDateTimeA.Should().BeExactly(nullableDateTimeB); + + // Assert + action.Should().Throw(); + } + + [Fact] + public void Should_fail_with_descriptive_message_when_asserting_null_value_is_exactly_equal_to_another_value() + { + // Arrange + DateTimeOffset? nullableDateTime = null; + DateTimeOffset expectation = 27.March(2016).ToDateTimeOffset(1.Hours()); + + // Act + Action action = () => + nullableDateTime.Should().BeExactly(expectation, "because we want to test the failure {0}", "message"); + + // Assert + action.Should().Throw() + .WithMessage( + "Expected nullableDateTime to be exactly <2016-03-27 +1h> because we want to test the failure message, but found a DateTimeOffset."); + } + } + + public class NotBeExactly + { + [Fact] + public void Should_succeed_when_asserting_value_is_not_exactly_equal_to_a_different_value() + { + // Arrange + DateTimeOffset dateTime = 10.March(2012).WithOffset(1.Hours()); + DateTimeOffset otherDateTime = dateTime.ToUniversalTime(); + + // Act / Assert + dateTime.Should().NotBeExactly(otherDateTime); + } + + [Fact] + public void Should_succeed_when_asserting_value_is_not_exactly_equal_to_a_different_nullable_value() + { + // Arrange + DateTimeOffset dateTime = 10.March(2012).WithOffset(1.Hours()); + DateTimeOffset? otherDateTime = dateTime.ToUniversalTime(); + + // Act / Assert + dateTime.Should().NotBeExactly(otherDateTime); + } + + [Fact] + public void Should_fail_when_asserting_value_is_not_exactly_equal_to_the_same_value() + { + // Arrange + var dateTime = new DateTimeOffset(10.March(2012), 1.Hours()); + var sameDateTime = new DateTimeOffset(10.March(2012), 1.Hours()); + + // Act + Action act = + () => dateTime.Should().NotBeExactly(sameDateTime, "because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw().WithMessage( + "Did not expect dateTime to be exactly <2012-03-10 +1h> because we want to test the failure message, but it was."); + } + + [Fact] + public void Should_fail_when_asserting_value_is_not_exactly_equal_to_the_same_nullable_value() + { + // Arrange + DateTimeOffset dateTime = new(10.March(2012), 1.Hours()); + DateTimeOffset? sameDateTime = new DateTimeOffset(10.March(2012), 1.Hours()); + + // Act + Action act = + () => dateTime.Should().NotBeExactly(sameDateTime, "because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw().WithMessage( + "Did not expect dateTime to be exactly <2012-03-10 +1h> because we want to test the failure message, but it was."); + } + + [Fact] + public void When_time_is_not_at_exactly_20_minutes_before_another_time_it_should_throw() + { + // Arrange + DateTimeOffset target = 1.January(0001).At(12, 55).ToDateTimeOffset(); + DateTimeOffset subject = 1.January(0001).At(12, 36).ToDateTimeOffset(); + + // Act + Action act = + () => subject.Should().BeExactly(TimeSpan.FromMinutes(20)).Before(target, "{0} minutes is enough", 20); + + // Assert + act.Should().Throw().WithMessage( + "Expected subject <12:36:00 +0h> to be exactly 20m before <12:55:00 +0h> because 20 minutes is enough, but it is behind by 19m."); + } + + [Fact] + public void When_time_is_exactly_90_seconds_before_another_time_it_should_not_throw() + { + // Arrange + DateTimeOffset target = 1.January(0001).At(12, 55).ToDateTimeOffset(); + DateTimeOffset subject = 1.January(0001).At(12, 53, 30).ToDateTimeOffset(); + + // Act / Assert + subject.Should().BeExactly(TimeSpan.FromSeconds(90)).Before(target); + } + + [Fact] + public void When_asserting_subject_be_exactly_10_seconds_after_target_but_subject_is_before_target_it_should_throw() + { + // Arrange + var expectation = 1.January(0001).At(0, 0, 30).WithOffset(0.Hours()); + var subject = 1.January(0001).At(0, 0, 20).WithOffset(0.Hours()); + + // Act + Action action = () => subject.Should().BeExactly(10.Seconds()).After(expectation); + + // Assert + action.Should().Throw() + .WithMessage("Expected subject <00:00:20 +0h> to be exactly 10s after <00:00:30 +0h>, but it is behind by 10s."); + } + + [Fact] + public void When_asserting_subject_be_exactly_10_seconds_before_target_but_subject_is_after_target_it_should_throw() + { + // Arrange + var expectation = 1.January(0001).At(0, 0, 30).WithOffset(0.Hours()); + var subject = 1.January(0001).At(0, 0, 40).WithOffset(0.Hours()); + + // Act + Action action = () => subject.Should().BeExactly(10.Seconds()).Before(expectation); + + // Assert + action.Should().Throw() + .WithMessage("Expected subject <00:00:40 +0h> to be exactly 10s before <00:00:30 +0h>, but it is ahead by 10s."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.BeLessThan.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.BeLessThan.cs new file mode 100644 index 0000000000..eeb861a53c --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.BeLessThan.cs @@ -0,0 +1,84 @@ +using System; +using FluentAssertions.Extensions; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeOffsetAssertionSpecs +{ + public class BeLessThan + { + [Fact] + public void When_time_is_not_less_than_30s_after_another_time_it_should_throw() + { + // Arrange + var target = 1.January(1).At(12, 0, 30).WithOffset(1.Hours()); + DateTimeOffset subject = target + 30.Seconds(); + + // Act + Action act = + () => subject.Should().BeLessThan(TimeSpan.FromSeconds(30)).After(target, "{0}s is the max", 30); + + // Assert + act.Should().Throw().WithMessage( + "Expected subject <12:01:00 +1h> to be less than 30s after <12:00:30 +1h> because 30s is the max, but it is ahead by 30s."); + } + + [Fact] + public void When_time_is_less_than_30s_after_another_time_it_should_not_throw() + { + // Arrange + var target = new DateTimeOffset(1.January(1).At(12, 0, 30)); + DateTimeOffset subject = target + 20.Seconds(); + + // Act / Assert + subject.Should().BeLessThan(TimeSpan.FromSeconds(30)).After(target); + } + + [Fact] + public void When_asserting_subject_be_less_than_10_seconds_after_target_but_subject_is_before_target_it_should_throw() + { + // Arrange + var expectation = 1.January(0001).At(0, 0, 30).WithOffset(0.Hours()); + var subject = 1.January(0001).At(0, 0, 25).WithOffset(0.Hours()); + + // Act + Action action = () => subject.Should().BeLessThan(10.Seconds()).After(expectation); + + // Assert + action.Should().Throw() + .WithMessage("Expected subject <00:00:25 +0h> to be less than 10s after <00:00:30 +0h>, but it is behind by 5s."); + } + + [Fact] + public void When_asserting_subject_be_less_than_10_seconds_before_target_but_subject_is_after_target_it_should_throw() + { + // Arrange + var expectation = 1.January(0001).At(0, 0, 30).WithOffset(0.Hours()); + var subject = 1.January(0001).At(0, 0, 45).WithOffset(0.Hours()); + + // Act + Action action = () => subject.Should().BeLessThan(10.Seconds()).Before(expectation); + + // Assert + action.Should().Throw() + .WithMessage( + "Expected subject <00:00:45 +0h> to be less than 10s before <00:00:30 +0h>, but it is ahead by 15s."); + } + + [Fact] + public void Should_throw_a_helpful_error_when_accidentally_using_equals_with_a_range() + { + // Arrange + DateTimeOffset someDateTimeOffset = new(2022, 9, 25, 13, 48, 42, 0, TimeSpan.Zero); + + // Act + var action = () => someDateTimeOffset.Should().BeLessThan(0.Seconds()).Equals(null); + + // Assert + action.Should().Throw() + .WithMessage("Equals is not part of Fluent Assertions. Did you mean Before() or After() instead?"); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.BeMoreThan.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.BeMoreThan.cs new file mode 100644 index 0000000000..f76d37b69d --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.BeMoreThan.cs @@ -0,0 +1,70 @@ +using System; +using FluentAssertions.Extensions; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeOffsetAssertionSpecs +{ + public class BeMoreThan + { + [Fact] + public void When_date_is_not_more_than_the_required_one_day_before_another_it_should_throw() + { + // Arrange + var target = new DateTimeOffset(2.October(2009), 0.Hours()); + DateTimeOffset subject = target - 1.Days(); + + // Act + Action act = () => subject.Should().BeMoreThan(TimeSpan.FromDays(1)).Before(target, "we like {0}", "that"); + + // Assert + act.Should().Throw().WithMessage( + "Expected subject <2009-10-01 +0h> to be more than 1d before <2009-10-02 +0h> because we like that, but it is behind by 1d."); + } + + [Fact] + public void When_date_is_more_than_the_required_one_day_before_another_it_should_not_throw() + { + // Arrange + var target = new DateTimeOffset(2.October(2009)); + DateTimeOffset subject = target - 25.Hours(); + + // Act / Assert + subject.Should().BeMoreThan(TimeSpan.FromDays(1)).Before(target); + } + + [Fact] + public void When_asserting_subject_be_more_than_10_seconds_after_target_but_subject_is_before_target_it_should_throw() + { + // Arrange + var expectation = 1.January(0001).At(0, 0, 30).WithOffset(0.Hours()); + var subject = 1.January(0001).At(0, 0, 15).WithOffset(0.Hours()); + + // Act + Action action = () => subject.Should().BeMoreThan(10.Seconds()).After(expectation); + + // Assert + action.Should().Throw() + .WithMessage( + "Expected subject <00:00:15 +0h> to be more than 10s after <00:00:30 +0h>, but it is behind by 15s."); + } + + [Fact] + public void When_asserting_subject_be_more_than_10_seconds_before_target_but_subject_is_after_target_it_should_throw() + { + // Arrange + var expectation = 1.January(0001).At(0, 0, 30).WithOffset(0.Hours()); + var subject = 1.January(0001).At(0, 0, 45).WithOffset(0.Hours()); + + // Act + Action action = () => subject.Should().BeMoreThan(10.Seconds()).Before(expectation); + + // Assert + action.Should().Throw() + .WithMessage( + "Expected subject <00:00:45 +0h> to be more than 10s before <00:00:30 +0h>, but it is ahead by 15s."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.BeNull.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.BeNull.cs new file mode 100644 index 0000000000..dc4ce0a7fc --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.BeNull.cs @@ -0,0 +1,69 @@ +using System; +using FluentAssertions.Common; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeOffsetAssertionSpecs +{ + public class BeNull + { + [Fact] + public void Should_succeed_when_asserting_nullable_datetimeoffset_value_without_a_value_to_be_null() + { + // Arrange + DateTimeOffset? nullableDateTime = null; + + // Act + Action action = () => + nullableDateTime.Should().BeNull(); + + // Assert + action.Should().NotThrow(); + } + + [Fact] + public void Should_fail_when_asserting_nullable_datetimeoffset_value_with_a_value_to_be_null() + { + // Arrange + DateTimeOffset? nullableDateTime = new DateTime(2016, 06, 04).ToDateTimeOffset(); + + // Act + Action action = () => + nullableDateTime.Should().BeNull(); + + // Assert + action.Should().Throw(); + } + } + + public class NotBeNull + { + [Fact] + public void When_nullable_datetimeoffset_value_with_a_value_not_be_null_it_should_succeed() + { + // Arrange + DateTimeOffset? nullableDateTime = new DateTime(2016, 06, 04).ToDateTimeOffset(); + + // Act + Action action = () => nullableDateTime.Should().NotBeNull(); + + // Assert + action.Should().NotThrow(); + } + + [Fact] + public void Should_fail_when_asserting_nullable_datetimeoffset_value_without_a_value_to_not_be_null() + { + // Arrange + DateTimeOffset? nullableDateTime = null; + + // Act + Action action = () => nullableDateTime.Should().NotBeNull(); + + // Assert + action.Should().Throw(); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.BeOnOrAfter.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.BeOnOrAfter.cs new file mode 100644 index 0000000000..a04969806c --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.BeOnOrAfter.cs @@ -0,0 +1,101 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeOffsetAssertionSpecs +{ + public class BeOnOrAfter + { + [Fact] + public void When_asserting_subject_datetimeoffset_is_on_or_after_earlier_expected_datetimeoffset_should_succeed() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); + DateTimeOffset expectation = new(new DateTime(2016, 06, 03), TimeSpan.Zero); + + // Act + Action act = () => subject.Should().BeOnOrAfter(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_datetimeoffset_is_on_or_after_the_same_expected_datetimeoffset_should_succeed() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); + DateTimeOffset expectation = new(new DateTime(2016, 06, 04), TimeSpan.Zero); + + // Act + Action act = () => subject.Should().BeOnOrAfter(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_datetimeoffset_is_on_or_after_later_expected_datetimeoffset_should_throw() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); + DateTimeOffset expectation = new(new DateTime(2016, 06, 05), TimeSpan.Zero); + + // Act + Action act = () => subject.Should().BeOnOrAfter(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be on or after <2016-06-05 +0h>, but it was <2016-06-04 +0h>."); + } + } + + public class NotBeOnOrAfter + { + [Fact] + public void When_asserting_subject_datetimeoffset_is_not_on_or_after_earlier_expected_datetimeoffset_should_throw() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); + DateTimeOffset expectation = new(new DateTime(2016, 06, 03), TimeSpan.Zero); + + // Act + Action act = () => subject.Should().NotBeOnOrAfter(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be before <2016-06-03 +0h>, but it was <2016-06-04 +0h>."); + } + + [Fact] + public void When_asserting_subject_datetimeoffset_is_not_on_or_after_the_same_expected_datetimeoffset_should_throw() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); + DateTimeOffset expectation = new(new DateTime(2016, 06, 04), TimeSpan.Zero); + + // Act + Action act = () => subject.Should().NotBeOnOrAfter(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be before <2016-06-04 +0h>, but it was <2016-06-04 +0h>."); + } + + [Fact] + public void When_asserting_subject_datetimeoffset_is_not_on_or_after_later_expected_datetimeoffset_should_succeed() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); + DateTimeOffset expectation = new(new DateTime(2016, 06, 05), TimeSpan.Zero); + + // Act + Action act = () => subject.Should().NotBeOnOrAfter(expectation); + + // Assert + act.Should().NotThrow(); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.BeOnOrBefore.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.BeOnOrBefore.cs new file mode 100644 index 0000000000..ff9a76c09d --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.BeOnOrBefore.cs @@ -0,0 +1,103 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeOffsetAssertionSpecs +{ + public class BeOnOrBefore + { + [Fact] + public void When_asserting_subject_datetimeoffset_is_on_or_before_expected_datetimeoffset_should_succeed() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); + DateTimeOffset expectation = new(new DateTime(2016, 06, 05), TimeSpan.Zero); + + // Act + Action act = () => subject.Should().BeOnOrBefore(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void + When_asserting_subject_datetimeoffset_is_on_or_before_the_same_date_as_the_expected_datetimeoffset_should_succeed() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); + DateTimeOffset expectation = new(new DateTime(2016, 06, 04), TimeSpan.Zero); + + // Act + Action act = () => subject.Should().BeOnOrBefore(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_datetimeoffset_is_not_on_or_before_earlier_expected_datetimeoffset_should_throw() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); + DateTimeOffset expectation = new(new DateTime(2016, 06, 03), TimeSpan.Zero); + + // Act + Action act = () => subject.Should().BeOnOrBefore(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be on or before <2016-06-03 +0h>, but it was <2016-06-04 +0h>."); + } + } + + public class NotBeOnOrBefore + { + [Fact] + public void When_asserting_subject_datetimeoffset_is_on_or_before_expected_datetimeoffset_should_throw() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); + DateTimeOffset expectation = new(new DateTime(2016, 06, 05), TimeSpan.Zero); + + // Act + Action act = () => subject.Should().NotBeOnOrBefore(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be after <2016-06-05 +0h>, but it was <2016-06-04 +0h>."); + } + + [Fact] + public void + When_asserting_subject_datetimeoffset_is_on_or_before_the_same_date_as_the_expected_datetimeoffset_should_throw() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); + DateTimeOffset expectation = new(new DateTime(2016, 06, 04), TimeSpan.Zero); + + // Act + Action act = () => subject.Should().NotBeOnOrBefore(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be after <2016-06-04 +0h>, but it was <2016-06-04 +0h>."); + } + + [Fact] + public void When_asserting_subject_datetimeoffset_is_not_on_or_before_earlier_expected_datetimeoffset_should_succeed() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); + DateTimeOffset expectation = new(new DateTime(2016, 06, 03), TimeSpan.Zero); + + // Act + Action act = () => subject.Should().NotBeOnOrBefore(expectation); + + // Assert + act.Should().NotThrow(); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.BeOneOf.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.BeOneOf.cs new file mode 100644 index 0000000000..d60ab13c0d --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.BeOneOf.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using FluentAssertions.Extensions; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeOffsetAssertionSpecs +{ + public class BeOneOf + { + [Fact] + public void When_a_value_is_not_one_of_the_specified_values_it_should_throw() + { + // Arrange + var value = new DateTimeOffset(31.December(2016), 1.Hours()); + + // Act + Action action = () => value.Should().BeOneOf(value + 1.Days(), value + 4.Hours()); + + // Assert + action.Should().Throw().WithMessage( + "Expected value to be one of {<2017-01-01 +1h>, <2016-12-31 04:00:00 +1h>}, but it was <2016-12-31 +1h>."); + } + + [Fact] + public void When_a_value_is_one_of_the_specified_param_values_follow_up_assertions_works() + { + // Arrange + var value = new DateTimeOffset(31.December(2016), 1.Hours()); + + // Act / Assert + value.Should().BeOneOf(value, value + 1.Hours()) + .And.Be(31.December(2016).WithOffset(1.Hours())); + } + + [Fact] + public void When_a_value_is_one_of_the_specified_nullable_params_values_follow_up_assertions_works() + { + // Arrange + var value = new DateTimeOffset(31.December(2016), 1.Hours()); + + // Act / Assert + value.Should().BeOneOf(null, value, value + 1.Hours()) + .And.Be(31.December(2016).WithOffset(1.Hours())); + } + + [Fact] + public void When_a_value_is_one_of_the_specified_enumerable_values_follow_up_assertions_works() + { + // Arrange + var value = new DateTimeOffset(31.December(2016), 1.Hours()); + IEnumerable expected = [value, value + 1.Hours()]; + + // Act / Assert + value.Should().BeOneOf(expected) + .And.Be(31.December(2016).WithOffset(1.Hours())); + } + + [Fact] + public void When_a_value_is_one_of_the_specified_nullable_enumerable_follow_up_assertions_works() + { + // Arrange + var value = new DateTimeOffset(31.December(2016), 1.Hours()); + IEnumerable expected = [null, value, value + 1.Hours()]; + + // Act / Assert + value.Should().BeOneOf(expected) + .And.Be(31.December(2016).WithOffset(1.Hours())); + } + + [Fact] + public void When_a_value_is_not_one_of_the_specified_values_it_should_throw_with_descriptive_message() + { + // Arrange + DateTimeOffset value = 31.December(2016).WithOffset(1.Hours()); + + // Act + Action action = () => value.Should().BeOneOf(new[] { value + 1.Days(), value + 2.Days() }, "because it's true"); + + // Assert + action.Should().Throw().WithMessage( + "Expected value to be one of {<2017-01-01 +1h>, <2017-01-02 +1h>} because it's true, but it was <2016-12-31 +1h>."); + } + + [Fact] + public void When_a_value_is_one_of_the_specified_values_it_should_succeed() + { + // Arrange + DateTimeOffset value = new(2016, 12, 30, 23, 58, 57, TimeSpan.FromHours(4)); + + // Act + Action action = () => value.Should().BeOneOf(new DateTimeOffset(2216, 1, 30, 0, 5, 7, TimeSpan.FromHours(2)), + new DateTimeOffset(2016, 12, 30, 23, 58, 57, TimeSpan.FromHours(4))); + + // Assert + action.Should().NotThrow(); + } + + [Fact] + public void When_a_null_value_is_not_one_of_the_specified_values_it_should_throw() + { + // Arrange + DateTimeOffset? value = null; + + // Act + Action action = () => value.Should().BeOneOf(new DateTimeOffset(2216, 1, 30, 0, 5, 7, TimeSpan.FromHours(1)), + new DateTimeOffset(2016, 2, 10, 2, 45, 7, TimeSpan.FromHours(2))); + + // Assert + action.Should().Throw().WithMessage( + "Expected value to be one of {<2216-01-30 00:05:07 +1h>, <2016-02-10 02:45:07 +2h>}, but it was ."); + } + + [Fact] + public void When_a_value_is_one_of_the_specified_values_it_should_succeed_when_datetimeoffset_is_null() + { + // Arrange + DateTimeOffset? value = null; + + // Act + Action action = () => value.Should().BeOneOf(new DateTimeOffset(2216, 1, 30, 0, 5, 7, TimeSpan.Zero), null); + + // Assert + action.Should().NotThrow(); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.BeSameDateAs.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.BeSameDateAs.cs new file mode 100644 index 0000000000..dda39ebaf3 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.BeSameDateAs.cs @@ -0,0 +1,159 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeOffsetAssertionSpecs +{ + public class BeSameDateAs + { + [Fact] + public void When_asserting_subject_datetimeoffset_should_be_same_date_as_another_with_the_same_date_it_should_succeed() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 12, 31, 4, 5, 6), TimeSpan.Zero); + DateTimeOffset expectation = new(new DateTime(2009, 12, 31), TimeSpan.Zero); + + // Act + Action act = () => subject.Should().BeSameDateAs(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void + When_asserting_subject_datetimeoffset_should_be_same_as_another_with_same_date_but_different_time_it_should_succeed() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 12, 31, 4, 5, 6), TimeSpan.Zero); + DateTimeOffset expectation = new(new DateTime(2009, 12, 31, 11, 15, 11), TimeSpan.Zero); + + // Act + Action act = () => subject.Should().BeSameDateAs(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_null_datetimeoffset_to_be_same_date_as_another_datetimeoffset_it_should_throw() + { + // Arrange + DateTimeOffset? subject = null; + DateTimeOffset expectation = new(new DateTime(2009, 12, 31), TimeSpan.Zero); + + // Act + Action act = () => subject.Should().BeSameDateAs(expectation); + + // Assert + act.Should().Throw().WithMessage( + "Expected the date part of subject to be <2009-12-31>, but found a DateTimeOffset."); + } + + [Fact] + public void When_asserting_subject_datetimeoffset_should_have_same_date_as_another_but_it_doesnt_it_should_throw() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 12, 31), TimeSpan.Zero); + DateTimeOffset expectation = new(new DateTime(2009, 12, 30), TimeSpan.Zero); + + // Act + Action act = () => subject.Should().BeSameDateAs(expectation); + + // Assert + act.Should().Throw().WithMessage( + "Expected the date part of subject to be <2009-12-30>, but it was <2009-12-31>."); + } + + [Fact] + public void Can_chain_follow_up_assertions() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 12, 31, 4, 5, 6), TimeSpan.Zero); + DateTimeOffset expectation = new(new DateTime(2009, 12, 31, 4, 5, 6), TimeSpan.Zero); + + // Act / Assert + subject.Should().BeSameDateAs(expectation).And.Be(subject); + } + } + + public class NotBeSameDateAs + { + [Fact] + public void When_asserting_subject_datetimeoffset_should_not_be_same_date_as_another_with_the_same_date_it_should_throw() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 12, 31, 4, 5, 6), TimeSpan.Zero); + DateTimeOffset expectation = new(new DateTime(2009, 12, 31), TimeSpan.Zero); + + // Act + Action act = () => subject.Should().NotBeSameDateAs(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the date part of subject to be <2009-12-31>, but it was."); + } + + [Fact] + public void Can_chain_follow_up_assertions() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 12, 31, 4, 5, 6), TimeSpan.Zero); + DateTimeOffset expectation = new(new DateTime(2009, 12, 31), TimeSpan.Zero); + + // Act + Action act = () => subject.Should().NotBeSameDateAs(expectation).And.Be(subject); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the date part of subject to be <2009-12-31>, but it was."); + } + + [Fact] + public void + When_asserting_subject_datetimeoffset_should_not_be_same_as_another_with_same_date_but_different_time_it_should_throw() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 12, 31, 4, 5, 6), TimeSpan.Zero); + DateTimeOffset expectation = new(new DateTime(2009, 12, 31, 11, 15, 11), TimeSpan.Zero); + + // Act + Action act = () => subject.Should().NotBeSameDateAs(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the date part of subject to be <2009-12-31>, but it was."); + } + + [Fact] + public void When_asserting_subject_null_datetimeoffset_to_not_be_same_date_as_another_datetimeoffset_it_should_throw() + { + // Arrange + DateTimeOffset? subject = null; + DateTimeOffset expectation = new(new DateTime(2009, 12, 31), TimeSpan.Zero); + + // Act + Action act = () => subject.Should().NotBeSameDateAs(expectation); + + // Assert + act.Should().Throw().WithMessage( + "Did not expect the date part of subject to be <2009-12-31>, but found a DateTimeOffset."); + } + + [Fact] + public void When_asserting_subject_datetimeoffset_should_not_have_same_date_as_another_but_it_doesnt_it_should_succeed() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 12, 31), TimeSpan.Zero); + DateTimeOffset expectation = new(new DateTime(2009, 12, 30), TimeSpan.Zero); + + // Act + Action act = () => subject.Should().NotBeSameDateAs(expectation); + + // Assert + act.Should().NotThrow(); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.BeWithin.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.BeWithin.cs new file mode 100644 index 0000000000..4ebb9eaa22 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.BeWithin.cs @@ -0,0 +1,108 @@ +using System; +using FluentAssertions.Extensions; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeOffsetAssertionSpecs +{ + public class BeWithin + { + [Fact] + public void When_date_is_not_within_50_hours_before_another_date_it_should_throw() + { + // Arrange + var target = 10.April(2010).At(12, 0).WithOffset(0.Hours()); + DateTimeOffset subject = target - 50.Hours() - 1.Seconds(); + + // Act + Action act = + () => subject.Should().BeWithin(TimeSpan.FromHours(50)).Before(target, "{0} hours is enough", 50); + + // Assert + act.Should().Throw().WithMessage( + "Expected subject <2010-04-08 09:59:59 +0h> to be within 2d and 2h before <2010-04-10 12:00:00 +0h> because 50 hours is enough, but it is behind by 2d, 2h and 1s."); + } + + [Fact] + public void When_date_is_exactly_within_1d_before_another_date_it_should_not_throw() + { + // Arrange + var target = new DateTimeOffset(10.April(2010)); + DateTimeOffset subject = target - 1.Days(); + + // Act / Assert + subject.Should().BeWithin(TimeSpan.FromHours(24)).Before(target); + } + + [Fact] + public void When_date_is_within_1d_before_another_date_it_should_not_throw() + { + // Arrange + var target = new DateTimeOffset(10.April(2010)); + DateTimeOffset subject = target - 23.Hours(); + + // Act / Assert + subject.Should().BeWithin(TimeSpan.FromHours(24)).Before(target); + } + + [Fact] + public void When_a_utc_date_is_within_0s_before_itself_it_should_not_throw() + { + // Arrange + var date = DateTimeOffset.UtcNow; // local timezone differs from +0h + + // Act / Assert + date.Should().BeWithin(TimeSpan.Zero).Before(date); + } + + [Fact] + public void When_a_utc_date_is_within_0s_after_itself_it_should_not_throw() + { + // Arrange + var date = DateTimeOffset.UtcNow; // local timezone differs from +0h + + // Act / Assert + date.Should().BeWithin(TimeSpan.Zero).After(date); + } + + [Theory] + [InlineData(30, 20)] // edge case + [InlineData(30, 25)] + public void When_asserting_subject_be_within_10_seconds_after_target_but_subject_is_before_target_it_should_throw( + int targetSeconds, int subjectSeconds) + { + // Arrange + var expectation = 1.January(0001).At(0, 0, targetSeconds).WithOffset(0.Hours()); + var subject = 1.January(0001).At(0, 0, subjectSeconds).WithOffset(0.Hours()); + + // Act + Action action = () => subject.Should().BeWithin(10.Seconds()).After(expectation); + + // Assert + action.Should().Throw() + .WithMessage( + $"Expected subject <00:00:{subjectSeconds} +0h> to be within 10s after <00:00:30 +0h>, but it is behind by {Math.Abs(subjectSeconds - targetSeconds)}s."); + } + + [Theory] + [InlineData(30, 40)] // edge case + [InlineData(30, 35)] + public void When_asserting_subject_be_within_10_seconds_before_target_but_subject_is_after_target_it_should_throw( + int targetSeconds, int subjectSeconds) + { + // Arrange + var expectation = 1.January(0001).At(0, 0, targetSeconds).WithOffset(0.Hours()); + var subject = 1.January(0001).At(0, 0, subjectSeconds).WithOffset(0.Hours()); + + // Act + Action action = () => subject.Should().BeWithin(10.Seconds()).Before(expectation); + + // Assert + action.Should().Throw() + .WithMessage( + $"Expected subject <00:00:{subjectSeconds} +0h> to be within 10s before <00:00:30 +0h>, but it is ahead by {Math.Abs(subjectSeconds - targetSeconds)}s."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.HaveDay.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.HaveDay.cs new file mode 100644 index 0000000000..4a42ea1fd1 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.HaveDay.cs @@ -0,0 +1,102 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeOffsetAssertionSpecs +{ + public class HaveDay + { + [Fact] + public void When_asserting_subject_datetimeoffset_should_have_day_with_the_same_value_it_should_succeed() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 12, 31), TimeSpan.Zero); + int expectation = 31; + + // Act + Action act = () => subject.Should().HaveDay(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_datetimeoffset_should_have_day_with_a_different_value_it_should_throw() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 12, 31), TimeSpan.Zero); + int expectation = 30; + + // Act + Action act = () => subject.Should().HaveDay(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the day part of subject to be 30, but it was 31."); + } + + [Fact] + public void When_asserting_subject_null_datetimeoffset_should_have_day_should_throw() + { + // Arrange + DateTimeOffset? subject = null; + int expectation = 22; + + // Act + Action act = () => subject.Should().HaveDay(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the day part of subject to be 22, but found a DateTimeOffset."); + } + } + + public class NotHaveDay + { + [Fact] + public void When_asserting_subject_datetimeoffset_should_not_have_day_with_the_same_value_it_should_throw() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 12, 31), TimeSpan.Zero); + int expectation = 31; + + // Act + Action act = () => subject.Should().NotHaveDay(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the day part of subject to be 31, but it was."); + } + + [Fact] + public void When_asserting_subject_datetimeoffset_should_not_have_day_with_a_different_value_it_should_succeed() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 12, 31), TimeSpan.Zero); + int expectation = 30; + + // Act + Action act = () => subject.Should().NotHaveDay(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_null_datetimeoffset_should_not_have_day_should_throw() + { + // Arrange + DateTimeOffset? subject = null; + int expectation = 22; + + // Act + Action act = () => subject.Should().NotHaveDay(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the day part of subject to be 22, but found a DateTimeOffset."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.HaveHour.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.HaveHour.cs new file mode 100644 index 0000000000..e87ea18fe0 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.HaveHour.cs @@ -0,0 +1,102 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeOffsetAssertionSpecs +{ + public class HaveHour + { + [Fact] + public void When_asserting_subject_datetimeoffset_should_have_hour_with_the_same_value_it_should_succeed() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 12, 31, 23, 59, 00), TimeSpan.Zero); + int expectation = 23; + + // Act + Action act = () => subject.Should().HaveHour(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_datetimeoffset_should_have_hour_with_different_value_it_should_throw() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 12, 31, 23, 59, 00), TimeSpan.Zero); + int expectation = 22; + + // Act + Action act = () => subject.Should().HaveHour(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the hour part of subject to be 22, but it was 23."); + } + + [Fact] + public void When_asserting_subject_null_datetimeoffset_should_have_hour_should_throw() + { + // Arrange + DateTimeOffset? subject = null; + int expectation = 22; + + // Act + Action act = () => subject.Should().HaveHour(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the hour part of subject to be 22, but found a DateTimeOffset."); + } + } + + public class NotHaveHour + { + [Fact] + public void When_asserting_subject_datetimeoffset_should_not_have_hour_with_the_same_value_it_should_throw() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 12, 31, 23, 59, 00), TimeSpan.Zero); + int expectation = 23; + + // Act + Action act = () => subject.Should().NotHaveHour(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the hour part of subject to be 23, but it was."); + } + + [Fact] + public void When_asserting_subject_datetimeoffset_should_not_have_hour_with_different_value_it_should_succeed() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 12, 31, 23, 59, 00), TimeSpan.Zero); + int expectation = 22; + + // Act + Action act = () => subject.Should().NotHaveHour(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_null_datetimeoffset_should_not_have_hour_should_throw() + { + // Arrange + DateTimeOffset? subject = null; + int expectation = 22; + + // Act + Action act = () => subject.Should().NotHaveHour(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the hour part of subject to be 22, but found a DateTimeOffset."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.HaveMinute.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.HaveMinute.cs new file mode 100644 index 0000000000..ad54f84334 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.HaveMinute.cs @@ -0,0 +1,102 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeOffsetAssertionSpecs +{ + public class HaveMinute + { + [Fact] + public void When_asserting_subject_datetimeoffset_should_have_minutes_with_the_same_value_it_should_succeed() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 12, 31, 23, 59, 00), TimeSpan.Zero); + int expectation = 59; + + // Act + Action act = () => subject.Should().HaveMinute(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_datetimeoffset_should_have_minutes_with_different_value_it_should_throw() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 12, 31, 23, 59, 00), TimeSpan.Zero); + int expectation = 58; + + // Act + Action act = () => subject.Should().HaveMinute(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the minute part of subject to be 58, but it was 59."); + } + + [Fact] + public void When_asserting_subject_null_datetimeoffset_should_have_minute_should_throw() + { + // Arrange + DateTimeOffset? subject = null; + int expectation = 22; + + // Act + Action act = () => subject.Should().HaveMinute(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the minute part of subject to be 22, but found a DateTimeOffset."); + } + } + + public class NotHaveMinute + { + [Fact] + public void When_asserting_subject_datetimeoffset_should_not_have_minutes_with_the_same_value_it_should_throw() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 12, 31, 23, 59, 00), TimeSpan.Zero); + int expectation = 59; + + // Act + Action act = () => subject.Should().NotHaveMinute(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the minute part of subject to be 59, but it was."); + } + + [Fact] + public void When_asserting_subject_datetimeoffset_should_not_have_minutes_with_different_value_it_should_succeed() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 12, 31, 23, 59, 00), TimeSpan.Zero); + int expectation = 58; + + // Act + Action act = () => subject.Should().NotHaveMinute(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_null_datetimeoffset_should_not_have_minute_should_throw() + { + // Arrange + DateTimeOffset? subject = null; + int expectation = 22; + + // Act + Action act = () => subject.Should().NotHaveMinute(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the minute part of subject to be 22, but found a DateTimeOffset."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.HaveMonth.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.HaveMonth.cs new file mode 100644 index 0000000000..d49d9f371f --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.HaveMonth.cs @@ -0,0 +1,102 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeOffsetAssertionSpecs +{ + public class HaveMonth + { + [Fact] + public void When_asserting_subject_datetimeoffset_should_have_month_with_the_same_value_it_should_succeed() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 12, 31), TimeSpan.Zero); + int expectation = 12; + + // Act + Action act = () => subject.Should().HaveMonth(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_datetimeoffset_should_have_a_month_with_a_different_value_it_should_throw() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 12, 31), TimeSpan.Zero); + int expectation = 11; + + // Act + Action act = () => subject.Should().HaveMonth(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the month part of subject to be 11, but it was 12."); + } + + [Fact] + public void When_asserting_subject_null_datetimeoffset_should_have_month_should_throw() + { + // Arrange + DateTimeOffset? subject = null; + int expectation = 12; + + // Act + Action act = () => subject.Should().HaveMonth(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the month part of subject to be 12, but found a DateTimeOffset."); + } + } + + public class NotHaveMonth + { + [Fact] + public void When_asserting_subject_datetimeoffset_should_not_have_month_with_the_same_value_it_should_throw() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 12, 31), TimeSpan.Zero); + int expectation = 12; + + // Act + Action act = () => subject.Should().NotHaveMonth(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the month part of subject to be 12, but it was."); + } + + [Fact] + public void When_asserting_subject_datetimeoffset_should_not_have_a_month_with_a_different_value_it_should_succeed() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 12, 31), TimeSpan.Zero); + int expectation = 11; + + // Act + Action act = () => subject.Should().NotHaveMonth(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_null_datetimeoffset_should_not_have_month_should_throw() + { + // Arrange + DateTimeOffset? subject = null; + int expectation = 12; + + // Act + Action act = () => subject.Should().NotHaveMonth(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the month part of subject to be 12, but found a DateTimeOffset."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.HaveOffset.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.HaveOffset.cs new file mode 100644 index 0000000000..57db04b8d3 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.HaveOffset.cs @@ -0,0 +1,102 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeOffsetAssertionSpecs +{ + public class HaveOffset + { + [Fact] + public void When_asserting_subject_datetimeoffset_should_have_offset_with_the_same_value_it_should_succeed() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 12, 31, 23, 59, 00), TimeSpan.FromHours(7)); + TimeSpan expectation = TimeSpan.FromHours(7); + + // Act + Action act = () => subject.Should().HaveOffset(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_datetimeoffset_should_have_offset_with_different_value_it_should_throw() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 12, 31, 23, 59, 10), TimeSpan.Zero); + TimeSpan expectation = TimeSpan.FromHours(3); + + // Act + Action act = () => subject.Should().HaveOffset(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the offset of subject to be 3h, but it was default."); + } + + [Fact] + public void When_asserting_subject_null_datetimeoffset_should_have_offset_should_throw() + { + // Arrange + DateTimeOffset? subject = null; + TimeSpan expectation = TimeSpan.FromHours(3); + + // Act + Action act = () => subject.Should().HaveOffset(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the offset of subject to be 3h, but found a DateTimeOffset."); + } + } + + public class NotHaveOffset + { + [Fact] + public void When_asserting_subject_datetimeoffset_should_not_have_offset_with_the_same_value_it_should_throw() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 12, 31, 23, 59, 00), TimeSpan.FromHours(7)); + TimeSpan expectation = TimeSpan.FromHours(7); + + // Act + Action act = () => subject.Should().NotHaveOffset(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the offset of subject to be 7h, but it was."); + } + + [Fact] + public void When_asserting_subject_datetimeoffset_should_not_have_offset_with_different_value_it_should_succeed() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 12, 31, 23, 59, 00), TimeSpan.Zero); + TimeSpan expectation = TimeSpan.FromHours(3); + + // Act + Action act = () => subject.Should().NotHaveOffset(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_null_datetimeoffset_should_not_have_offset_should_throw() + { + // Arrange + DateTimeOffset? subject = null; + TimeSpan expectation = TimeSpan.FromHours(3); + + // Act + Action act = () => subject.Should().NotHaveOffset(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the offset of subject to be 3h, but found a DateTimeOffset."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.HaveSecond.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.HaveSecond.cs new file mode 100644 index 0000000000..18fa8066fd --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.HaveSecond.cs @@ -0,0 +1,102 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeOffsetAssertionSpecs +{ + public class HaveSecond + { + [Fact] + public void When_asserting_subject_datetimeoffset_should_have_seconds_with_the_same_value_it_should_succeed() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 12, 31, 23, 59, 00), TimeSpan.Zero); + int expectation = 0; + + // Act + Action act = () => subject.Should().HaveSecond(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_datetimeoffset_should_have_seconds_with_different_value_it_should_throw() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 12, 31, 23, 59, 00), TimeSpan.Zero); + int expectation = 1; + + // Act + Action act = () => subject.Should().HaveSecond(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the seconds part of subject to be 1, but it was 0."); + } + + [Fact] + public void When_asserting_subject_null_datetimeoffset_should_have_second_should_throw() + { + // Arrange + DateTimeOffset? subject = null; + int expectation = 22; + + // Act + Action act = () => subject.Should().HaveSecond(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the seconds part of subject to be 22, but found a DateTimeOffset."); + } + } + + public class NotHaveSecond + { + [Fact] + public void When_asserting_subject_datetimeoffset_should_not_have_seconds_with_the_same_value_it_should_throw() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 12, 31, 23, 59, 00), TimeSpan.Zero); + int expectation = 0; + + // Act + Action act = () => subject.Should().NotHaveSecond(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the seconds part of subject to be 0, but it was."); + } + + [Fact] + public void When_asserting_subject_datetimeoffset_should_not_have_seconds_with_different_value_it_should_succeed() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 12, 31, 23, 59, 00), TimeSpan.Zero); + int expectation = 1; + + // Act + Action act = () => subject.Should().NotHaveSecond(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_null_datetimeoffset_should_not_have_second_should_throw() + { + // Arrange + DateTimeOffset? subject = null; + int expectation = 22; + + // Act + Action act = () => subject.Should().NotHaveSecond(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the seconds part of subject to be 22, but found a DateTimeOffset."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.HaveValue.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.HaveValue.cs new file mode 100644 index 0000000000..6c29dff3f0 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.HaveValue.cs @@ -0,0 +1,69 @@ +using System; +using FluentAssertions.Common; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeOffsetAssertionSpecs +{ + public class HaveValue + { + [Fact] + public void When_nullable_datetimeoffset_value_with_a_value_to_have_a_value_it_should_succeed() + { + // Arrange + DateTimeOffset? nullableDateTime = new DateTime(2016, 06, 04).ToDateTimeOffset(); + + // Act + Action action = () => nullableDateTime.Should().HaveValue(); + + // Assert + action.Should().NotThrow(); + } + + [Fact] + public void Should_fail_when_asserting_nullable_datetimeoffset_value_without_a_value_to_have_a_value() + { + // Arrange + DateTimeOffset? nullableDateTime = null; + + // Act + Action action = () => nullableDateTime.Should().HaveValue(); + + // Assert + action.Should().Throw(); + } + } + + public class NotHaveValue + { + [Fact] + public void Should_succeed_when_asserting_nullable_datetimeoffset_value_without_a_value_to_not_have_a_value() + { + // Arrange + DateTimeOffset? nullableDateTime = null; + + // Act + Action action = () => + nullableDateTime.Should().NotHaveValue(); + + // Assert + action.Should().NotThrow(); + } + + [Fact] + public void Should_fail_when_asserting_nullable_datetimeoffset_value_with_a_value_to_not_have_a_value() + { + // Arrange + DateTimeOffset? nullableDateTime = new DateTime(2016, 06, 04).ToDateTimeOffset(); + + // Act + Action action = () => + nullableDateTime.Should().NotHaveValue(); + + // Assert + action.Should().Throw(); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.HaveYear.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.HaveYear.cs new file mode 100644 index 0000000000..4c2d773237 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.HaveYear.cs @@ -0,0 +1,102 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class DateTimeOffsetAssertionSpecs +{ + public class HaveYear + { + [Fact] + public void When_asserting_subject_datetimeoffset_should_have_year_with_the_same_value_should_succeed() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 06, 04), TimeSpan.Zero); + int expectation = 2009; + + // Act + Action act = () => subject.Should().HaveYear(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_datetimeoffset_should_have_year_with_a_different_value_should_throw() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 06, 04), TimeSpan.Zero); + int expectation = 2008; + + // Act + Action act = () => subject.Should().HaveYear(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the year part of subject to be 2008, but it was 2009."); + } + + [Fact] + public void When_asserting_subject_null_datetimeoffset_should_have_year_should_throw() + { + // Arrange + DateTimeOffset? subject = null; + int expectation = 2008; + + // Act + Action act = () => subject.Should().HaveYear(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the year part of subject to be 2008, but found a DateTimeOffset."); + } + } + + public class NotHaveYear + { + [Fact] + public void When_asserting_subject_datetimeoffset_should_not_have_year_with_the_same_value_should_throw() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 06, 04), TimeSpan.Zero); + int expectation = 2009; + + // Act + Action act = () => subject.Should().NotHaveYear(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the year part of subject to be 2009, but it was."); + } + + [Fact] + public void When_asserting_subject_datetimeoffset_should_not_have_year_with_a_different_value_should_succeed() + { + // Arrange + DateTimeOffset subject = new(new DateTime(2009, 06, 04), TimeSpan.Zero); + int expectation = 2008; + + // Act + Action act = () => subject.Should().NotHaveYear(expectation); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_subject_null_datetimeoffset_should_not_have_year_should_throw() + { + // Arrange + DateTimeOffset? subject = null; + int expectation = 2008; + + // Act + Action act = () => subject.Should().NotHaveYear(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the year part of subject to be 2008, but found a DateTimeOffset."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.cs index 99e3de1016..4b79d69fdd 100644 --- a/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Primitives/DateTimeOffsetAssertionSpecs.cs @@ -1,2489 +1,11 @@ using System; using FluentAssertions.Common; -using FluentAssertions.Extensions; using Xunit; -using Xunit.Sdk; namespace FluentAssertions.Specs.Primitives; -public class DateTimeOffsetAssertionSpecs +public partial class DateTimeOffsetAssertionSpecs { - public class HaveValue - { - [Fact] - public void When_nullable_datetimeoffset_value_with_a_value_to_have_a_value_it_should_succeed() - { - // Arrange - DateTimeOffset? nullableDateTime = new DateTime(2016, 06, 04).ToDateTimeOffset(); - - // Act - Action action = () => nullableDateTime.Should().HaveValue(); - - // Assert - action.Should().NotThrow(); - } - - [Fact] - public void Should_fail_when_asserting_nullable_datetimeoffset_value_without_a_value_to_have_a_value() - { - // Arrange - DateTimeOffset? nullableDateTime = null; - - // Act - Action action = () => nullableDateTime.Should().HaveValue(); - - // Assert - action.Should().Throw(); - } - } - - public class NotHaveValue - { - [Fact] - public void Should_succeed_when_asserting_nullable_datetimeoffset_value_without_a_value_to_not_have_a_value() - { - // Arrange - DateTimeOffset? nullableDateTime = null; - - // Act - Action action = () => - nullableDateTime.Should().NotHaveValue(); - - // Assert - action.Should().NotThrow(); - } - - [Fact] - public void Should_fail_when_asserting_nullable_datetimeoffset_value_with_a_value_to_not_have_a_value() - { - // Arrange - DateTimeOffset? nullableDateTime = new DateTime(2016, 06, 04).ToDateTimeOffset(); - - // Act - Action action = () => - nullableDateTime.Should().NotHaveValue(); - - // Assert - action.Should().Throw(); - } - } - - public class NotBeNull - { - [Fact] - public void When_nullable_datetimeoffset_value_with_a_value_not_be_null_it_should_succeed() - { - // Arrange - DateTimeOffset? nullableDateTime = new DateTime(2016, 06, 04).ToDateTimeOffset(); - - // Act - Action action = () => nullableDateTime.Should().NotBeNull(); - - // Assert - action.Should().NotThrow(); - } - - [Fact] - public void Should_fail_when_asserting_nullable_datetimeoffset_value_without_a_value_to_not_be_null() - { - // Arrange - DateTimeOffset? nullableDateTime = null; - - // Act - Action action = () => nullableDateTime.Should().NotBeNull(); - - // Assert - action.Should().Throw(); - } - } - - public class BeNull - { - [Fact] - public void Should_succeed_when_asserting_nullable_datetimeoffset_value_without_a_value_to_be_null() - { - // Arrange - DateTimeOffset? nullableDateTime = null; - - // Act - Action action = () => - nullableDateTime.Should().BeNull(); - - // Assert - action.Should().NotThrow(); - } - - [Fact] - public void Should_fail_when_asserting_nullable_datetimeoffset_value_with_a_value_to_be_null() - { - // Arrange - DateTimeOffset? nullableDateTime = new DateTime(2016, 06, 04).ToDateTimeOffset(); - - // Act - Action action = () => - nullableDateTime.Should().BeNull(); - - // Assert - action.Should().Throw(); - } - } - - public class Be - { - [Fact] - public void Should_succeed_when_asserting_datetimeoffset_value_is_equal_to_the_same_value() - { - // Arrange - DateTimeOffset dateTime = new DateTime(2016, 06, 04).ToDateTimeOffset(); - DateTimeOffset sameDateTime = new DateTime(2016, 06, 04).ToDateTimeOffset(); - - // Act - Action act = () => dateTime.Should().Be(sameDateTime); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_datetimeoffset_value_is_equal_to_the_same_nullable_value_be_should_succeed() - { - // Arrange - DateTimeOffset dateTime = 4.June(2016).ToDateTimeOffset(); - DateTimeOffset? sameDateTime = 4.June(2016).ToDateTimeOffset(); - - // Act - Action act = () => dateTime.Should().Be(sameDateTime); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_both_values_are_at_their_minimum_then_it_should_succeed() - { - // Arrange - DateTimeOffset dateTime = DateTimeOffset.MinValue; - DateTimeOffset sameDateTime = DateTimeOffset.MinValue; - - // Act - Action act = () => dateTime.Should().Be(sameDateTime); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_both_values_are_at_their_maximum_then_it_should_succeed() - { - // Arrange - DateTimeOffset dateTime = DateTimeOffset.MaxValue; - DateTimeOffset sameDateTime = DateTimeOffset.MaxValue; - - // Act - Action act = () => dateTime.Should().Be(sameDateTime); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void Should_fail_when_asserting_datetimeoffset_value_is_equal_to_the_different_value() - { - // Arrange - var dateTime = 10.March(2012).WithOffset(1.Hours()); - var otherDateTime = 11.March(2012).WithOffset(1.Hours()); - - // Act - Action act = () => dateTime.Should().Be(otherDateTime, "because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dateTime to represent the same point in time as <2012-03-11 +1h>*failure message, but <2012-03-10 +1h> does not."); - } - - [Fact] - public void When_datetimeoffset_value_is_equal_to_the_different_nullable_value_be_should_failed() - { - // Arrange - DateTimeOffset dateTime = 10.March(2012).WithOffset(1.Hours()); - DateTimeOffset? otherDateTime = 11.March(2012).WithOffset(1.Hours()); - - // Act - Action act = () => dateTime.Should().Be(otherDateTime, "because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dateTime to represent the same point in time as <2012-03-11 +1h>*failure message, but <2012-03-10 +1h> does not."); - } - - [Fact] - public void Should_succeed_when_asserting_nullable_datetimeoffset_value_equals_the_same_value() - { - // Arrange - DateTimeOffset? nullableDateTimeA = new DateTime(2016, 06, 04).ToDateTimeOffset(); - DateTimeOffset? nullableDateTimeB = new DateTime(2016, 06, 04).ToDateTimeOffset(); - - // Act - Action action = () => - nullableDateTimeA.Should().Be(nullableDateTimeB); - - // Assert - action.Should().NotThrow(); - } - - [Fact] - public void Should_succeed_when_asserting_nullable_datetimeoffset_null_value_equals_null() - { - // Arrange - DateTimeOffset? nullableDateTimeA = null; - DateTimeOffset? nullableDateTimeB = null; - - // Act / Assert - nullableDateTimeA.Should().Be(nullableDateTimeB); - } - - [Fact] - public void Should_fail_when_asserting_nullable_datetimeoffset_value_equals_a_different_value() - { - // Arrange - DateTimeOffset? nullableDateTimeA = new DateTime(2016, 06, 04).ToDateTimeOffset(); - DateTimeOffset? nullableDateTimeB = new DateTime(2016, 06, 06).ToDateTimeOffset(); - - // Act - Action action = () => - nullableDateTimeA.Should().Be(nullableDateTimeB); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void Should_fail_with_descriptive_message_when_asserting_datetimeoffset_null_value_is_equal_to_another_value() - { - // Arrange - DateTimeOffset? nullableDateTime = null; - DateTimeOffset expectation = 27.March(2016).ToDateTimeOffset(1.Hours()); - - // Act - Action action = () => - nullableDateTime.Should().Be(expectation, "because we want to test the failure {0}", "message"); - - // Assert - action.Should().Throw() - .WithMessage( - "Expected nullableDateTime to represent the same point in time as <2016-03-27 +1h> because we want to test the failure message, but found a DateTimeOffset."); - } - - [Fact] - public void Should_fail_with_descriptive_message_when_asserting_non_null_value_is_equal_to_null_value() - { - // Arrange - DateTimeOffset? nullableDateTime = 27.March(2016).ToDateTimeOffset(1.Hours()); - DateTimeOffset? expectation = null; - - // Act - Action action = () => - nullableDateTime.Should().Be(expectation, "because we want to test the failure {0}", "message"); - - // Assert - action.Should().Throw() - .WithMessage( - "Expected nullableDateTime to be because we want to test the failure message, but it was <2016-03-27 +1h>."); - } - - [Fact] - public void - When_asserting_different_date_time_offsets_representing_the_same_world_time_it_should_succeed() - { - // Arrange - var specificDate = 1.May(2008).At(6, 32); - - var dateWithFiveHourOffset = new DateTimeOffset(specificDate - 5.Hours(), -5.Hours()); - - var dateWithSixHourOffset = new DateTimeOffset(specificDate - 6.Hours(), -6.Hours()); - - // Act / Assert - dateWithFiveHourOffset.Should().Be(dateWithSixHourOffset); - } - } - - public class NotBe - { - [Fact] - public void Should_succeed_when_asserting_datetimeoffset_value_is_not_equal_to_a_different_value() - { - // Arrange - DateTimeOffset dateTime = new DateTime(2016, 06, 04).ToDateTimeOffset(); - DateTimeOffset otherDateTime = new DateTime(2016, 06, 05).ToDateTimeOffset(); - - // Act - Action act = () => dateTime.Should().NotBe(otherDateTime); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_datetimeoffset_value_is_not_equal_to_a_nullable_different_value_notbe_should_succeed() - { - // Arrange - DateTimeOffset dateTime = 4.June(2016).ToDateTimeOffset(); - DateTimeOffset? otherDateTime = 5.June(2016).ToDateTimeOffset(); - - // Act - Action act = () => dateTime.Should().NotBe(otherDateTime); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void Should_fail_when_asserting_datetimeoffset_value_is_not_equal_to_the_same_value() - { - // Arrange - var dateTime = new DateTimeOffset(10.March(2012), 1.Hours()); - var sameDateTime = new DateTimeOffset(10.March(2012), 1.Hours()); - - // Act - Action act = - () => dateTime.Should().NotBe(sameDateTime, "because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw().WithMessage( - "Did not expect dateTime to represent the same point in time as <2012-03-10 +1h> because we want to test the failure message, but it did."); - } - - [Fact] - public void When_datetimeoffset_value_is_not_equal_to_the_same_nullable_value_notbe_should_failed() - { - // Arrange - DateTimeOffset dateTime = new(10.March(2012), 1.Hours()); - DateTimeOffset? sameDateTime = new DateTimeOffset(10.March(2012), 1.Hours()); - - // Act - Action act = - () => dateTime.Should().NotBe(sameDateTime, "because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw().WithMessage( - "Did not expect dateTime to represent the same point in time as <2012-03-10 +1h> because we want to test the failure message, but it did."); - } - - [Fact] - public void - When_asserting_different_date_time_offsets_representing_different_world_times_it_should_not_succeed() - { - // Arrange - var specificDate = 1.May(2008).At(6, 32); - - var dateWithZeroHourOffset = new DateTimeOffset(specificDate, TimeSpan.Zero); - var dateWithOneHourOffset = new DateTimeOffset(specificDate, 1.Hours()); - - // Act / Assert - dateWithZeroHourOffset.Should().NotBe(dateWithOneHourOffset); - } - } - - public class BeExactly - { - [Fact] - public void Should_succeed_when_asserting_value_is_exactly_equal_to_the_same_value() - { - // Arrange - DateTimeOffset dateTime = new DateTime(2016, 06, 04).ToDateTimeOffset(); - DateTimeOffset sameDateTime = new DateTime(2016, 06, 04).ToDateTimeOffset(); - - // Act / Assert - dateTime.Should().BeExactly(sameDateTime); - } - - [Fact] - public void Should_succeed_when_asserting_value_is_exactly_equal_to_the_same_nullable_value() - { - // Arrange - DateTimeOffset dateTime = 4.June(2016).ToDateTimeOffset(); - DateTimeOffset? sameDateTime = 4.June(2016).ToDateTimeOffset(); - - // Act / Assert - dateTime.Should().BeExactly(sameDateTime); - } - - [Fact] - public void Should_fail_when_asserting_value_is_exactly_equal_to_a_different_value() - { - // Arrange - var dateTime = 10.March(2012).WithOffset(1.Hours()); - var otherDateTime = dateTime.ToUniversalTime(); - - // Act - Action act = () => dateTime.Should().BeExactly(otherDateTime, "because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dateTime to be exactly <2012-03-09 23:00:00 +0h>*failure message, but it was <2012-03-10 +1h>."); - } - - [Fact] - public void Should_fail_when_asserting_value_is_exactly_equal_to_a_different_nullable_value() - { - // Arrange - DateTimeOffset dateTime = 10.March(2012).WithOffset(1.Hours()); - DateTimeOffset? otherDateTime = dateTime.ToUniversalTime(); - - // Act - Action act = () => dateTime.Should().BeExactly(otherDateTime, "because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw().WithMessage( - "Expected dateTime to be exactly <2012-03-09 23:00:00 +0h>*failure message, but it was <2012-03-10 +1h>."); - } - - [Fact] - public void Should_succeed_when_asserting_nullable_value_is_exactly_equal_to_the_same_nullable_value() - { - // Arrange - DateTimeOffset? nullableDateTimeA = new DateTime(2016, 06, 04).ToDateTimeOffset(); - DateTimeOffset? nullableDateTimeB = new DateTime(2016, 06, 04).ToDateTimeOffset(); - - // Act / Assert - nullableDateTimeA.Should().BeExactly(nullableDateTimeB); - } - - [Fact] - public void Should_succeed_when_asserting_nullable_null_value_exactly_equals_null() - { - // Arrange - DateTimeOffset? nullableDateTimeA = null; - DateTimeOffset? nullableDateTimeB = null; - - // Act / Assert - nullableDateTimeA.Should().BeExactly(nullableDateTimeB); - } - - [Fact] - public void Should_fail_when_asserting_nullable_value_exactly_equals_a_different_value() - { - // Arrange - DateTimeOffset? nullableDateTimeA = new DateTime(2016, 06, 04).ToDateTimeOffset(); - DateTimeOffset? nullableDateTimeB = new DateTime(2016, 06, 06).ToDateTimeOffset(); - - // Act - Action action = () => - nullableDateTimeA.Should().BeExactly(nullableDateTimeB); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void Should_fail_with_descriptive_message_when_asserting_null_value_is_exactly_equal_to_another_value() - { - // Arrange - DateTimeOffset? nullableDateTime = null; - DateTimeOffset expectation = 27.March(2016).ToDateTimeOffset(1.Hours()); - - // Act - Action action = () => - nullableDateTime.Should().BeExactly(expectation, "because we want to test the failure {0}", "message"); - - // Assert - action.Should().Throw() - .WithMessage( - "Expected nullableDateTime to be exactly <2016-03-27 +1h> because we want to test the failure message, but found a DateTimeOffset."); - } - } - - public class NotBeExactly - { - [Fact] - public void Should_succeed_when_asserting_value_is_not_exactly_equal_to_a_different_value() - { - // Arrange - DateTimeOffset dateTime = 10.March(2012).WithOffset(1.Hours()); - DateTimeOffset otherDateTime = dateTime.ToUniversalTime(); - - // Act / Assert - dateTime.Should().NotBeExactly(otherDateTime); - } - - [Fact] - public void Should_succeed_when_asserting_value_is_not_exactly_equal_to_a_different_nullable_value() - { - // Arrange - DateTimeOffset dateTime = 10.March(2012).WithOffset(1.Hours()); - DateTimeOffset? otherDateTime = dateTime.ToUniversalTime(); - - // Act / Assert - dateTime.Should().NotBeExactly(otherDateTime); - } - - [Fact] - public void Should_fail_when_asserting_value_is_not_exactly_equal_to_the_same_value() - { - // Arrange - var dateTime = new DateTimeOffset(10.March(2012), 1.Hours()); - var sameDateTime = new DateTimeOffset(10.March(2012), 1.Hours()); - - // Act - Action act = - () => dateTime.Should().NotBeExactly(sameDateTime, "because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw().WithMessage( - "Did not expect dateTime to be exactly <2012-03-10 +1h> because we want to test the failure message, but it was."); - } - - [Fact] - public void Should_fail_when_asserting_value_is_not_exactly_equal_to_the_same_nullable_value() - { - // Arrange - DateTimeOffset dateTime = new(10.March(2012), 1.Hours()); - DateTimeOffset? sameDateTime = new DateTimeOffset(10.March(2012), 1.Hours()); - - // Act - Action act = - () => dateTime.Should().NotBeExactly(sameDateTime, "because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw().WithMessage( - "Did not expect dateTime to be exactly <2012-03-10 +1h> because we want to test the failure message, but it was."); - } - } - - public class BeCloseTo - { - [Fact] - public void When_asserting_that_time_is_close_to_a_negative_precision_it_should_throw() - { - // Arrange - var dateTime = DateTimeOffset.UtcNow; - var actual = new DateTimeOffset(dateTime.Ticks - 1, TimeSpan.Zero); - - // Act - Action act = () => actual.Should().BeCloseTo(dateTime, -1.Ticks()); - - // Assert - act.Should().Throw() - .WithParameterName("precision") - .WithMessage("*must be non-negative*"); - } - - [Fact] - public void When_a_datetimeoffset_is_close_to_a_later_datetimeoffset_by_one_tick_it_should_succeed() - { - // Arrange - var dateTime = DateTimeOffset.UtcNow; - var actual = new DateTimeOffset(dateTime.Ticks - 1, TimeSpan.Zero); - - // Act - Action act = () => actual.Should().BeCloseTo(dateTime, TimeSpan.FromTicks(1)); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_a_datetimeoffset_is_close_to_an_earlier_datetimeoffset_by_one_tick_it_should_succeed() - { - // Arrange - var dateTime = DateTimeOffset.UtcNow; - var actual = new DateTimeOffset(dateTime.Ticks + 1, TimeSpan.Zero); - - // Act - Action act = () => actual.Should().BeCloseTo(dateTime, TimeSpan.FromTicks(1)); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_a_datetimeoffset_is_close_to_a_MinValue_by_one_tick_it_should_succeed() - { - // Arrange - var dateTime = DateTimeOffset.MinValue; - var actual = new DateTimeOffset(dateTime.Ticks + 1, TimeSpan.Zero); - - // Act - Action act = () => actual.Should().BeCloseTo(dateTime, TimeSpan.FromTicks(1)); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_a_datetimeoffset_is_close_to_a_MaxValue_by_one_tick_it_should_succeed() - { - // Arrange - var dateTime = DateTimeOffset.MaxValue; - var actual = new DateTimeOffset(dateTime.Ticks - 1, TimeSpan.Zero); - - // Act - Action act = () => actual.Should().BeCloseTo(dateTime, TimeSpan.FromTicks(1)); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_datetimeoffset_is_close_to_a_later_datetimeoffset_it_should_succeed() - { - // Arrange - DateTimeOffset time = new(2016, 06, 04, 12, 15, 30, 980, TimeSpan.Zero); - DateTimeOffset nearbyTime = new(2016, 06, 04, 12, 15, 31, 0, TimeSpan.Zero); - - // Act - Action act = () => time.Should().BeCloseTo(nearbyTime, 20.Milliseconds()); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_datetimeoffset_is_close_to_an_earlier_datetimeoffset_it_should_succeed() - { - // Arrange - DateTimeOffset time = new(2016, 06, 04, 12, 15, 31, 020, TimeSpan.Zero); - DateTimeOffset nearbyTime = new(2016, 06, 04, 12, 15, 31, 0, TimeSpan.Zero); - - // Act - Action act = () => time.Should().BeCloseTo(nearbyTime, 20.Milliseconds()); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void - When_asserting_subject_datetimeoffset_is_close_to_another_value_that_is_later_by_more_than_20ms_it_should_throw() - { - // Arrange - DateTimeOffset time = 13.March(2012).At(12, 15, 30, 979).ToDateTimeOffset(1.Hours()); - DateTimeOffset nearbyTime = 13.March(2012).At(12, 15, 31).ToDateTimeOffset(1.Hours()); - - // Act - Action act = () => time.Should().BeCloseTo(nearbyTime, 20.Milliseconds()); - - // Assert - act.Should().Throw() - .WithMessage( - "Expected time to be within 20ms from <2012-03-13 12:15:31 +1H>, but <2012-03-13 12:15:30.979 +1H> was off by 21ms."); - } - - [Fact] - public void - When_asserting_subject_datetimeoffset_is_close_to_another_value_that_is_earlier_by_more_than_20ms_it_should_throw() - { - // Arrange - DateTimeOffset time = 13.March(2012).At(12, 15, 31, 021).ToDateTimeOffset(1.Hours()); - DateTimeOffset nearbyTime = 13.March(2012).At(12, 15, 31).ToDateTimeOffset(1.Hours()); - - // Act - Action act = () => time.Should().BeCloseTo(nearbyTime, 20.Milliseconds()); - - // Assert - act.Should().Throw() - .WithMessage( - "Expected time to be within 20ms from <2012-03-13 12:15:31 +1h>, but <2012-03-13 12:15:31.021 +1h> was off by 21ms."); - } - - [Fact] - public void - When_asserting_subject_datetimeoffset_is_close_to_another_value_that_is_earlier_by_more_than_a_35ms_timespan_it_should_throw() - { - // Arrange - DateTimeOffset time = 13.March(2012).At(12, 15, 31, 036).WithOffset(1.Hours()); - DateTimeOffset nearbyTime = 13.March(2012).At(12, 15, 31).WithOffset(1.Hours()); - - // Act - Action act = () => time.Should().BeCloseTo(nearbyTime, TimeSpan.FromMilliseconds(35)); - - // Assert - act.Should().Throw() - .WithMessage( - "Expected time to be within 35ms from <2012-03-13 12:15:31 +1h>, but <2012-03-13 12:15:31.036 +1h> was off by 36ms."); - } - - [Fact] - public void When_asserting_subject_datetimeoffset_is_close_to_an_earlier_datetimeoffset_by_35ms_it_should_succeed() - { - // Arrange - DateTimeOffset time = 13.March(2012).At(12, 15, 31, 035).ToDateTimeOffset(1.Hours()); - DateTimeOffset nearbyTime = 13.March(2012).At(12, 15, 31).ToDateTimeOffset(1.Hours()); - - // Act - Action act = () => time.Should().BeCloseTo(nearbyTime, 35.Milliseconds()); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_null_datetimeoffset_is_close_to_another_it_should_throw() - { - // Arrange - DateTimeOffset? time = null; - DateTimeOffset nearbyTime = 13.March(2012).At(12, 15, 31).ToDateTimeOffset(5.Hours()); - - // Act - Action act = () => time.Should().BeCloseTo(nearbyTime, 35.Milliseconds()); - - // Assert - act.Should().Throw() - .WithMessage("Expected*, but found ."); - } - - [Fact] - public void When_asserting_subject_datetimeoffset_is_close_to_the_maximum_datetimeoffset_it_should_succeed() - { - // Arrange - DateTimeOffset time = DateTimeOffset.MaxValue - 50.Milliseconds(); - DateTimeOffset nearbyTime = DateTimeOffset.MaxValue; - - // Act - Action act = () => time.Should().BeCloseTo(nearbyTime, 100.Milliseconds()); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_datetimeoffset_is_close_to_the_minimum_datetimeoffset_it_should_succeed() - { - // Arrange - DateTimeOffset time = DateTimeOffset.MinValue + 50.Milliseconds(); - DateTimeOffset nearbyTime = DateTimeOffset.MinValue; - - // Act - Action act = () => time.Should().BeCloseTo(nearbyTime, 100.Milliseconds()); - - // Assert - act.Should().NotThrow(); - } - } - - public class NotBeCloseTo - { - [Fact] - public void When_asserting_that_time_is_not_close_to_a_negative_precision_it_should_throw() - { - // Arrange - var dateTime = DateTimeOffset.UtcNow; - var actual = new DateTimeOffset(dateTime.Ticks - 1, TimeSpan.Zero); - - // Act - Action act = () => actual.Should().NotBeCloseTo(dateTime, -1.Ticks()); - - // Assert - act.Should().Throw() - .WithParameterName("precision") - .WithMessage("*must be non-negative*"); - } - - [Fact] - public void When_a_datetimeoffset_is_close_to_a_later_datetimeoffset_by_one_tick_it_should_fail() - { - // Arrange - var dateTime = DateTimeOffset.UtcNow; - var actual = new DateTimeOffset(dateTime.Ticks - 1, TimeSpan.Zero); - - // Act - Action act = () => actual.Should().NotBeCloseTo(dateTime, TimeSpan.FromTicks(1)); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_a_datetimeoffset_is_close_to_an_earlier_datetimeoffset_by_one_tick_it_should_fail() - { - // Arrange - var dateTime = DateTimeOffset.UtcNow; - var actual = new DateTimeOffset(dateTime.Ticks + 1, TimeSpan.Zero); - - // Act - Action act = () => actual.Should().NotBeCloseTo(dateTime, TimeSpan.FromTicks(1)); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_a_datetimeoffset_is_close_to_a_MinValue_by_one_tick_it_should_fail() - { - // Arrange - var dateTime = DateTimeOffset.MinValue; - var actual = new DateTimeOffset(dateTime.Ticks + 1, TimeSpan.Zero); - - // Act - Action act = () => actual.Should().NotBeCloseTo(dateTime, TimeSpan.FromTicks(1)); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_a_datetimeoffset_is_close_to_a_MaxValue_by_one_tick_it_should_fail() - { - // Arrange - var dateTime = DateTimeOffset.MaxValue; - var actual = new DateTimeOffset(dateTime.Ticks - 1, TimeSpan.Zero); - - // Act - Action act = () => actual.Should().NotBeCloseTo(dateTime, TimeSpan.FromTicks(1)); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_asserting_subject_datetimeoffset_is_not_close_to_a_later_datetimeoffset_it_should_throw() - { - // Arrange - DateTimeOffset time = new(2016, 06, 04, 12, 15, 30, 980, TimeSpan.Zero); - DateTimeOffset nearbyTime = new(2016, 06, 04, 12, 15, 31, 0, TimeSpan.Zero); - - // Act - Action act = () => time.Should().NotBeCloseTo(nearbyTime, 20.Milliseconds()); - - // Assert - act.Should().Throw() - .WithMessage( - "Did not expect time to be within 20ms from <2016-06-04 12:15:31 +0h>, but it was <2016-06-04 12:15:30.980 +0h>."); - } - - [Fact] - public void - When_asserting_subject_datetimeoffset_is_not_close_to_a_later_datetimeoffset_by_a_20ms_timespan_it_should_throw() - { - // Arrange - DateTimeOffset time = new(2016, 06, 04, 12, 15, 30, 980, TimeSpan.Zero); - DateTimeOffset nearbyTime = new(2016, 06, 04, 12, 15, 31, 0, TimeSpan.Zero); - - // Act - Action act = () => time.Should().NotBeCloseTo(nearbyTime, TimeSpan.FromMilliseconds(20)); - - // Assert - act.Should().Throw().WithMessage( - "Did not expect time to be within 20ms from <2016-06-04 12:15:31 +0h>, but it was <2016-06-04 12:15:30.980 +0h>."); - } - - [Fact] - public void When_asserting_subject_datetimeoffset_is_not_close_to_an_earlier_datetimeoffset_it_should_throw() - { - // Arrange - DateTimeOffset time = new(2016, 06, 04, 12, 15, 31, 020, TimeSpan.Zero); - DateTimeOffset nearbyTime = new(2016, 06, 04, 12, 15, 31, 0, TimeSpan.Zero); - - // Act - Action act = () => time.Should().NotBeCloseTo(nearbyTime, 20.Milliseconds()); - - // Assert - act.Should().Throw() - .WithMessage( - "Did not expect time to be within 20ms from <2016-06-04 12:15:31 +0h>, but it was <2016-06-04 12:15:31.020 +0h>."); - } - - [Fact] - public void - When_asserting_subject_datetimeoffset_is_not_close_to_another_value_that_is_later_by_more_than_20ms_it_should_succeed() - { - // Arrange - DateTimeOffset time = 13.March(2012).At(12, 15, 30, 979).ToDateTimeOffset(1.Hours()); - DateTimeOffset nearbyTime = 13.March(2012).At(12, 15, 31).ToDateTimeOffset(1.Hours()); - - // Act - Action act = () => time.Should().NotBeCloseTo(nearbyTime, 20.Milliseconds()); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void - When_asserting_subject_datetimeoffset_is_not_close_to_another_value_that_is_earlier_by_more_than_20ms_it_should_succeed() - { - // Arrange - DateTimeOffset time = 13.March(2012).At(12, 15, 31, 021).ToDateTimeOffset(1.Hours()); - DateTimeOffset nearbyTime = 13.March(2012).At(12, 15, 31).ToDateTimeOffset(1.Hours()); - - // Act - Action act = () => time.Should().NotBeCloseTo(nearbyTime, 20.Milliseconds()); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_datetimeoffset_is_not_close_to_an_earlier_datetimeoffset_by_35ms_it_should_throw() - { - // Arrange - DateTimeOffset time = 13.March(2012).At(12, 15, 31, 035).ToDateTimeOffset(1.Hours()); - DateTimeOffset nearbyTime = 13.March(2012).At(12, 15, 31).ToDateTimeOffset(1.Hours()); - - // Act - Action act = () => time.Should().NotBeCloseTo(nearbyTime, 35.Milliseconds()); - - // Assert - act.Should().Throw() - .WithMessage( - "Did not expect time to be within 35ms from <2012-03-13 12:15:31 +1h>, but it was <2012-03-13 12:15:31.035 +1h>."); - } - - [Fact] - public void When_asserting_subject_null_datetimeoffset_is_not_close_to_another_it_should_throw() - { - // Arrange - DateTimeOffset? time = null; - DateTimeOffset nearbyTime = 13.March(2012).At(12, 15, 31).ToDateTimeOffset(5.Hours()); - - // Act - Action act = () => time.Should().NotBeCloseTo(nearbyTime, 35.Milliseconds()); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect*, but it was ."); - } - - [Fact] - public void When_asserting_subject_datetimeoffset_is_not_close_to_the_minimum_datetimeoffset_it_should_throw() - { - // Arrange - DateTimeOffset time = DateTimeOffset.MinValue + 50.Milliseconds(); - DateTimeOffset nearbyTime = DateTimeOffset.MinValue; - - // Act - Action act = () => time.Should().NotBeCloseTo(nearbyTime, 100.Milliseconds()); - - // Assert - act.Should().Throw() - .WithMessage( - "Did not expect time to be within 100ms from <0001-01-01 00:00:00.000>, but it was <00:00:00.050 +0h>."); - } - - [Fact] - public void When_asserting_subject_datetimeoffset_is_not_close_to_the_maximum_datetimeoffset_it_should_throw() - { - // Arrange - DateTimeOffset time = DateTimeOffset.MaxValue - 50.Milliseconds(); - DateTimeOffset nearbyTime = DateTimeOffset.MaxValue; - - // Act - Action act = () => time.Should().NotBeCloseTo(nearbyTime, 100.Milliseconds()); - - // Assert - act.Should().Throw() - .WithMessage( - "Did not expect time to be within 100ms from <9999-12-31 23:59:59.9999999 +0h>, but it was <9999-12-31 23:59:59.9499999 +0h>."); - } - } - - public class BeBefore - { - [Fact] - public void When_asserting_a_point_of_time_is_before_a_later_point_it_should_succeed() - { - // Arrange - DateTimeOffset earlierDate = new(new DateTime(2016, 06, 04), TimeSpan.Zero); - DateTimeOffset laterDate = new(new DateTime(2016, 06, 04, 0, 5, 0), TimeSpan.Zero); - - // Act - Action act = () => earlierDate.Should().BeBefore(laterDate); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_is_before_earlier_expected_datetimeoffset_it_should_throw() - { - // Arrange - DateTimeOffset expected = new(new DateTime(2016, 06, 03), TimeSpan.Zero); - DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); - - // Act - Action act = () => subject.Should().BeBefore(expected); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be before <2016-06-03 +0h>, but it was <2016-06-04 +0h>."); - } - - [Fact] - public void When_asserting_subject_datetimeoffset_is_before_the_same_datetimeoffset_it_should_throw() - { - // Arrange - DateTimeOffset expected = new(new DateTime(2016, 06, 04), TimeSpan.Zero); - DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); - - // Act - Action act = () => subject.Should().BeBefore(expected); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be before <2016-06-04 +0h>, but it was <2016-06-04 +0h>."); - } - } - - public class NotBeBefore - { - [Fact] - public void When_asserting_a_point_of_time_is_not_before_another_it_should_throw() - { - // Arrange - DateTimeOffset earlierDate = new(new DateTime(2016, 06, 04), TimeSpan.Zero); - DateTimeOffset laterDate = new(new DateTime(2016, 06, 04, 0, 5, 0), TimeSpan.Zero); - - // Act - Action act = () => earlierDate.Should().NotBeBefore(laterDate); - - // Assert - act.Should().Throw().WithMessage( - "Expected earlierDate to be on or after <2016-06-04 00:05:00 +0h>, but it was <2016-06-04 +0h>."); - } - - [Fact] - public void When_asserting_subject_is_not_before_earlier_expected_datetimeoffset_it_should_succeed() - { - // Arrange - DateTimeOffset expected = new(new DateTime(2016, 06, 03), TimeSpan.Zero); - DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); - - // Act - Action act = () => subject.Should().NotBeBefore(expected); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_datetimeoffset_is_not_before_the_same_datetimeoffset_it_should_succeed() - { - // Arrange - DateTimeOffset expected = new(new DateTime(2016, 06, 04), TimeSpan.Zero); - DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); - - // Act - Action act = () => subject.Should().NotBeBefore(expected); - - // Assert - act.Should().NotThrow(); - } - } - - public class BeOnOrBefore - { - [Fact] - public void When_asserting_subject_datetimeoffset_is_on_or_before_expected_datetimeoffset_should_succeed() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); - DateTimeOffset expectation = new(new DateTime(2016, 06, 05), TimeSpan.Zero); - - // Act - Action act = () => subject.Should().BeOnOrBefore(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void - When_asserting_subject_datetimeoffset_is_on_or_before_the_same_date_as_the_expected_datetimeoffset_should_succeed() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); - DateTimeOffset expectation = new(new DateTime(2016, 06, 04), TimeSpan.Zero); - - // Act - Action act = () => subject.Should().BeOnOrBefore(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_datetimeoffset_is_not_on_or_before_earlier_expected_datetimeoffset_should_throw() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); - DateTimeOffset expectation = new(new DateTime(2016, 06, 03), TimeSpan.Zero); - - // Act - Action act = () => subject.Should().BeOnOrBefore(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be on or before <2016-06-03 +0h>, but it was <2016-06-04 +0h>."); - } - } - - public class NotBeOnOrBefore - { - [Fact] - public void When_asserting_subject_datetimeoffset_is_on_or_before_expected_datetimeoffset_should_throw() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); - DateTimeOffset expectation = new(new DateTime(2016, 06, 05), TimeSpan.Zero); - - // Act - Action act = () => subject.Should().NotBeOnOrBefore(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be after <2016-06-05 +0h>, but it was <2016-06-04 +0h>."); - } - - [Fact] - public void - When_asserting_subject_datetimeoffset_is_on_or_before_the_same_date_as_the_expected_datetimeoffset_should_throw() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); - DateTimeOffset expectation = new(new DateTime(2016, 06, 04), TimeSpan.Zero); - - // Act - Action act = () => subject.Should().NotBeOnOrBefore(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be after <2016-06-04 +0h>, but it was <2016-06-04 +0h>."); - } - - [Fact] - public void When_asserting_subject_datetimeoffset_is_not_on_or_before_earlier_expected_datetimeoffset_should_succeed() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); - DateTimeOffset expectation = new(new DateTime(2016, 06, 03), TimeSpan.Zero); - - // Act - Action act = () => subject.Should().NotBeOnOrBefore(expectation); - - // Assert - act.Should().NotThrow(); - } - } - - public class BeAfter - { - [Fact] - public void When_asserting_subject_datetimeoffset_is_after_earlier_expected_datetimeoffset_should_succeed() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); - DateTimeOffset expectation = new(new DateTime(2016, 06, 03), TimeSpan.Zero); - - // Act - Action act = () => subject.Should().BeAfter(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_datetimeoffset_is_after_later_expected_datetimeoffset_should_throw() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); - DateTimeOffset expectation = new(new DateTime(2016, 06, 05), TimeSpan.Zero); - - // Act - Action act = () => subject.Should().BeAfter(expectation); - - // Assert - act.Should().Throw().WithMessage( - "Expected subject to be after <2016-06-05 +0h>, but it was <2016-06-04 +0h>."); - } - - [Fact] - public void When_asserting_subject_datetimeoffset_is_after_the_same_expected_datetimeoffset_should_throw() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); - DateTimeOffset expectation = new(new DateTime(2016, 06, 04), TimeSpan.Zero); - - // Act - Action act = () => subject.Should().BeAfter(expectation); - - // Assert - act.Should().Throw().WithMessage( - "Expected subject to be after <2016-06-04 +0h>, but it was <2016-06-04 +0h>."); - } - } - - public class NotBeAfter - { - [Fact] - public void When_asserting_subject_datetimeoffset_is_not_after_earlier_expected_datetimeoffset_should_throw() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); - DateTimeOffset expectation = new(new DateTime(2016, 06, 03), TimeSpan.Zero); - - // Act - Action act = () => subject.Should().NotBeAfter(expectation); - - // Assert - act.Should().Throw().WithMessage( - "Expected subject to be on or before <2016-06-03 +0h>, but it was <2016-06-04 +0h>."); - } - - [Fact] - public void When_asserting_subject_datetimeoffset_is_not_after_later_expected_datetimeoffset_should_succeed() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); - DateTimeOffset expectation = new(new DateTime(2016, 06, 05), TimeSpan.Zero); - - // Act - Action act = () => subject.Should().NotBeAfter(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_datetimeoffset_is_not_after_the_same_expected_datetimeoffset_should_succeed() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); - DateTimeOffset expectation = new(new DateTime(2016, 06, 04), TimeSpan.Zero); - - // Act - Action act = () => subject.Should().NotBeAfter(expectation); - - // Assert - act.Should().NotThrow(); - } - } - - public class BeOnOrAfter - { - [Fact] - public void When_asserting_subject_datetimeoffset_is_on_or_after_earlier_expected_datetimeoffset_should_succeed() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); - DateTimeOffset expectation = new(new DateTime(2016, 06, 03), TimeSpan.Zero); - - // Act - Action act = () => subject.Should().BeOnOrAfter(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_datetimeoffset_is_on_or_after_the_same_expected_datetimeoffset_should_succeed() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); - DateTimeOffset expectation = new(new DateTime(2016, 06, 04), TimeSpan.Zero); - - // Act - Action act = () => subject.Should().BeOnOrAfter(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_datetimeoffset_is_on_or_after_later_expected_datetimeoffset_should_throw() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); - DateTimeOffset expectation = new(new DateTime(2016, 06, 05), TimeSpan.Zero); - - // Act - Action act = () => subject.Should().BeOnOrAfter(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be on or after <2016-06-05 +0h>, but it was <2016-06-04 +0h>."); - } - } - - public class NotBeOnOrAfter - { - [Fact] - public void When_asserting_subject_datetimeoffset_is_not_on_or_after_earlier_expected_datetimeoffset_should_throw() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); - DateTimeOffset expectation = new(new DateTime(2016, 06, 03), TimeSpan.Zero); - - // Act - Action act = () => subject.Should().NotBeOnOrAfter(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be before <2016-06-03 +0h>, but it was <2016-06-04 +0h>."); - } - - [Fact] - public void When_asserting_subject_datetimeoffset_is_not_on_or_after_the_same_expected_datetimeoffset_should_throw() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); - DateTimeOffset expectation = new(new DateTime(2016, 06, 04), TimeSpan.Zero); - - // Act - Action act = () => subject.Should().NotBeOnOrAfter(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be before <2016-06-04 +0h>, but it was <2016-06-04 +0h>."); - } - - [Fact] - public void When_asserting_subject_datetimeoffset_is_not_on_or_after_later_expected_datetimeoffset_should_succeed() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2016, 06, 04), TimeSpan.Zero); - DateTimeOffset expectation = new(new DateTime(2016, 06, 05), TimeSpan.Zero); - - // Act - Action act = () => subject.Should().NotBeOnOrAfter(expectation); - - // Assert - act.Should().NotThrow(); - } - } - - public class HaveYear - { - [Fact] - public void When_asserting_subject_datetimeoffset_should_have_year_with_the_same_value_should_succeed() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2009, 06, 04), TimeSpan.Zero); - int expectation = 2009; - - // Act - Action act = () => subject.Should().HaveYear(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_datetimeoffset_should_have_year_with_a_different_value_should_throw() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2009, 06, 04), TimeSpan.Zero); - int expectation = 2008; - - // Act - Action act = () => subject.Should().HaveYear(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the year part of subject to be 2008, but it was 2009."); - } - - [Fact] - public void When_asserting_subject_null_datetimeoffset_should_have_year_should_throw() - { - // Arrange - DateTimeOffset? subject = null; - int expectation = 2008; - - // Act - Action act = () => subject.Should().HaveYear(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the year part of subject to be 2008, but found a DateTimeOffset."); - } - } - - public class NotHaveYear - { - [Fact] - public void When_asserting_subject_datetimeoffset_should_not_have_year_with_the_same_value_should_throw() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2009, 06, 04), TimeSpan.Zero); - int expectation = 2009; - - // Act - Action act = () => subject.Should().NotHaveYear(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the year part of subject to be 2009, but it was."); - } - - [Fact] - public void When_asserting_subject_datetimeoffset_should_not_have_year_with_a_different_value_should_succeed() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2009, 06, 04), TimeSpan.Zero); - int expectation = 2008; - - // Act - Action act = () => subject.Should().NotHaveYear(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_null_datetimeoffset_should_not_have_year_should_throw() - { - // Arrange - DateTimeOffset? subject = null; - int expectation = 2008; - - // Act - Action act = () => subject.Should().NotHaveYear(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the year part of subject to be 2008, but found a DateTimeOffset."); - } - } - - public class HaveMonth - { - [Fact] - public void When_asserting_subject_datetimeoffset_should_have_month_with_the_same_value_it_should_succeed() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2009, 12, 31), TimeSpan.Zero); - int expectation = 12; - - // Act - Action act = () => subject.Should().HaveMonth(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_datetimeoffset_should_have_a_month_with_a_different_value_it_should_throw() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2009, 12, 31), TimeSpan.Zero); - int expectation = 11; - - // Act - Action act = () => subject.Should().HaveMonth(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the month part of subject to be 11, but it was 12."); - } - - [Fact] - public void When_asserting_subject_null_datetimeoffset_should_have_month_should_throw() - { - // Arrange - DateTimeOffset? subject = null; - int expectation = 12; - - // Act - Action act = () => subject.Should().HaveMonth(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the month part of subject to be 12, but found a DateTimeOffset."); - } - } - - public class NotHaveMonth - { - [Fact] - public void When_asserting_subject_datetimeoffset_should_not_have_month_with_the_same_value_it_should_throw() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2009, 12, 31), TimeSpan.Zero); - int expectation = 12; - - // Act - Action act = () => subject.Should().NotHaveMonth(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the month part of subject to be 12, but it was."); - } - - [Fact] - public void When_asserting_subject_datetimeoffset_should_not_have_a_month_with_a_different_value_it_should_succeed() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2009, 12, 31), TimeSpan.Zero); - int expectation = 11; - - // Act - Action act = () => subject.Should().NotHaveMonth(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_null_datetimeoffset_should_not_have_month_should_throw() - { - // Arrange - DateTimeOffset? subject = null; - int expectation = 12; - - // Act - Action act = () => subject.Should().NotHaveMonth(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the month part of subject to be 12, but found a DateTimeOffset."); - } - } - - public class HaveDay - { - [Fact] - public void When_asserting_subject_datetimeoffset_should_have_day_with_the_same_value_it_should_succeed() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2009, 12, 31), TimeSpan.Zero); - int expectation = 31; - - // Act - Action act = () => subject.Should().HaveDay(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_datetimeoffset_should_have_day_with_a_different_value_it_should_throw() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2009, 12, 31), TimeSpan.Zero); - int expectation = 30; - - // Act - Action act = () => subject.Should().HaveDay(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the day part of subject to be 30, but it was 31."); - } - - [Fact] - public void When_asserting_subject_null_datetimeoffset_should_have_day_should_throw() - { - // Arrange - DateTimeOffset? subject = null; - int expectation = 22; - - // Act - Action act = () => subject.Should().HaveDay(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the day part of subject to be 22, but found a DateTimeOffset."); - } - } - - public class NotHaveDay - { - [Fact] - public void When_asserting_subject_datetimeoffset_should_not_have_day_with_the_same_value_it_should_throw() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2009, 12, 31), TimeSpan.Zero); - int expectation = 31; - - // Act - Action act = () => subject.Should().NotHaveDay(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the day part of subject to be 31, but it was."); - } - - [Fact] - public void When_asserting_subject_datetimeoffset_should_not_have_day_with_a_different_value_it_should_succeed() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2009, 12, 31), TimeSpan.Zero); - int expectation = 30; - - // Act - Action act = () => subject.Should().NotHaveDay(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_null_datetimeoffset_should_not_have_day_should_throw() - { - // Arrange - DateTimeOffset? subject = null; - int expectation = 22; - - // Act - Action act = () => subject.Should().NotHaveDay(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the day part of subject to be 22, but found a DateTimeOffset."); - } - } - - public class HaveHour - { - [Fact] - public void When_asserting_subject_datetimeoffset_should_have_hour_with_the_same_value_it_should_succeed() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2009, 12, 31, 23, 59, 00), TimeSpan.Zero); - int expectation = 23; - - // Act - Action act = () => subject.Should().HaveHour(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_datetimeoffset_should_have_hour_with_different_value_it_should_throw() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2009, 12, 31, 23, 59, 00), TimeSpan.Zero); - int expectation = 22; - - // Act - Action act = () => subject.Should().HaveHour(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the hour part of subject to be 22, but it was 23."); - } - - [Fact] - public void When_asserting_subject_null_datetimeoffset_should_have_hour_should_throw() - { - // Arrange - DateTimeOffset? subject = null; - int expectation = 22; - - // Act - Action act = () => subject.Should().HaveHour(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the hour part of subject to be 22, but found a DateTimeOffset."); - } - } - - public class NotHaveHour - { - [Fact] - public void When_asserting_subject_datetimeoffset_should_not_have_hour_with_the_same_value_it_should_throw() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2009, 12, 31, 23, 59, 00), TimeSpan.Zero); - int expectation = 23; - - // Act - Action act = () => subject.Should().NotHaveHour(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the hour part of subject to be 23, but it was."); - } - - [Fact] - public void When_asserting_subject_datetimeoffset_should_not_have_hour_with_different_value_it_should_succeed() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2009, 12, 31, 23, 59, 00), TimeSpan.Zero); - int expectation = 22; - - // Act - Action act = () => subject.Should().NotHaveHour(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_null_datetimeoffset_should_not_have_hour_should_throw() - { - // Arrange - DateTimeOffset? subject = null; - int expectation = 22; - - // Act - Action act = () => subject.Should().NotHaveHour(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the hour part of subject to be 22, but found a DateTimeOffset."); - } - } - - public class HaveMinute - { - [Fact] - public void When_asserting_subject_datetimeoffset_should_have_minutes_with_the_same_value_it_should_succeed() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2009, 12, 31, 23, 59, 00), TimeSpan.Zero); - int expectation = 59; - - // Act - Action act = () => subject.Should().HaveMinute(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_datetimeoffset_should_have_minutes_with_different_value_it_should_throw() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2009, 12, 31, 23, 59, 00), TimeSpan.Zero); - int expectation = 58; - - // Act - Action act = () => subject.Should().HaveMinute(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the minute part of subject to be 58, but it was 59."); - } - - [Fact] - public void When_asserting_subject_null_datetimeoffset_should_have_minute_should_throw() - { - // Arrange - DateTimeOffset? subject = null; - int expectation = 22; - - // Act - Action act = () => subject.Should().HaveMinute(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the minute part of subject to be 22, but found a DateTimeOffset."); - } - } - - public class NotHaveMinute - { - [Fact] - public void When_asserting_subject_datetimeoffset_should_not_have_minutes_with_the_same_value_it_should_throw() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2009, 12, 31, 23, 59, 00), TimeSpan.Zero); - int expectation = 59; - - // Act - Action act = () => subject.Should().NotHaveMinute(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the minute part of subject to be 59, but it was."); - } - - [Fact] - public void When_asserting_subject_datetimeoffset_should_not_have_minutes_with_different_value_it_should_succeed() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2009, 12, 31, 23, 59, 00), TimeSpan.Zero); - int expectation = 58; - - // Act - Action act = () => subject.Should().NotHaveMinute(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_null_datetimeoffset_should_not_have_minute_should_throw() - { - // Arrange - DateTimeOffset? subject = null; - int expectation = 22; - - // Act - Action act = () => subject.Should().NotHaveMinute(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the minute part of subject to be 22, but found a DateTimeOffset."); - } - } - - public class HaveSecond - { - [Fact] - public void When_asserting_subject_datetimeoffset_should_have_seconds_with_the_same_value_it_should_succeed() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2009, 12, 31, 23, 59, 00), TimeSpan.Zero); - int expectation = 0; - - // Act - Action act = () => subject.Should().HaveSecond(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_datetimeoffset_should_have_seconds_with_different_value_it_should_throw() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2009, 12, 31, 23, 59, 00), TimeSpan.Zero); - int expectation = 1; - - // Act - Action act = () => subject.Should().HaveSecond(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the seconds part of subject to be 1, but it was 0."); - } - - [Fact] - public void When_asserting_subject_null_datetimeoffset_should_have_second_should_throw() - { - // Arrange - DateTimeOffset? subject = null; - int expectation = 22; - - // Act - Action act = () => subject.Should().HaveSecond(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the seconds part of subject to be 22, but found a DateTimeOffset."); - } - } - - public class NotHaveSecond - { - [Fact] - public void When_asserting_subject_datetimeoffset_should_not_have_seconds_with_the_same_value_it_should_throw() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2009, 12, 31, 23, 59, 00), TimeSpan.Zero); - int expectation = 0; - - // Act - Action act = () => subject.Should().NotHaveSecond(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the seconds part of subject to be 0, but it was."); - } - - [Fact] - public void When_asserting_subject_datetimeoffset_should_not_have_seconds_with_different_value_it_should_succeed() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2009, 12, 31, 23, 59, 00), TimeSpan.Zero); - int expectation = 1; - - // Act - Action act = () => subject.Should().NotHaveSecond(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_null_datetimeoffset_should_not_have_second_should_throw() - { - // Arrange - DateTimeOffset? subject = null; - int expectation = 22; - - // Act - Action act = () => subject.Should().NotHaveSecond(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the seconds part of subject to be 22, but found a DateTimeOffset."); - } - } - - public class HaveOffset - { - [Fact] - public void When_asserting_subject_datetimeoffset_should_have_offset_with_the_same_value_it_should_succeed() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2009, 12, 31, 23, 59, 00), TimeSpan.FromHours(7)); - TimeSpan expectation = TimeSpan.FromHours(7); - - // Act - Action act = () => subject.Should().HaveOffset(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_datetimeoffset_should_have_offset_with_different_value_it_should_throw() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2009, 12, 31, 23, 59, 10), TimeSpan.Zero); - TimeSpan expectation = TimeSpan.FromHours(3); - - // Act - Action act = () => subject.Should().HaveOffset(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the offset of subject to be 3h, but it was default."); - } - - [Fact] - public void When_asserting_subject_null_datetimeoffset_should_have_offset_should_throw() - { - // Arrange - DateTimeOffset? subject = null; - TimeSpan expectation = TimeSpan.FromHours(3); - - // Act - Action act = () => subject.Should().HaveOffset(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the offset of subject to be 3h, but found a DateTimeOffset."); - } - } - - public class NotHaveOffset - { - [Fact] - public void When_asserting_subject_datetimeoffset_should_not_have_offset_with_the_same_value_it_should_throw() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2009, 12, 31, 23, 59, 00), TimeSpan.FromHours(7)); - TimeSpan expectation = TimeSpan.FromHours(7); - - // Act - Action act = () => subject.Should().NotHaveOffset(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the offset of subject to be 7h, but it was."); - } - - [Fact] - public void When_asserting_subject_datetimeoffset_should_not_have_offset_with_different_value_it_should_succeed() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2009, 12, 31, 23, 59, 00), TimeSpan.Zero); - TimeSpan expectation = TimeSpan.FromHours(3); - - // Act - Action act = () => subject.Should().NotHaveOffset(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_null_datetimeoffset_should_not_have_offset_should_throw() - { - // Arrange - DateTimeOffset? subject = null; - TimeSpan expectation = TimeSpan.FromHours(3); - - // Act - Action act = () => subject.Should().NotHaveOffset(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the offset of subject to be 3h, but found a DateTimeOffset."); - } - } - - public class BeSameDateAs - { - [Fact] - public void When_asserting_subject_datetimeoffset_should_be_same_date_as_another_with_the_same_date_it_should_succeed() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2009, 12, 31, 4, 5, 6), TimeSpan.Zero); - DateTimeOffset expectation = new(new DateTime(2009, 12, 31), TimeSpan.Zero); - - // Act - Action act = () => subject.Should().BeSameDateAs(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void - When_asserting_subject_datetimeoffset_should_be_same_as_another_with_same_date_but_different_time_it_should_succeed() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2009, 12, 31, 4, 5, 6), TimeSpan.Zero); - DateTimeOffset expectation = new(new DateTime(2009, 12, 31, 11, 15, 11), TimeSpan.Zero); - - // Act - Action act = () => subject.Should().BeSameDateAs(expectation); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_asserting_subject_null_datetimeoffset_to_be_same_date_as_another_datetimeoffset_it_should_throw() - { - // Arrange - DateTimeOffset? subject = null; - DateTimeOffset expectation = new(new DateTime(2009, 12, 31), TimeSpan.Zero); - - // Act - Action act = () => subject.Should().BeSameDateAs(expectation); - - // Assert - act.Should().Throw().WithMessage( - "Expected the date part of subject to be <2009-12-31>, but found a DateTimeOffset."); - } - - [Fact] - public void When_asserting_subject_datetimeoffset_should_have_same_date_as_another_but_it_doesnt_it_should_throw() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2009, 12, 31), TimeSpan.Zero); - DateTimeOffset expectation = new(new DateTime(2009, 12, 30), TimeSpan.Zero); - - // Act - Action act = () => subject.Should().BeSameDateAs(expectation); - - // Assert - act.Should().Throw().WithMessage( - "Expected the date part of subject to be <2009-12-30>, but it was <2009-12-31>."); - } - } - - public class NotBeSameDateAs - { - [Fact] - public void When_asserting_subject_datetimeoffset_should_not_be_same_date_as_another_with_the_same_date_it_should_throw() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2009, 12, 31, 4, 5, 6), TimeSpan.Zero); - DateTimeOffset expectation = new(new DateTime(2009, 12, 31), TimeSpan.Zero); - - // Act - Action act = () => subject.Should().NotBeSameDateAs(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the date part of subject to be <2009-12-31>, but it was."); - } - - [Fact] - public void - When_asserting_subject_datetimeoffset_should_not_be_same_as_another_with_same_date_but_different_time_it_should_throw() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2009, 12, 31, 4, 5, 6), TimeSpan.Zero); - DateTimeOffset expectation = new(new DateTime(2009, 12, 31, 11, 15, 11), TimeSpan.Zero); - - // Act - Action act = () => subject.Should().NotBeSameDateAs(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the date part of subject to be <2009-12-31>, but it was."); - } - - [Fact] - public void When_asserting_subject_null_datetimeoffset_to_not_be_same_date_as_another_datetimeoffset_it_should_throw() - { - // Arrange - DateTimeOffset? subject = null; - DateTimeOffset expectation = new(new DateTime(2009, 12, 31), TimeSpan.Zero); - - // Act - Action act = () => subject.Should().NotBeSameDateAs(expectation); - - // Assert - act.Should().Throw().WithMessage( - "Did not expect the date part of subject to be <2009-12-31>, but found a DateTimeOffset."); - } - - [Fact] - public void When_asserting_subject_datetimeoffset_should_not_have_same_date_as_another_but_it_doesnt_it_should_succeed() - { - // Arrange - DateTimeOffset subject = new(new DateTime(2009, 12, 31), TimeSpan.Zero); - DateTimeOffset expectation = new(new DateTime(2009, 12, 30), TimeSpan.Zero); - - // Act - Action act = () => subject.Should().NotBeSameDateAs(expectation); - - // Assert - act.Should().NotThrow(); - } - } - - public class TimespanComparison - { - [Fact] - public void When_date_is_not_more_than_the_required_one_day_before_another_it_should_throw() - { - // Arrange - var target = new DateTimeOffset(2.October(2009), 0.Hours()); - DateTimeOffset subject = target - 1.Days(); - - // Act - Action act = () => subject.Should().BeMoreThan(TimeSpan.FromDays(1)).Before(target, "we like {0}", "that"); - - // Assert - act.Should().Throw().WithMessage( - "Expected subject <2009-10-01 +0h> to be more than 1d before <2009-10-02 +0h> because we like that, but it is behind by 1d."); - } - - [Fact] - public void When_date_is_more_than_the_required_one_day_before_another_it_should_not_throw() - { - // Arrange - var target = new DateTimeOffset(2.October(2009)); - DateTimeOffset subject = target - 25.Hours(); - - // Act / Assert - subject.Should().BeMoreThan(TimeSpan.FromDays(1)).Before(target); - } - - [Fact] - public void When_date_is_not_at_least_one_day_before_another_it_should_throw() - { - // Arrange - var target = new DateTimeOffset(2.October(2009), 0.Hours()); - DateTimeOffset subject = target - 23.Hours(); - - // Act - Action act = () => subject.Should().BeAtLeast(TimeSpan.FromDays(1)).Before(target, "we like {0}", "that"); - - // Assert - act.Should().Throw().WithMessage( - "Expected subject <2009-10-01 01:00:00 +0h> to be at least 1d before <2009-10-02 +0h> because we like that, but it is behind by 23h."); - } - - [Fact] - public void When_date_is_at_least_one_day_before_another_it_should_not_throw() - { - // Arrange - var target = new DateTimeOffset(2.October(2009)); - DateTimeOffset subject = target - 24.Hours(); - - // Act / Assert - subject.Should().BeAtLeast(TimeSpan.FromDays(1)).Before(target); - } - - [Fact] - public void When_time_is_not_at_exactly_20_minutes_before_another_time_it_should_throw() - { - // Arrange - DateTimeOffset target = 1.January(0001).At(12, 55).ToDateTimeOffset(); - DateTimeOffset subject = 1.January(0001).At(12, 36).ToDateTimeOffset(); - - // Act - Action act = - () => subject.Should().BeExactly(TimeSpan.FromMinutes(20)).Before(target, "{0} minutes is enough", 20); - - // Assert - act.Should().Throw().WithMessage( - "Expected subject <12:36:00 +0h> to be exactly 20m before <12:55:00 +0h> because 20 minutes is enough, but it is behind by 19m."); - } - - [Fact] - public void When_time_is_exactly_90_seconds_before_another_time_it_should_not_throw() - { - // Arrange - DateTimeOffset target = 1.January(0001).At(12, 55).ToDateTimeOffset(); - DateTimeOffset subject = 1.January(0001).At(12, 53, 30).ToDateTimeOffset(); - - // Act / Assert - subject.Should().BeExactly(TimeSpan.FromSeconds(90)).Before(target); - } - - [Fact] - public void When_date_is_not_within_50_hours_before_another_date_it_should_throw() - { - // Arrange - var target = 10.April(2010).At(12, 0).WithOffset(0.Hours()); - DateTimeOffset subject = target - 50.Hours() - 1.Seconds(); - - // Act - Action act = - () => subject.Should().BeWithin(TimeSpan.FromHours(50)).Before(target, "{0} hours is enough", 50); - - // Assert - act.Should().Throw().WithMessage( - "Expected subject <2010-04-08 09:59:59 +0h> to be within 2d and 2h before <2010-04-10 12:00:00 +0h> because 50 hours is enough, but it is behind by 2d, 2h and 1s."); - } - - [Fact] - public void When_date_is_exactly_within_1d_before_another_date_it_should_not_throw() - { - // Arrange - var target = new DateTimeOffset(10.April(2010)); - DateTimeOffset subject = target - 1.Days(); - - // Act / Assert - subject.Should().BeWithin(TimeSpan.FromHours(24)).Before(target); - } - - [Fact] - public void When_date_is_within_1d_before_another_date_it_should_not_throw() - { - // Arrange - var target = new DateTimeOffset(10.April(2010)); - DateTimeOffset subject = target - 23.Hours(); - - // Act / Assert - subject.Should().BeWithin(TimeSpan.FromHours(24)).Before(target); - } - - [Fact] - public void When_a_utc_date_is_within_0s_before_itself_it_should_not_throw() - { - // Arrange - var date = DateTimeOffset.UtcNow; // local timezone differs from +0h - - // Act / Assert - date.Should().BeWithin(TimeSpan.Zero).Before(date); - } - - [Fact] - public void When_a_utc_date_is_within_0s_after_itself_it_should_not_throw() - { - // Arrange - var date = DateTimeOffset.UtcNow; // local timezone differs from +0h - - // Act / Assert - date.Should().BeWithin(TimeSpan.Zero).After(date); - } - - [Fact] - public void When_time_is_not_less_than_30s_after_another_time_it_should_throw() - { - // Arrange - var target = 1.January(1).At(12, 0, 30).WithOffset(1.Hours()); - DateTimeOffset subject = target + 30.Seconds(); - - // Act - Action act = - () => subject.Should().BeLessThan(TimeSpan.FromSeconds(30)).After(target, "{0}s is the max", 30); - - // Assert - act.Should().Throw().WithMessage( - "Expected subject <12:01:00 +1h> to be less than 30s after <12:00:30 +1h> because 30s is the max, but it is ahead by 30s."); - } - - [Fact] - public void When_time_is_less_than_30s_after_another_time_it_should_not_throw() - { - // Arrange - var target = new DateTimeOffset(1.January(1).At(12, 0, 30)); - DateTimeOffset subject = target + 20.Seconds(); - - // Act / Assert - subject.Should().BeLessThan(TimeSpan.FromSeconds(30)).After(target); - } - - [Fact] - public void When_asserting_subject_be_more_than_10_seconds_after_target_but_subject_is_before_target_it_should_throw() - { - // Arrange - var expectation = 1.January(0001).At(0, 0, 30).WithOffset(0.Hours()); - var subject = 1.January(0001).At(0, 0, 15).WithOffset(0.Hours()); - - // Act - Action action = () => subject.Should().BeMoreThan(10.Seconds()).After(expectation); - - // Assert - action.Should().Throw() - .WithMessage( - "Expected subject <00:00:15 +0h> to be more than 10s after <00:00:30 +0h>, but it is behind by 15s."); - } - - [Theory] - [InlineData(30, 20)] // edge case - [InlineData(30, 15)] - public void When_asserting_subject_be_at_least_10_seconds_after_target_but_subject_is_before_target_it_should_throw( - int targetSeconds, int subjectSeconds) - { - // Arrange - var expectation = 1.January(0001).At(0, 0, targetSeconds).WithOffset(0.Hours()); - var subject = 1.January(0001).At(0, 0, subjectSeconds).WithOffset(0.Hours()); - - // Act - Action action = () => subject.Should().BeAtLeast(10.Seconds()).After(expectation); - - // Assert - action.Should().Throw() - .WithMessage( - $"Expected subject <00:00:{subjectSeconds} +0h> to be at least 10s after <00:00:30 +0h>, but it is behind by {Math.Abs(subjectSeconds - targetSeconds)}s."); - } - - [Fact] - public void When_asserting_subject_be_exactly_10_seconds_after_target_but_subject_is_before_target_it_should_throw() - { - // Arrange - var expectation = 1.January(0001).At(0, 0, 30).WithOffset(0.Hours()); - var subject = 1.January(0001).At(0, 0, 20).WithOffset(0.Hours()); - - // Act - Action action = () => subject.Should().BeExactly(10.Seconds()).After(expectation); - - // Assert - action.Should().Throw() - .WithMessage("Expected subject <00:00:20 +0h> to be exactly 10s after <00:00:30 +0h>, but it is behind by 10s."); - } - - [Theory] - [InlineData(30, 20)] // edge case - [InlineData(30, 25)] - public void When_asserting_subject_be_within_10_seconds_after_target_but_subject_is_before_target_it_should_throw( - int targetSeconds, int subjectSeconds) - { - // Arrange - var expectation = 1.January(0001).At(0, 0, targetSeconds).WithOffset(0.Hours()); - var subject = 1.January(0001).At(0, 0, subjectSeconds).WithOffset(0.Hours()); - - // Act - Action action = () => subject.Should().BeWithin(10.Seconds()).After(expectation); - - // Assert - action.Should().Throw() - .WithMessage( - $"Expected subject <00:00:{subjectSeconds} +0h> to be within 10s after <00:00:30 +0h>, but it is behind by {Math.Abs(subjectSeconds - targetSeconds)}s."); - } - - [Fact] - public void When_asserting_subject_be_less_than_10_seconds_after_target_but_subject_is_before_target_it_should_throw() - { - // Arrange - var expectation = 1.January(0001).At(0, 0, 30).WithOffset(0.Hours()); - var subject = 1.January(0001).At(0, 0, 25).WithOffset(0.Hours()); - - // Act - Action action = () => subject.Should().BeLessThan(10.Seconds()).After(expectation); - - // Assert - action.Should().Throw() - .WithMessage("Expected subject <00:00:25 +0h> to be less than 10s after <00:00:30 +0h>, but it is behind by 5s."); - } - - [Fact] - public void When_asserting_subject_be_more_than_10_seconds_before_target_but_subject_is_after_target_it_should_throw() - { - // Arrange - var expectation = 1.January(0001).At(0, 0, 30).WithOffset(0.Hours()); - var subject = 1.January(0001).At(0, 0, 45).WithOffset(0.Hours()); - - // Act - Action action = () => subject.Should().BeMoreThan(10.Seconds()).Before(expectation); - - // Assert - action.Should().Throw() - .WithMessage( - "Expected subject <00:00:45 +0h> to be more than 10s before <00:00:30 +0h>, but it is ahead by 15s."); - } - - [Theory] - [InlineData(30, 40)] // edge case - [InlineData(30, 45)] - public void When_asserting_subject_be_at_least_10_seconds_before_target_but_subject_is_after_target_it_should_throw( - int targetSeconds, int subjectSeconds) - { - // Arrange - var expectation = 1.January(0001).At(0, 0, targetSeconds).WithOffset(0.Hours()); - var subject = 1.January(0001).At(0, 0, subjectSeconds).WithOffset(0.Hours()); - - // Act - Action action = () => subject.Should().BeAtLeast(10.Seconds()).Before(expectation); - - // Assert - action.Should().Throw() - .WithMessage( - $"Expected subject <00:00:{subjectSeconds} +0h> to be at least 10s before <00:00:30 +0h>, but it is ahead by {Math.Abs(subjectSeconds - targetSeconds)}s."); - } - - [Fact] - public void When_asserting_subject_be_exactly_10_seconds_before_target_but_subject_is_after_target_it_should_throw() - { - // Arrange - var expectation = 1.January(0001).At(0, 0, 30).WithOffset(0.Hours()); - var subject = 1.January(0001).At(0, 0, 40).WithOffset(0.Hours()); - - // Act - Action action = () => subject.Should().BeExactly(10.Seconds()).Before(expectation); - - // Assert - action.Should().Throw() - .WithMessage("Expected subject <00:00:40 +0h> to be exactly 10s before <00:00:30 +0h>, but it is ahead by 10s."); - } - - [Theory] - [InlineData(30, 40)] // edge case - [InlineData(30, 35)] - public void When_asserting_subject_be_within_10_seconds_before_target_but_subject_is_after_target_it_should_throw( - int targetSeconds, int subjectSeconds) - { - // Arrange - var expectation = 1.January(0001).At(0, 0, targetSeconds).WithOffset(0.Hours()); - var subject = 1.January(0001).At(0, 0, subjectSeconds).WithOffset(0.Hours()); - - // Act - Action action = () => subject.Should().BeWithin(10.Seconds()).Before(expectation); - - // Assert - action.Should().Throw() - .WithMessage( - $"Expected subject <00:00:{subjectSeconds} +0h> to be within 10s before <00:00:30 +0h>, but it is ahead by {Math.Abs(subjectSeconds - targetSeconds)}s."); - } - - [Fact] - public void When_asserting_subject_be_less_than_10_seconds_before_target_but_subject_is_after_target_it_should_throw() - { - // Arrange - var expectation = 1.January(0001).At(0, 0, 30).WithOffset(0.Hours()); - var subject = 1.January(0001).At(0, 0, 45).WithOffset(0.Hours()); - - // Act - Action action = () => subject.Should().BeLessThan(10.Seconds()).Before(expectation); - - // Assert - action.Should().Throw() - .WithMessage( - "Expected subject <00:00:45 +0h> to be less than 10s before <00:00:30 +0h>, but it is ahead by 15s."); - } - - [Fact] - public void Should_throw_a_helpful_error_when_accidentally_using_equals_with_a_range() - { - // Arrange - DateTimeOffset someDateTimeOffset = new(2022, 9, 25, 13, 48, 42, 0, TimeSpan.Zero); - - // Act - var action = () => someDateTimeOffset.Should().BeLessThan(0.Seconds()).Equals(null); - - // Assert - action.Should().Throw() - .WithMessage("Equals is not part of Fluent Assertions. Did you mean Before() or After() instead?"); - } - } - public class ChainingConstraint { [Fact] @@ -2505,79 +27,6 @@ public void Should_support_chaining_constraints_with_and() } } - public class BeOneOf - { - [Fact] - public void When_a_value_is_not_one_of_the_specified_values_it_should_throw() - { - // Arrange - var value = new DateTimeOffset(31.December(2016), 1.Hours()); - - // Act - Action action = () => value.Should().BeOneOf(value + 1.Days(), value + 4.Hours()); - - // Assert - action.Should().Throw().WithMessage( - "Expected value to be one of {<2017-01-01 +1h>, <2016-12-31 04:00:00 +1h>}, but it was <2016-12-31 +1h>."); - } - - [Fact] - public void When_a_value_is_not_one_of_the_specified_values_it_should_throw_with_descriptive_message() - { - // Arrange - DateTimeOffset value = 31.December(2016).WithOffset(1.Hours()); - - // Act - Action action = () => value.Should().BeOneOf(new[] { value + 1.Days(), value + 2.Days() }, "because it's true"); - - // Assert - action.Should().Throw().WithMessage( - "Expected value to be one of {<2017-01-01 +1h>, <2017-01-02 +1h>} because it's true, but it was <2016-12-31 +1h>."); - } - - [Fact] - public void When_a_value_is_one_of_the_specified_values_it_should_succeed() - { - // Arrange - DateTimeOffset value = new(2016, 12, 30, 23, 58, 57, TimeSpan.FromHours(4)); - - // Act - Action action = () => value.Should().BeOneOf(new DateTimeOffset(2216, 1, 30, 0, 5, 7, TimeSpan.FromHours(2)), - new DateTimeOffset(2016, 12, 30, 23, 58, 57, TimeSpan.FromHours(4))); - - // Assert - action.Should().NotThrow(); - } - - [Fact] - public void When_a_null_value_is_not_one_of_the_specified_values_it_should_throw() - { - // Arrange - DateTimeOffset? value = null; - - // Act - Action action = () => value.Should().BeOneOf(new DateTimeOffset(2216, 1, 30, 0, 5, 7, TimeSpan.FromHours(1)), - new DateTimeOffset(2016, 2, 10, 2, 45, 7, TimeSpan.FromHours(2))); - - // Assert - action.Should().Throw().WithMessage( - "Expected value to be one of {<2216-01-30 00:05:07 +1h>, <2016-02-10 02:45:07 +2h>}, but it was ."); - } - - [Fact] - public void When_a_value_is_one_of_the_specified_values_it_should_succeed_when_datetimeoffset_is_null() - { - // Arrange - DateTimeOffset? value = null; - - // Act - Action action = () => value.Should().BeOneOf(new DateTimeOffset(2216, 1, 30, 0, 5, 7, TimeSpan.Zero), null); - - // Assert - action.Should().NotThrow(); - } - } - public class Miscellaneous { [Fact] diff --git a/Tests/FluentAssertions.Specs/Primitives/EnumAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Primitives/EnumAssertionSpecs.cs index c5bb469ec2..b7ced5ff5e 100644 --- a/Tests/FluentAssertions.Specs/Primitives/EnumAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Primitives/EnumAssertionSpecs.cs @@ -769,7 +769,7 @@ public void Throws_when_the_enums_is_not_one_of_the_expected_enums() // Act / Assert Action act = () => - flags.Should().BeOneOf(new[] { BindingFlags.Public, BindingFlags.ExactBinding }, "that's what we need"); + flags.Should().BeOneOf([BindingFlags.Public, BindingFlags.ExactBinding], "that's what we need"); act.Should() .Throw() @@ -783,7 +783,7 @@ public void An_enum_cannot_be_part_of_an_empty_list() BindingFlags flags = BindingFlags.DeclaredOnly; // Act / Assert - Action act = () => flags.Should().BeOneOf(Array.Empty()); + Action act = () => flags.Should().BeOneOf([]); act.Should() .Throw() diff --git a/Tests/FluentAssertions.Specs/Primitives/HttpResponseMessageAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Primitives/HttpResponseMessageAssertionSpecs.cs deleted file mode 100644 index b19c491a4c..0000000000 --- a/Tests/FluentAssertions.Specs/Primitives/HttpResponseMessageAssertionSpecs.cs +++ /dev/null @@ -1,270 +0,0 @@ -using System; -using System.Net; -using System.Net.Http; -using Xunit; -using Xunit.Sdk; - -namespace FluentAssertions.Specs.Primitives; - -public class HttpResponseMessageAssertionSpecs -{ - [Theory] - [InlineData(HttpStatusCode.OK)] - [InlineData(HttpStatusCode.Accepted)] - public void Should_succeed_when_status_code_is_successful(HttpStatusCode statusCodeOfResponse) - { - // Arrange - var testee = new HttpResponseMessage(statusCodeOfResponse); - - // Act / Assert - testee.Should().BeSuccessful(); - } - - [Fact] - public void Should_fail_with_descriptive_message_when_status_code_error_is_successful() - { - // Arrange - Action action = () => new HttpResponseMessage(HttpStatusCode.Gone).Should() - .BeSuccessful("because we want to test the failure {0}", "message"); - - // Act / Assert - action.Should().Throw() - .WithMessage( - "Expected HttpStatusCode to be successful (2xx) because we want to test the failure message, but found HttpStatusCode.Gone {value: 410}."); - } - - [Fact] - public void Should_fail_with_descriptive_message_when_asserting_success_but_response_is_null() - { - // Arrange - Action action = () => - ((HttpResponseMessage)null).Should().BeSuccessful("because we want to test the failure {0}", "message"); - - // Act / Assert - action.Should().Throw() - .WithMessage( - "Expected HttpStatusCode to be successful (2xx) because we want to test the failure message, but HttpResponseMessage was ."); - } - - [Theory] - [InlineData(HttpStatusCode.Moved)] - public void Should_succeed_when_status_code_is_redirect(HttpStatusCode statusCodeOfResponse) - { - // Arrange - var testee = new HttpResponseMessage(statusCodeOfResponse); - - // Act / Assert - testee.Should().BeRedirection(); - } - - [Fact] - public void Should_fail_with_descriptive_message_when_status_code_error_is_redirection() - { - // Arrange - Action action = () => new HttpResponseMessage(HttpStatusCode.Gone).Should() - .BeRedirection("because we want to test the failure {0}", "message"); - - // Act / Assert - action.Should().Throw() - .WithMessage( - "Expected HttpStatusCode to be redirection (3xx) because we want to test the failure message, but found HttpStatusCode.Gone {value: 410}."); - } - - [Fact] - public void Should_fail_with_descriptive_message_when_asserting_redirect_but_response_is_null() - { - // Arrange - Action action = () => - ((HttpResponseMessage)null).Should().BeRedirection("because we want to test the failure {0}", "message"); - - // Act / Assert - action.Should().Throw() - .WithMessage( - "Expected HttpStatusCode to be redirection (3xx) because we want to test the failure message, but HttpResponseMessage was ."); - } - - [Theory] - [InlineData(HttpStatusCode.Gone)] - [InlineData(HttpStatusCode.BadRequest)] - public void Should_succeed_when_status_code_is_client_error(HttpStatusCode statusCodeOfResponse) - { - // Arrange - var testee = new HttpResponseMessage(statusCodeOfResponse); - - // Act / Assert - testee.Should().HaveClientError(); - } - - [Fact] - public void Should_fail_with_descriptive_message_when_status_code_success_is_client_error() - { - // Arrange - Action action = () => new HttpResponseMessage(HttpStatusCode.OK).Should() - .HaveClientError("because we want to test the failure {0}", "message"); - - // Act / Assert - action.Should().Throw() - .WithMessage( - "Expected HttpStatusCode to be client error (4xx) because we want to test the failure message, but found HttpStatusCode.OK {value: 200}."); - } - - [Fact] - public void Should_fail_with_descriptive_message_when_asserting_client_error_but_response_is_null() - { - // Arrange - Action action = () => - ((HttpResponseMessage)null).Should().HaveClientError("because we want to test the failure {0}", "message"); - - // Act / Assert - action.Should().Throw() - .WithMessage( - "Expected HttpStatusCode to be client error (4xx) because we want to test the failure message, but HttpResponseMessage was ."); - } - - [Theory] - [InlineData(HttpStatusCode.InternalServerError)] - public void Should_succeed_when_status_code_is_server_error(HttpStatusCode statusCodeOfResponse) - { - // Arrange - var testee = new HttpResponseMessage(statusCodeOfResponse); - - // Act / Assert - testee.Should().HaveServerError(); - } - - [Fact] - public void Should_fail_with_descriptive_message_when_status_code_success_is_server_error() - { - // Arrange - Action action = () => new HttpResponseMessage(HttpStatusCode.OK).Should() - .HaveServerError("because we want to test the failure {0}", "message"); - - // Act / Assert - action.Should().Throw() - .WithMessage( - "Expected HttpStatusCode to be server error (5xx) because we want to test the failure message, but found HttpStatusCode.OK {value: 200}."); - } - - [Fact] - public void Should_fail_with_descriptive_message_when_asserting_server_error_but_response_is_null() - { - // Arrange - Action action = () => - ((HttpResponseMessage)null).Should().HaveServerError("because we want to test the failure {0}", "message"); - - // Act / Assert - action.Should().Throw() - .WithMessage( - "Expected HttpStatusCode to be server error (5xx) because we want to test the failure message, but HttpResponseMessage was ."); - } - - [Theory] - [InlineData(HttpStatusCode.BadRequest)] - [InlineData(HttpStatusCode.InternalServerError)] - public void Should_succeed_when_status_code_is_error(HttpStatusCode statusCodeOfResponse) - { - // Arrange - var testee = new HttpResponseMessage(statusCodeOfResponse); - - // Act / Assert - testee.Should().HaveError(); - } - - [Fact] - public void Should_fail_with_descriptive_message_when_status_code_success_is_error() - { - // Arrange - Action action = () => new HttpResponseMessage(HttpStatusCode.OK).Should() - .HaveError("because we want to test the failure {0}", "message"); - - // Act / Assert - action.Should().Throw() - .WithMessage( - "Expected HttpStatusCode to be an error because we want to test the failure message, but found HttpStatusCode.OK {value: 200}."); - } - - [Fact] - public void Should_fail_with_descriptive_message_when_asserting_error_but_response_is_null() - { - // Arrange - Action action = () => - ((HttpResponseMessage)null).Should().HaveError("because we want to test the failure {0}", "message"); - - // Act / Assert - action.Should().Throw() - .WithMessage( - "Expected HttpStatusCode to be an error because we want to test the failure message, but HttpResponseMessage was ."); - } - - [Fact] - public void Should_succeed_when_status_code_to_be_equal_to_the_same_value() - { - // Arrange - Action action = () => new HttpResponseMessage(HttpStatusCode.OK).Should().HaveStatusCode(HttpStatusCode.OK); - - // Act / Assert - action.Should().NotThrow(); - } - - [Fact] - public void Should_fail_with_descriptive_message_when_status_code_value_to_be_equal_to_a_different_value() - { - // Arrange - Action action = () => new HttpResponseMessage(HttpStatusCode.OK).Should().HaveStatusCode(HttpStatusCode.Gone, - "because we want to test the failure {0}", "message"); - - // Act / Assert - action.Should().Throw() - .WithMessage( - "Expected HttpStatusCode to be HttpStatusCode.Gone {value: 410} because we want to test the failure message, but found HttpStatusCode.OK {value: 200}.*"); - } - - [Fact] - public void Should_fail_with_descriptive_message_when_asserting_certain_status_code_but_response_is_null() - { - // Arrange - Action action = () => ((HttpResponseMessage)null).Should() - .HaveStatusCode(HttpStatusCode.Gone, "because we want to test the failure {0}", "message"); - - // Act / Assert - action.Should().Throw() - .WithMessage( - "Expected HttpStatusCode to be HttpStatusCode.Gone {value: 410} because we want to test the failure message, but HttpResponseMessage was ."); - } - - [Fact] - public void Should_succeed_when_status_code_value_not_to_be_equal_to_the_same_value() - { - // Arrange - Action action = () => new HttpResponseMessage(HttpStatusCode.OK).Should().NotHaveStatusCode(HttpStatusCode.Gone); - - // Act / Assert - action.Should().NotThrow(); - } - - [Fact] - public void Should_fail_with_descriptive_message_when_status_code_value_not_to_be_equal_to_a_different_value() - { - // Arrange - Action action = () => new HttpResponseMessage(HttpStatusCode.OK).Should().NotHaveStatusCode(HttpStatusCode.OK, - "because we want to test the failure {0}", "message"); - - // Act / Assert - action.Should().Throw() - .WithMessage( - "Expected HttpStatusCode not to be HttpStatusCode.OK {value: 200} because we want to test the failure message, but found HttpStatusCode.OK {value: 200}.*"); - } - - [Fact] - public void Should_fail_with_descriptive_message_when_asserting_against_certain_status_code_but_response_is_null() - { - // Arrange - Action action = () => ((HttpResponseMessage)null).Should() - .NotHaveStatusCode(HttpStatusCode.Gone, "because we want to test the failure {0}", "message"); - - // Act / Assert - action.Should().Throw() - .WithMessage( - "Expected HttpStatusCode not to be HttpStatusCode.Gone {value: 410} because we want to test the failure message, but HttpResponseMessage was ."); - } -} diff --git a/Tests/FluentAssertions.Specs/Primitives/NullableBooleanAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Primitives/NullableBooleanAssertionSpecs.cs index 6703fb54e9..4eb2097533 100644 --- a/Tests/FluentAssertions.Specs/Primitives/NullableBooleanAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Primitives/NullableBooleanAssertionSpecs.cs @@ -4,6 +4,7 @@ namespace FluentAssertions.Specs.Primitives; +// ReSharper disable ConditionIsAlwaysTrueOrFalse public class NullableBooleanAssertionSpecs { [Fact] diff --git a/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.Be.cs b/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.Be.cs new file mode 100644 index 0000000000..e617d5b309 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.Be.cs @@ -0,0 +1,354 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class ObjectAssertionSpecs +{ + public class Be + { + [Fact] + public void When_two_equal_object_are_expected_to_be_equal_it_should_not_fail() + { + // Arrange + var someObject = new ClassWithCustomEqualMethod(1); + var equalObject = new ClassWithCustomEqualMethod(1); + + // Act / Assert + someObject.Should().Be(equalObject); + } + + [Fact] + public void When_two_different_objects_are_expected_to_be_equal_it_should_fail_with_a_clear_explanation() + { + // Arrange + var someObject = new ClassWithCustomEqualMethod(1); + var nonEqualObject = new ClassWithCustomEqualMethod(2); + + // Act + Action act = () => someObject.Should().Be(nonEqualObject); + + // Assert + act.Should().Throw().WithMessage( + "Expected someObject to be ClassWithCustomEqualMethod(2), but found ClassWithCustomEqualMethod(1)."); + } + + [Fact] + public void When_both_subject_and_expected_are_null_it_should_succeed() + { + // Arrange + object someObject = null; + object expectedObject = null; + + // Act / Assert + someObject.Should().Be(expectedObject); + } + + [Fact] + public void When_the_subject_is_null_it_should_fail() + { + // Arrange + object someObject = null; + var nonEqualObject = new ClassWithCustomEqualMethod(2); + + // Act + Action act = () => someObject.Should().Be(nonEqualObject); + + // Assert + act.Should().Throw() + .WithMessage("Expected someObject to be ClassWithCustomEqualMethod(2), but found ."); + } + + [Fact] + public void When_two_different_objects_are_expected_to_be_equal_it_should_fail_and_use_the_reason() + { + // Arrange + var someObject = new ClassWithCustomEqualMethod(1); + var nonEqualObject = new ClassWithCustomEqualMethod(2); + + // Act + Action act = () => someObject.Should().Be(nonEqualObject, "because it should use the {0}", "reason"); + + // Assert + act.Should().Throw() + .WithMessage( + "Expected someObject to be ClassWithCustomEqualMethod(2) because it should use the reason, but found ClassWithCustomEqualMethod(1)."); + } + + [Fact] + public void When_comparing_a_numeric_and_an_enum_for_equality_it_should_throw() + { + // Arrange + object subject = 1; + MyEnum expected = MyEnum.One; + + // Act + Action act = () => subject.Should().Be(expected); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void An_untyped_value_is_equal_to_another_according_to_a_comparer() + { + // Arrange + object value = new SomeClass(5); + + // Act / Assert + value.Should().Be(new SomeClass(5), new SomeClassEqualityComparer()); + } + + [Fact] + public void A_typed_value_is_equal_to_another_according_to_a_comparer() + { + // Arrange + var value = new SomeClass(5); + + // Act / Assert + value.Should().Be(new SomeClass(5), new SomeClassEqualityComparer()); + } + + [Fact] + public void An_untyped_value_is_not_equal_to_another_according_to_a_comparer() + { + // Arrange + object value = new SomeClass(3); + + // Act + Action act = () => value.Should().Be(new SomeClass(4), new SomeClassEqualityComparer(), "I said so"); + + // Assert + act.Should().Throw().WithMessage("Expected value to be SomeClass(4)*I said so*found SomeClass(3)."); + } + + [Fact] + public void A_typed_value_is_not_equal_to_another_according_to_a_comparer() + { + // Arrange + var value = new SomeClass(3); + + // Act + Action act = () => value.Should().Be(new SomeClass(4), new SomeClassEqualityComparer(), "I said so"); + + // Assert + act.Should().Throw().WithMessage("Expected value to be SomeClass(4)*I said so*found SomeClass(3)."); + } + + [Fact] + public void A_typed_value_is_not_of_the_same_type() + { + // Arrange + var value = new ClassWithCustomEqualMethod(3); + + // Act + Action act = () => value.Should().Be(new SomeClass(3), new SomeClassEqualityComparer(), "I said so"); + + // Assert + act.Should().Throw() + .WithMessage("Expected value to be SomeClass(3)*I said so*found ClassWithCustomEqualMethod(3)."); + } + + [Fact] + public void A_untyped_value_requires_a_comparer() + { + // Arrange + object value = new SomeClass(3); + + // Act + Action act = () => value.Should().Be(new SomeClass(3), comparer: null); + + // Assert + act.Should().Throw().WithParameterName("comparer"); + } + + [Fact] + public void A_typed_value_requires_a_comparer() + { + // Arrange + var value = new SomeClass(3); + + // Act + Action act = () => value.Should().Be(new SomeClass(3), comparer: null); + + // Assert + act.Should().Throw().WithParameterName("comparer"); + } + + [Fact] + public void Chaining_after_one_assertion() + { + // Arrange + var value = new SomeClass(3); + + // Act / Assert + value.Should().Be(value).And.NotBeNull(); + } + + [Fact] + public void Can_chain_multiple_assertions() + { + // Arrange + var value = new object(); + + // Act / Assert + value.Should().Be(value, new DumbObjectEqualityComparer()).And.NotBeNull(); + } + } + + public class NotBe + { + [Fact] + public void When_non_equal_objects_are_expected_to_be_not_equal_it_should_not_fail() + { + // Arrange + var someObject = new ClassWithCustomEqualMethod(1); + var nonEqualObject = new ClassWithCustomEqualMethod(2); + + // Act / Assert + someObject.Should().NotBe(nonEqualObject); + } + + [Fact] + public void When_two_equal_objects_are_expected_not_to_be_equal_it_should_fail_with_a_clear_explanation() + { + // Arrange + var someObject = new ClassWithCustomEqualMethod(1); + var equalObject = new ClassWithCustomEqualMethod(1); + + // Act + Action act = () => + someObject.Should().NotBe(equalObject); + + // Assert + act.Should().Throw().WithMessage( + "Did not expect someObject to be equal to ClassWithCustomEqualMethod(1)."); + } + + [Fact] + public void When_two_equal_objects_are_expected_not_to_be_equal_it_should_fail_and_use_the_reason() + { + // Arrange + var someObject = new ClassWithCustomEqualMethod(1); + var equalObject = new ClassWithCustomEqualMethod(1); + + // Act + Action act = () => + someObject.Should().NotBe(equalObject, "because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw().WithMessage( + "Did not expect someObject to be equal to ClassWithCustomEqualMethod(1) " + + "because we want to test the failure message."); + } + + [Fact] + public void An_untyped_value_is_not_equal_to_another_according_to_a_comparer() + { + // Arrange + object value = new SomeClass(5); + + // Act / Assert + value.Should().NotBe(new SomeClass(4), new SomeClassEqualityComparer()); + } + + [Fact] + public void A_typed_value_is_not_equal_to_another_according_to_a_comparer() + { + // Arrange + var value = new SomeClass(5); + + // Act / Assert + value.Should().NotBe(new SomeClass(4), new SomeClassEqualityComparer()); + } + + [Fact] + public void An_untyped_value_is_equal_to_another_according_to_a_comparer() + { + // Arrange + object value = new SomeClass(3); + + // Act + Action act = () => value.Should().NotBe(new SomeClass(3), new SomeClassEqualityComparer(), "I said so"); + + // Assert + act.Should().Throw().WithMessage("Did not expect value to be equal to SomeClass(3)*I said so*"); + } + + [Fact] + public void A_typed_value_is_equal_to_another_according_to_a_comparer() + { + // Arrange + var value = new SomeClass(3); + + // Act + Action act = () => value.Should().NotBe(new SomeClass(3), new SomeClassEqualityComparer(), "I said so"); + + // Assert + act.Should().Throw().WithMessage("Did not expect value to be equal to SomeClass(3)*I said so*"); + } + + [Fact] + public void A_typed_value_is_not_of_the_same_type() + { + // Arrange + var value = new ClassWithCustomEqualMethod(3); + + // Act / Assert + value.Should().NotBe(new SomeClass(3), new SomeClassEqualityComparer(), "I said so"); + } + + [Fact] + public void An_untyped_value_requires_a_comparer() + { + // Arrange + object value = new SomeClass(3); + + // Act + Action act = () => value.Should().NotBe(new SomeClass(3), comparer: null); + + // Assert + act.Should().Throw().WithParameterName("comparer"); + } + + [Fact] + public void A_typed_value_requires_a_comparer() + { + // Arrange + var value = new SomeClass(3); + + // Act + Action act = () => value.Should().NotBe(new SomeClass(3), comparer: null); + + // Assert + act.Should().Throw().WithParameterName("comparer"); + } + + [Fact] + public void Chaining_after_one_assertion() + { + // Arrange + var value = new SomeClass(3); + + // Act / Assert + value.Should().NotBe(new SomeClass(3)).And.NotBeNull(); + } + + [Fact] + public void Can_chain_multiple_assertions() + { + // Arrange + var value = new object(); + + // Act / Assert + value.Should().NotBe(new object(), new DumbObjectEqualityComparer()).And.NotBeNull(); + } + } + + private enum MyEnum + { + One = 1, + Two = 2 + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.BeAssignableTo.cs b/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.BeAssignableTo.cs new file mode 100644 index 0000000000..1e923dc998 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.BeAssignableTo.cs @@ -0,0 +1,393 @@ +using System; +using System.Collections.Generic; +using FluentAssertions.Execution; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class ObjectAssertionSpecs +{ + public class BeAssignableTo + { + [Fact] + public void When_object_type_is_matched_against_null_type_it_should_throw() + { + // Arrange + var someObject = new object(); + + // Act + Action act = () => someObject.Should().BeAssignableTo(null); + + // Assert + act.Should().Throw() + .WithParameterName("type"); + } + + [Fact] + public void When_its_own_type_it_should_succeed() + { + // Arrange + var someObject = new DummyImplementingClass(); + + // Act / Assert + someObject.Should().BeAssignableTo(); + } + + [Fact] + public void When_its_base_type_it_should_succeed() + { + // Arrange + var someObject = new DummyImplementingClass(); + + // Act / Assert + someObject.Should().BeAssignableTo(); + } + + [Fact] + public void When_an_implemented_interface_type_it_should_succeed() + { + // Arrange + var someObject = new DummyImplementingClass(); + + // Act / Assert + someObject.Should().BeAssignableTo(); + } + + [Fact] + public void When_an_unrelated_type_it_should_fail_with_a_descriptive_message() + { + // Arrange + var someObject = new DummyImplementingClass(); + Action act = () => someObject.Should().BeAssignableTo("because we want to test the failure {0}", "message"); + + // Act / Assert + act.Should().Throw() + .WithMessage($"*assignable to {typeof(DateTime)}*failure message*{typeof(DummyImplementingClass)} is not*"); + } + + [Fact] + public void When_to_the_expected_type_it_should_cast_the_returned_object_for_chaining() + { + // Arrange + var someObject = new Exception("Actual Message"); + + // Act + Action act = () => someObject.Should().BeAssignableTo().Which.Message.Should().Be("Other Message"); + + // Assert + act.Should().Throw().WithMessage("*Expected*Actual*Other*"); + } + + [Fact] + public void When_a_null_instance_is_asserted_to_be_assignableOfT_it_should_fail() + { + // Arrange + object someObject = null; + + // Act + Action act = () => + { + using var _ = new AssertionScope(); + someObject.Should().BeAssignableTo("because we want to test the failure {0}", "message"); + }; + + // Assert + act.Should().Throw() + .WithMessage($"*assignable to {typeof(DateTime)}*failure message*found *"); + } + + [Fact] + public void When_its_own_type_instance_it_should_succeed() + { + // Arrange + var someObject = new DummyImplementingClass(); + + // Act / Assert + someObject.Should().BeAssignableTo(typeof(DummyImplementingClass)); + } + + [Fact] + public void When_its_base_type_instance_it_should_succeed() + { + // Arrange + var someObject = new DummyImplementingClass(); + + // Act / Assert + someObject.Should().BeAssignableTo(typeof(DummyBaseClass)); + } + + [Fact] + public void When_an_implemented_interface_type_instance_it_should_succeed() + { + // Arrange + var someObject = new DummyImplementingClass(); + + // Act / Assert + someObject.Should().BeAssignableTo(typeof(IDisposable)); + } + + [Fact] + public void When_an_implemented_open_generic_interface_type_instance_it_should_succeed() + { + // Arrange + var someObject = new List(); + + // Act / Assert + someObject.Should().BeAssignableTo(typeof(IList<>)); + } + + [Fact] + public void When_a_null_instance_is_asserted_to_be_assignable_it_should_fail_with_a_descriptive_message() + { + // Arrange + object someObject = null; + + // Act + Action act = () => + { + using var _ = new AssertionScope(); + someObject.Should().BeAssignableTo(typeof(DateTime), "because we want to test the failure {0}", "message"); + }; + + // Assert + act.Should().Throw() + .WithMessage($"*assignable to {typeof(DateTime)}*failure message*found *"); + } + + [Fact] + public void When_an_unrelated_type_instance_it_should_fail_with_a_descriptive_message() + { + // Arrange + var someObject = new DummyImplementingClass(); + + Action act = () => + someObject.Should().BeAssignableTo(typeof(DateTime), "because we want to test the failure {0}", "message"); + + // Act / Assert + act.Should().Throw() + .WithMessage($"*assignable to {typeof(DateTime)}*failure message*{typeof(DummyImplementingClass)} is not*"); + } + + [Fact] + public void When_unrelated_to_open_generic_type_it_should_fail_with_a_descriptive_message() + { + // Arrange + var someObject = new DummyImplementingClass(); + + Action act = () => + someObject.Should().BeAssignableTo(typeof(IList<>), "because we want to test the failure {0}", "message"); + + // Act / Assert + act.Should().Throw() + .WithMessage($"*assignable to {typeof(IList<>)}*failure message*{typeof(DummyImplementingClass)} is not*"); + } + + [Fact] + public void When_an_assertion_fails_on_BeAssignableTo_succeeding_message_should_be_included() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(); + var item = string.Empty; + item.Should().BeAssignableTo(); + item.Should().BeAssignableTo(); + }; + + // Assert + act.Should().Throw() + .WithMessage( + "Expected * to be assignable to System.Int32, but System.String is not.*" + + "Expected * to be assignable to System.Int64, but System.String is not."); + } + } + + public class NotBeAssignableTo + { + [Fact] + public void When_object_type_is_matched_negatively_against_null_type_it_should_throw() + { + // Arrange + var someObject = new object(); + + // Act + Action act = () => someObject.Should().NotBeAssignableTo(null); + + // Assert + act.Should().Throw() + .WithParameterName("type"); + } + + [Fact] + public void When_its_own_type_and_asserting_not_assignable_it_should_fail_with_a_useful_message() + { + // Arrange + var someObject = new DummyImplementingClass(); + + Action act = () => + someObject.Should() + .NotBeAssignableTo("because we want to test the failure {0}", "message"); + + // Act / Assert + act.Should().Throw() + .WithMessage( + $"*not be assignable to {typeof(DummyImplementingClass)}*failure message*{typeof(DummyImplementingClass)} is*"); + } + + [Fact] + public void When_its_base_type_and_asserting_not_assignable_it_should_fail_with_a_useful_message() + { + // Arrange + var someObject = new DummyImplementingClass(); + + Action act = () => + someObject.Should().NotBeAssignableTo("because we want to test the failure {0}", "message"); + + // Act / Assert + act.Should().Throw() + .WithMessage( + $"*not be assignable to {typeof(DummyBaseClass)}*failure message*{typeof(DummyImplementingClass)} is*"); + } + + [Fact] + public void When_an_implemented_interface_type_and_asserting_not_assignable_it_should_fail_with_a_useful_message() + { + // Arrange + var someObject = new DummyImplementingClass(); + + Action act = () => + someObject.Should().NotBeAssignableTo("because we want to test the failure {0}", "message"); + + // Act / Assert + act.Should().Throw() + .WithMessage($"*not be assignable to {typeof(IDisposable)}*failure message*{typeof(DummyImplementingClass)} is*"); + } + + [Fact] + public void When_an_unrelated_type_and_asserting_not_assignable_it_should_succeed() + { + // Arrange + var someObject = new DummyImplementingClass(); + + // Act / Assert + someObject.Should().NotBeAssignableTo(); + } + + [Fact] + public void + When_not_to_the_unexpected_type_and_asserting_not_assignable_it_should_not_cast_the_returned_object_for_chaining() + { + // Arrange + var someObject = new Exception("Actual Message"); + + // Act + Action act = () => someObject.Should().NotBeAssignableTo() + .And.Subject.Should().BeOfType() + .Which.Message.Should().Be("Other Message"); + + // Assert + act.Should().Throw().WithMessage("*Expected*Actual*Other*"); + } + + [Fact] + public void When_its_own_type_instance_and_asserting_not_assignable_it_should_fail_with_a_useful_message() + { + // Arrange + var someObject = new DummyImplementingClass(); + + Action act = () => + someObject.Should().NotBeAssignableTo(typeof(DummyImplementingClass), "because we want to test the failure {0}", + "message"); + + // Act / Assert + act.Should().Throw() + .WithMessage( + $"*not be assignable to {typeof(DummyImplementingClass)}*failure message*{typeof(DummyImplementingClass)} is*"); + } + + [Fact] + public void When_its_base_type_instance_and_asserting_not_assignable_it_should_fail_with_a_useful_message() + { + // Arrange + var someObject = new DummyImplementingClass(); + + Action act = () => + someObject.Should() + .NotBeAssignableTo(typeof(DummyBaseClass), "because we want to test the failure {0}", "message"); + + // Act / Assert + act.Should().Throw() + .WithMessage( + $"*not be assignable to {typeof(DummyBaseClass)}*failure message*{typeof(DummyImplementingClass)} is*"); + } + + [Fact] + public void + When_an_implemented_interface_type_instance_and_asserting_not_assignable_it_should_fail_with_a_useful_message() + { + // Arrange + var someObject = new DummyImplementingClass(); + + Action act = () => + someObject.Should().NotBeAssignableTo(typeof(IDisposable), "because we want to test the failure {0}", "message"); + + // Act / Assert + act.Should().Throw() + .WithMessage($"*not be assignable to {typeof(IDisposable)}*failure message*{typeof(DummyImplementingClass)} is*"); + } + + [Fact] + public void + When_an_implemented_open_generic_interface_type_instance_and_asserting_not_assignable_it_should_fail_with_a_useful_message() + { + // Arrange + var someObject = new List(); + + Action act = () => + someObject.Should().NotBeAssignableTo(typeof(IList<>), "because we want to test the failure {0}", "message"); + + // Act / Assert + act.Should().Throw() + .WithMessage($"*not be assignable to {typeof(IList<>)}*failure message*{typeof(List)} is*"); + } + + [Fact] + public void When_a_null_instance_is_asserted_to_not_be_assignable_it_should_fail_with_a_descriptive_message() + { + // Arrange + object someObject = null; + + // Act + Action act = () => + { + using var _ = new AssertionScope(); + someObject.Should().NotBeAssignableTo(typeof(DateTime), "because we want to test the failure {0}", "message"); + }; + + // Assert + act.Should().Throw() + .WithMessage($"*not be assignable to {typeof(DateTime)}*failure message*found *"); + } + + [Fact] + public void When_an_unrelated_type_instance_and_asserting_not_assignable_it_should_succeed() + { + // Arrange + var someObject = new DummyImplementingClass(); + + // Act / Assert + someObject.Should().NotBeAssignableTo(typeof(DateTime), "because we want to test the failure {0}", "message"); + } + + [Fact] + public void When_unrelated_to_open_generic_type_and_asserting_not_assignable_it_should_succeed() + { + // Arrange + var someObject = new DummyImplementingClass(); + + // Act / Assert + someObject.Should().NotBeAssignableTo(typeof(IList<>), "because we want to test the failure {0}", "message"); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.BeDataContractSerializable.cs b/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.BeDataContractSerializable.cs new file mode 100644 index 0000000000..81eab5b349 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.BeDataContractSerializable.cs @@ -0,0 +1,127 @@ +using System; +using System.Runtime.Serialization; +using FluentAssertions.Extensions; +using JetBrains.Annotations; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class ObjectAssertionSpecs +{ + public class BeDataContractSerializable + { + [Fact] + public void When_an_object_is_data_contract_serializable_it_should_succeed() + { + // Arrange + var subject = new DataContractSerializableClass + { + Name = "John", + Id = 1 + }; + + // Act + Action act = () => subject.Should().BeDataContractSerializable(); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_an_object_is_not_data_contract_serializable_it_should_fail() + { + // Arrange + var subject = new NonDataContractSerializableClass(); + + // Act + Action act = () => subject.Should().BeDataContractSerializable("we need to store it on {0}", "disk"); + + // Assert + act + .Should().Throw() + .WithMessage("*we need to store it on disk*EnumMemberAttribute*"); + } + + [Fact] + public void When_an_object_is_data_contract_serializable_but_doesnt_restore_all_properties_it_should_fail() + { + // Arrange + var subject = new DataContractSerializableClassNotRestoringAllProperties + { + Name = "John", + BirthDay = 20.September(1973) + }; + + // Act + Action act = () => subject.Should().BeDataContractSerializable(); + + // Assert + act.Should().Throw() + .WithMessage("*to be serializable, but serialization failed with:*property subject.Name*to be*"); + } + + [Fact] + public void When_a_data_contract_serializable_object_doesnt_restore_an_ignored_property_it_should_succeed() + { + // Arrange + var subject = new DataContractSerializableClassNotRestoringAllProperties + { + Name = "John", + BirthDay = 20.September(1973) + }; + + // Act + Action act = () => subject.Should() + .BeDataContractSerializable( + options => options.Excluding(x => x.Name)); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_injecting_null_options_to_BeDataContractSerializable_it_should_throw() + { + // Arrange + var subject = new DataContractSerializableClassNotRestoringAllProperties(); + + // Act + Action act = () => subject.Should() + .BeDataContractSerializable( + options: null); + + // Assert + act.Should().ThrowExactly() + .WithParameterName("options"); + } + } + + public class NonDataContractSerializableClass + { + public Color Color { get; set; } + } + + public class DataContractSerializableClass + { + [UsedImplicitly] + public string Name { get; set; } + + public int Id; + } + + [DataContract] + public class DataContractSerializableClassNotRestoringAllProperties + { + public string Name { get; set; } + + [DataMember] + public DateTime BirthDay { get; set; } + } + + public enum Color + { + Red = 1, + Yellow = 2 + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.BeEquivalentTo.cs b/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.BeEquivalentTo.cs new file mode 100644 index 0000000000..7314247e55 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.BeEquivalentTo.cs @@ -0,0 +1,53 @@ +using Xunit; + +namespace FluentAssertions.Specs.Primitives; + +public partial class ObjectAssertionSpecs +{ + public class BeEquivalentTo + { + [Fact] + public void Can_ignore_casing_while_comparing_objects_with_string_properties() + { + // Arrange + var actual = new { foo = "test" }; + var expectation = new { foo = "TEST" }; + + // Act / Assert + actual.Should().BeEquivalentTo(expectation, o => o.IgnoringCase()); + } + + [Fact] + public void Can_ignore_leading_whitespace_while_comparing_objects_with_string_properties() + { + // Arrange + var actual = new { foo = " test" }; + var expectation = new { foo = "test" }; + + // Act / Assert + actual.Should().BeEquivalentTo(expectation, o => o.IgnoringLeadingWhitespace()); + } + + [Fact] + public void Can_ignore_trailing_whitespace_while_comparing_objects_with_string_properties() + { + // Arrange + var actual = new { foo = "test " }; + var expectation = new { foo = "test" }; + + // Act / Assert + actual.Should().BeEquivalentTo(expectation, o => o.IgnoringTrailingWhitespace()); + } + + [Fact] + public void Can_ignore_newline_style_while_comparing_objects_with_string_properties() + { + // Arrange + var actual = new { foo = "A\nB\r\nC" }; + var expectation = new { foo = "A\r\nB\nC" }; + + // Act / Assert + actual.Should().BeEquivalentTo(expectation, o => o.IgnoringNewlineStyle()); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.BeNull.cs b/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.BeNull.cs new file mode 100644 index 0000000000..8806ee451a --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.BeNull.cs @@ -0,0 +1,91 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class ObjectAssertionSpecs +{ + public class BeNull + { + [Fact] + public void Should_succeed_when_asserting_null_object_to_be_null() + { + // Arrange + object someObject = null; + + // Act / Assert + someObject.Should().BeNull(); + } + + [Fact] + public void Should_fail_when_asserting_non_null_object_to_be_null() + { + // Arrange + var someObject = new object(); + + // Act + Action act = () => someObject.Should().BeNull(); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_a_non_null_object_is_expected_to_be_null_it_should_fail() + { + // Arrange + var someObject = new object(); + + // Act + Action act = () => someObject.Should().BeNull("because we want to test the failure {0}", "message"); + + // Assert + act + .Should().Throw() + .Where(e => e.Message.StartsWith( + "Expected someObject to be because we want to test the failure message, but found System.Object", + StringComparison.Ordinal)); + } + } + + public class BeNotNull + { + [Fact] + public void Should_succeed_when_asserting_non_null_object_not_to_be_null() + { + // Arrange + var someObject = new object(); + + // Act / Assert + someObject.Should().NotBeNull(); + } + + [Fact] + public void Should_fail_when_asserting_null_object_not_to_be_null() + { + // Arrange + object someObject = null; + + // Act + Action act = () => someObject.Should().NotBeNull(); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Should_fail_with_descriptive_message_when_asserting_null_object_not_to_be_null() + { + // Arrange + object someObject = null; + + // Act + Action act = () => someObject.Should().NotBeNull("because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw().WithMessage( + "Expected someObject not to be because we want to test the failure message."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.BeOfType.cs b/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.BeOfType.cs new file mode 100644 index 0000000000..fb046ecf2b --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.BeOfType.cs @@ -0,0 +1,253 @@ +using System; +using AssemblyA; +using AssemblyB; +using FluentAssertions.Execution; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class ObjectAssertionSpecs +{ + public class BeOfType + { + [Fact] + public void When_object_type_is_matched_against_null_type_exactly_it_should_throw() + { + // Arrange + var someObject = new object(); + + // Act + Action act = () => someObject.Should().BeOfType(null); + + // Assert + act.Should().Throw() + .WithParameterName("expectedType"); + } + + [Fact] + public void When_object_type_is_exactly_equal_to_the_specified_type_it_should_not_fail() + { + // Arrange + var someObject = new Exception(); + + // Act + Action act = () => someObject.Should().BeOfType(); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_object_type_is_value_type_and_matches_received_type_should_not_fail_and_assert_correctly() + { + // Arrange + int valueTypeObject = 42; + + // Act + Action act = () => valueTypeObject.Should().BeOfType(typeof(int)); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_object_is_matched_against_a_null_type_it_should_throw() + { + // Arrange + int valueTypeObject = 42; + + // Act + Action act = () => valueTypeObject.Should().BeOfType(null); + + // Assert + act.Should().Throw() + .WithParameterName("expectedType"); + } + + [Fact] + public void When_null_object_is_matched_against_a_type_it_should_throw() + { + // Arrange + int? valueTypeObject = null; + + // Act + Action act = () => + valueTypeObject.Should().BeOfType(typeof(int), "because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw() + .WithMessage("*type to be System.Int32*because we want to test the failure message*"); + } + + [Fact] + public void When_object_type_is_value_type_and_doesnt_match_received_type_should_fail() + { + // Arrange + int valueTypeObject = 42; + var doubleType = typeof(double); + + // Act + Action act = () => valueTypeObject.Should().BeOfType(doubleType); + + // Assert + act.Should().Throw() + .WithMessage($"Expected type to be {doubleType}, but found {valueTypeObject.GetType()}."); + } + + [Fact] + public void When_object_is_of_the_expected_type_it_should_cast_the_returned_object_for_chaining() + { + // Arrange + var someObject = new Exception("Actual Message"); + + // Act + Action act = () => someObject.Should().BeOfType().Which.Message.Should().Be("Other Message"); + + // Assert + act.Should().Throw().WithMessage("*Expected*Actual*Other*"); + } + + [Fact] + public void When_object_type_is_different_than_expected_type_it_should_fail_with_descriptive_message() + { + // Arrange + var someObject = new object(); + + // Act + Action act = () => someObject.Should().BeOfType("because they are {0} {1}", "of different", "type"); + + // Assert + act.Should().Throw().WithMessage( + "Expected type to be System.Int32 because they are of different type, but found System.Object."); + } + + [Fact] + public void When_asserting_the_type_of_a_null_object_it_should_throw() + { + // Arrange + object someObject = null; + + // Act + Action act = () => someObject.Should().BeOfType(); + + // Assert + act.Should().Throw() + .WithMessage("Expected someObject to be System.Int32, but found ."); + } + + [Fact] + public void + When_object_type_is_same_as_expected_type_but_in_different_assembly_it_should_fail_with_assembly_qualified_name() + { + // Arrange + var typeFromOtherAssembly = + new ClassA().ReturnClassC(); + + // Act +#pragma warning disable 436 // disable the warning on conflicting types, as this is the intention for the spec + + Action act = () => + typeFromOtherAssembly.Should().BeOfType(); + +#pragma warning restore 436 + + // Assert + act.Should().Throw() + .WithMessage( + "Expected type to be [AssemblyB.ClassC, FluentAssertions.Specs*], but found [AssemblyB.ClassC, AssemblyB*]."); + } + + [Fact] + public void When_object_type_is_a_subclass_of_the_expected_type_it_should_fail() + { + // Arrange + var someObject = new DummyImplementingClass(); + + // Act + Action act = () => someObject.Should().BeOfType(); + + // Assert + act.Should().Throw().WithMessage( + "Expected type to be FluentAssertions*DummyBaseClass, but found FluentAssertions*DummyImplementingClass."); + } + } + + public class NotBeOfType + { + [Fact] + public void When_object_type_is_matched_negatively_against_null_type_exactly_it_should_throw() + { + // Arrange + var someObject = new object(); + + // Act + Action act = () => someObject.Should().NotBeOfType(null); + + // Assert + act.Should().Throw() + .WithParameterName("unexpectedType"); + } + + [Fact] + public void When_object_is_matched_negatively_against_a_null_type_it_should_throw() + { + // Arrange + int valueTypeObject = 42; + + // Act + Action act = () => valueTypeObject.Should().NotBeOfType(null); + + // Assert + act.Should().Throw() + .WithParameterName("unexpectedType"); + } + + [Fact] + public void + When_object_type_is_value_type_and_doesnt_match_received_type_as_expected_should_not_fail_and_assert_correctly() + { + // Arrange + int valueTypeObject = 42; + + // Act + Action act = () => valueTypeObject.Should().NotBeOfType(typeof(double)); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_null_object_is_matched_negatively_against_a_type_it_should_throw() + { + // Arrange + int? valueTypeObject = null; + + // Act + Action act = () => + { + using var _ = new AssertionScope(); + valueTypeObject.Should().NotBeOfType(typeof(int), "because we want to test the failure {0}", "message"); + }; + + // Assert + act.Should().Throw() + .WithMessage("*type not to be System.Int32*because we want to test the failure message*"); + } + + [Fact] + public void When_object_type_is_value_type_and_matches_received_type_not_as_expected_should_fail() + { + // Arrange + int valueTypeObject = 42; + var expectedType = typeof(int); + + // Act + Action act = () => valueTypeObject.Should().NotBeOfType(expectedType); + + // Assert + act.Should().Throw() + .WithMessage($"Expected type not to be [{expectedType.AssemblyQualifiedName}], but it is."); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.BeOneOf.cs b/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.BeOneOf.cs new file mode 100644 index 0000000000..868c732342 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.BeOneOf.cs @@ -0,0 +1,221 @@ +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class ObjectAssertionSpecs +{ + public class BeOneOf + { + [Fact] + public void When_a_value_is_not_one_of_the_specified_values_it_should_throw() + { + // Arrange + var value = new ClassWithCustomEqualMethod(3); + + // Act + Action act = () => value.Should().BeOneOf(new ClassWithCustomEqualMethod(4), new ClassWithCustomEqualMethod(5)); + + // Assert + act + .Should().Throw() + .WithMessage( + "Expected value to be one of {ClassWithCustomEqualMethod(4), ClassWithCustomEqualMethod(5)}, but found ClassWithCustomEqualMethod(3)."); + } + + [Fact] + public void When_a_value_is_not_one_of_the_specified_values_it_should_throw_with_descriptive_message() + { + // Arrange + var value = new ClassWithCustomEqualMethod(3); + + // Act + Action act = () => + value.Should().BeOneOf([new ClassWithCustomEqualMethod(4), new ClassWithCustomEqualMethod(5)], + "because those are the valid values"); + + // Assert + act + .Should().Throw() + .WithMessage( + "Expected value to be one of {ClassWithCustomEqualMethod(4), ClassWithCustomEqualMethod(5)} because those are the valid values, but found ClassWithCustomEqualMethod(3)."); + } + + [Fact] + public void When_a_value_is_one_of_the_specified_values_it_should_succeed() + { + // Arrange + var value = new ClassWithCustomEqualMethod(4); + + // Act + Action act = () => value.Should().BeOneOf(new ClassWithCustomEqualMethod(4), new ClassWithCustomEqualMethod(5)); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void An_untyped_value_is_one_of_the_specified_values() + { + // Arrange + object value = new SomeClass(5); + + // Act / Assert + value.Should().BeOneOf([new SomeClass(4), new SomeClass(5)], new SomeClassEqualityComparer()); + } + + [Fact] + public void A_typed_value_is_one_of_the_specified_values() + { + // Arrange + var value = new SomeClass(5); + + // Act / Assert + value.Should().BeOneOf([new SomeClass(4), new SomeClass(5)], new SomeClassEqualityComparer()); + } + + [Fact] + public void An_untyped_value_is_not_one_of_the_specified_values() + { + // Arrange + object value = new SomeClass(3); + + // Act + Action act = () => value.Should().BeOneOf([new SomeClass(4), new SomeClass(5)], + new SomeClassEqualityComparer(), "I said so"); + + // Assert + act.Should().Throw() + .WithMessage("Expected value to be one of {SomeClass(4), SomeClass(5)}*I said so*SomeClass(3)."); + } + + [Fact] + public void An_untyped_value_is_not_one_of_no_values() + { + // Arrange + object value = new SomeClass(3); + + // Act + Action act = () => value.Should().BeOneOf([], new SomeClassEqualityComparer()); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void A_typed_value_is_not_one_of_the_specified_values() + { + // Arrange + var value = new SomeClass(3); + + // Act + Action act = () => value.Should().BeOneOf([new SomeClass(4), new SomeClass(5)], + new SomeClassEqualityComparer(), "I said so"); + + // Assert + act.Should().Throw() + .WithMessage("Expected value to be one of {SomeClass(4), SomeClass(5)}*I said so*SomeClass(3)."); + } + + [Fact] + public void A_typed_value_is_not_one_of_no_values() + { + // Arrange + var value = new SomeClass(3); + + // Act + Action act = () => value.Should().BeOneOf([], new SomeClassEqualityComparer()); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void A_typed_value_is_not_the_same_type_as_the_specified_values() + { + // Arrange + var value = new ClassWithCustomEqualMethod(3); + + // Act + Action act = () => value.Should().BeOneOf([new SomeClass(4), new SomeClass(5)], + new SomeClassEqualityComparer(), "I said so"); + + // Assert + act.Should().Throw() + .WithMessage("Expected value to be one of {SomeClass(4), SomeClass(5)}*I said so*ClassWithCustomEqualMethod(3)."); + } + + [Fact] + public void An_untyped_value_requires_an_expectation() + { + // Arrange + object value = new SomeClass(3); + + // Act + Action act = () => value.Should().BeOneOf(null, new SomeClassEqualityComparer()); + + // Assert + act.Should().Throw().WithParameterName("validValues"); + } + + [Fact] + public void A_typed_value_requires_an_expectation() + { + // Arrange + var value = new SomeClass(3); + + // Act + Action act = () => value.Should().BeOneOf(null, new SomeClassEqualityComparer()); + + // Assert + act.Should().Throw().WithParameterName("validValues"); + } + + [Fact] + public void An_untyped_value_requires_a_comparer() + { + // Arrange + object value = new SomeClass(3); + + // Act + Action act = () => value.Should().BeOneOf([], comparer: null); + + // Assert + act.Should().Throw().WithParameterName("comparer"); + } + + [Fact] + public void A_typed_value_requires_a_comparer() + { + // Arrange + var value = new SomeClass(3); + + // Act + Action act = () => value.Should().BeOneOf([], comparer: null); + + // Assert + act.Should().Throw().WithParameterName("comparer"); + } + + [Fact] + public void Chaining_after_one_assertion() + { + // Arrange + var value = new SomeClass(3); + + // Act / Assert + value.Should().BeOneOf(value).And.NotBeNull(); + } + + [Fact] + public void Can_chain_multiple_assertions() + { + // Arrange + var value = new object(); + + // Act / Assert + value.Should().BeOneOf([value], new DumbObjectEqualityComparer()).And.NotBeNull(); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.BeXmlSerializable.cs b/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.BeXmlSerializable.cs new file mode 100644 index 0000000000..ea773f33bf --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.BeXmlSerializable.cs @@ -0,0 +1,107 @@ +using System; +using System.Globalization; +using System.Xml; +using System.Xml.Schema; +using System.Xml.Serialization; +using FluentAssertions.Extensions; +using JetBrains.Annotations; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class ObjectAssertionSpecs +{ + public class BeXmlSerializable + { + [Fact] + public void When_an_object_is_xml_serializable_it_should_succeed() + { + // Arrange + var subject = new XmlSerializableClass + { + Name = "John", + Id = 1 + }; + + // Act + Action act = () => subject.Should().BeXmlSerializable(); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_an_object_is_not_xml_serializable_it_should_fail() + { + // Arrange + var subject = new NonPublicClass + { + Name = "John" + }; + + // Act + Action act = () => subject.Should().BeXmlSerializable("we need to store it on {0}", "disk"); + + // Assert + act.Should().Throw() + .WithMessage( + "*to be serializable because we need to store it on disk, but serialization failed with:*NonPublicClass*"); + } + + [Fact] + public void When_an_object_is_xml_serializable_but_doesnt_restore_all_properties_it_should_fail() + { + // Arrange + var subject = new XmlSerializableClassNotRestoringAllProperties + { + Name = "John", + BirthDay = 20.September(1973) + }; + + // Act + Action act = () => subject.Should().BeXmlSerializable(); + + // Assert + act.Should().Throw() + .WithMessage("*to be serializable, but serialization failed with:*Name*to be*"); + } + } + + public class XmlSerializableClass + { + [UsedImplicitly] + public string Name { get; set; } + + public int Id; + } + + public class XmlSerializableClassNotRestoringAllProperties : IXmlSerializable + { + [UsedImplicitly] + public string Name { get; set; } + + public DateTime BirthDay { get; set; } + + public XmlSchema GetSchema() + { + return null; + } + + public void ReadXml(XmlReader reader) + { + BirthDay = DateTime.Parse(reader.ReadElementContentAsString(), CultureInfo.InvariantCulture); + } + + public void WriteXml(XmlWriter writer) + { + writer.WriteString(BirthDay.ToString(CultureInfo.InvariantCulture)); + } + } + + internal class NonPublicClass + { + [UsedImplicitly] + public string Name { get; set; } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.cs index cf049500a1..38970cc6c8 100644 --- a/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Primitives/ObjectAssertionSpecs.cs @@ -1,1230 +1,14 @@ using System; using System.Collections.Generic; -using System.Globalization; -using System.Runtime.Serialization; -using System.Xml; -using System.Xml.Schema; -using System.Xml.Serialization; -using AssemblyA; -using AssemblyB; +using System.Diagnostics.CodeAnalysis; using FluentAssertions.Execution; -using FluentAssertions.Extensions; using FluentAssertions.Primitives; -using JetBrains.Annotations; using Xunit; -using Xunit.Sdk; namespace FluentAssertions.Specs.Primitives; -public class ObjectAssertionSpecs +public partial class ObjectAssertionSpecs { - public class Be - { - [Fact] - public void When_two_equal_object_are_expected_to_be_equal_it_should_not_fail() - { - // Arrange - var someObject = new ClassWithCustomEqualMethod(1); - var equalObject = new ClassWithCustomEqualMethod(1); - - // Act / Assert - someObject.Should().Be(equalObject); - } - - [Fact] - public void When_two_different_objects_are_expected_to_be_equal_it_should_fail_with_a_clear_explanation() - { - // Arrange - var someObject = new ClassWithCustomEqualMethod(1); - var nonEqualObject = new ClassWithCustomEqualMethod(2); - - // Act - Action act = () => someObject.Should().Be(nonEqualObject); - - // Assert - act.Should().Throw().WithMessage( - "Expected someObject to be ClassWithCustomEqualMethod(2), but found ClassWithCustomEqualMethod(1)."); - } - - [Fact] - public void When_both_subject_and_expected_are_null_it_should_succeed() - { - // Arrange - object someObject = null; - object expectedObject = null; - - // Act / Assert - someObject.Should().Be(expectedObject); - } - - [Fact] - public void When_the_subject_is_null_it_should_fail() - { - // Arrange - object someObject = null; - var nonEqualObject = new ClassWithCustomEqualMethod(2); - - // Act - Action act = () => someObject.Should().Be(nonEqualObject); - - // Assert - act.Should().Throw() - .WithMessage("Expected someObject to be ClassWithCustomEqualMethod(2), but found ."); - } - - [Fact] - public void When_two_different_objects_are_expected_to_be_equal_it_should_fail_and_use_the_reason() - { - // Arrange - var someObject = new ClassWithCustomEqualMethod(1); - var nonEqualObject = new ClassWithCustomEqualMethod(2); - - // Act - Action act = () => someObject.Should().Be(nonEqualObject, "because it should use the {0}", "reason"); - - // Assert - act.Should().Throw() - .WithMessage( - "Expected someObject to be ClassWithCustomEqualMethod(2) because it should use the reason, but found ClassWithCustomEqualMethod(1)."); - } - - [Fact] - public void When_comparing_a_numeric_and_an_enum_for_equality_it_should_throw() - { - // Arrange - object subject = 1; - MyEnum expected = MyEnum.One; - - // Act - Action act = () => subject.Should().Be(expected); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void An_untyped_value_is_equal_to_another_according_to_a_comparer() - { - // Arrange - object value = new SomeClass(5); - - // Act / Assert - value.Should().Be(new SomeClass(5), new SomeClassEqualityComparer()); - } - - [Fact] - public void A_typed_value_is_equal_to_another_according_to_a_comparer() - { - // Arrange - var value = new SomeClass(5); - - // Act / Assert - value.Should().Be(new SomeClass(5), new SomeClassEqualityComparer()); - } - - [Fact] - public void An_untyped_value_is_not_equal_to_another_according_to_a_comparer() - { - // Arrange - object value = new SomeClass(3); - - // Act - Action act = () => value.Should().Be(new SomeClass(4), new SomeClassEqualityComparer(), "I said so"); - - // Assert - act.Should().Throw().WithMessage("Expected value to be SomeClass(4)*I said so*found SomeClass(3)."); - } - - [Fact] - public void A_typed_value_is_not_equal_to_another_according_to_a_comparer() - { - // Arrange - var value = new SomeClass(3); - - // Act - Action act = () => value.Should().Be(new SomeClass(4), new SomeClassEqualityComparer(), "I said so"); - - // Assert - act.Should().Throw().WithMessage("Expected value to be SomeClass(4)*I said so*found SomeClass(3)."); - } - - [Fact] - public void A_typed_value_is_not_of_the_same_type() - { - // Arrange - var value = new ClassWithCustomEqualMethod(3); - - // Act - Action act = () => value.Should().Be(new SomeClass(3), new SomeClassEqualityComparer(), "I said so"); - - // Assert - act.Should().Throw() - .WithMessage("Expected value to be SomeClass(3)*I said so*found ClassWithCustomEqualMethod(3)."); - } - - [Fact] - public void A_untyped_value_requires_a_comparer() - { - // Arrange - object value = new SomeClass(3); - - // Act - Action act = () => value.Should().Be(new SomeClass(3), comparer: null); - - // Assert - act.Should().Throw().WithParameterName("comparer"); - } - - [Fact] - public void A_typed_value_requires_a_comparer() - { - // Arrange - var value = new SomeClass(3); - - // Act - Action act = () => value.Should().Be(new SomeClass(3), comparer: null); - - // Assert - act.Should().Throw().WithParameterName("comparer"); - } - } - - public class NotBe - { - [Fact] - public void When_non_equal_objects_are_expected_to_be_not_equal_it_should_not_fail() - { - // Arrange - var someObject = new ClassWithCustomEqualMethod(1); - var nonEqualObject = new ClassWithCustomEqualMethod(2); - - // Act / Assert - someObject.Should().NotBe(nonEqualObject); - } - - [Fact] - public void When_two_equal_objects_are_expected_not_to_be_equal_it_should_fail_with_a_clear_explanation() - { - // Arrange - var someObject = new ClassWithCustomEqualMethod(1); - var equalObject = new ClassWithCustomEqualMethod(1); - - // Act - Action act = () => - someObject.Should().NotBe(equalObject); - - // Assert - act.Should().Throw().WithMessage( - "Did not expect someObject to be equal to ClassWithCustomEqualMethod(1)."); - } - - [Fact] - public void When_two_equal_objects_are_expected_not_to_be_equal_it_should_fail_and_use_the_reason() - { - // Arrange - var someObject = new ClassWithCustomEqualMethod(1); - var equalObject = new ClassWithCustomEqualMethod(1); - - // Act - Action act = () => - someObject.Should().NotBe(equalObject, "because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw().WithMessage( - "Did not expect someObject to be equal to ClassWithCustomEqualMethod(1) " + - "because we want to test the failure message."); - } - - [Fact] - public void An_untyped_value_is_not_equal_to_another_according_to_a_comparer() - { - // Arrange - object value = new SomeClass(5); - - // Act / Assert - value.Should().NotBe(new SomeClass(4), new SomeClassEqualityComparer()); - } - - [Fact] - public void A_typed_value_is_not_equal_to_another_according_to_a_comparer() - { - // Arrange - var value = new SomeClass(5); - - // Act / Assert - value.Should().NotBe(new SomeClass(4), new SomeClassEqualityComparer()); - } - - [Fact] - public void An_untyped_value_is_equal_to_another_according_to_a_comparer() - { - // Arrange - object value = new SomeClass(3); - - // Act - Action act = () => value.Should().NotBe(new SomeClass(3), new SomeClassEqualityComparer(), "I said so"); - - // Assert - act.Should().Throw().WithMessage("Did not expect value to be equal to SomeClass(3)*I said so*"); - } - - [Fact] - public void A_typed_value_is_equal_to_another_according_to_a_comparer() - { - // Arrange - var value = new SomeClass(3); - - // Act - Action act = () => value.Should().NotBe(new SomeClass(3), new SomeClassEqualityComparer(), "I said so"); - - // Assert - act.Should().Throw().WithMessage("Did not expect value to be equal to SomeClass(3)*I said so*"); - } - - [Fact] - public void A_typed_value_is_not_of_the_same_type() - { - // Arrange - var value = new ClassWithCustomEqualMethod(3); - - // Act / Assert - value.Should().NotBe(new SomeClass(3), new SomeClassEqualityComparer(), "I said so"); - } - - [Fact] - public void An_untyped_value_requires_a_comparer() - { - // Arrange - object value = new SomeClass(3); - - // Act - Action act = () => value.Should().NotBe(new SomeClass(3), comparer: null); - - // Assert - act.Should().Throw().WithParameterName("comparer"); - } - - [Fact] - public void A_typed_value_requires_a_comparer() - { - // Arrange - var value = new SomeClass(3); - - // Act - Action act = () => value.Should().NotBe(new SomeClass(3), comparer: null); - - // Assert - act.Should().Throw().WithParameterName("comparer"); - } - } - - public class BeOneOf - { - [Fact] - public void When_a_value_is_not_one_of_the_specified_values_it_should_throw() - { - // Arrange - var value = new ClassWithCustomEqualMethod(3); - - // Act - Action act = () => value.Should().BeOneOf(new ClassWithCustomEqualMethod(4), new ClassWithCustomEqualMethod(5)); - - // Assert - act - .Should().Throw() - .WithMessage( - "Expected value to be one of {ClassWithCustomEqualMethod(4), ClassWithCustomEqualMethod(5)}, but found ClassWithCustomEqualMethod(3)."); - } - - [Fact] - public void When_a_value_is_not_one_of_the_specified_values_it_should_throw_with_descriptive_message() - { - // Arrange - var value = new ClassWithCustomEqualMethod(3); - - // Act - Action act = () => - value.Should().BeOneOf(new[] { new ClassWithCustomEqualMethod(4), new ClassWithCustomEqualMethod(5) }, - "because those are the valid values"); - - // Assert - act - .Should().Throw() - .WithMessage( - "Expected value to be one of {ClassWithCustomEqualMethod(4), ClassWithCustomEqualMethod(5)} because those are the valid values, but found ClassWithCustomEqualMethod(3)."); - } - - [Fact] - public void When_a_value_is_one_of_the_specified_values_it_should_succeed() - { - // Arrange - var value = new ClassWithCustomEqualMethod(4); - - // Act - Action act = () => value.Should().BeOneOf(new ClassWithCustomEqualMethod(4), new ClassWithCustomEqualMethod(5)); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void An_untyped_value_is_one_of_the_specified_values() - { - // Arrange - object value = new SomeClass(5); - - // Act / Assert - value.Should().BeOneOf(new[] { new SomeClass(4), new SomeClass(5) }, new SomeClassEqualityComparer()); - } - - [Fact] - public void A_typed_value_is_one_of_the_specified_values() - { - // Arrange - var value = new SomeClass(5); - - // Act / Assert - value.Should().BeOneOf(new[] { new SomeClass(4), new SomeClass(5) }, new SomeClassEqualityComparer()); - } - - [Fact] - public void An_untyped_value_is_not_one_of_the_specified_values() - { - // Arrange - object value = new SomeClass(3); - - // Act - Action act = () => value.Should().BeOneOf(new[] { new SomeClass(4), new SomeClass(5) }, - new SomeClassEqualityComparer(), "I said so"); - - // Assert - act.Should().Throw() - .WithMessage("Expected value to be one of {SomeClass(4), SomeClass(5)}*I said so*SomeClass(3)."); - } - - [Fact] - public void An_untyped_value_is_not_one_of_no_values() - { - // Arrange - object value = new SomeClass(3); - - // Act - Action act = () => value.Should().BeOneOf(Array.Empty(), new SomeClassEqualityComparer()); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void A_typed_value_is_not_one_of_the_specified_values() - { - // Arrange - var value = new SomeClass(3); - - // Act - Action act = () => value.Should().BeOneOf(new[] { new SomeClass(4), new SomeClass(5) }, - new SomeClassEqualityComparer(), "I said so"); - - // Assert - act.Should().Throw() - .WithMessage("Expected value to be one of {SomeClass(4), SomeClass(5)}*I said so*SomeClass(3)."); - } - - [Fact] - public void A_typed_value_is_not_one_of_no_values() - { - // Arrange - var value = new SomeClass(3); - - // Act - Action act = () => value.Should().BeOneOf(Array.Empty(), new SomeClassEqualityComparer()); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void A_typed_value_is_not_the_same_type_as_the_specified_values() - { - // Arrange - var value = new ClassWithCustomEqualMethod(3); - - // Act - Action act = () => value.Should().BeOneOf(new[] { new SomeClass(4), new SomeClass(5) }, - new SomeClassEqualityComparer(), "I said so"); - - // Assert - act.Should().Throw() - .WithMessage("Expected value to be one of {SomeClass(4), SomeClass(5)}*I said so*ClassWithCustomEqualMethod(3)."); - } - - [Fact] - public void An_untyped_value_requires_an_expectation() - { - // Arrange - object value = new SomeClass(3); - - // Act - Action act = () => value.Should().BeOneOf(null, new SomeClassEqualityComparer()); - - // Assert - act.Should().Throw().WithParameterName("validValues"); - } - - [Fact] - public void A_typed_value_requires_an_expectation() - { - // Arrange - var value = new SomeClass(3); - - // Act - Action act = () => value.Should().BeOneOf(null, new SomeClassEqualityComparer()); - - // Assert - act.Should().Throw().WithParameterName("validValues"); - } - - [Fact] - public void An_untyped_value_requires_a_comparer() - { - // Arrange - object value = new SomeClass(3); - - // Act - Action act = () => value.Should().BeOneOf(Array.Empty(), comparer: null); - - // Assert - act.Should().Throw().WithParameterName("comparer"); - } - - [Fact] - public void A_typed_value_requires_a_comparer() - { - // Arrange - var value = new SomeClass(3); - - // Act - Action act = () => value.Should().BeOneOf(Array.Empty(), comparer: null); - - // Assert - act.Should().Throw().WithParameterName("comparer"); - } - } - - private enum MyEnum - { - One = 1, - Two = 2 - } - - public class BeNull - { - [Fact] - public void Should_succeed_when_asserting_null_object_to_be_null() - { - // Arrange - object someObject = null; - - // Act / Assert - someObject.Should().BeNull(); - } - - [Fact] - public void Should_fail_when_asserting_non_null_object_to_be_null() - { - // Arrange - var someObject = new object(); - - // Act - Action act = () => someObject.Should().BeNull(); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_a_non_null_object_is_expected_to_be_null_it_should_fail() - { - // Arrange - var someObject = new object(); - - // Act - Action act = () => someObject.Should().BeNull("because we want to test the failure {0}", "message"); - - // Assert - act - .Should().Throw() - .Where(e => e.Message.StartsWith( - "Expected someObject to be because we want to test the failure message, but found System.Object", - StringComparison.Ordinal)); - } - } - - public class BeNotNull - { - [Fact] - public void Should_succeed_when_asserting_non_null_object_not_to_be_null() - { - // Arrange - var someObject = new object(); - - // Act / Assert - someObject.Should().NotBeNull(); - } - - [Fact] - public void Should_fail_when_asserting_null_object_not_to_be_null() - { - // Arrange - object someObject = null; - - // Act - Action act = () => someObject.Should().NotBeNull(); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void Should_fail_with_descriptive_message_when_asserting_null_object_not_to_be_null() - { - // Arrange - object someObject = null; - - // Act - Action act = () => someObject.Should().NotBeNull("because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw().WithMessage( - "Expected someObject not to be because we want to test the failure message."); - } - } - - public class BeOfType - { - [Fact] - public void When_object_type_is_matched_against_null_type_exactly_it_should_throw() - { - // Arrange - var someObject = new object(); - - // Act - Action act = () => someObject.Should().BeOfType(null); - - // Assert - act.Should().Throw() - .WithParameterName("expectedType"); - } - - [Fact] - public void When_object_type_is_exactly_equal_to_the_specified_type_it_should_not_fail() - { - // Arrange - var someObject = new Exception(); - - // Act - Action act = () => someObject.Should().BeOfType(); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_object_type_is_value_type_and_matches_received_type_should_not_fail_and_assert_correctly() - { - // Arrange - int valueTypeObject = 42; - - // Act - Action act = () => valueTypeObject.Should().BeOfType(typeof(int)); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_object_is_matched_against_a_null_type_it_should_throw() - { - // Arrange - int valueTypeObject = 42; - - // Act - Action act = () => valueTypeObject.Should().BeOfType(null); - - // Assert - act.Should().Throw() - .WithParameterName("expectedType"); - } - - [Fact] - public void When_null_object_is_matched_against_a_type_it_should_throw() - { - // Arrange - int? valueTypeObject = null; - - // Act - Action act = () => - valueTypeObject.Should().BeOfType(typeof(int), "because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw() - .WithMessage("*type to be System.Int32*because we want to test the failure message*"); - } - - [Fact] - public void When_object_type_is_value_type_and_doesnt_match_received_type_should_fail() - { - // Arrange - int valueTypeObject = 42; - var doubleType = typeof(double); - - // Act - Action act = () => valueTypeObject.Should().BeOfType(doubleType); - - // Assert - act.Should().Throw() - .WithMessage($"Expected type to be {doubleType}, but found {valueTypeObject.GetType()}."); - } - - [Fact] - public void When_object_is_of_the_expected_type_it_should_cast_the_returned_object_for_chaining() - { - // Arrange - var someObject = new Exception("Actual Message"); - - // Act - Action act = () => someObject.Should().BeOfType().Which.Message.Should().Be("Other Message"); - - // Assert - act.Should().Throw().WithMessage("*Expected*Other*Actual*"); - } - - [Fact] - public void When_object_type_is_different_than_expected_type_it_should_fail_with_descriptive_message() - { - // Arrange - var someObject = new object(); - - // Act - Action act = () => someObject.Should().BeOfType("because they are {0} {1}", "of different", "type"); - - // Assert - act.Should().Throw().WithMessage( - "Expected type to be System.Int32 because they are of different type, but found System.Object."); - } - - [Fact] - public void When_asserting_the_type_of_a_null_object_it_should_throw() - { - // Arrange - object someObject = null; - - // Act - Action act = () => someObject.Should().BeOfType(); - - // Assert - act.Should().Throw() - .WithMessage("Expected someObject to be System.Int32, but found ."); - } - - [Fact] - public void - When_object_type_is_same_as_expected_type_but_in_different_assembly_it_should_fail_with_assembly_qualified_name() - { - // Arrange - var typeFromOtherAssembly = - new ClassA().ReturnClassC(); - - // Act -#pragma warning disable 436 // disable the warning on conflicting types, as this is the intention for the spec - - Action act = () => - typeFromOtherAssembly.Should().BeOfType(); - -#pragma warning restore 436 - - // Assert - act.Should().Throw() - .WithMessage( - "Expected type to be [AssemblyB.ClassC, FluentAssertions.Specs*], but found [AssemblyB.ClassC, AssemblyB*]."); - } - - [Fact] - public void When_object_type_is_a_subclass_of_the_expected_type_it_should_fail() - { - // Arrange - var someObject = new DummyImplementingClass(); - - // Act - Action act = () => someObject.Should().BeOfType(); - - // Assert - act.Should().Throw().WithMessage( - "Expected type to be FluentAssertions*DummyBaseClass, but found FluentAssertions*DummyImplementingClass."); - } - } - - public class NotBeOfType - { - [Fact] - public void When_object_type_is_matched_negatively_against_null_type_exactly_it_should_throw() - { - // Arrange - var someObject = new object(); - - // Act - Action act = () => someObject.Should().NotBeOfType(null); - - // Assert - act.Should().Throw() - .WithParameterName("unexpectedType"); - } - - [Fact] - public void When_object_is_matched_negatively_against_a_null_type_it_should_throw() - { - // Arrange - int valueTypeObject = 42; - - // Act - Action act = () => valueTypeObject.Should().NotBeOfType(null); - - // Assert - act.Should().Throw() - .WithParameterName("unexpectedType"); - } - - [Fact] - public void - When_object_type_is_value_type_and_doesnt_match_received_type_as_expected_should_not_fail_and_assert_correctly() - { - // Arrange - int valueTypeObject = 42; - - // Act - Action act = () => valueTypeObject.Should().NotBeOfType(typeof(double)); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_null_object_is_matched_negatively_against_a_type_it_should_throw() - { - // Arrange - int? valueTypeObject = null; - - // Act - Action act = () => - { - using var _ = new AssertionScope(); - valueTypeObject.Should().NotBeOfType(typeof(int), "because we want to test the failure {0}", "message"); - }; - - // Assert - act.Should().Throw() - .WithMessage("*type not to be System.Int32*because we want to test the failure message*"); - } - - [Fact] - public void When_object_type_is_value_type_and_matches_received_type_not_as_expected_should_fail() - { - // Arrange - int valueTypeObject = 42; - var expectedType = typeof(int); - - // Act - Action act = () => valueTypeObject.Should().NotBeOfType(expectedType); - - // Assert - act.Should().Throw() - .WithMessage($"Expected type not to be [{expectedType.AssemblyQualifiedName}], but it is."); - } - } - - public class BeAssignableTo - { - [Fact] - public void When_object_type_is_matched_against_null_type_it_should_throw() - { - // Arrange - var someObject = new object(); - - // Act - Action act = () => someObject.Should().BeAssignableTo(null); - - // Assert - act.Should().Throw() - .WithParameterName("type"); - } - - [Fact] - public void When_its_own_type_it_should_succeed() - { - // Arrange - var someObject = new DummyImplementingClass(); - - // Act / Assert - someObject.Should().BeAssignableTo(); - } - - [Fact] - public void When_its_base_type_it_should_succeed() - { - // Arrange - var someObject = new DummyImplementingClass(); - - // Act / Assert - someObject.Should().BeAssignableTo(); - } - - [Fact] - public void When_an_implemented_interface_type_it_should_succeed() - { - // Arrange - var someObject = new DummyImplementingClass(); - - // Act / Assert - someObject.Should().BeAssignableTo(); - } - - [Fact] - public void When_an_unrelated_type_it_should_fail_with_a_descriptive_message() - { - // Arrange - var someObject = new DummyImplementingClass(); - Action act = () => someObject.Should().BeAssignableTo("because we want to test the failure {0}", "message"); - - // Act / Assert - act.Should().Throw() - .WithMessage($"*assignable to {typeof(DateTime)}*failure message*{typeof(DummyImplementingClass)} is not*"); - } - - [Fact] - public void When_to_the_expected_type_it_should_cast_the_returned_object_for_chaining() - { - // Arrange - var someObject = new Exception("Actual Message"); - - // Act - Action act = () => someObject.Should().BeAssignableTo().Which.Message.Should().Be("Other Message"); - - // Assert - act.Should().Throw().WithMessage("*Expected*Other*Actual*"); - } - - [Fact] - public void When_a_null_instance_is_asserted_to_be_assignableOfT_it_should_fail() - { - // Arrange - object someObject = null; - - // Act - Action act = () => - { - using var _ = new AssertionScope(); - someObject.Should().BeAssignableTo("because we want to test the failure {0}", "message"); - }; - - // Assert - act.Should().Throw() - .WithMessage($"*assignable to {typeof(DateTime)}*failure message*found *"); - } - - [Fact] - public void When_its_own_type_instance_it_should_succeed() - { - // Arrange - var someObject = new DummyImplementingClass(); - - // Act / Assert - someObject.Should().BeAssignableTo(typeof(DummyImplementingClass)); - } - - [Fact] - public void When_its_base_type_instance_it_should_succeed() - { - // Arrange - var someObject = new DummyImplementingClass(); - - // Act / Assert - someObject.Should().BeAssignableTo(typeof(DummyBaseClass)); - } - - [Fact] - public void When_an_implemented_interface_type_instance_it_should_succeed() - { - // Arrange - var someObject = new DummyImplementingClass(); - - // Act / Assert - someObject.Should().BeAssignableTo(typeof(IDisposable)); - } - - [Fact] - public void When_an_implemented_open_generic_interface_type_instance_it_should_succeed() - { - // Arrange - var someObject = new List(); - - // Act / Assert - someObject.Should().BeAssignableTo(typeof(IList<>)); - } - - [Fact] - public void When_a_null_instance_is_asserted_to_be_assignable_it_should_fail_with_a_descriptive_message() - { - // Arrange - object someObject = null; - - // Act - Action act = () => - { - using var _ = new AssertionScope(); - someObject.Should().BeAssignableTo(typeof(DateTime), "because we want to test the failure {0}", "message"); - }; - - // Assert - act.Should().Throw() - .WithMessage($"*assignable to {typeof(DateTime)}*failure message*found *"); - } - - [Fact] - public void When_an_unrelated_type_instance_it_should_fail_with_a_descriptive_message() - { - // Arrange - var someObject = new DummyImplementingClass(); - - Action act = () => - someObject.Should().BeAssignableTo(typeof(DateTime), "because we want to test the failure {0}", "message"); - - // Act / Assert - act.Should().Throw() - .WithMessage($"*assignable to {typeof(DateTime)}*failure message*{typeof(DummyImplementingClass)} is not*"); - } - - [Fact] - public void When_unrelated_to_open_generic_type_it_should_fail_with_a_descriptive_message() - { - // Arrange - var someObject = new DummyImplementingClass(); - - Action act = () => - someObject.Should().BeAssignableTo(typeof(IList<>), "because we want to test the failure {0}", "message"); - - // Act / Assert - act.Should().Throw() - .WithMessage($"*assignable to {typeof(IList<>)}*failure message*{typeof(DummyImplementingClass)} is not*"); - } - - [Fact] - public void When_an_assertion_fails_on_BeAssignableTo_succeeding_message_should_be_included() - { - // Act - Action act = () => - { - using var _ = new AssertionScope(); - var item = string.Empty; - item.Should().BeAssignableTo(); - item.Should().BeAssignableTo(); - }; - - // Assert - act.Should().Throw() - .WithMessage( - "Expected * to be assignable to System.Int32, but System.String is not.*" + - "Expected * to be assignable to System.Int64, but System.String is not."); - } - } - - public class NotBeAssignableTo - { - [Fact] - public void When_object_type_is_matched_negatively_against_null_type_it_should_throw() - { - // Arrange - var someObject = new object(); - - // Act - Action act = () => someObject.Should().NotBeAssignableTo(null); - - // Assert - act.Should().Throw() - .WithParameterName("type"); - } - - [Fact] - public void When_its_own_type_and_asserting_not_assignable_it_should_fail_with_a_useful_message() - { - // Arrange - var someObject = new DummyImplementingClass(); - - Action act = () => - someObject.Should() - .NotBeAssignableTo("because we want to test the failure {0}", "message"); - - // Act / Assert - act.Should().Throw() - .WithMessage( - $"*not be assignable to {typeof(DummyImplementingClass)}*failure message*{typeof(DummyImplementingClass)} is*"); - } - - [Fact] - public void When_its_base_type_and_asserting_not_assignable_it_should_fail_with_a_useful_message() - { - // Arrange - var someObject = new DummyImplementingClass(); - - Action act = () => - someObject.Should().NotBeAssignableTo("because we want to test the failure {0}", "message"); - - // Act / Assert - act.Should().Throw() - .WithMessage( - $"*not be assignable to {typeof(DummyBaseClass)}*failure message*{typeof(DummyImplementingClass)} is*"); - } - - [Fact] - public void When_an_implemented_interface_type_and_asserting_not_assignable_it_should_fail_with_a_useful_message() - { - // Arrange - var someObject = new DummyImplementingClass(); - - Action act = () => - someObject.Should().NotBeAssignableTo("because we want to test the failure {0}", "message"); - - // Act / Assert - act.Should().Throw() - .WithMessage($"*not be assignable to {typeof(IDisposable)}*failure message*{typeof(DummyImplementingClass)} is*"); - } - - [Fact] - public void When_an_unrelated_type_and_asserting_not_assignable_it_should_succeed() - { - // Arrange - var someObject = new DummyImplementingClass(); - - // Act / Assert - someObject.Should().NotBeAssignableTo(); - } - - [Fact] - public void - When_not_to_the_unexpected_type_and_asserting_not_assignable_it_should_not_cast_the_returned_object_for_chaining() - { - // Arrange - var someObject = new Exception("Actual Message"); - - // Act - Action act = () => someObject.Should().NotBeAssignableTo() - .And.Subject.Should().BeOfType() - .Which.Message.Should().Be("Other Message"); - - // Assert - act.Should().Throw().WithMessage("*Expected*Other*Actual*"); - } - - [Fact] - public void When_its_own_type_instance_and_asserting_not_assignable_it_should_fail_with_a_useful_message() - { - // Arrange - var someObject = new DummyImplementingClass(); - - Action act = () => - someObject.Should().NotBeAssignableTo(typeof(DummyImplementingClass), "because we want to test the failure {0}", - "message"); - - // Act / Assert - act.Should().Throw() - .WithMessage( - $"*not be assignable to {typeof(DummyImplementingClass)}*failure message*{typeof(DummyImplementingClass)} is*"); - } - - [Fact] - public void When_its_base_type_instance_and_asserting_not_assignable_it_should_fail_with_a_useful_message() - { - // Arrange - var someObject = new DummyImplementingClass(); - - Action act = () => - someObject.Should() - .NotBeAssignableTo(typeof(DummyBaseClass), "because we want to test the failure {0}", "message"); - - // Act / Assert - act.Should().Throw() - .WithMessage( - $"*not be assignable to {typeof(DummyBaseClass)}*failure message*{typeof(DummyImplementingClass)} is*"); - } - - [Fact] - public void - When_an_implemented_interface_type_instance_and_asserting_not_assignable_it_should_fail_with_a_useful_message() - { - // Arrange - var someObject = new DummyImplementingClass(); - - Action act = () => - someObject.Should().NotBeAssignableTo(typeof(IDisposable), "because we want to test the failure {0}", "message"); - - // Act / Assert - act.Should().Throw() - .WithMessage($"*not be assignable to {typeof(IDisposable)}*failure message*{typeof(DummyImplementingClass)} is*"); - } - - [Fact] - public void - When_an_implemented_open_generic_interface_type_instance_and_asserting_not_assignable_it_should_fail_with_a_useful_message() - { - // Arrange - var someObject = new List(); - - Action act = () => - someObject.Should().NotBeAssignableTo(typeof(IList<>), "because we want to test the failure {0}", "message"); - - // Act / Assert - act.Should().Throw() - .WithMessage($"*not be assignable to {typeof(IList<>)}*failure message*{typeof(List)} is*"); - } - - [Fact] - public void When_a_null_instance_is_asserted_to_not_be_assignable_it_should_fail_with_a_descriptive_message() - { - // Arrange - object someObject = null; - - // Act - Action act = () => - { - using var _ = new AssertionScope(); - someObject.Should().NotBeAssignableTo(typeof(DateTime), "because we want to test the failure {0}", "message"); - }; - - // Assert - act.Should().Throw() - .WithMessage($"*not be assignable to {typeof(DateTime)}*failure message*found *"); - } - - [Fact] - public void When_an_unrelated_type_instance_and_asserting_not_assignable_it_should_succeed() - { - // Arrange - var someObject = new DummyImplementingClass(); - - // Act / Assert - someObject.Should().NotBeAssignableTo(typeof(DateTime), "because we want to test the failure {0}", "message"); - } - - [Fact] - public void When_unrelated_to_open_generic_type_and_asserting_not_assignable_it_should_succeed() - { - // Arrange - var someObject = new DummyImplementingClass(); - - // Act / Assert - someObject.Should().NotBeAssignableTo(typeof(IList<>), "because we want to test the failure {0}", "message"); - } - } - public class Miscellaneous { [Fact] @@ -1255,399 +39,16 @@ public void Should_throw_a_helpful_error_when_accidentally_using_equals() } } -#if !NET8_0_OR_GREATER - - public class BeBinarySerializable - { - [Fact] - public void When_an_object_is_binary_serializable_it_should_succeed() - { - // Arrange - var subject = new SerializableClass - { - Name = "John", - Id = 2 - }; - - // Act - Action act = () => subject.Should().BeBinarySerializable(); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_an_object_is_binary_serializable_with_non_serializable_members_it_should_succeed() - { - // Arrange - var subject = new SerializableClassWithNonSerializableMember - { - Name = "John", - NonSerializable = "Nonserializable value" - }; - - // Act - Action act = () => subject.Should().BeBinarySerializable(options => - options.Excluding(s => s.NonSerializable)); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_injecting_null_options_it_should_throw() - { - // Arrange - var subject = new SerializableClassWithNonSerializableMember(); - - // Act - Action act = () => subject.Should().BeBinarySerializable(options: null); - - // Assert - act.Should().ThrowExactly() - .WithParameterName("options"); - } - - [Fact] - public void When_an_object_is_not_binary_serializable_it_should_fail() - { - // Arrange - var subject = new UnserializableClass - { - Name = "John" - }; - - // Act - Action act = () => subject.Should().BeBinarySerializable("we need to store it on {0}", "disk"); - - // Assert - act.Should().Throw() - .WithMessage( - "*to be serializable because we need to store it on disk, but serialization failed with:*UnserializableClass*"); - } - - [Fact] - public void When_an_object_is_binary_serializable_but_not_deserializable_it_should_fail() - { - // Arrange - var subject = new BinarySerializableClassMissingDeserializationConstructor - { - Name = "John", - BirthDay = 20.September(1973) - }; - - // Act - Action act = () => subject.Should().BeBinarySerializable(); - - // Assert - act.Should().Throw() - .WithMessage( - "*to be serializable, but serialization failed with:*BinarySerializableClassMissingDeserializationConstructor*"); - } - - [Fact] - public void When_an_object_is_binary_serializable_but_doesnt_restore_all_properties_it_should_fail() - { - // Arrange - var subject = new BinarySerializableClassNotRestoringAllProperties - { - Name = "John", - BirthDay = 20.September(1973) - }; - - // Act - Action act = () => subject.Should().BeBinarySerializable(); - - // Assert - act.Should().Throw() - .WithMessage("*to be serializable, but serialization failed with:*subject.Name*to be*"); - } - - [Fact] - public void When_a_system_exception_is_asserted_to_be_serializable_it_should_compare_its_fields_and_properties() - { - // Arrange - var subject = new Exception("some error"); - - // Act - Action act = () => subject.Should().BeBinarySerializable(); - - // Assert - act.Should().NotThrow(); - } - } - -#endif - - internal class UnserializableClass - { - public string Name { get; set; } - } - - [Serializable] - public class SerializableClass - { - public string Name { get; set; } - - public int Id; - } - - [Serializable] - public class SerializableClassWithNonSerializableMember - { - [NonSerialized] - private string nonSerializable; - - public string Name { get; set; } - - public string NonSerializable - { - get => nonSerializable; - set => nonSerializable = value; - } - } - - [Serializable] - internal class BinarySerializableClassMissingDeserializationConstructor : ISerializable - { - public string Name { get; set; } - - public DateTime BirthDay { get; set; } - - public void GetObjectData(SerializationInfo info, StreamingContext context) - { - } - } - - [Serializable] - internal class BinarySerializableClassNotRestoringAllProperties : ISerializable - { - public string Name { get; set; } - - public DateTime BirthDay { get; set; } - - public BinarySerializableClassNotRestoringAllProperties() - { - } - - public BinarySerializableClassNotRestoringAllProperties(SerializationInfo info, StreamingContext context) - { - BirthDay = info.GetDateTime("BirthDay"); - } - - public void GetObjectData(SerializationInfo info, StreamingContext context) - { - info.AddValue("BirthDay", BirthDay); - } - } - - public class BeXmlSerializable - { - [Fact] - public void When_an_object_is_xml_serializable_it_should_succeed() - { - // Arrange - var subject = new XmlSerializableClass - { - Name = "John", - Id = 1 - }; - - // Act - Action act = () => subject.Should().BeXmlSerializable(); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_an_object_is_not_xml_serializable_it_should_fail() - { - // Arrange - var subject = new NonPublicClass - { - Name = "John" - }; - - // Act - Action act = () => subject.Should().BeXmlSerializable("we need to store it on {0}", "disk"); - - // Assert - act.Should().Throw() - .WithMessage( - "*to be serializable because we need to store it on disk, but serialization failed with:*NonPublicClass*"); - } - - [Fact] - public void When_an_object_is_xml_serializable_but_doesnt_restore_all_properties_it_should_fail() - { - // Arrange - var subject = new XmlSerializableClassNotRestoringAllProperties - { - Name = "John", - BirthDay = 20.September(1973) - }; - - // Act - Action act = () => subject.Should().BeXmlSerializable(); - - // Assert - act.Should().Throw() - .WithMessage("*to be serializable, but serialization failed with:*Name*to be*"); - } - } - - internal class NonPublicClass - { - [UsedImplicitly] - public string Name { get; set; } - } - - public class XmlSerializableClass - { - [UsedImplicitly] - public string Name { get; set; } - - public int Id; - } - - public class XmlSerializableClassNotRestoringAllProperties : IXmlSerializable - { - [UsedImplicitly] - public string Name { get; set; } - - public DateTime BirthDay { get; set; } - - public XmlSchema GetSchema() - { - return null; - } - - public void ReadXml(XmlReader reader) - { - BirthDay = DateTime.Parse(reader.ReadElementContentAsString(), CultureInfo.InvariantCulture); - } - - public void WriteXml(XmlWriter writer) - { - writer.WriteString(BirthDay.ToString(CultureInfo.InvariantCulture)); - } - } - - public class BeDataContractSerializable + internal class DumbObjectEqualityComparer : IEqualityComparer { - [Fact] - public void When_an_object_is_data_contract_serializable_it_should_succeed() - { - // Arrange - var subject = new DataContractSerializableClass - { - Name = "John", - Id = 1 - }; - - // Act - Action act = () => subject.Should().BeDataContractSerializable(); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_an_object_is_not_data_contract_serializable_it_should_fail() - { - // Arrange - var subject = new NonDataContractSerializableClass(); - - // Act - Action act = () => subject.Should().BeDataContractSerializable("we need to store it on {0}", "disk"); - - // Assert - act - .Should().Throw() - .WithMessage("*we need to store it on disk*EnumMemberAttribute*"); - } - - [Fact] - public void When_an_object_is_data_contract_serializable_but_doesnt_restore_all_properties_it_should_fail() - { - // Arrange - var subject = new DataContractSerializableClassNotRestoringAllProperties - { - Name = "John", - BirthDay = 20.September(1973) - }; - - // Act - Action act = () => subject.Should().BeDataContractSerializable(); - - // Assert - act.Should().Throw() - .WithMessage("*to be serializable, but serialization failed with:*property subject.Name*to be*"); - } - - [Fact] - public void When_a_data_contract_serializable_object_doesnt_restore_an_ignored_property_it_should_succeed() - { - // Arrange - var subject = new DataContractSerializableClassNotRestoringAllProperties - { - Name = "John", - BirthDay = 20.September(1973) - }; - - // Act - Action act = () => subject.Should() - .BeDataContractSerializable( - options => options.Excluding(x => x.Name)); - - // Assert - act.Should().NotThrow(); - } - - [Fact] - public void When_injecting_null_options_to_BeDataContractSerializable_it_should_throw() + // ReSharper disable once MemberHidesStaticFromOuterClass + [SuppressMessage("Class Design", "AV1010:Member hides inherited member")] + public new bool Equals(object x, object y) { - // Arrange - var subject = new DataContractSerializableClassNotRestoringAllProperties(); - - // Act - Action act = () => subject.Should() - .BeDataContractSerializable( - options: null); - - // Assert - act.Should().ThrowExactly() - .WithParameterName("options"); + return (x == y) || (x is not null && y is not null && x.Equals(y)); } - } - - public enum Color - { - Red = 1, - Yellow = 2 - } - - public class NonDataContractSerializableClass - { - public Color Color { get; set; } - } - public class DataContractSerializableClass - { - [UsedImplicitly] - public string Name { get; set; } - - public int Id; - } - - [DataContract] - public class DataContractSerializableClassNotRestoringAllProperties - { - public string Name { get; set; } - - [DataMember] - public DateTime BirthDay { get; set; } + public int GetHashCode(object obj) => obj.GetHashCode(); } } @@ -1675,7 +76,10 @@ public SomeClass(int key) internal class SomeClassEqualityComparer : IEqualityComparer { - public bool Equals(SomeClass x, SomeClass y) => x.Key == y.Key; + public bool Equals(SomeClass x, SomeClass y) + { + return (x == y) || (x is not null && y is not null && x.Key.Equals(y.Key)); + } public int GetHashCode(SomeClass obj) => obj.Key; } @@ -1683,7 +87,7 @@ internal class SomeClassEqualityComparer : IEqualityComparer internal class SomeClassAssertions : ObjectAssertions { public SomeClassAssertions(SomeClass value) - : base(value) + : base(value, AssertionChain.GetOrCreate()) { } } diff --git a/Tests/FluentAssertions.Specs/Primitives/ReferenceTypeAssertionsSpecs.Satisfy.cs b/Tests/FluentAssertions.Specs/Primitives/ReferenceTypeAssertionsSpecs.Satisfy.cs new file mode 100644 index 0000000000..620b879c0d --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/ReferenceTypeAssertionsSpecs.Satisfy.cs @@ -0,0 +1,392 @@ +using System; +using FluentAssertions.Execution; +using FluentAssertions.Extensions; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class ReferenceTypeAssertionsSpecs +{ + public class Satisfy + { + [Fact] + public void Object_satisfying_inspector_does_not_throw() + { + // Arrange + var someObject = new object(); + + // Act / Assert + someObject.Should().Satisfy(x => x.Should().NotBeNull()); + } + + [Fact] + public void Object_not_satisfying_inspector_throws() + { + // Arrange + var someObject = new object(); + + // Act + Action act = () => someObject.Should().Satisfy(o => o.Should().BeNull("it is not initialized yet")); + + // Assert + act.Should().Throw().WithMessage( + $""" + Expected {nameof(someObject)} to match inspector, but the inspector was not satisfied: + *Expected o to be because it is not initialized yet, but found System.Object* + """); + } + + [Fact] + public void Object_satisfied_against_null_throws() + { + // Arrange + var someObject = new object(); + + // Act + Action act = () => someObject.Should().Satisfy(null); + + // Assert + act.Should().Throw() + .WithMessage("Cannot verify an object against a inspector.*"); + } + + [Fact] + public void Typed_object_satisfying_inspector_does_not_throw() + { + // Arrange + var personDto = new PersonDto + { + Name = "Name Nameson", + Birthdate = new DateTime(2000, 1, 1), + }; + + // Act / Assert + personDto.Should().Satisfy(o => o.Age.Should().BeGreaterThan(0)); + } + + [Fact] + public void Complex_typed_object_satisfying_inspector_does_not_throw() + { + // Arrange + var complexDto = new PersonAndAddressDto + { + Person = new PersonDto + { + Name = "Name Nameson", + Birthdate = new DateTime(2000, 1, 1), + }, + Address = new AddressDto + { + Street = "Named St.", + Number = "42", + City = "Nowhere", + Country = "Neverland", + PostalCode = "12345", + } + }; + + // Act / Assert + complexDto.Should().Satisfy(dto => + { + dto.Person.Should().Satisfy(person => + { + person.Name.Should().Be("Name Nameson"); + person.Age.Should().BeGreaterThan(0); + person.Birthdate.Should().Be(1.January(2000)); + }); + + dto.Address.Should().Satisfy(address => + { + address.Street.Should().Be("Named St."); + address.Number.Should().Be("42"); + address.City.Should().Be("Nowhere"); + address.Country.Should().Be("Neverland"); + address.PostalCode.Should().Be("12345"); + }); + }); + } + + [Fact] + public void Typed_object_not_satisfying_inspector_throws() + { + // Arrange + var personDto = new PersonDto + { + Name = "Name Nameson", + Birthdate = new DateTime(2000, 1, 1), + }; + + // Act + Action act = () => personDto.Should().Satisfy(d => + { + d.Name.Should().Be("Someone Else"); + d.Age.Should().BeLessThan(20); + d.Birthdate.Should().BeAfter(1.January(2001)); + }); + + // Assert + act.Should().Throw().WithMessage( + $""" + Expected {nameof(personDto)} to match inspector, but the inspector was not satisfied: + *Expected d.Name* + *Expected d.Age* + *Expected d.Birthdate* + """); + } + + [Fact] + public void Complex_typed_object_not_satisfying_inspector_throws() + { + // Arrange + var complexDto = new PersonAndAddressDto + { + Person = new PersonDto + { + Name = "Buford Howard Tannen", + Birthdate = new DateTime(1937, 3, 26), + }, + Address = new AddressDto + { + Street = "Mason Street", + Number = "1809", + City = "Hill Valley", + Country = "United States", + PostalCode = "CA 91905", + }, + }; + + // Act + Action act = () => complexDto.Should().Satisfy(dto => + { + dto.Person.Should().Satisfy(person => + { + person.Name.Should().Be("Biff Tannen"); + person.Age.Should().Be(48); + person.Birthdate.Should().Be(26.March(1937)); + }); + + dto.Address.Should().Satisfy(address => + { + address.Street.Should().Be("Mason Street"); + address.Number.Should().Be("1809"); + address.City.Should().Be("Hill Valley, San Diego County, California"); + address.Country.Should().Be("United States"); + address.PostalCode.Should().Be("CA 91905"); + }); + }); + + // Assert + act.Should().Throw().WithMessage( + $""" + Expected {nameof(complexDto)} to match inspector, but the inspector was not satisfied: + *Expected dto.Person to match inspector* + *Expected person.Name* + *Expected dto.Address to match inspector* + *Expected address.City* + """); + } + + [Fact] + public void Typed_object_satisfied_against_incorrect_type_throws() + { + // Arrange + var personDto = new PersonDto(); + + // Act + Action act = () => personDto.Should().Satisfy(dto => dto.Should().NotBeNull()); + + // Assert + act.Should().Throw() + .WithMessage( + $"Expected {nameof(personDto)} to be assignable to {typeof(AddressDto)}, but {typeof(PersonDto)} is not."); + } + + [Fact] + public void Sub_class_satisfied_against_base_class_does_not_throw() + { + // Arrange + var subClass = new SubClass + { + Number = 42, + Date = new DateTime(2021, 1, 1), + Text = "Some text" + }; + + // Act / Assert + subClass.Should().Satisfy(x => + { + x.Number.Should().Be(42); + x.Date.Should().Be(1.January(2021)); + }); + } + + [Fact] + public void Base_class_satisfied_against_sub_class_throws() + { + // Arrange + var baseClass = new BaseClass + { + Number = 42, + Date = new DateTime(2021, 1, 1), + }; + + // Act + Action act = () => baseClass.Should().Satisfy(x => + { + x.Number.Should().Be(42); + x.Date.Should().Be(1.January(2021)); + x.Text.Should().Be("Some text"); + }); + + // Assert + act.Should().Throw() + .WithMessage( + $"Expected {nameof(baseClass)} to be assignable to {typeof(SubClass)}, but {typeof(BaseClass)} is not."); + } + + [Fact] + public void Nested_assertion_on_null_throws() + { + // Arrange + var complexDto = new PersonAndAddressDto + { + Person = new PersonDto + { + Name = "Buford Howard Tannen", + }, + Address = null, + }; + + // Act + Action act = () => complexDto.Should().Satisfy(dto => + { + dto.Person.Name.Should().Be("Buford Howard Tannen"); + dto.Address.Should().Satisfy(address => address.City.Should().Be("Hill Valley")); + }); + + // Assert + act.Should().Throw() + .WithMessage( + $""" + Expected {nameof(complexDto)} to match inspector, but the inspector was not satisfied: + *Expected dto.Address to be assignable to {typeof(AddressDto)}, but found . + """); + } + + [Fact] + public void Using_nested_assertion_scope() + { + // Arrange + var complexDto = new PersonAndAddressDto + { + Person = new PersonDto + { + Name = "Buford Howard Tannen", + }, + Address = null, + }; + + // Act + Action act = () => complexDto.Should().Satisfy(dto => + { + dto.Person.Name.Should().Be("Buford Howard Tannen"); + + using (new AssertionScope()) + { + dto.Address.Should().Satisfy(address => address.City.Should().Be("Hill Valley")); + } + }); + + // Assert + act.Should().Throw() + .WithMessage( + $""" + Expected {nameof(complexDto)} to match inspector, but the inspector was not satisfied: + *Expected dto.Address to be assignable to {typeof(AddressDto)}, but found . + """); + } + + [Fact] + public void Using_assertion_scope_with_null_subject() + { + // Arrange + object subject = null; + + // Act + Action act = () => + { + using (new AssertionScope()) + { + subject.Should().Satisfy(x => x.Should().NotBeNull()); + } + }; + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be assignable to System.Object, but found ."); + } + + [Fact] + public void Using_assertion_scope_with_subject_satisfied_against_incorrect_type_throws() + { + // Arrange + var personDto = new PersonDto(); + + // Act + Action act = () => + { + using (new AssertionScope()) + { + personDto.Should().Satisfy(dto => dto.Should().NotBeNull()); + } + }; + + // Assert + act.Should().Throw() + .WithMessage( + $"Expected {nameof(personDto)} to be assignable to {typeof(AddressDto)}, but {typeof(PersonDto)} is not."); + } + } + + private class PersonDto + { + public string Name { get; init; } + + public DateTime Birthdate { get; init; } + + public int Age => DateTime.UtcNow.Subtract(Birthdate).Days / 365; + } + + private class PersonAndAddressDto + { + public PersonDto Person { get; init; } + + public AddressDto Address { get; init; } + } + + private class AddressDto + { + public string Street { get; init; } + + public string Number { get; init; } + + public string City { get; init; } + + public string PostalCode { get; init; } + + public string Country { get; init; } + } + + private class BaseClass + { + public int Number { get; init; } + + public DateTime Date { get; init; } + } + + private sealed class SubClass : BaseClass + { + public string Text { get; init; } + } +} diff --git a/Tests/FluentAssertions.Specs/Primitives/ReferenceTypeAssertionsSpecs.cs b/Tests/FluentAssertions.Specs/Primitives/ReferenceTypeAssertionsSpecs.cs index 1f2369981a..4bf90fff87 100644 --- a/Tests/FluentAssertions.Specs/Primitives/ReferenceTypeAssertionsSpecs.cs +++ b/Tests/FluentAssertions.Specs/Primitives/ReferenceTypeAssertionsSpecs.cs @@ -8,7 +8,7 @@ namespace FluentAssertions.Specs.Primitives; -public class ReferenceTypeAssertionsSpecs +public partial class ReferenceTypeAssertionsSpecs { [Fact] public void When_the_same_objects_are_expected_to_be_the_same_it_should_not_fail() @@ -43,11 +43,9 @@ 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,8 +59,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 { Statement = "goodbye" @@ -454,7 +451,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 e208d68d20..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 @@ -357,6 +358,16 @@ public void When_asserting_value_to_be_greater_than_or_equal_to_same_value_it_sh twoSeconds.Should().BeGreaterThanOrEqualTo(twoSeconds); } + [Fact] + public void Chaining_after_one_assertion_1() + { + // Arrange + var twoSeconds = 2.Seconds(); + + // Act / Assert + twoSeconds.Should().BeGreaterThanOrEqualTo(twoSeconds).And.Be(2.Seconds()); + } + [Fact] public void When_asserting_value_to_be_greater_than_or_equal_to_greater_value_it_should_fail() { @@ -464,6 +475,17 @@ public void When_asserting_value_to_be_less_than_or_equal_to_greater_value_it_sh actual.Should().BeLessThanOrEqualTo(greater); } + [Fact] + public void Chaining_after_one_assertion_2() + { + // Arrange + TimeSpan actual = 1.Seconds(); + TimeSpan greater = 2.Seconds(); + + // Act / Assert + actual.Should().BeLessThanOrEqualTo(greater).And.Be(1.Seconds()); + } + [Fact] public void When_asserting_null_value_to_be_less_than_or_equal_to_other_value_it_should_fail() { diff --git a/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.Be.cs b/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.Be.cs index 30de5f6751..6b158cc2b3 100644 --- a/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.Be.cs +++ b/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.Be.cs @@ -173,8 +173,13 @@ public void When_two_strings_differ_and_one_of_them_is_long_it_should_display_bo Action act = () => "1234567890".Should().Be("0987654321"); // Assert - act.Should().Throw().WithMessage( - "Expected string to be*\"0987654321\", but*\"1234567890\" differs near \"123\" (index 0)."); + act.Should().Throw().WithMessage(""" + Expected string to be the same string, but they differ at index 0: + ↓ (actual) + "1234567890" + "0987654321" + ↑ (expected). + """); } [Fact] @@ -184,8 +189,167 @@ public void When_two_strings_differ_and_one_of_them_is_multiline_it_should_displ Action act = () => "A\r\nB".Should().Be("A\r\nC"); // Assert - act.Should().Throw().WithMessage( - "Expected string to be \r\n\"A\\r\\nC\", but \r\n\"A\\r\\nB\" differs near \"B\" (index 3)."); + act.Should().Throw().Which.Message.Should().Be(""" + Expected string to be the same string, but they differ on line 2 and column 1 (index 3): + ↓ (actual) + "A\r\nB" + "A\r\nC" + ↑ (expected). + """); + } + + [Fact] + public void Use_arrows_for_text_longer_than_8_characters() + { + const string subject = "this is a long text that differs in between two words"; + const string expected = "this is a long text which differs in between two words"; + + // Act + Action act = () => subject.Should().Be(expected, "because we use arrows now"); + + // Assert + act.Should().Throw().WithMessage(""" + Expected subject to be the same string because we use arrows now, but they differ at index 20: + ↓ (actual) + "…is a long text that…" + "…is a long text which…" + ↑ (expected). + """); + } + + [Fact] + public void Only_add_ellipsis_for_long_text() + { + const string subject = "this is a long text that differs"; + const string expected = "this was too short"; + + // Act + Action act = () => subject.Should().Be(expected, "because we use arrows now"); + + // Assert + act.Should().Throw().WithMessage(""" + Expected subject to be the same string because we use arrows now, but they differ at index 5: + ↓ (actual) + "this is a long text that…" + "this was too short" + ↑ (expected). + """); + } + + [Theory] + [InlineData("ThisIsUsedTo Check a difference after 5 characters")] + [InlineData("ThisIsUsedTo CheckADifferenc e after 15 characters")] + public void Will_look_for_a_word_boundary_between_5_and_15_characters_before_the_mismatching_index_to_highlight_the_mismatch(string expected) + { + const string subject = "ThisIsUsedTo CheckADifferenceInThe WordBoundaryAlgorithm"; + + // Act + Action act = () => subject.Should().Be(expected); + + // Assert + act.Should().Throw().WithMessage("*\"…CheckADifferenceInThe*"); + } + + [Theory] + [InlineData("ThisIsUsedTo Chec k a difference after 4 characters", "\"…sedTo CheckADifferen")] + [InlineData("ThisIsUsedTo CheckADifference after 16 characters", "\"…Difference")] + public void Will_fallback_to_10_characters_if_no_word_boundary_can_be_found_before_the_mismatching_index( + string expected, string expectedMessagePart) + { + const string subject = "ThisIsUsedTo CheckADifferenceInThe WordBoundaryAlgorithm"; + + // Act + Action act = () => subject.Should().Be(expected); + + // Assert + act.Should().Throw().WithMessage($"*{expectedMessagePart}*"); + } + + [Theory] + [InlineData("ThisLongTextIsUsedToCheckADifferenceAtTheEnd after 10 + 5 characters")] + [InlineData("ThisLongTextIsUsedToCheckADifferen after 10 + 15 characters")] + public void Will_look_for_a_word_boundary_between_15_and_25_characters_after_the_mismatching_index_to_highlight_the_mismatch(string expected) + { + const string subject = "ThisLongTextIsUsedToCheckADifferenceAtTheEndOfThe WordBoundaryAlgorithm"; + + // Act + Action act = () => subject.Should().Be(expected); + + // Assert + act.Should().Throw().WithMessage("*AtTheEndOfThe…\"*"); + } + + [Fact] + public void An_empty_string_is_always_shorter_than_a_long_text() + { + // Act + Action act = () => "".Should().Be("ThisIsALongText"); + + // Assert + act.Should().Throw().WithMessage("*length*15*differs*"); + } + + [Fact] + public void A_mismatch_below_index_11_includes_all_text_preceding_the_index_in_the_failure() + { + // Act + Action act = () => "This is a long text".Should().Be("This is a text that differs at index 10"); + + // Assert + act.Should().Throw().WithMessage("*\"This is a long*"); + } + + [Theory] + [InlineData("ThisLongTextIsUsedToCheckADifferenceAtTheEndO after 10 + 4 characters", "eAtTheEndOfThe WordB…\"")] + [InlineData("ThisLongTextIsUsedToCheckADiffere after 10 + 16 characters", "ckADifferenceAtTheEn…\"")] + public void Will_fallback_to_20_characters_if_no_word_boundary_can_be_found_after_the_mismatching_index( + string expected, string expectedMessagePart) + { + const string subject = "ThisLongTextIsUsedToCheckADifferenceAtTheEndOfThe WordBoundaryAlgorithm"; + + // Act + Action act = () => subject.Should().Be(expected); + + // Assert + act.Should().Throw().WithMessage($"*{expectedMessagePart}*"); + } + + [Fact] + public void Mismatches_in_multiline_text_includes_the_line_number() + { + var expectedIndex = 100 + (4 * Environment.NewLine.Length); + + var subject = """ + @startuml + Alice -> Bob : Authentication Request + Bob --> Alice : Authentication Response + + Alice -> Bob : Another authentication Request + Alice <-- Bob : Another authentication Response + @enduml + """; + + var expected = """ + @startuml + Alice -> Bob : Authentication Request + Bob --> Alice : Authentication Response + + Alice -> Bob : Invalid authentication Request + Alice <-- Bob : Another authentication Response + @enduml + """; + + // Act + Action act = () => subject.Should().Be(expected); + + // Assert + act.Should().Throw().WithMessage($""" + Expected subject to be the same string, but they differ on line 5 and column 16 (index {expectedIndex}): + ↓ (actual) + "…-> Bob : Another…" + "…-> Bob : Invalid…" + ↑ (expected). + """); } } diff --git a/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.BeEquivalentTo.cs b/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.BeEquivalentTo.cs index 5bdf8fbe81..224ba27749 100644 --- a/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.BeEquivalentTo.cs +++ b/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.BeEquivalentTo.cs @@ -12,6 +12,77 @@ public partial class StringAssertionSpecs { public class BeEquivalentTo { + [Fact] + public void Succeed_for_different_strings_using_custom_matching_comparer() + { + // Arrange + var comparer = new AlwaysMatchingEqualityComparer(); + string actual = "ABC"; + string expect = "XYZ"; + + // Act / Assert + actual.Should().BeEquivalentTo(expect, o => o.Using(comparer)); + } + + [Fact] + public void Fail_for_same_strings_using_custom_not_matching_comparer() + { + // Arrange + var comparer = new NeverMatchingEqualityComparer(); + string actual = "ABC"; + string expect = "ABC"; + + // Act + Action act = () => actual.Should().BeEquivalentTo(expect, o => o.Using(comparer)); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Can_ignore_casing_while_comparing_strings_to_be_equivalent() + { + // Arrange + string actual = "test"; + string expect = "TEST"; + + // Act / Assert + actual.Should().BeEquivalentTo(expect, o => o.IgnoringCase()); + } + + [Fact] + public void Can_ignore_leading_whitespace_while_comparing_strings_to_be_equivalent() + { + // Arrange + string actual = " test"; + string expect = "test"; + + // Act / Assert + actual.Should().BeEquivalentTo(expect, o => o.IgnoringLeadingWhitespace()); + } + + [Fact] + public void Can_ignore_trailing_whitespace_while_comparing_strings_to_be_equivalent() + { + // Arrange + string actual = "test "; + string expect = "test"; + + // Act / Assert + actual.Should().BeEquivalentTo(expect, o => o.IgnoringTrailingWhitespace()); + } + + [Fact] + public void Can_ignore_newline_style_while_comparing_strings_to_be_equivalent() + { + // Arrange + string actual = "A\nB\r\nC"; + string expect = "A\r\nB\nC"; + + // Act / Assert + actual.Should().BeEquivalentTo(expect, o => o.IgnoringNewlineStyle()); + } + [Fact] public void When_strings_are_the_same_while_ignoring_case_it_should_not_throw() { @@ -108,6 +179,89 @@ public void public class NotBeEquivalentTo { + [Fact] + public void Succeed_for_same_strings_using_custom_not_matching_comparer() + { + // Arrange + var comparer = new NeverMatchingEqualityComparer(); + string actual = "ABC"; + string expect = "ABC"; + + // Act / Assert + actual.Should().NotBeEquivalentTo(expect, o => o.Using(comparer)); + } + + [Fact] + public void Fail_for_different_strings_using_custom_matching_comparer() + { + // Arrange + var comparer = new AlwaysMatchingEqualityComparer(); + string actual = "ABC"; + string expect = "XYZ"; + + // Act + Action act = () => actual.Should().NotBeEquivalentTo(expect, o => o.Using(comparer)); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Can_ignore_casing_while_comparing_strings_to_not_be_equivalent() + { + // Arrange + string actual = "test"; + string expect = "TEST"; + + // Act + Action act = () => actual.Should().NotBeEquivalentTo(expect, o => o.IgnoringCase()); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Can_ignore_leading_whitespace_while_comparing_strings_to_not_be_equivalent() + { + // Arrange + string actual = " test"; + string expect = "test"; + + // Act + Action act = () => actual.Should().NotBeEquivalentTo(expect, o => o.IgnoringLeadingWhitespace()); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Can_ignore_trailing_whitespace_while_comparing_strings_to_not_be_equivalent() + { + // Arrange + string actual = "test "; + string expect = "test"; + + // Act + Action act = () => actual.Should().NotBeEquivalentTo(expect, o => o.IgnoringTrailingWhitespace()); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Can_ignore_newline_style_while_comparing_strings_to_not_be_equivalent() + { + // Arrange + string actual = "\rA\nB\r\nC\n"; + string expect = "\nA\r\nB\nC\r"; + + // Act + Action act = () => actual.Should().NotBeEquivalentTo(expect, o => o.IgnoringNewlineStyle()); + + // Assert + act.Should().Throw(); + } + [Fact] public void When_strings_are_the_same_while_ignoring_case_it_should_throw() { diff --git a/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.BeLowerCased.cs b/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.BeLowerCased.cs index 016b67fc82..38c1ac56d6 100644 --- a/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.BeLowerCased.cs +++ b/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.BeLowerCased.cs @@ -12,7 +12,7 @@ public partial class StringAssertionSpecs public class BeLowerCased { [Fact] - public void When_a_lower_string_is_supposed_to_be_lower_it_should_succeed() + public void Lower_case_characters_are_okay() { // Arrange string actual = "abc"; @@ -22,7 +22,7 @@ public void When_a_lower_string_is_supposed_to_be_lower_it_should_succeed() } [Fact] - public void When_an_empty_string_is_supposed_to_be_lower_it_should_succeed() + public void The_empty_string_is_okay() { // Arrange string actual = ""; @@ -32,7 +32,7 @@ public void When_an_empty_string_is_supposed_to_be_lower_it_should_succeed() } [Fact] - public void When_a_non_lower_string_is_supposed_to_be_lower_it_should_fail() + public void Upper_case_characters_are_not_okay() { // Arrange string actual = "ABC"; @@ -45,10 +45,10 @@ public void When_a_non_lower_string_is_supposed_to_be_lower_it_should_fail() } [Fact] - public void When_a_lower_case_string_with_numbers_is_supposed_to_be_in_lower_case_only_it_should_throw() + public void A_mixed_case_string_is_not_okay() { // Arrange - string actual = "a1"; + string actual = "AbC"; // Act Action act = () => actual.Should().BeLowerCased(); @@ -58,7 +58,27 @@ public void When_a_lower_case_string_with_numbers_is_supposed_to_be_in_lower_cas } [Fact] - public void When_a_non_lower_string_is_supposed_to_be_lower_it_should_fail_with_descriptive_message() + public void Lower_case_and_caseless_characters_are_okay() + { + // Arrange + string actual = "a1!"; + + // Act / Assert + actual.Should().BeLowerCased(); + } + + [Fact] + public void Caseless_characters_are_okay() + { + // Arrange + string actual = "1!漢字"; + + // Act / Assert + actual.Should().BeLowerCased(); + } + + [Fact] + public void The_assertion_fails_with_a_descriptive_message() { // Arrange string actual = "ABC"; @@ -68,11 +88,11 @@ public void When_a_non_lower_string_is_supposed_to_be_lower_it_should_fail_with_ // Assert act.Should().Throw().WithMessage( - "Expected all characters in actual to be lower cased because we want to test the failure message, but found \"ABC\"."); + "Expected all alphabetic characters in actual to be lower cased because we want to test the failure message, but found \"ABC\"."); } [Fact] - public void When_checking_for_a_lower_string_and_it_is_null_it_should_throw() + public void The_null_string_is_not_okay() { // Arrange string nullString = null; @@ -82,24 +102,24 @@ public void When_checking_for_a_lower_string_and_it_is_null_it_should_throw() // Assert act.Should().Throw().WithMessage( - "Expected all characters in nullString to be lower cased because strings should never be null, but found ."); + "Expected all alphabetic characters in nullString to be lower cased because strings should never be null, but found ."); } } public class NotBeLowerCased { [Fact] - public void When_a_non_lower_string_is_supposed_to_be_upper_it_should_succeed() + public void A_mixed_case_string_is_okay() { // Arrange - string actual = "ABC"; + string actual = "AbC"; // Act / Assert actual.Should().NotBeLowerCased(); } [Fact] - public void When_a_null_string_is_not_supposed_to_be_lower_it_should_succeed() + public void The_null_string_is_okay() { // Arrange string actual = null; @@ -109,7 +129,17 @@ public void When_a_null_string_is_not_supposed_to_be_lower_it_should_succeed() } [Fact] - public void When_a_lower_string_is_supposed_to_be_upper_it_should_throw() + public void The_empty_string_is_okay() + { + // Arrange + string actual = ""; + + // Act / Assert + actual.Should().NotBeLowerCased(); + } + + [Fact] + public void A_lower_case_string_is_not_okay() { // Arrange string actual = "abc"; @@ -122,17 +152,40 @@ public void When_a_lower_string_is_supposed_to_be_upper_it_should_throw() } [Fact] - public void When_a_lower_case_string_with_numbers_is_not_supposed_to_be_in_lower_case_only_it_should_succeed() + public void Lower_case_characters_with_upper_case_characters_are_okay() + { + // Arrange + string actual = "Ab1!"; + + // Act / Assert + actual.Should().NotBeLowerCased(); + } + + [Fact] + public void All_cased_characters_being_lower_case_is_not_okay() + { + // Arrange + string actual = "a1b!"; + + // Act + Action act = () => actual.Should().NotBeLowerCased(); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Caseless_characters_are_okay() { // Arrange - string actual = "A1"; + string actual = "1!漢字"; // Act / Assert actual.Should().NotBeLowerCased(); } [Fact] - public void When_a_lower_string_is_not_supposed_to_be_lower_it_should_fail_with_descriptive_message() + public void The_assertion_fails_with_a_descriptive_message() { // Arrange string actual = "abc"; @@ -142,7 +195,7 @@ public void When_a_lower_string_is_not_supposed_to_be_lower_it_should_fail_with_ // Assert act.Should().Throw().WithMessage( - "Did not expect any characters in actual to be lower cased because we want to test the failure message."); + "Expected some characters in actual to be upper-case because we want to test the failure message."); } } } diff --git a/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.BeOneOf.cs b/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.BeOneOf.cs index 39d7e24c2a..7cf04c3d4b 100644 --- a/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.BeOneOf.cs +++ b/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.BeOneOf.cs @@ -32,7 +32,7 @@ public void When_a_value_is_not_one_of_the_specified_values_it_should_throw_with string value = "abc"; // Act - Action action = () => value.Should().BeOneOf(new[] { "def", "xyz" }, "because those are the valid values"); + Action action = () => value.Should().BeOneOf(["def", "xyz"], "because those are the valid values"); // Assert action.Should().Throw() diff --git a/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.BeUpperCased.cs b/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.BeUpperCased.cs index 3502f1728f..7b16655243 100644 --- a/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.BeUpperCased.cs +++ b/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.BeUpperCased.cs @@ -12,7 +12,7 @@ public partial class StringAssertionSpecs public class BeUpperCased { [Fact] - public void When_an_upper_case_string_is_supposed_to_be_in_upper_case_only_it_should_not_throw() + public void Upper_case_characters_are_okay() { // Arrange string actual = "ABC"; @@ -22,7 +22,17 @@ public void When_an_upper_case_string_is_supposed_to_be_in_upper_case_only_it_sh } [Fact] - public void When_a_non_upper_string_is_supposed_to_be_upper_it_should_throw() + public void The_empty_string_is_okay() + { + // Arrange + string actual = ""; + + // Act / Assert + actual.Should().BeUpperCased(); + } + + [Fact] + public void A_lower_case_string_is_not_okay() { // Arrange string actual = "abc"; @@ -35,20 +45,27 @@ public void When_a_non_upper_string_is_supposed_to_be_upper_it_should_throw() } [Fact] - public void When_an_upper_case_string_with_numbers_is_supposed_to_be_in_upper_case_only_it_should_throw() + public void Upper_case_and_caseless_characters_are_ok() { // Arrange - string actual = "A1"; + string actual = "A1!"; - // Act - Action act = () => actual.Should().BeUpperCased(); + // Act / Assert + actual.Should().BeUpperCased(); + } - // Assert - act.Should().Throw(); + [Fact] + public void Caseless_characters_are_okay() + { + // Arrange + string actual = "1!漢字"; + + // Act / Assert + actual.Should().BeUpperCased(); } [Fact] - public void When_a_non_upper_string_is_supposed_to_be_upper_it_should_fail_with_descriptive_message() + public void The_assertion_fails_with_a_descriptive_message() { // Arrange string actual = "abc"; @@ -58,11 +75,11 @@ public void When_a_non_upper_string_is_supposed_to_be_upper_it_should_fail_with_ // Assert act.Should().Throw().WithMessage( - @"Expected all characters in actual to be upper cased because we want to test the failure message, but found ""abc""."); + "Expected all alphabetic characters in actual to be upper-case because we want to test the failure message, but found \"abc\"."); } [Fact] - public void When_checking_for_an_upper_string_and_it_is_null_it_should_throw() + public void The_null_string_is_not_okay() { // Arrange string nullString = null; @@ -72,24 +89,24 @@ public void When_checking_for_an_upper_string_and_it_is_null_it_should_throw() // Assert act.Should().Throw().WithMessage( - "Expected all characters in nullString to be upper cased because strings should never be null, but found ."); + "Expected all alphabetic characters in nullString to be upper-case because strings should never be null, but found ."); } } public class NotBeUpperCased { [Fact] - public void When_a_non_upper_string_is_supposed_to_be_non_upper_it_should_succeed() + public void A_mixed_case_string_is_okay() { // Arrange - string actual = "abc"; + string actual = "aBc"; // Act / Assert actual.Should().NotBeUpperCased(); } [Fact] - public void When_a_null_string_is_not_supposed_to_be_upper_it_should_succeed() + public void The_null_string_is_okay() { // Arrange string actual = null; @@ -99,7 +116,17 @@ public void When_a_null_string_is_not_supposed_to_be_upper_it_should_succeed() } [Fact] - public void When_an_upper_string_is_not_supposed_to_be_upper_it_should_throw() + public void The_empty_string_is_okay() + { + // Arrange + string actual = ""; + + // Act / Assert + actual.Should().NotBeUpperCased(); + } + + [Fact] + public void A_string_of_all_upper_case_characters_is_not_okay() { // Arrange string actual = "ABC"; @@ -112,17 +139,40 @@ public void When_an_upper_string_is_not_supposed_to_be_upper_it_should_throw() } [Fact] - public void When_an_upper_case_string_with_numbers_is_not_supposed_to_be_in_upper_case_only_it_should_succeed() + public void Upper_case_characters_with_lower_case_characters_are_okay() + { + // Arrange + string actual = "Ab1!"; + + // Act / Assert + actual.Should().NotBeUpperCased(); + } + + [Fact] + public void All_cased_characters_being_upper_case_is_not_okay() + { + // Arrange + string actual = "A1B!"; + + // Act + Action act = () => actual.Should().NotBeUpperCased(); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Caseless_characters_are_okay() { // Arrange - string actual = "a1"; + string actual = "1!漢字"; // Act / Assert actual.Should().NotBeUpperCased(); } [Fact] - public void When_an_upper_string_is_not_supposed_to_be_upper_it_should_fail_with_descriptive_message() + public void The_assertion_fails_with_a_descriptive_message() { // Arrange string actual = "ABC"; @@ -132,7 +182,7 @@ public void When_an_upper_string_is_not_supposed_to_be_upper_it_should_fail_with // Assert act.Should().Throw().WithMessage( - "Did not expect any characters in actual to be upper cased because we want to test the failure message."); + "Expected some characters in actual to be lower-case because we want to test the failure message."); } } } diff --git a/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.ContainAll.cs b/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.ContainAll.cs index 1bf0a1b4a4..06721878ec 100644 --- a/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.ContainAll.cs +++ b/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.ContainAll.cs @@ -103,7 +103,7 @@ public void var testString = $"{red} {green}"; // Act - Action act = () => testString.Should().ContainAll(new[] { yellow, blue }, "some {0} reason", "special"); + Action act = () => testString.Should().ContainAll([yellow, blue], "some {0} reason", "special"); // Assert act @@ -187,7 +187,7 @@ public void When_exclusion_of_all_strings_is_asserted_with_reason_and_assertion_ var testString = $"{red} {green} {yellow}"; // Act - Action act = () => testString.Should().NotContainAll(new[] { red, green, yellow }, "some {0} reason", "special"); + Action act = () => testString.Should().NotContainAll([red, green, yellow], "some {0} reason", "special"); // Assert act diff --git a/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.ContainAny.cs b/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.ContainAny.cs index 4fb6232cbb..c75b204e4e 100644 --- a/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.ContainAny.cs +++ b/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.ContainAny.cs @@ -121,7 +121,7 @@ public void var testString = $"{red} {green}"; // Act - Action act = () => testString.Should().ContainAny(new[] { blue, purple }, "some {0} reason", "special"); + Action act = () => testString.Should().ContainAny([blue, purple], "some {0} reason", "special"); // Assert act @@ -204,7 +204,7 @@ public void When_exclusion_of_any_strings_is_asserted_with_reason_and_assertion_ var testString = $"{red} {green} {yellow}"; // Act - Action act = () => testString.Should().NotContainAny(new[] { red }, "some {0} reason", "special"); + Action act = () => testString.Should().NotContainAny([red], "some {0} reason", "special"); // Assert act diff --git a/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.ContainEquivalentOf.cs b/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.ContainEquivalentOf.cs index 69c7796239..8bcf20ee55 100644 --- a/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.ContainEquivalentOf.cs +++ b/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.ContainEquivalentOf.cs @@ -11,6 +11,77 @@ public partial class StringAssertionSpecs { public class ContainEquivalentOf { + [Fact] + public void Succeed_for_different_strings_using_custom_matching_comparer() + { + // Arrange + var comparer = new AlwaysMatchingEqualityComparer(); + string actual = "ABC"; + string expect = "XYZ"; + + // Act / Assert + actual.Should().ContainEquivalentOf(expect, o => o.Using(comparer)); + } + + [Fact] + public void Fail_for_same_strings_using_custom_not_matching_comparer() + { + // Arrange + var comparer = new NeverMatchingEqualityComparer(); + string actual = "ABC"; + string expect = "ABC"; + + // Act + Action act = () => actual.Should().ContainEquivalentOf(expect, o => o.Using(comparer)); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Can_ignore_casing_while_checking_a_string_to_contain_another() + { + // Arrange + string actual = "this is a string containing test."; + string expect = "TEST"; + + // Act / Assert + actual.Should().ContainEquivalentOf(expect, o => o.IgnoringCase()); + } + + [Fact] + public void Can_ignore_leading_whitespace_while_checking_a_string_to_contain_another() + { + // Arrange + string actual = " this is a string containing test."; + string expect = "test"; + + // Act / Assert + actual.Should().ContainEquivalentOf(expect, o => o.IgnoringLeadingWhitespace()); + } + + [Fact] + public void Can_ignore_trailing_whitespace_while_checking_a_string_to_contain_another() + { + // Arrange + string actual = "this is a string containing test. "; + string expect = "test"; + + // Act / Assert + actual.Should().ContainEquivalentOf(expect, o => o.IgnoringTrailingWhitespace()); + } + + [Fact] + public void Can_ignore_newline_style_while_checking_a_string_to_contain_another() + { + // Arrange + string actual = "this is a string containing \rA\nB\r\nC.\n"; + string expect = "A\r\nB\nC"; + + // Act / Assert + actual.Should().ContainEquivalentOf(expect, o => o.IgnoringNewlineStyle()); + } + [InlineData("aa", "A")] [InlineData("aCCa", "acca")] [Theory] @@ -29,7 +100,7 @@ public void Should_fail_contain_equivalent_of_when_not_contains() // Assert act.Should().Throw() - .WithMessage("Expected string \"a\" to contain the equivalent of \"aa\"."); + .WithMessage("Expected string \"a\" to contain the equivalent of \"aa\" at least 1 time, but found it 0 times."); } [Fact] @@ -91,7 +162,7 @@ public void // Assert act.Should().Throw() .WithMessage( - "Expected * to contain equivalent of \"XyZ\" exactly 1 time because that is required, but found it 0 times."); + "Expected * to contain the equivalent of \"XyZ\" exactly 1 time because that is required, but found it 0 times."); } [Fact] @@ -123,7 +194,7 @@ public void // Assert act.Should().Throw() .WithMessage( - "Expected * \"abCDEBcDF\" to contain equivalent of \"Bcd\" exactly 3 times, but found it 2 times."); + "Expected * \"abCDEBcDF\" to contain the equivalent of \"Bcd\" exactly 3 times, but found it 2 times."); } [Fact] @@ -139,7 +210,7 @@ public void // Assert act.Should().Throw() - .WithMessage("Expected * \"abCDEf\" to contain equivalent of \"xyS\" exactly 1 time, but found it 0 times."); + .WithMessage("Expected * \"abCDEf\" to contain the equivalent of \"xyS\" exactly 1 time, but found it 0 times."); } [Fact] @@ -190,7 +261,7 @@ public void // Assert act.Should().Throw() - .WithMessage("Expected * \"abCDEBcDF\" to contain equivalent of \"Bcd\" at least 3 times, but found it 2 times."); + .WithMessage("Expected * \"abCDEBcDF\" to contain the equivalent of \"Bcd\" at least 3 times, but found it 2 times."); } [Fact] @@ -206,7 +277,7 @@ public void // Assert act.Should().Throw() - .WithMessage("Expected * \"abCDEf\" to contain equivalent of \"xyS\" at least 1 time, but found it 0 times."); + .WithMessage("Expected * \"abCDEf\" to contain the equivalent of \"xyS\" at least 1 time, but found it 0 times."); } [Fact] @@ -222,7 +293,7 @@ public void // Assert act.Should().Throw() - .WithMessage("Expected * to contain equivalent of \"XyZ\" at least 1 time, but found it 0 times."); + .WithMessage("Expected * to contain the equivalent of \"XyZ\" at least 1 time, but found it 0 times."); } } @@ -257,7 +328,7 @@ public void // Assert act.Should().Throw() .WithMessage( - "Expected * \"abCDEBcDF\" to contain equivalent of \"Bcd\" more than 2 times, but found it 2 times."); + "Expected * \"abCDEBcDF\" to contain the equivalent of \"Bcd\" more than 2 times, but found it 2 times."); } [Fact] @@ -273,7 +344,7 @@ public void // Assert act.Should().Throw() - .WithMessage("Expected * \"abCDEf\" to contain equivalent of \"xyS\" more than 1 time, but found it 0 times."); + .WithMessage("Expected * \"abCDEf\" to contain the equivalent of \"xyS\" more than 1 time, but found it 0 times."); } [Fact] @@ -289,7 +360,7 @@ public void // Assert act.Should().Throw() - .WithMessage("Expected * to contain equivalent of \"XyZ\" more than 1 time, but found it 0 times."); + .WithMessage("Expected * to contain the equivalent of \"XyZ\" more than 1 time, but found it 0 times."); } } @@ -323,7 +394,7 @@ public void // Assert act.Should().Throw() - .WithMessage("Expected * \"abCDEBcDF\" to contain equivalent of \"Bcd\" at most 1 time, but found it 2 times."); + .WithMessage("Expected * \"abCDEBcDF\" to contain the equivalent of \"Bcd\" at most 1 time, but found it 2 times."); } [Fact] @@ -388,7 +459,7 @@ public void // Assert act.Should().Throw() .WithMessage( - "Expected * \"abCDEBcDF\" to contain equivalent of \"Bcd\" less than 2 times, but found it 2 times."); + "Expected * \"abCDEBcDF\" to contain the equivalent of \"Bcd\" less than 2 times, but found it 2 times."); } [Fact] @@ -424,6 +495,89 @@ public void public class NotContainEquivalentOf { + [Fact] + public void Succeed_for_same_strings_using_custom_not_matching_comparer() + { + // Arrange + var comparer = new NeverMatchingEqualityComparer(); + string actual = "ABC"; + string expect = "ABC"; + + // Act / Assert + actual.Should().NotContainEquivalentOf(expect, o => o.Using(comparer)); + } + + [Fact] + public void Fail_for_different_strings_using_custom_matching_comparer() + { + // Arrange + var comparer = new AlwaysMatchingEqualityComparer(); + string actual = "ABC"; + string expect = "XYZ"; + + // Act + Action act = () => actual.Should().NotContainEquivalentOf(expect, o => o.Using(comparer)); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Can_ignore_casing_while_checking_a_string_to_not_contain_another() + { + // Arrange + string actual = "this is a string containing test."; + string expect = "TEST"; + + // Act + Action act = () => actual.Should().NotContainEquivalentOf(expect, o => o.IgnoringCase()); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Can_ignore_leading_whitespace_while_checking_a_string_to_not_contain_another() + { + // Arrange + string actual = " this is a string containing test."; + string expect = "test"; + + // Act + Action act = () => actual.Should().NotContainEquivalentOf(expect, o => o.IgnoringLeadingWhitespace()); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Can_ignore_trailing_whitespace_while_checking_a_string_to_not_contain_another() + { + // Arrange + string actual = "this is a string containing test. "; + string expect = "test"; + + // Act + Action act = () => actual.Should().NotContainEquivalentOf(expect, o => o.IgnoringTrailingWhitespace()); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Can_ignore_newline_style_while_checking_a_string_to_not_contain_another() + { + // Arrange + string actual = "this is a string containing \rA\nB\r\nC.\n"; + string expect = "\nA\r\nB\rC"; + + // Act + Action act = () => actual.Should().NotContainEquivalentOf(expect, o => o.IgnoringNewlineStyle()); + + // Assert + act.Should().Throw(); + } + [Fact] public void Should_fail_when_asserting_string_does_not_contain_equivalent_of_null() { @@ -433,7 +587,7 @@ public void Should_fail_when_asserting_string_does_not_contain_equivalent_of_nul // Assert act.Should().Throw() - .WithMessage("Did not expect string to contain equivalent of but found \"a\"."); + .WithMessage("Did not expect string to contain the equivalent of , but found \"a\"."); } [Fact] @@ -445,7 +599,7 @@ public void Should_fail_when_asserting_string_does_not_contain_equivalent_of_emp // Assert act.Should().Throw() - .WithMessage("Did not expect string to contain equivalent of \"\" but found \"a\"."); + .WithMessage("Did not expect string to contain the equivalent of \"\", but found \"a\"."); } [Fact] @@ -457,7 +611,7 @@ public void Should_fail_when_asserting_string_does_not_contain_equivalent_of_ano // Assert act.Should().Throw() - .WithMessage("Did not expect string to contain equivalent of \", worLD!\" but found \"Hello, world!\"."); + .WithMessage("Did not expect string to contain the equivalent of \", worLD!\" but found \"Hello, world!\"."); } [Fact] diff --git a/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.EndWith.cs b/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.EndWith.cs index 768baf7af0..2d92079989 100644 --- a/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.EndWith.cs +++ b/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.EndWith.cs @@ -46,7 +46,7 @@ public void When_string_does_not_end_with_expected_phrase_it_should_throw() // Assert act.Should().Throw().WithMessage( - "Expected string \"ABC\" to end with \"AB\" because it should."); + "Expected string to end with \"AB\" because it should, but \"ABC\" differs near \"ABC\" (index 0)."); } [Fact] @@ -83,6 +83,21 @@ public void When_string_ending_is_compared_with_string_that_is_longer_it_should_ "\"ABC\" is too short."); } + [Fact] + public void Correctly_stop_further_execution_when_inside_assertion_scope() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(); + "ABC".Should().EndWith("00ABC").And.EndWith("CBA00"); + }; + + // Assert + act.Should().Throw().WithMessage( + "*\"00ABC\"*"); + } + [Fact] public void When_string_ending_is_compared_and_actual_value_is_null_then_it_should_throw() { @@ -98,7 +113,7 @@ public void When_string_ending_is_compared_and_actual_value_is_null_then_it_shou // Assert act.Should().Throw().WithMessage( - "Expected someString to end with \"ABC\"."); + "Expected someString to end with \"ABC\", but found ."); } } @@ -130,7 +145,7 @@ public void When_asserting_string_does_not_end_with_a_value_but_it_does_it_shoul // Assert action.Should().Throw().WithMessage( - "Expected value \"ABC\" not to end with \"BC\" because of some reason."); + "Expected value not to end with \"BC\" because of some reason, but found \"ABC\"."); } [Fact] @@ -160,7 +175,7 @@ public void When_asserting_string_does_not_end_with_a_value_that_is_empty_it_sho // Assert action.Should().Throw().WithMessage( - "Expected value \"ABC\" not to end with \"\"."); + "Expected value not to end with \"\", but found \"ABC\"."); } [Fact] @@ -178,7 +193,7 @@ public void When_asserting_string_does_not_end_with_a_value_and_actual_value_is_ // Assert act.Should().Throw().WithMessage( - "Expected someString that does not end with \"ABC\"*some reason*, but found ."); + "Expected someString not to end with \"ABC\"*some reason*, but found ."); } } } diff --git a/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.EndWithEquivalentOf.cs b/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.EndWithEquivalentOf.cs index e4d9e3e247..d50012b9c6 100644 --- a/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.EndWithEquivalentOf.cs +++ b/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.EndWithEquivalentOf.cs @@ -10,8 +10,79 @@ namespace FluentAssertions.Specs.Primitives; /// public partial class StringAssertionSpecs { - public class EndWithEquivalent + public class EndWithEquivalentOf { + [Fact] + public void Succeed_for_different_strings_using_custom_matching_comparer() + { + // Arrange + var comparer = new AlwaysMatchingEqualityComparer(); + string actual = "ABC"; + string expect = "XYZ"; + + // Act / Assert + actual.Should().EndWithEquivalentOf(expect, o => o.Using(comparer)); + } + + [Fact] + public void Fail_for_same_strings_using_custom_not_matching_comparer() + { + // Arrange + var comparer = new NeverMatchingEqualityComparer(); + string actual = "ABC"; + string expect = "ABC"; + + // Act + Action act = () => actual.Should().EndWithEquivalentOf(expect, o => o.Using(comparer)); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Can_ignore_casing_while_checking_a_string_to_end_with_another() + { + // Arrange + string actual = "prefix for test"; + string expect = "TEST"; + + // Act / Assert + actual.Should().EndWithEquivalentOf(expect, o => o.IgnoringCase()); + } + + [Fact] + public void Can_ignore_leading_whitespace_while_checking_a_string_to_end_with_another() + { + // Arrange + string actual = " prefix for test"; + string expect = "test"; + + // Act / Assert + actual.Should().EndWithEquivalentOf(expect, o => o.IgnoringLeadingWhitespace()); + } + + [Fact] + public void Can_ignore_trailing_whitespace_while_checking_a_string_to_end_with_another() + { + // Arrange + string actual = "prefix for test "; + string expect = "test"; + + // Act / Assert + actual.Should().EndWithEquivalentOf(expect, o => o.IgnoringTrailingWhitespace()); + } + + [Fact] + public void Can_ignore_newline_style_while_checking_a_string_to_end_with_another() + { + // Arrange + string actual = "prefix for \rA\nB\r\nC"; + string expect = "A\r\nB\nC"; + + // Act / Assert + actual.Should().EndWithEquivalentOf(expect, o => o.IgnoringNewlineStyle()); + } + [Fact] public void When_suffix_of_string_differs_by_case_only_it_should_not_throw() { @@ -42,7 +113,7 @@ public void When_end_of_string_does_not_meet_equivalent_it_should_throw() // Assert act.Should().Throw().WithMessage( - "Expected string that ends with equivalent of \"ab\" because it should end, but found \"ABC\"."); + "Expected string to end with equivalent of \"ab\" because it should end, but \"ABC\" differs near \"ABC\" (index 0)."); } [Fact] @@ -94,12 +165,95 @@ public void When_string_ending_is_compared_with_equivalent_and_actual_value_is_n // Assert act.Should().Throw().WithMessage( - "Expected someString that ends with equivalent of \"abC\", but found ."); + "Expected someString to end with equivalent of \"abC\", but found ."); } } - public class NotEndWithEquivalent + public class NotEndWithEquivalentOf { + [Fact] + public void Succeed_for_same_strings_using_custom_not_matching_comparer() + { + // Arrange + var comparer = new NeverMatchingEqualityComparer(); + string actual = "ABC"; + string expect = "ABC"; + + // Act / Assert + actual.Should().NotEndWithEquivalentOf(expect, o => o.Using(comparer)); + } + + [Fact] + public void Fail_for_different_strings_using_custom_matching_comparer() + { + // Arrange + var comparer = new AlwaysMatchingEqualityComparer(); + string actual = "ABC"; + string expect = "XYZ"; + + // Act + Action act = () => actual.Should().NotEndWithEquivalentOf(expect, o => o.Using(comparer)); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Can_ignore_casing_while_checking_a_string_to_not_end_with_another() + { + // Arrange + string actual = "prefix for test"; + string expect = "TEST"; + + // Act + Action act = () => actual.Should().NotEndWithEquivalentOf(expect, o => o.IgnoringCase()); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Can_ignore_leading_whitespace_while_checking_a_string_to_not_end_with_another() + { + // Arrange + string actual = " prefix for test"; + string expect = "test"; + + // Act + Action act = () => actual.Should().NotEndWithEquivalentOf(expect, o => o.IgnoringLeadingWhitespace()); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Can_ignore_trailing_whitespace_while_checking_a_string_to_not_end_with_another() + { + // Arrange + string actual = "prefix for test "; + string expect = "test"; + + // Act + Action act = () => actual.Should().NotEndWithEquivalentOf(expect, o => o.IgnoringTrailingWhitespace()); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Can_ignore_newline_style_while_checking_a_string_to_not_end_with_another() + { + // Arrange + string actual = "prefix for \rA\nB\r\nC\n"; + string expect = "A\r\nB\nC\r"; + + // Act + Action act = () => actual.Should().NotEndWithEquivalentOf(expect, o => o.IgnoringNewlineStyle()); + + // Assert + act.Should().Throw(); + } + [Fact] public void When_asserting_string_does_not_end_with_equivalent_of_a_value_and_it_does_not_it_should_succeed() { @@ -127,7 +281,7 @@ public void // Assert action.Should().Throw().WithMessage( - "Expected value that does not end with equivalent of \"Bc\" because of some reason, but found \"ABC\"."); + "Expected value not to end with equivalent of \"Bc\" because of some reason, but found \"ABC\"."); } [Fact] @@ -157,7 +311,7 @@ public void When_asserting_string_does_not_end_with_equivalent_of_a_value_that_i // Assert action.Should().Throw().WithMessage( - "Expected value that does not end with equivalent of \"\", but found \"ABC\"."); + "Expected value not to end with equivalent of \"\", but found \"ABC\"."); } [Fact] @@ -175,7 +329,7 @@ public void When_asserting_string_does_not_end_with_equivalent_of_a_value_and_ac // Assert act.Should().Throw().WithMessage( - "Expected someString that does not end with equivalent of \"Abc\"*some reason*, but found ."); + "Expected someString not to end with equivalent of \"Abc\"*some reason*, but found ."); } } } diff --git a/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.MatchEquivalentOf.cs b/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.MatchEquivalentOf.cs index 1d3c3e9d88..deb4105708 100644 --- a/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.MatchEquivalentOf.cs +++ b/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.MatchEquivalentOf.cs @@ -11,6 +11,50 @@ public partial class StringAssertionSpecs { public class MatchEquivalentOf { + [Fact] + public void Can_ignore_casing_while_checking_a_string_to_match_another() + { + // Arrange + string actual = "test"; + string expect = "T*T"; + + // Act / Assert + actual.Should().MatchEquivalentOf(expect, o => o.IgnoringCase()); + } + + [Fact] + public void Can_ignore_leading_whitespace_while_checking_a_string_to_match_another() + { + // Arrange + string actual = " test"; + string expect = "t*t"; + + // Act / Assert + actual.Should().MatchEquivalentOf(expect, o => o.IgnoringLeadingWhitespace()); + } + + [Fact] + public void Can_ignore_trailing_whitespace_while_checking_a_string_to_match_another() + { + // Arrange + string actual = "test "; + string expect = "t*t"; + + // Act / Assert + actual.Should().MatchEquivalentOf(expect, o => o.IgnoringTrailingWhitespace()); + } + + [Fact] + public void Can_ignore_newline_style_while_checking_a_string_to_match_another() + { + // Arrange + string actual = "\rA\nB\r\nC\n"; + string expect = "\nA\r\n?\nC\r"; + + // Act / Assert + actual.Should().MatchEquivalentOf(expect, o => o.IgnoringNewlineStyle()); + } + [Fact] public void When_a_string_does_not_match_the_equivalent_of_a_wildcard_pattern_it_should_throw() { @@ -86,6 +130,62 @@ public void When_a_string_is_matched_against_the_equivalent_of_an_empty_string_i public class NotMatchEquivalentOf { + [Fact] + public void Can_ignore_casing_while_checking_a_string_to_not_match_another() + { + // Arrange + string actual = "test"; + string expect = "T*T"; + + // Act + Action act = () => actual.Should().NotMatchEquivalentOf(expect, o => o.IgnoringCase()); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Can_ignore_leading_whitespace_while_checking_a_string_to_not_match_another() + { + // Arrange + string actual = " test"; + string expect = "t*t"; + + // Act + Action act = () => actual.Should().NotMatchEquivalentOf(expect, o => o.IgnoringLeadingWhitespace()); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Can_ignore_trailing_whitespace_while_checking_a_string_to_not_match_another() + { + // Arrange + string actual = "test "; + string expect = "t*t"; + + // Act + Action act = () => actual.Should().NotMatchEquivalentOf(expect, o => o.IgnoringTrailingWhitespace()); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Can_ignore_newline_style_while_checking_a_string_to_not_match_another() + { + // Arrange + string actual = "\rA\nB\r\nC\n"; + string expect = "\nA\r\n?\nC\r"; + + // Act + Action act = () => actual.Should().NotMatchEquivalentOf(expect, o => o.IgnoringNewlineStyle()); + + // Assert + act.Should().Throw(); + } + [Fact] public void When_a_string_is_not_equivalent_to_a_pattern_and_that_is_expected_it_should_not_throw() { @@ -160,5 +260,16 @@ public void "Cannot match string against an empty string. Provide a wildcard pattern or use the NotBeEmpty method.*") .WithParameterName("wildcardPattern"); } + + [Fact] + public void Does_not_treat_escaped_newlines_as_newlines() + { + // Arrange + string actual = "te\r\nst"; + string expect = "te\\r\\nst"; + + // Act / Assert + actual.Should().NotMatchEquivalentOf(expect); + } } } diff --git a/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.MatchRegex.cs b/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.MatchRegex.cs index a8527618b2..f39221f4f5 100644 --- a/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.MatchRegex.cs +++ b/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.MatchRegex.cs @@ -78,7 +78,9 @@ public void When_a_string_is_matched_against_an_invalid_regex_string_it_should_t { // Arrange string subject = "hello world!"; +#pragma warning disable RE0001 // Invalid regex pattern string invalidRegex = ".**"; // Use local variable for this invalid regex to avoid static R# analysis errors +#pragma warning restore RE0001 // Invalid regex pattern // Act Action act = () => subject.Should().MatchRegex(invalidRegex); @@ -93,7 +95,9 @@ public void When_a_string_is_matched_against_an_invalid_regex_string_it_should_o { // Arrange string subject = "hello world!"; +#pragma warning disable RE0001 // Invalid regex pattern string invalidRegex = ".**"; // Use local variable for this invalid regex to avoid static R# analysis errors +#pragma warning restore RE0001 // Invalid regex pattern // Act Action act = () => @@ -421,7 +425,9 @@ public void When_a_string_is_negatively_matched_against_an_invalid_regex_string_ { // Arrange string subject = "hello world!"; +#pragma warning disable RE0001 // Invalid regex pattern string invalidRegex = ".**"; // Use local variable for this invalid regex to avoid static R# analysis errors +#pragma warning restore RE0001 // Invalid regex pattern // Act Action act = () => subject.Should().NotMatchRegex(invalidRegex); @@ -436,7 +442,9 @@ public void When_a_string_is_negatively_matched_against_an_invalid_regex_string_ { // Arrange string subject = "hello world!"; +#pragma warning disable RE0001 // Invalid regex pattern string invalidRegex = ".**"; // Use local variable for this invalid regex to avoid static R# analysis errors +#pragma warning restore RE0001 // Invalid regex pattern // Act Action act = () => diff --git a/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.StartWith.cs b/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.StartWith.cs index 1ffccec4f7..48dc191efc 100644 --- a/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.StartWith.cs +++ b/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.StartWith.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics.CodeAnalysis; +using FluentAssertions.Execution; using Xunit; using Xunit.Sdk; @@ -18,12 +19,18 @@ public void When_asserting_string_starts_with_the_same_value_it_should_not_throw // Arrange string value = "ABC"; - // Act - Action action = () => - value.Should().StartWith("AB"); + // Act / Assert + value.Should().StartWith("AB"); + } - // Assert - action.Should().NotThrow(); + [Fact] + public void When_expected_string_is_the_same_value_it_should_not_throw() + { + // Arrange + string value = "ABC"; + + // Act / Assert + value.Should().StartWith(value); } [Fact] @@ -67,11 +74,8 @@ public void When_string_start_is_compared_with_null_it_should_throw() [Fact] public void When_string_start_is_compared_with_empty_string_it_should_not_throw() { - // Act - Action act = () => "ABC".Should().StartWith(""); - - // Assert - act.Should().NotThrow(); + // Act / Assert + "ABC".Should().StartWith(""); } [Fact] @@ -86,6 +90,22 @@ public void When_string_start_is_compared_with_string_that_is_longer_it_should_t "Expected string to start with \"ABCDEF\", but \"ABC\" is too short."); } + [Fact] + [SuppressMessage("ReSharper", "StringLiteralTypo")] + public void Correctly_stop_further_execution_when_inside_assertion_scope() + { + // Act + Action act = () => + { + using var _ = new AssertionScope(); + "ABC".Should().StartWith("ABCDEF").And.StartWith("FEDCBA"); + }; + + // Assert + act.Should().Throw().WithMessage( + "*\"ABCDEF\"*"); + } + [Fact] public void When_string_start_is_compared_and_actual_value_is_null_then_it_should_throw() { @@ -107,12 +127,8 @@ public void When_asserting_string_does_not_start_with_a_value_and_it_does_not_it // Arrange string value = "ABC"; - // Act - Action action = () => - value.Should().NotStartWith("DE"); - - // Assert - action.Should().NotThrow(); + // Act / Assert + value.Should().NotStartWith("DE"); } [Fact] @@ -127,7 +143,7 @@ public void When_asserting_string_does_not_start_with_a_value_but_it_does_it_sho // Assert action.Should().Throw().WithMessage( - "Expected value that does not start with \"AB\" because of some reason, but found \"ABC\"."); + "Expected value not to start with \"AB\" because of some reason, but found \"ABC\"."); } [Fact] @@ -157,7 +173,7 @@ public void When_asserting_string_does_not_start_with_a_value_that_is_empty_it_s // Assert action.Should().Throw().WithMessage( - "Expected value that does not start with \"\", but found \"ABC\"."); + "Expected value not to start with \"\", but found \"ABC\"."); } [Fact] @@ -169,7 +185,7 @@ public void When_asserting_string_does_not_start_with_a_value_and_actual_value_i // Assert act.Should().Throw().WithMessage( - "Expected someString that does not start with \"ABC\", but found ."); + "Expected someString not to start with \"ABC\", but found ."); } } } diff --git a/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.StartWithEquivalentOf.cs b/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.StartWithEquivalentOf.cs index 5a092d8785..1caf965987 100644 --- a/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.StartWithEquivalentOf.cs +++ b/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.StartWithEquivalentOf.cs @@ -10,8 +10,79 @@ namespace FluentAssertions.Specs.Primitives; /// public partial class StringAssertionSpecs { - public class StartWithEquivalent + public class StartWithEquivalentOf { + [Fact] + public void Succeed_for_different_strings_using_custom_matching_comparer() + { + // Arrange + var comparer = new AlwaysMatchingEqualityComparer(); + string actual = "ABC"; + string expect = "XYZ"; + + // Act / Assert + actual.Should().StartWithEquivalentOf(expect, o => o.Using(comparer)); + } + + [Fact] + public void Fail_for_same_strings_using_custom_not_matching_comparer() + { + // Arrange + var comparer = new NeverMatchingEqualityComparer(); + string actual = "ABC"; + string expect = "ABC"; + + // Act + Action act = () => actual.Should().StartWithEquivalentOf(expect, o => o.Using(comparer)); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Can_ignore_casing_while_checking_a_string_to_start_with_another() + { + // Arrange + string actual = "test with suffix"; + string expect = "TEST"; + + // Act / Assert + actual.Should().StartWithEquivalentOf(expect, o => o.IgnoringCase()); + } + + [Fact] + public void Can_ignore_leading_whitespace_while_checking_a_string_to_start_with_another() + { + // Arrange + string actual = " test with suffix"; + string expect = "test"; + + // Act / Assert + actual.Should().StartWithEquivalentOf(expect, o => o.IgnoringLeadingWhitespace()); + } + + [Fact] + public void Can_ignore_trailing_whitespace_while_checking_a_string_to_start_with_another() + { + // Arrange + string actual = "test with suffix "; + string expect = "test"; + + // Act / Assert + actual.Should().StartWithEquivalentOf(expect, o => o.IgnoringTrailingWhitespace()); + } + + [Fact] + public void Can_ignore_newline_style_while_checking_a_string_to_start_with_another() + { + // Arrange + string actual = "\rA\nB\r\nC\n with suffix"; + string expect = "\r\nA\rB\nC"; + + // Act / Assert + actual.Should().StartWithEquivalentOf(expect, o => o.IgnoringNewlineStyle()); + } + [Fact] public void When_start_of_string_differs_by_case_only_it_should_not_throw() { @@ -97,8 +168,91 @@ public void When_string_start_is_compared_with_equivalent_and_actual_value_is_nu } } - public class NotStartWithEquivalent + public class NotStartWithEquivalentOf { + [Fact] + public void Succeed_for_same_strings_using_custom_not_matching_comparer() + { + // Arrange + var comparer = new NeverMatchingEqualityComparer(); + string actual = "ABC"; + string expect = "ABC"; + + // Act / Assert + actual.Should().NotStartWithEquivalentOf(expect, o => o.Using(comparer)); + } + + [Fact] + public void Fail_for_different_strings_using_custom_matching_comparer() + { + // Arrange + var comparer = new AlwaysMatchingEqualityComparer(); + string actual = "ABC"; + string expect = "XYZ"; + + // Act + Action act = () => actual.Should().NotStartWithEquivalentOf(expect, o => o.Using(comparer)); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Can_ignore_casing_while_checking_a_string_to_not_start_with_another() + { + // Arrange + string actual = "test with suffix"; + string expect = "TEST"; + + // Act + Action act = () => actual.Should().NotStartWithEquivalentOf(expect, o => o.IgnoringCase()); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Can_ignore_leading_whitespace_while_checking_a_string_to_not_start_with_another() + { + // Arrange + string actual = " test with suffix"; + string expect = "test"; + + // Act + Action act = () => actual.Should().NotStartWithEquivalentOf(expect, o => o.IgnoringLeadingWhitespace()); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Can_ignore_trailing_whitespace_while_checking_a_string_to_not_start_with_another() + { + // Arrange + string actual = "test with suffix "; + string expect = "test"; + + // Act + Action act = () => actual.Should().NotStartWithEquivalentOf(expect, o => o.IgnoringTrailingWhitespace()); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Can_ignore_newline_style_while_checking_a_string_to_not_start_with_another() + { + // Arrange + string actual = "\rA\nB\r\nC\n with suffix"; + string expect = "\nA\r\nB\rC"; + + // Act + Action act = () => actual.Should().NotStartWithEquivalentOf(expect, o => o.IgnoringNewlineStyle()); + + // Assert + act.Should().Throw(); + } + [Fact] public void When_asserting_string_does_not_start_with_equivalent_of_a_value_and_it_does_not_it_should_succeed() { @@ -126,7 +280,7 @@ public void // Assert action.Should().Throw().WithMessage( - "Expected value that does not start with equivalent of \"aB\" because of some reason, but found \"ABC\"."); + "Expected value not to start with equivalent of \"aB\" because of some reason, but found \"ABC\"."); } [Fact] @@ -156,7 +310,7 @@ public void When_asserting_string_does_not_start_with_equivalent_of_a_value_that // Assert action.Should().Throw().WithMessage( - "Expected value that does not start with equivalent of \"\", but found \"ABC\"."); + "Expected value not to start with equivalent of \"\", but found \"ABC\"."); } [Fact] @@ -170,7 +324,7 @@ public void When_asserting_string_does_not_start_with_equivalent_of_a_value_and_ // Assert act.Should().Throw().WithMessage( - "Expected someString that does not start with equivalent of \"ABC\", but found ."); + "Expected someString not to start with equivalent of \"ABC\", but found ."); } } } diff --git a/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.cs index 5a51095fd0..6a66c2ecb4 100644 --- a/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Primitives/StringAssertionSpecs.cs @@ -1,4 +1,5 @@ -using System.Diagnostics.CodeAnalysis; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using Xunit; namespace FluentAssertions.Specs.Primitives; @@ -23,4 +24,30 @@ public void When_chaining_multiple_assertions_it_should_assert_all_conditions() .Contain(substring).And .HaveLength(length); } + + private sealed class AlwaysMatchingEqualityComparer : IEqualityComparer + { + public bool Equals(string x, string y) + { + return true; + } + + public int GetHashCode(string obj) + { + return obj.GetHashCode(); + } + } + + private sealed class NeverMatchingEqualityComparer : IEqualityComparer + { + public bool Equals(string x, string y) + { + return false; + } + + public int GetHashCode(string obj) + { + return obj.GetHashCode(); + } + } } 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/Primitives/TimeOnlyAssertionSpecs.Be.cs b/Tests/FluentAssertions.Specs/Primitives/TimeOnlyAssertionSpecs.Be.cs new file mode 100644 index 0000000000..c695144d0e --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/TimeOnlyAssertionSpecs.Be.cs @@ -0,0 +1,212 @@ +#if NET6_0_OR_GREATER +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class TimeOnlyAssertionSpecs +{ + public class Be + { + [Fact] + public void Should_succeed_when_asserting_timeonly_value_is_equal_to_the_same_value() + { + // Arrange + TimeOnly timeOnly = new(15, 06, 04, 146); + TimeOnly sameTimeOnly = new(15, 06, 04, 146); + + // Act/Assert + timeOnly.Should().Be(sameTimeOnly); + } + + [Fact] + public void When_timeonly_value_is_equal_to_the_same_nullable_value_be_should_succeed() + { + // Arrange + TimeOnly timeOnly = new(15, 06, 04, 146); + TimeOnly? sameTimeOnly = new(15, 06, 04, 146); + + // Act/Assert + timeOnly.Should().Be(sameTimeOnly); + } + + [Fact] + public void When_both_values_are_at_their_minimum_then_it_should_succeed() + { + // Arrange + TimeOnly timeOnly = TimeOnly.MinValue; + TimeOnly sameTimeOnly = TimeOnly.MinValue; + + // Act/Assert + timeOnly.Should().Be(sameTimeOnly); + } + + [Fact] + public void When_both_values_are_at_their_maximum_then_it_should_succeed() + { + // Arrange + TimeOnly timeOnly = TimeOnly.MaxValue; + TimeOnly sameTimeOnly = TimeOnly.MaxValue; + + // Act/Assert + timeOnly.Should().Be(sameTimeOnly); + } + + [Fact] + public void Should_fail_when_asserting_timeonly_value_is_equal_to_the_different_value() + { + // Arrange + var timeOnly = new TimeOnly(15, 03, 10); + var otherTimeOnly = new TimeOnly(15, 03, 11); + + // Act + Action act = () => timeOnly.Should().Be(otherTimeOnly, "because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw() + .WithMessage("Expected timeOnly to be <15:03:11.000>*failure message, but found <15:03:10.000>."); + } + + [Fact] + public void Should_fail_when_asserting_timeonly_value_is_equal_to_the_different_value_by_milliseconds() + { + // Arrange + var timeOnly = new TimeOnly(15, 03, 10, 556); + var otherTimeOnly = new TimeOnly(15, 03, 10, 175); + + // Act + Action act = () => timeOnly.Should().Be(otherTimeOnly, "because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw() + .WithMessage("Expected timeOnly to be <15:03:10.175>*failure message, but found <15:03:10.556>."); + } + + [Fact] + public void Should_succeed_when_asserting_nullable_numeric_value_equals_the_same_value() + { + // Arrange + TimeOnly? nullableTimeOnlyA = new TimeOnly(15, 06, 04, 123); + TimeOnly? nullableTimeOnlyB = new TimeOnly(15, 06, 04, 123); + + // Act/Assert + nullableTimeOnlyA.Should().Be(nullableTimeOnlyB); + } + + [Fact] + public void Should_succeed_when_asserting_nullable_numeric_null_value_equals_null() + { + // Arrange + TimeOnly? nullableTimeOnlyA = null; + TimeOnly? nullableTimeOnlyB = null; + + // Act/Assert + nullableTimeOnlyA.Should().Be(nullableTimeOnlyB); + } + + [Fact] + public void Should_fail_when_asserting_nullable_numeric_value_equals_a_different_value() + { + // Arrange + TimeOnly? nullableTimeOnlyA = new TimeOnly(15, 06, 04); + TimeOnly? nullableTimeOnlyB = new TimeOnly(15, 06, 06); + + // Act + Action action = () => + nullableTimeOnlyA.Should().Be(nullableTimeOnlyB); + + // Assert + action.Should().Throw(); + } + + [Fact] + public void Should_fail_with_descriptive_message_when_asserting_timeonly_null_value_is_equal_to_another_value() + { + // Arrange + TimeOnly? nullableTimeOnly = null; + + // Act + Action action = () => + nullableTimeOnly.Should().Be(new TimeOnly(15, 06, 04), "because we want to test the failure {0}", + "message"); + + // Assert + action.Should().Throw() + .WithMessage( + "Expected nullableTimeOnly to be <15:06:04.000> because we want to test the failure message, but found ."); + } + + [Fact] + public void Should_succeed_when_asserting_timeonly_value_is_not_equal_to_a_different_value() + { + // Arrange + TimeOnly timeOnly = new(15, 06, 04); + TimeOnly otherTimeOnly = new(15, 06, 05); + + // Act/Assert + timeOnly.Should().NotBe(otherTimeOnly); + } + } + + public class NotBe + { + [Fact] + public void Different_timeonly_values_are_valid() + { + // Arrange + TimeOnly time = new(19, 06, 04); + TimeOnly otherTime = new(20, 06, 05); + + // Act & Assert + time.Should().NotBe(otherTime); + } + + [Fact] + public void Different_timeonly_values_with_different_nullability_are_valid() + { + // Arrange + TimeOnly time = new(19, 06, 04); + TimeOnly? otherTime = new(19, 07, 05); + + // Act & Assert + time.Should().NotBe(otherTime); + } + + [Fact] + public void Same_timeonly_values_are_invalid() + { + // Arrange + TimeOnly time = new(19, 06, 04); + TimeOnly sameTime = new(19, 06, 04); + + // Act + Action act = + () => time.Should().NotBe(sameTime, "because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw() + .WithMessage( + "Expected time not to be <19:06:04.000> because we want to test the failure message, but it is."); + } + + [Fact] + public void Same_timeonly_values_with_different_nullability_are_invalid() + { + // Arrange + TimeOnly time = new(19, 06, 04); + TimeOnly? sameTime = new(19, 06, 04); + + // Act + Action act = + () => time.Should().NotBe(sameTime, "because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw() + .WithMessage( + "Expected time not to be <19:06:04.000> because we want to test the failure message, but it is."); + } + } +} + +#endif diff --git a/Tests/FluentAssertions.Specs/Primitives/TimeOnlyAssertionSpecs.BeAfter.cs b/Tests/FluentAssertions.Specs/Primitives/TimeOnlyAssertionSpecs.BeAfter.cs new file mode 100644 index 0000000000..30f53d3f1f --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/TimeOnlyAssertionSpecs.BeAfter.cs @@ -0,0 +1,170 @@ +#if NET6_0_OR_GREATER +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class TimeOnlyAssertionSpecs +{ + public class BeAfter + { + [Fact] + public void When_asserting_subject_timeonly_is_after_earlier_expected_timeonly_should_succeed() + { + // Arrange + TimeOnly subject = new(15, 06, 04, 123); + TimeOnly expectation = new(15, 06, 03, 45); + + // Act/Assert + subject.Should().BeAfter(expectation); + } + + [Fact] + public void When_asserting_subject_timeonly_is_not_after_earlier_expected_timeonly_should_throw() + { + // Arrange + TimeOnly subject = new(15, 06, 04); + TimeOnly expectation = new(15, 06, 03); + + // Act + Action act = () => subject.Should().NotBeAfter(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be on or before <15:06:03.000>, but found <15:06:04.000>."); + } + + [Fact] + public void When_asserting_subject_timeonly_is_after_later_expected_timeonly_should_throw() + { + // Arrange + TimeOnly subject = new(15, 06, 04); + TimeOnly expectation = new(15, 06, 05); + + // Act + Action act = () => subject.Should().BeAfter(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be after <15:06:05.000>, but found <15:06:04.000>."); + } + + [Fact] + public void When_asserting_subject_timeonly_is_not_after_later_expected_timeonly_should_succeed() + { + // Arrange + TimeOnly subject = new(15, 06, 04); + TimeOnly expectation = new(15, 06, 05); + + // Act/Assert + subject.Should().NotBeAfter(expectation); + } + + [Fact] + public void When_asserting_subject_timeonly_is_after_the_same_expected_timeonly_should_throw() + { + // Arrange + TimeOnly subject = new(15, 06, 04, 145); + TimeOnly expectation = new(15, 06, 04, 145); + + // Act + Action act = () => subject.Should().BeAfter(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be after <15:06:04.145>, but found <15:06:04.145>."); + } + + [Fact] + public void When_asserting_subject_timeonly_is_not_after_the_same_expected_timeonly_should_succeed() + { + // Arrange + TimeOnly subject = new(15, 06, 04, 123); + TimeOnly expectation = new(15, 06, 04, 123); + + // Act/Assert + subject.Should().NotBeAfter(expectation); + } + + [Fact] + public void When_asserting_subject_timeonly_is_on_or_after_earlier_expected_timeonly_should_succeed() + { + // Arrange + TimeOnly subject = new(15, 07); + TimeOnly expectation = new(15, 06); + + // Act/Assert + subject.Should().BeOnOrAfter(expectation); + } + + [Fact] + public void When_asserting_subject_timeonly_is_not_on_or_after_earlier_expected_timeonly_should_throw() + { + // Arrange + TimeOnly subject = new(15, 06, 04); + TimeOnly expectation = new(15, 06, 03); + + // Act + Action act = () => subject.Should().NotBeOnOrAfter(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be before <15:06:03.000>, but found <15:06:04.000>."); + } + + [Fact] + public void When_asserting_subject_timeonly_is_on_or_after_the_same_expected_timeonly_should_succeed() + { + // Arrange + TimeOnly subject = new(15, 06, 04); + TimeOnly expectation = new(15, 06, 04); + + // Act/Assert + subject.Should().BeOnOrAfter(expectation); + } + + [Fact] + public void When_asserting_subject_timeonly_is_not_on_or_after_the_same_expected_timeonly_should_throw() + { + // Arrange + TimeOnly subject = new(15, 06); + TimeOnly expectation = new(15, 06); + + // Act + Action act = () => subject.Should().NotBeOnOrAfter(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be before <15:06:00.000>, but found <15:06:00.000>."); + } + + [Fact] + public void When_asserting_subject_timeonly_is_on_or_after_later_expected_timeonly_should_throw() + { + // Arrange + TimeOnly subject = new(15, 06, 04); + TimeOnly expectation = new(15, 06, 05); + + // Act + Action act = () => subject.Should().BeOnOrAfter(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be on or after <15:06:05.000>, but found <15:06:04.000>."); + } + + [Fact] + public void When_asserting_subject_timeonly_is_not_on_or_after_later_expected_timeonly_should_succeed() + { + // Arrange + TimeOnly subject = new(15, 06, 04); + TimeOnly expectation = new(15, 06, 05); + + // Act/Assert + subject.Should().NotBeOnOrAfter(expectation); + } + } +} + +#endif diff --git a/Tests/FluentAssertions.Specs/Primitives/TimeOnlyAssertionSpecs.BeBefore.cs b/Tests/FluentAssertions.Specs/Primitives/TimeOnlyAssertionSpecs.BeBefore.cs new file mode 100644 index 0000000000..91b59a6714 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/TimeOnlyAssertionSpecs.BeBefore.cs @@ -0,0 +1,130 @@ +#if NET6_0_OR_GREATER +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class TimeOnlyAssertionSpecs +{ + public class BeBefore + { + [Fact] + public void When_asserting_subject_is_not_before_earlier_expected_timeonly_it_should_succeed() + { + // Arrange + TimeOnly expected = new(15, 06, 03); + TimeOnly subject = new(15, 06, 04); + + // Act/Assert + subject.Should().NotBeBefore(expected); + } + + [Fact] + public void When_asserting_subject_timeonly_is_before_the_same_timeonly_it_should_throw() + { + // Arrange + TimeOnly expected = new(15, 06, 04); + TimeOnly subject = new(15, 06, 04); + + // Act + Action act = () => subject.Should().BeBefore(expected); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be before <15:06:04.000>, but found <15:06:04.000>."); + } + + [Fact] + public void When_asserting_subject_timeonly_is_not_before_the_same_timeonly_it_should_succeed() + { + // Arrange + TimeOnly expected = new(15, 06, 04); + TimeOnly subject = new(15, 06, 04); + + // Act/Assert + subject.Should().NotBeBefore(expected); + } + + [Fact] + public void When_asserting_subject_timeonly_is_on_or_before_expected_timeonly_should_succeed() + { + // Arrange + TimeOnly subject = new(15, 06, 04, 175); + TimeOnly expectation = new(15, 06, 05, 23); + + // Act/Assert + subject.Should().BeOnOrBefore(expectation); + } + + [Fact] + public void When_asserting_subject_timeonly_is_on_or_before_expected_timeonly_should_throw() + { + // Arrange + TimeOnly subject = new(15, 06, 04, 150); + TimeOnly expectation = new(15, 06, 05, 340); + + // Act + Action act = () => subject.Should().NotBeOnOrBefore(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be after <15:06:05.340>, but found <15:06:04.150>."); + } + + [Fact] + public void + When_asserting_subject_timeonly_is_on_or_before_the_same_time_as_the_expected_timeonly_should_succeed() + { + // Arrange + TimeOnly subject = new(15, 06, 04); + TimeOnly expectation = new(15, 06, 04); + + // Act/Assert + subject.Should().BeOnOrBefore(expectation); + } + + [Fact] + public void When_asserting_subject_timeonly_is_on_or_before_the_same_time_as_the_expected_timeonly_should_throw() + { + // Arrange + TimeOnly subject = new(15, 06, 04, 123); + TimeOnly expectation = new(15, 06, 04, 123); + + // Act + Action act = () => subject.Should().NotBeOnOrBefore(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be after <15:06:04.123>, but found <15:06:04.123>."); + } + + [Fact] + public void When_asserting_subject_timeonly_is_not_on_or_before_earlier_expected_timeonly_should_throw() + { + // Arrange + TimeOnly subject = new(15, 07); + TimeOnly expectation = new(15, 06); + + // Act + Action act = () => subject.Should().BeOnOrBefore(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected subject to be on or before <15:06:00.000>, but found <15:07:00.000>."); + } + + [Fact] + public void When_asserting_subject_timeonly_is_not_on_or_before_earlier_expected_timeonly_should_succeed() + { + // Arrange + TimeOnly subject = new(15, 06, 04); + TimeOnly expectation = new(15, 06, 03); + + // Act/Assert + subject.Should().NotBeOnOrBefore(expectation); + } + } +} + +#endif diff --git a/Tests/FluentAssertions.Specs/Primitives/TimeOnlyAssertionSpecs.BeCloseTo.cs b/Tests/FluentAssertions.Specs/Primitives/TimeOnlyAssertionSpecs.BeCloseTo.cs new file mode 100644 index 0000000000..0f9d0681f8 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/TimeOnlyAssertionSpecs.BeCloseTo.cs @@ -0,0 +1,464 @@ +#if NET6_0_OR_GREATER +using System; +using FluentAssertions.Execution; +using FluentAssertions.Extensions; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class TimeOnlyAssertionSpecs +{ + public class BeCloseTo + { + [Fact] + public void When_time_is_close_to_a_negative_precision_it_should_throw() + { + // Arrange + var time = TimeOnly.FromDateTime(DateTime.UtcNow); + var actual = new TimeOnly(time.Ticks - 1); + + // Act + Action act = () => actual.Should().BeCloseTo(time, -1.Ticks()); + + // Assert + act.Should().Throw() + .WithParameterName("precision") + .WithMessage("*must be non-negative*"); + } + + [Fact] + public void When_a_time_is_close_to_a_later_time_by_one_tick_it_should_succeed() + { + // Arrange + var time = TimeOnly.FromDateTime(DateTime.UtcNow); + var actual = new TimeOnly(time.Ticks - 1); + + // Act / Assert + actual.Should().BeCloseTo(time, TimeSpan.FromTicks(1)); + } + + [Fact] + public void When_a_time_is_close_to_an_earlier_time_by_one_tick_it_should_succeed() + { + // Arrange + var time = TimeOnly.FromDateTime(DateTime.UtcNow); + var actual = new TimeOnly(time.Ticks + 1); + + // Act / Assert + actual.Should().BeCloseTo(time, TimeSpan.FromTicks(1)); + } + + [Fact] + public void When_subject_time_is_close_to_the_minimum_time_it_should_succeed() + { + // Arrange + TimeOnly time = TimeOnly.MinValue.Add(50.Milliseconds()); + TimeOnly nearbyTime = TimeOnly.MinValue; + + // Act / Assert + time.Should().BeCloseTo(nearbyTime, 100.Milliseconds()); + } + + [Fact] + public void When_subject_time_is_close_to_the_maximum_time_it_should_succeed() + { + // Arrange + TimeOnly time = TimeOnly.MaxValue.Add(-50.Milliseconds()); + TimeOnly nearbyTime = TimeOnly.MaxValue; + + // Act / Assert + time.Should().BeCloseTo(nearbyTime, 100.Milliseconds()); + } + + [Fact] + public void When_subject_time_is_close_to_another_value_that_is_later_by_more_than_20ms_it_should_throw() + { + // Arrange + TimeOnly time = new(12, 15, 30, 979); + TimeOnly nearbyTime = new(12, 15, 31); + + // Act + Action act = () => time.Should().BeCloseTo(nearbyTime, 20.Milliseconds()); + + // Assert + act.Should().Throw() + .WithMessage( + "Expected time to be within 20ms from <12:15:31.000>, but <12:15:30.979> was off by 21ms."); + } + + [Fact] + public void When_subject_time_is_close_to_another_value_that_is_earlier_by_more_than_20ms_it_should_throw() + { + // Arrange + TimeOnly time = new(12, 15, 31, 021); + TimeOnly nearbyTime = new(12, 15, 31); + + // Act + Action act = () => time.Should().BeCloseTo(nearbyTime, 20.Milliseconds()); + + // Assert + act.Should().Throw() + .WithMessage( + "Expected time to be within 20ms from <12:15:31.000>, but <12:15:31.021> was off by 21ms."); + } + + [Fact] + public void When_subject_time_is_close_to_an_earlier_time_by_35ms_it_should_succeed() + { + // Arrange + TimeOnly time = new(12, 15, 31, 035); + TimeOnly nearbyTime = new(12, 15, 31); + + // Act / Assert + time.Should().BeCloseTo(nearbyTime, 35.Milliseconds()); + } + + [Fact] + public void A_time_is_close_to_a_later_time_when_passing_midnight() + { + // Arrange + TimeOnly time = new(23, 59, 0); + TimeOnly nearbyTime = new(0, 1, 0); + + // Act / Assert + time.Should().BeCloseTo(nearbyTime, 2.Minutes()); + } + + [Fact] + public void A_time_is_close_to_an_earlier_time_when_passing_midnight() + { + // Arrange + TimeOnly time = new(0, 1, 0); + TimeOnly nearbyTime = new(23, 59, 0); + + // Act / Assert + time.Should().BeCloseTo(nearbyTime, 2.Minutes()); + } + + [Fact] + public void A_time_outside_of_the_precision_to_a_later_time_when_passing_midnight_fails() + { + // Arrange + TimeOnly time = new(23, 58, 59); + TimeOnly nearbyTime = new(0, 1, 0); + + // Act + Action act = () => time.Should().BeCloseTo(nearbyTime, 2.Minutes()); + + // Assert + act.Should().Throw() + .WithMessage("Expected * to be within 2m from <00:01:00.000>*, but <23:58:59.000> was off by 2m and 1s*"); + } + + [Fact] + public void A_time_outside_of_the_precision_to_an_earlier_time_when_passing_midnight_fails() + { + // Arrange + TimeOnly time = new(0, 1, 0); + TimeOnly nearbyTime = new(23, 58, 59); + + // Act + Action act = () => time.Should().BeCloseTo(nearbyTime, 2.Minutes()); + + // Assert + act.Should().Throw() + .WithMessage("Expected * to be within 2m from <23:58:59.000>*, but <00:01:00.000> was off by 2m and 1s*"); + } + + [Fact] + public void When_subject_nulltime_is_close_to_another_it_should_throw() + { + // Arrange + TimeOnly? time = null; + TimeOnly nearbyTime = new(12, 15, 31); + + // Act + Action act = () => time.Should().BeCloseTo(nearbyTime, 35.Milliseconds()); + + // Assert + act.Should().Throw() + .WithMessage("Expected*, but found ."); + } + + [Fact] + public void A_null_time_inside_an_assertion_scope_fails() + { + // Arrange + TimeOnly? time = null; + TimeOnly nearbyTime = new(12, 15, 31); + + // Act + Action act = () => + { + using var _ = new AssertionScope(); + time.Should().BeCloseTo(nearbyTime, 35.Milliseconds()); + }; + + // Assert + act.Should().Throw() + .WithMessage("Expected*, but found ."); + } + } + + public class NotBeCloseTo + { + [Fact] + public void A_null_time_is_never_unclose_to_an_other_time() + { + // Arrange + TimeOnly? time = null; + TimeOnly nearbyTime = new(12, 15, 31); + + // Act + Action act = () => time.Should().NotBeCloseTo(nearbyTime, 35.Milliseconds()); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect*, but found ."); + } + + [Fact] + public void A_null_time_inside_an_assertion_scope_is_never_unclose_to_an_other_time() + { + // Arrange + TimeOnly? time = null; + TimeOnly nearbyTime = new(12, 15, 31); + + // Act + Action act = () => + { + using var _ = new AssertionScope(); + time.Should().NotBeCloseTo(nearbyTime, 35.Milliseconds()); + }; + + // Assert + act.Should().Throw() + .WithMessage("Did not expect*, but found ."); + } + + [Fact] + public void When_time_is_not_close_to_a_negative_precision_it_should_throw() + { + // Arrange + var time = TimeOnly.FromDateTime(DateTime.UtcNow); + var actual = new TimeOnly(time.Ticks - 1); + + // Act + Action act = () => actual.Should().NotBeCloseTo(time, -1.Ticks()); + + // Assert + act.Should().Throw() + .WithParameterName("precision") + .WithMessage("*must be non-negative*"); + } + + [Fact] + public void When_a_time_is_close_to_a_later_time_by_one_tick_it_should_fail() + { + // Arrange + var time = TimeOnly.FromDateTime(DateTime.UtcNow); + var actual = new TimeOnly(time.Ticks - 1); + + // Act + Action act = () => actual.Should().NotBeCloseTo(time, TimeSpan.FromTicks(1)); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_a_time_is_close_to_an_earlier_time_by_one_tick_it_should_fail() + { + // Arrange + var time = TimeOnly.FromDateTime(DateTime.UtcNow); + var actual = new TimeOnly(time.Ticks + 1); + + // Act + Action act = () => actual.Should().NotBeCloseTo(time, TimeSpan.FromTicks(1)); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_a_time_is_close_to_a_min_value_by_one_tick_it_should_fail() + { + // Arrange + var time = TimeOnly.MinValue; + var actual = new TimeOnly(time.Ticks + 1); + + // Act + Action act = () => actual.Should().NotBeCloseTo(time, TimeSpan.FromTicks(1)); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_a_time_is_close_to_a_max_value_by_one_tick_it_should_fail() + { + // Arrange + var time = TimeOnly.MaxValue; + var actual = new TimeOnly(time.Ticks - 1); + + // Act + Action act = () => actual.Should().NotBeCloseTo(time, TimeSpan.FromTicks(1)); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_asserting_subject_time_is_not_close_to_an_earlier_time_it_should_throw() + { + // Arrange + TimeOnly time = new(12, 15, 31, 020); + TimeOnly nearbyTime = new(12, 15, 31); + + // Act + Action act = () => time.Should().NotBeCloseTo(nearbyTime, 20.Milliseconds()); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect time to be within 20ms from <12:15:31.000>, but it was <12:15:31.020>."); + } + + [Fact] + public void When_asserting_subject_time_is_not_close_to_an_earlier_time_by_a_20ms_timespan_it_should_throw() + { + // Arrange + TimeOnly time = new(12, 15, 31, 020); + TimeOnly nearbyTime = new(12, 15, 31); + + // Act + Action act = () => time.Should().NotBeCloseTo(nearbyTime, TimeSpan.FromMilliseconds(20)); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect time to be within 20ms from <12:15:31.000>, but it was <12:15:31.020>."); + } + + [Fact] + public void When_asserting_subject_time_is_not_close_to_another_value_that_is_later_by_more_than_20ms_it_should_succeed() + { + // Arrange + TimeOnly time = new(12, 15, 30, 979); + TimeOnly nearbyTime = new(12, 15, 31); + + // Act / Assert + time.Should().NotBeCloseTo(nearbyTime, 20.Milliseconds()); + } + + [Fact] + public void + When_asserting_subject_time_is_not_close_to_another_value_that_is_earlier_by_more_than_20ms_it_should_succeed() + { + // Arrange + TimeOnly time = new(12, 15, 31, 021); + TimeOnly nearbyTime = new(12, 15, 31); + + // Act / Assert + time.Should().NotBeCloseTo(nearbyTime, 20.Milliseconds()); + } + + [Fact] + public void When_asserting_subject_datetime_is_not_close_to_an_earlier_datetime_by_35ms_it_should_throw() + { + // Arrange + TimeOnly time = new(12, 15, 31, 035); + TimeOnly nearbyTime = new(12, 15, 31); + + // Act + Action act = () => time.Should().NotBeCloseTo(nearbyTime, 35.Milliseconds()); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect time to be within 35ms from <12:15:31.000>, but it was <12:15:31.035>."); + } + + [Fact] + public void When_asserting_subject_time_is_not_close_to_the_minimum_time_it_should_throw() + { + // Arrange + TimeOnly time = TimeOnly.MinValue.Add(50.Milliseconds()); + TimeOnly nearbyTime = TimeOnly.MinValue; + + // Act + Action act = () => time.Should().NotBeCloseTo(nearbyTime, 100.Milliseconds()); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect time to be within 100ms from <00:00:00.000>, but it was <00:00:00.050>."); + } + + [Fact] + public void When_asserting_subject_time_is_not_close_to_the_maximum_time_it_should_throw() + { + // Arrange + TimeOnly time = TimeOnly.MaxValue.Add(-50.Milliseconds()); + TimeOnly nearbyTime = TimeOnly.MaxValue; + + // Act + Action act = () => time.Should().NotBeCloseTo(nearbyTime, 100.Milliseconds()); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect time to be within 100ms from <23:59:59.999>, but it was <23:59:59.949>."); + } + + [Fact] + public void A_time_is_not_close_to_a_later_time_when_passing_midnight() + { + // Arrange + TimeOnly time = new(23, 58, 0); + TimeOnly nearbyTime = new(0, 1, 0); + + // Act / Assert + time.Should().NotBeCloseTo(nearbyTime, 2.Minutes()); + } + + [Fact] + public void A_time_is_not_close_to_an_earlier_time_when_passing_midnight() + { + // Arrange + TimeOnly time = new(0, 2, 0); + TimeOnly nearbyTime = new(23, 59, 0); + + // Act / Assert + time.Should().NotBeCloseTo(nearbyTime, 2.Minutes()); + } + + [Fact] + public void A_time_inside_of_the_precision_to_a_later_time_when_passing_midnight_fails() + { + // Arrange + TimeOnly time = new(23, 59, 0); + TimeOnly nearbyTime = new(0, 1, 0); + + // Act + Action act = () => time.Should().NotBeCloseTo(nearbyTime, 2.Minutes()); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect * to be within 2m from <00:01:00.000>*, but it was <23:59:00.000>*"); + } + + [Fact] + public void A_time_inside_of_the_precision_to_an_earlier_time_when_passing_midnight_fails() + { + // Arrange + TimeOnly time = new(0, 1, 0); + TimeOnly nearbyTime = new(23, 59, 0); + + // Act + Action act = () => time.Should().NotBeCloseTo(nearbyTime, 2.Minutes()); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect * to be within 2m from <23:59:00.000>*, but it was <00:01:00.000>*"); + } + } +} + +#endif diff --git a/Tests/FluentAssertions.Specs/Primitives/TimeOnlyAssertionSpecs.BeOneOf.cs b/Tests/FluentAssertions.Specs/Primitives/TimeOnlyAssertionSpecs.BeOneOf.cs new file mode 100644 index 0000000000..85b70302c6 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/TimeOnlyAssertionSpecs.BeOneOf.cs @@ -0,0 +1,62 @@ +#if NET6_0_OR_GREATER +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class TimeOnlyAssertionSpecs +{ + public class BeOneOf + { + [Fact] + public void When_a_value_is_not_one_of_the_specified_values_it_should_throw() + { + // Arrange + TimeOnly value = new(15, 12, 20); + + // Act + Action action = () => value.Should().BeOneOf(value.AddHours(1), value.AddMinutes(-1)); + + // Assert + action.Should().Throw() + .WithMessage("Expected value to be one of {<16:12:20.000>, <15:11:20.000>}, but found <15:12:20.000>."); + } + + [Fact] + public void When_a_value_is_one_of_the_specified_values_it_should_succeed() + { + // Arrange + TimeOnly value = new(15, 12, 30); + + // Act/Assert + value.Should().BeOneOf(new TimeOnly(4, 1, 30), new TimeOnly(15, 12, 30)); + } + + [Fact] + public void When_a_null_value_is_not_one_of_the_specified_values_it_should_throw() + { + // Arrange + TimeOnly? value = null; + + // Act + Action action = () => value.Should().BeOneOf(new TimeOnly(15, 1, 30), new TimeOnly(5, 4, 10, 123)); + + // Assert + action.Should().Throw() + .WithMessage("Expected value to be one of {<15:01:30.000>, <05:04:10.123>}, but found ."); + } + + [Fact] + public void When_a_value_is_one_of_the_specified_values_it_should_succeed_when_timeonly_is_null() + { + // Arrange + TimeOnly? value = null; + + // Act/Assert + value.Should().BeOneOf(new TimeOnly(15, 1, 30), null); + } + } +} + +#endif diff --git a/Tests/FluentAssertions.Specs/Primitives/TimeOnlyAssertionSpecs.HaveHours.cs b/Tests/FluentAssertions.Specs/Primitives/TimeOnlyAssertionSpecs.HaveHours.cs new file mode 100644 index 0000000000..9b4b4a1e6b --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/TimeOnlyAssertionSpecs.HaveHours.cs @@ -0,0 +1,96 @@ +#if NET6_0_OR_GREATER +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class TimeOnlyAssertionSpecs +{ + public class HaveHours + { + [Fact] + public void When_asserting_subject_timeonly_should_have_hours_with_the_same_value_should_succeed() + { + // Arrange + TimeOnly subject = new(15, 12, 31); + const int expectation = 15; + + // Act/Assert + subject.Should().HaveHours(expectation); + } + + [Fact] + public void When_asserting_subject_timeonly_should_not_have_hours_with_the_same_value_should_throw() + { + // Arrange + TimeOnly subject = new(15, 12, 31); + const int expectation = 15; + + // Act + Action act = () => subject.Should().NotHaveHours(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the hours part of subject to be 15, but it was."); + } + + [Fact] + public void When_asserting_subject_timeonly_should_have_hours_with_a_different_value_should_throw() + { + // Arrange + TimeOnly subject = new(15, 12, 31); + const int expectation = 14; + + // Act + Action act = () => subject.Should().HaveHours(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the hours part of subject to be 14, but found 15."); + } + + [Fact] + public void When_asserting_subject_timeonly_should_not_have_hours_with_a_different_value_should_succeed() + { + // Arrange + TimeOnly subject = new(21, 12, 31); + const int expectation = 23; + + // Act/Assert + subject.Should().NotHaveHours(expectation); + } + + [Fact] + public void When_asserting_subject_null_timeonly_should_have_hours_should_throw() + { + // Arrange + TimeOnly? subject = null; + const int expectation = 21; + + // Act + Action act = () => subject.Should().HaveHours(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the hours part of subject to be 21, but found ."); + } + + [Fact] + public void When_asserting_subject_null_timeonly_should_not_have_hours_should_throw() + { + // Arrange + TimeOnly? subject = null; + const int expectation = 19; + + // Act + Action act = () => subject.Should().NotHaveHours(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the hours part of subject to be 19, but found a TimeOnly."); + } + } +} + +#endif diff --git a/Tests/FluentAssertions.Specs/Primitives/TimeOnlyAssertionSpecs.HaveMilliseconds.cs b/Tests/FluentAssertions.Specs/Primitives/TimeOnlyAssertionSpecs.HaveMilliseconds.cs new file mode 100644 index 0000000000..e00e8afea7 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/TimeOnlyAssertionSpecs.HaveMilliseconds.cs @@ -0,0 +1,96 @@ +#if NET6_0_OR_GREATER +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class TimeOnlyAssertionSpecs +{ + public class HaveMilliseconds + { + [Fact] + public void When_asserting_subject_timeonly_should_have_milliseconds_with_the_same_value_it_should_succeed() + { + // Arrange + TimeOnly subject = new(14, 12, 31, 123); + const int expectation = 123; + + // Act/Assert + subject.Should().HaveMilliseconds(expectation); + } + + [Fact] + public void When_asserting_subject_timeonly_should_not_have_milliseconds_with_the_same_value_it_should_throw() + { + // Arrange + TimeOnly subject = new(14, 12, 31, 445); + const int expectation = 445; + + // Act + Action act = () => subject.Should().NotHaveMilliseconds(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the milliseconds part of subject to be 445, but it was."); + } + + [Fact] + public void When_asserting_subject_timeonly_should_have_milliseconds_with_a_different_value_it_should_throw() + { + // Arrange + TimeOnly subject = new(15, 12, 31, 555); + const int expectation = 12; + + // Act + Action act = () => subject.Should().HaveMilliseconds(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the milliseconds part of subject to be 12, but found 555."); + } + + [Fact] + public void When_asserting_subject_timeonly_should_not_have_milliseconds_with_a_different_value_it_should_succeed() + { + // Arrange + TimeOnly subject = new(15, 12, 31, 445); + const int expectation = 31; + + // Act/Assert + subject.Should().NotHaveMilliseconds(expectation); + } + + [Fact] + public void When_asserting_subject_null_timeonly_should_have_milliseconds_should_throw() + { + // Arrange + TimeOnly? subject = null; + const int expectation = 22; + + // Act + Action act = () => subject.Should().HaveMilliseconds(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the milliseconds part of subject to be 22, but found a TimeOnly."); + } + + [Fact] + public void When_asserting_subject_null_timeonly_should_not_have_milliseconds_should_throw() + { + // Arrange + TimeOnly? subject = null; + const int expectation = 22; + + // Act + Action act = () => subject.Should().NotHaveMilliseconds(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the milliseconds part of subject to be 22, but found a TimeOnly."); + } + } +} + +#endif diff --git a/Tests/FluentAssertions.Specs/Primitives/TimeOnlyAssertionSpecs.HaveMinutes.cs b/Tests/FluentAssertions.Specs/Primitives/TimeOnlyAssertionSpecs.HaveMinutes.cs new file mode 100644 index 0000000000..9e462ceda1 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/TimeOnlyAssertionSpecs.HaveMinutes.cs @@ -0,0 +1,96 @@ +#if NET6_0_OR_GREATER +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class TimeOnlyAssertionSpecs +{ + public class HaveMinutes + { + [Fact] + public void When_asserting_subject_timeonly_should_have_minutes_with_the_same_value_it_should_succeed() + { + // Arrange + TimeOnly subject = new(21, 12, 31); + const int expectation = 12; + + // Act/Assert + subject.Should().HaveMinutes(expectation); + } + + [Fact] + public void When_asserting_subject_timeonly_should_not_have_minutes_with_the_same_value_it_should_throw() + { + // Arrange + TimeOnly subject = new(21, 12, 31); + const int expectation = 12; + + // Act + Action act = () => subject.Should().NotHaveMinutes(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the minutes part of subject to be 12, but it was."); + } + + [Fact] + public void When_asserting_subject_timeonly_should_have_a_minute_with_a_different_value_it_should_throw() + { + // Arrange + TimeOnly subject = new(15, 12, 31); + const int expectation = 11; + + // Act + Action act = () => subject.Should().HaveMinutes(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the minutes part of subject to be 11, but found 12."); + } + + [Fact] + public void When_asserting_subject_timeonly_should_not_have_a_minute_with_a_different_value_it_should_succeed() + { + // Arrange + TimeOnly subject = new(15, 12, 31); + const int expectation = 11; + + // Act/Assert + subject.Should().NotHaveMinutes(expectation); + } + + [Fact] + public void When_asserting_subject_null_timeonly_should_have_minutes_should_throw() + { + // Arrange + TimeOnly? subject = null; + const int expectation = 12; + + // Act + Action act = () => subject.Should().HaveMinutes(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the minutes part of subject to be 12, but found a TimeOnly."); + } + + [Fact] + public void When_asserting_subject_null_timeonly_should_not_have_minutes_should_throw() + { + // Arrange + TimeOnly? subject = null; + const int expectation = 12; + + // Act + Action act = () => subject.Should().NotHaveMinutes(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the minutes part of subject to be 12, but found a TimeOnly."); + } + } +} + +#endif diff --git a/Tests/FluentAssertions.Specs/Primitives/TimeOnlyAssertionSpecs.HaveSeconds.cs b/Tests/FluentAssertions.Specs/Primitives/TimeOnlyAssertionSpecs.HaveSeconds.cs new file mode 100644 index 0000000000..df13410581 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Primitives/TimeOnlyAssertionSpecs.HaveSeconds.cs @@ -0,0 +1,96 @@ +#if NET6_0_OR_GREATER +using System; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Primitives; + +public partial class TimeOnlyAssertionSpecs +{ + public class HaveSeconds + { + [Fact] + public void When_asserting_subject_timeonly_should_have_seconds_with_the_same_value_it_should_succeed() + { + // Arrange + TimeOnly subject = new(14, 12, 31); + const int expectation = 31; + + // Act/Assert + subject.Should().HaveSeconds(expectation); + } + + [Fact] + public void When_asserting_subject_timeonly_should_not_have_seconds_with_the_same_value_it_should_throw() + { + // Arrange + TimeOnly subject = new(14, 12, 31); + const int expectation = 31; + + // Act + Action act = () => subject.Should().NotHaveSeconds(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the seconds part of subject to be 31, but it was."); + } + + [Fact] + public void When_asserting_subject_timeonly_should_have_seconds_with_a_different_value_it_should_throw() + { + // Arrange + TimeOnly subject = new(15, 12, 31); + const int expectation = 30; + + // Act + Action act = () => subject.Should().HaveSeconds(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the seconds part of subject to be 30, but found 31."); + } + + [Fact] + public void When_asserting_subject_timeonly_should_not_have_seconds_with_a_different_value_it_should_succeed() + { + // Arrange + TimeOnly subject = new(15, 12, 31); + const int expectation = 30; + + // Act/Assert + subject.Should().NotHaveSeconds(expectation); + } + + [Fact] + public void When_asserting_subject_null_timeonly_should_have_seconds_should_throw() + { + // Arrange + TimeOnly? subject = null; + const int expectation = 22; + + // Act + Action act = () => subject.Should().HaveSeconds(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Expected the seconds part of subject to be 22, but found a TimeOnly."); + } + + [Fact] + public void When_asserting_subject_null_timeonly_should_not_have_seconds_should_throw() + { + // Arrange + TimeOnly? subject = null; + const int expectation = 22; + + // Act + Action act = () => subject.Should().NotHaveSeconds(expectation); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect the seconds part of subject to be 22, but found a TimeOnly."); + } + } +} + +#endif diff --git a/Tests/FluentAssertions.Specs/Primitives/TimeOnlyAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Primitives/TimeOnlyAssertionSpecs.cs index b84f1386af..e5901fb197 100644 --- a/Tests/FluentAssertions.Specs/Primitives/TimeOnlyAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Primitives/TimeOnlyAssertionSpecs.cs @@ -1,13 +1,10 @@ +#if NET6_0_OR_GREATER using System; -using FluentAssertions.Execution; -using FluentAssertions.Extensions; using Xunit; -using Xunit.Sdk; -#if NET6_0_OR_GREATER namespace FluentAssertions.Specs.Primitives; -public class TimeOnlyAssertionSpecs +public partial class TimeOnlyAssertionSpecs { [Fact] public void Should_succeed_when_asserting_nullable_timeonly_value_with_value_to_have_a_value() @@ -39,1359 +36,32 @@ public void Should_succeed_when_asserting_nullable_timeonly_value_with_null_to_b timeOnly.Should().BeNull(); } - public class Be - { - [Fact] - public void Should_succeed_when_asserting_timeonly_value_is_equal_to_the_same_value() - { - // Arrange - TimeOnly timeOnly = new(15, 06, 04, 146); - TimeOnly sameTimeOnly = new(15, 06, 04, 146); - - // Act/Assert - timeOnly.Should().Be(sameTimeOnly); - } - - [Fact] - public void When_timeonly_value_is_equal_to_the_same_nullable_value_be_should_succeed() - { - // Arrange - TimeOnly timeOnly = new(15, 06, 04, 146); - TimeOnly? sameTimeOnly = new(15, 06, 04, 146); - - // Act/Assert - timeOnly.Should().Be(sameTimeOnly); - } - - [Fact] - public void When_both_values_are_at_their_minimum_then_it_should_succeed() - { - // Arrange - TimeOnly timeOnly = TimeOnly.MinValue; - TimeOnly sameTimeOnly = TimeOnly.MinValue; - - // Act/Assert - timeOnly.Should().Be(sameTimeOnly); - } - - [Fact] - public void When_both_values_are_at_their_maximum_then_it_should_succeed() - { - // Arrange - TimeOnly timeOnly = TimeOnly.MaxValue; - TimeOnly sameTimeOnly = TimeOnly.MaxValue; - - // Act/Assert - timeOnly.Should().Be(sameTimeOnly); - } - - [Fact] - public void Should_fail_when_asserting_timeonly_value_is_equal_to_the_different_value() - { - // Arrange - var timeOnly = new TimeOnly(15, 03, 10); - var otherTimeOnly = new TimeOnly(15, 03, 11); - - // Act - Action act = () => timeOnly.Should().Be(otherTimeOnly, "because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw() - .WithMessage("Expected timeOnly to be <15:03:11.000>*failure message, but found <15:03:10.000>."); - } - - [Fact] - public void Should_fail_when_asserting_timeonly_value_is_equal_to_the_different_value_by_milliseconds() - { - // Arrange - var timeOnly = new TimeOnly(15, 03, 10, 556); - var otherTimeOnly = new TimeOnly(15, 03, 10, 175); - - // Act - Action act = () => timeOnly.Should().Be(otherTimeOnly, "because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw() - .WithMessage("Expected timeOnly to be <15:03:10.175>*failure message, but found <15:03:10.556>."); - } - - [Fact] - public void Should_succeed_when_asserting_nullable_numeric_value_equals_the_same_value() - { - // Arrange - TimeOnly? nullableTimeOnlyA = new TimeOnly(15, 06, 04, 123); - TimeOnly? nullableTimeOnlyB = new TimeOnly(15, 06, 04, 123); - - // Act/Assert - nullableTimeOnlyA.Should().Be(nullableTimeOnlyB); - } - - [Fact] - public void Should_succeed_when_asserting_nullable_numeric_null_value_equals_null() - { - // Arrange - TimeOnly? nullableTimeOnlyA = null; - TimeOnly? nullableTimeOnlyB = null; - - // Act/Assert - nullableTimeOnlyA.Should().Be(nullableTimeOnlyB); - } - - [Fact] - public void Should_fail_when_asserting_nullable_numeric_value_equals_a_different_value() - { - // Arrange - TimeOnly? nullableTimeOnlyA = new TimeOnly(15, 06, 04); - TimeOnly? nullableTimeOnlyB = new TimeOnly(15, 06, 06); - - // Act - Action action = () => - nullableTimeOnlyA.Should().Be(nullableTimeOnlyB); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void Should_fail_with_descriptive_message_when_asserting_timeonly_null_value_is_equal_to_another_value() - { - // Arrange - TimeOnly? nullableTimeOnly = null; - - // Act - Action action = () => - nullableTimeOnly.Should().Be(new TimeOnly(15, 06, 04), "because we want to test the failure {0}", - "message"); - - // Assert - action.Should().Throw() - .WithMessage( - "Expected nullableTimeOnly to be <15:06:04.000> because we want to test the failure message, but found ."); - } - - [Fact] - public void Should_succeed_when_asserting_timeonly_value_is_not_equal_to_a_different_value() - { - // Arrange - TimeOnly timeOnly = new(15, 06, 04); - TimeOnly otherTimeOnly = new(15, 06, 05); - - // Act/Assert - timeOnly.Should().NotBe(otherTimeOnly); - } - } - - public class BeCloseTo - { - [Fact] - public void When_time_is_close_to_a_negative_precision_it_should_throw() - { - // Arrange - var time = TimeOnly.FromDateTime(DateTime.UtcNow); - var actual = new TimeOnly(time.Ticks - 1); - - // Act - Action act = () => actual.Should().BeCloseTo(time, -1.Ticks()); - - // Assert - act.Should().Throw() - .WithParameterName("precision") - .WithMessage("*must be non-negative*"); - } - - [Fact] - public void When_a_time_is_close_to_a_later_time_by_one_tick_it_should_succeed() - { - // Arrange - var time = TimeOnly.FromDateTime(DateTime.UtcNow); - var actual = new TimeOnly(time.Ticks - 1); - - // Act / Assert - actual.Should().BeCloseTo(time, TimeSpan.FromTicks(1)); - } - - [Fact] - public void When_a_time_is_close_to_an_earlier_time_by_one_tick_it_should_succeed() - { - // Arrange - var time = TimeOnly.FromDateTime(DateTime.UtcNow); - var actual = new TimeOnly(time.Ticks + 1); - - // Act / Assert - actual.Should().BeCloseTo(time, TimeSpan.FromTicks(1)); - } - - [Fact] - public void When_subject_time_is_close_to_the_minimum_time_it_should_succeed() - { - // Arrange - TimeOnly time = TimeOnly.MinValue.Add(50.Milliseconds()); - TimeOnly nearbyTime = TimeOnly.MinValue; - - // Act / Assert - time.Should().BeCloseTo(nearbyTime, 100.Milliseconds()); - } - - [Fact] - public void When_subject_time_is_close_to_the_maximum_time_it_should_succeed() - { - // Arrange - TimeOnly time = TimeOnly.MaxValue.Add(-50.Milliseconds()); - TimeOnly nearbyTime = TimeOnly.MaxValue; - - // Act / Assert - time.Should().BeCloseTo(nearbyTime, 100.Milliseconds()); - } - - [Fact] - public void When_subject_time_is_close_to_another_value_that_is_later_by_more_than_20ms_it_should_throw() - { - // Arrange - TimeOnly time = new(12, 15, 30, 979); - TimeOnly nearbyTime = new(12, 15, 31); - - // Act - Action act = () => time.Should().BeCloseTo(nearbyTime, 20.Milliseconds()); - - // Assert - act.Should().Throw() - .WithMessage( - "Expected time to be within 20ms from <12:15:31.000>, but <12:15:30.979> was off by 21ms."); - } - - [Fact] - public void When_subject_time_is_close_to_another_value_that_is_earlier_by_more_than_20ms_it_should_throw() - { - // Arrange - TimeOnly time = new(12, 15, 31, 021); - TimeOnly nearbyTime = new(12, 15, 31); - - // Act - Action act = () => time.Should().BeCloseTo(nearbyTime, 20.Milliseconds()); - - // Assert - act.Should().Throw() - .WithMessage( - "Expected time to be within 20ms from <12:15:31.000>, but <12:15:31.021> was off by 21ms."); - } - - [Fact] - public void When_subject_time_is_close_to_an_earlier_time_by_35ms_it_should_succeed() - { - // Arrange - TimeOnly time = new(12, 15, 31, 035); - TimeOnly nearbyTime = new(12, 15, 31); - - // Act / Assert - time.Should().BeCloseTo(nearbyTime, 35.Milliseconds()); - } - - [Fact] - public void A_time_is_close_to_a_later_time_when_passing_midnight() - { - // Arrange - TimeOnly time = new(23, 59, 0); - TimeOnly nearbyTime = new(0, 1, 0); - - // Act / Assert - time.Should().BeCloseTo(nearbyTime, 2.Minutes()); - } - - [Fact] - public void A_time_is_close_to_an_earlier_time_when_passing_midnight() - { - // Arrange - TimeOnly time = new(0, 1, 0); - TimeOnly nearbyTime = new(23, 59, 0); - - // Act / Assert - time.Should().BeCloseTo(nearbyTime, 2.Minutes()); - } - - [Fact] - public void A_time_outside_of_the_precision_to_a_later_time_when_passing_midnight_fails() - { - // Arrange - TimeOnly time = new(23, 58, 59); - TimeOnly nearbyTime = new(0, 1, 0); - - // Act - Action act = () => time.Should().BeCloseTo(nearbyTime, 2.Minutes()); - - // Assert - act.Should().Throw() - .WithMessage("Expected * to be within 2m from <00:01:00.000>*, but <23:58:59.000> was off by 2m and 1s*"); - } - - [Fact] - public void A_time_outside_of_the_precision_to_an_earlier_time_when_passing_midnight_fails() - { - // Arrange - TimeOnly time = new(0, 1, 0); - TimeOnly nearbyTime = new(23, 58, 59); - - // Act - Action act = () => time.Should().BeCloseTo(nearbyTime, 2.Minutes()); - - // Assert - act.Should().Throw() - .WithMessage("Expected * to be within 2m from <23:58:59.000>*, but <00:01:00.000> was off by 2m and 1s*"); - } - - [Fact] - public void When_subject_nulltime_is_close_to_another_it_should_throw() - { - // Arrange - TimeOnly? time = null; - TimeOnly nearbyTime = new(12, 15, 31); - - // Act - Action act = () => time.Should().BeCloseTo(nearbyTime, 35.Milliseconds()); - - // Assert - act.Should().Throw() - .WithMessage("Expected*, but found ."); - } - - [Fact] - public void A_null_time_inside_an_assertion_scope_fails() - { - // Arrange - TimeOnly? time = null; - TimeOnly nearbyTime = new(12, 15, 31); - - // Act - Action act = () => - { - using var _ = new AssertionScope(); - time.Should().BeCloseTo(nearbyTime, 35.Milliseconds()); - }; - - // Assert - act.Should().Throw() - .WithMessage("Expected*, but found ."); - } - } - - public class NotBeCloseTo - { - [Fact] - public void A_null_time_is_never_unclose_to_an_other_time() - { - // Arrange - TimeOnly? time = null; - TimeOnly nearbyTime = new(12, 15, 31); - - // Act - Action act = () => time.Should().NotBeCloseTo(nearbyTime, 35.Milliseconds()); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect*, but found ."); - } - - [Fact] - public void A_null_time_inside_an_assertion_scope_is_never_unclose_to_an_other_time() - { - // Arrange - TimeOnly? time = null; - TimeOnly nearbyTime = new(12, 15, 31); - - // Act - Action act = () => - { - using var _ = new AssertionScope(); - time.Should().NotBeCloseTo(nearbyTime, 35.Milliseconds()); - }; - - // Assert - act.Should().Throw() - .WithMessage("Did not expect*, but found ."); - } - - [Fact] - public void When_time_is_not_close_to_a_negative_precision_it_should_throw() - { - // Arrange - var time = TimeOnly.FromDateTime(DateTime.UtcNow); - var actual = new TimeOnly(time.Ticks - 1); - - // Act - Action act = () => actual.Should().NotBeCloseTo(time, -1.Ticks()); - - // Assert - act.Should().Throw() - .WithParameterName("precision") - .WithMessage("*must be non-negative*"); - } - - [Fact] - public void When_a_time_is_close_to_a_later_time_by_one_tick_it_should_fail() - { - // Arrange - var time = TimeOnly.FromDateTime(DateTime.UtcNow); - var actual = new TimeOnly(time.Ticks - 1); - - // Act - Action act = () => actual.Should().NotBeCloseTo(time, TimeSpan.FromTicks(1)); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_a_time_is_close_to_an_earlier_time_by_one_tick_it_should_fail() - { - // Arrange - var time = TimeOnly.FromDateTime(DateTime.UtcNow); - var actual = new TimeOnly(time.Ticks + 1); - - // Act - Action act = () => actual.Should().NotBeCloseTo(time, TimeSpan.FromTicks(1)); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_a_time_is_close_to_a_min_value_by_one_tick_it_should_fail() - { - // Arrange - var time = TimeOnly.MinValue; - var actual = new TimeOnly(time.Ticks + 1); - - // Act - Action act = () => actual.Should().NotBeCloseTo(time, TimeSpan.FromTicks(1)); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_a_time_is_close_to_a_max_value_by_one_tick_it_should_fail() - { - // Arrange - var time = TimeOnly.MaxValue; - var actual = new TimeOnly(time.Ticks - 1); - - // Act - Action act = () => actual.Should().NotBeCloseTo(time, TimeSpan.FromTicks(1)); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_asserting_subject_time_is_not_close_to_an_earlier_time_it_should_throw() - { - // Arrange - TimeOnly time = new(12, 15, 31, 020); - TimeOnly nearbyTime = new(12, 15, 31); - - // Act - Action act = () => time.Should().NotBeCloseTo(nearbyTime, 20.Milliseconds()); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect time to be within 20ms from <12:15:31.000>, but it was <12:15:31.020>."); - } - - [Fact] - public void When_asserting_subject_time_is_not_close_to_an_earlier_time_by_a_20ms_timespan_it_should_throw() - { - // Arrange - TimeOnly time = new(12, 15, 31, 020); - TimeOnly nearbyTime = new(12, 15, 31); - - // Act - Action act = () => time.Should().NotBeCloseTo(nearbyTime, TimeSpan.FromMilliseconds(20)); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect time to be within 20ms from <12:15:31.000>, but it was <12:15:31.020>."); - } - - [Fact] - public void When_asserting_subject_time_is_not_close_to_another_value_that_is_later_by_more_than_20ms_it_should_succeed() - { - // Arrange - TimeOnly time = new(12, 15, 30, 979); - TimeOnly nearbyTime = new(12, 15, 31); - - // Act / Assert - time.Should().NotBeCloseTo(nearbyTime, 20.Milliseconds()); - } - - [Fact] - public void - When_asserting_subject_time_is_not_close_to_another_value_that_is_earlier_by_more_than_20ms_it_should_succeed() - { - // Arrange - TimeOnly time = new(12, 15, 31, 021); - TimeOnly nearbyTime = new(12, 15, 31); - - // Act / Assert - time.Should().NotBeCloseTo(nearbyTime, 20.Milliseconds()); - } - - [Fact] - public void When_asserting_subject_datetime_is_not_close_to_an_earlier_datetime_by_35ms_it_should_throw() - { - // Arrange - TimeOnly time = new(12, 15, 31, 035); - TimeOnly nearbyTime = new(12, 15, 31); - - // Act - Action act = () => time.Should().NotBeCloseTo(nearbyTime, 35.Milliseconds()); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect time to be within 35ms from <12:15:31.000>, but it was <12:15:31.035>."); - } - - [Fact] - public void When_asserting_subject_time_is_not_close_to_the_minimum_time_it_should_throw() - { - // Arrange - TimeOnly time = TimeOnly.MinValue.Add(50.Milliseconds()); - TimeOnly nearbyTime = TimeOnly.MinValue; - - // Act - Action act = () => time.Should().NotBeCloseTo(nearbyTime, 100.Milliseconds()); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect time to be within 100ms from <00:00:00.000>, but it was <00:00:00.050>."); - } - - [Fact] - public void When_asserting_subject_time_is_not_close_to_the_maximum_time_it_should_throw() - { - // Arrange - TimeOnly time = TimeOnly.MaxValue.Add(-50.Milliseconds()); - TimeOnly nearbyTime = TimeOnly.MaxValue; - - // Act - Action act = () => time.Should().NotBeCloseTo(nearbyTime, 100.Milliseconds()); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect time to be within 100ms from <23:59:59.999>, but it was <23:59:59.949>."); - } - - [Fact] - public void A_time_is_not_close_to_a_later_time_when_passing_midnight() - { - // Arrange - TimeOnly time = new(23, 58, 0); - TimeOnly nearbyTime = new(0, 1, 0); - - // Act / Assert - time.Should().NotBeCloseTo(nearbyTime, 2.Minutes()); - } - - [Fact] - public void A_time_is_not_close_to_an_earlier_time_when_passing_midnight() - { - // Arrange - TimeOnly time = new(0, 2, 0); - TimeOnly nearbyTime = new(23, 59, 0); - - // Act / Assert - time.Should().NotBeCloseTo(nearbyTime, 2.Minutes()); - } - - [Fact] - public void A_time_inside_of_the_precision_to_a_later_time_when_passing_midnight_fails() - { - // Arrange - TimeOnly time = new(23, 59, 0); - TimeOnly nearbyTime = new(0, 1, 0); - - // Act - Action act = () => time.Should().NotBeCloseTo(nearbyTime, 2.Minutes()); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect * to be within 2m from <00:01:00.000>*, but it was <23:59:00.000>*"); - } - - [Fact] - public void A_time_inside_of_the_precision_to_an_earlier_time_when_passing_midnight_fails() - { - // Arrange - TimeOnly time = new(0, 1, 0); - TimeOnly nearbyTime = new(23, 59, 0); - - // Act - Action act = () => time.Should().NotBeCloseTo(nearbyTime, 2.Minutes()); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect * to be within 2m from <23:59:00.000>*, but it was <00:01:00.000>*"); - } - } - - public class BeBefore - { - [Fact] - public void When_asserting_subject_is_not_before_earlier_expected_timeonly_it_should_succeed() - { - // Arrange - TimeOnly expected = new(15, 06, 03); - TimeOnly subject = new(15, 06, 04); - - // Act/Assert - subject.Should().NotBeBefore(expected); - } - - [Fact] - public void When_asserting_subject_timeonly_is_before_the_same_timeonly_it_should_throw() - { - // Arrange - TimeOnly expected = new(15, 06, 04); - TimeOnly subject = new(15, 06, 04); - - // Act - Action act = () => subject.Should().BeBefore(expected); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be before <15:06:04.000>, but found <15:06:04.000>."); - } - - [Fact] - public void When_asserting_subject_timeonly_is_not_before_the_same_timeonly_it_should_succeed() - { - // Arrange - TimeOnly expected = new(15, 06, 04); - TimeOnly subject = new(15, 06, 04); - - // Act/Assert - subject.Should().NotBeBefore(expected); - } - - [Fact] - public void When_asserting_subject_timeonly_is_on_or_before_expected_timeonly_should_succeed() - { - // Arrange - TimeOnly subject = new(15, 06, 04, 175); - TimeOnly expectation = new(15, 06, 05, 23); - - // Act/Assert - subject.Should().BeOnOrBefore(expectation); - } - - [Fact] - public void When_asserting_subject_timeonly_is_on_or_before_expected_timeonly_should_throw() - { - // Arrange - TimeOnly subject = new(15, 06, 04, 150); - TimeOnly expectation = new(15, 06, 05, 340); - - // Act - Action act = () => subject.Should().NotBeOnOrBefore(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be after <15:06:05.340>, but found <15:06:04.150>."); - } - - [Fact] - public void - When_asserting_subject_timeonly_is_on_or_before_the_same_time_as_the_expected_timeonly_should_succeed() - { - // Arrange - TimeOnly subject = new(15, 06, 04); - TimeOnly expectation = new(15, 06, 04); - - // Act/Assert - subject.Should().BeOnOrBefore(expectation); - } - - [Fact] - public void When_asserting_subject_timeonly_is_on_or_before_the_same_time_as_the_expected_timeonly_should_throw() - { - // Arrange - TimeOnly subject = new(15, 06, 04, 123); - TimeOnly expectation = new(15, 06, 04, 123); - - // Act - Action act = () => subject.Should().NotBeOnOrBefore(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be after <15:06:04.123>, but found <15:06:04.123>."); - } - - [Fact] - public void When_asserting_subject_timeonly_is_not_on_or_before_earlier_expected_timeonly_should_throw() - { - // Arrange - TimeOnly subject = new(15, 07); - TimeOnly expectation = new(15, 06); - - // Act - Action act = () => subject.Should().BeOnOrBefore(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be on or before <15:06:00.000>, but found <15:07:00.000>."); - } - - [Fact] - public void When_asserting_subject_timeonly_is_not_on_or_before_earlier_expected_timeonly_should_succeed() - { - // Arrange - TimeOnly subject = new(15, 06, 04); - TimeOnly expectation = new(15, 06, 03); - - // Act/Assert - subject.Should().NotBeOnOrBefore(expectation); - } - } - - public class BeAfter - { - [Fact] - public void When_asserting_subject_timeonly_is_after_earlier_expected_timeonly_should_succeed() - { - // Arrange - TimeOnly subject = new(15, 06, 04, 123); - TimeOnly expectation = new(15, 06, 03, 45); - - // Act/Assert - subject.Should().BeAfter(expectation); - } - - [Fact] - public void When_asserting_subject_timeonly_is_not_after_earlier_expected_timeonly_should_throw() - { - // Arrange - TimeOnly subject = new(15, 06, 04); - TimeOnly expectation = new(15, 06, 03); - - // Act - Action act = () => subject.Should().NotBeAfter(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be on or before <15:06:03.000>, but found <15:06:04.000>."); - } - - [Fact] - public void When_asserting_subject_timeonly_is_after_later_expected_timeonly_should_throw() - { - // Arrange - TimeOnly subject = new(15, 06, 04); - TimeOnly expectation = new(15, 06, 05); - - // Act - Action act = () => subject.Should().BeAfter(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be after <15:06:05.000>, but found <15:06:04.000>."); - } - - [Fact] - public void When_asserting_subject_timeonly_is_not_after_later_expected_timeonly_should_succeed() - { - // Arrange - TimeOnly subject = new(15, 06, 04); - TimeOnly expectation = new(15, 06, 05); - - // Act/Assert - subject.Should().NotBeAfter(expectation); - } - - [Fact] - public void When_asserting_subject_timeonly_is_after_the_same_expected_timeonly_should_throw() - { - // Arrange - TimeOnly subject = new(15, 06, 04, 145); - TimeOnly expectation = new(15, 06, 04, 145); - - // Act - Action act = () => subject.Should().BeAfter(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be after <15:06:04.145>, but found <15:06:04.145>."); - } - - [Fact] - public void When_asserting_subject_timeonly_is_not_after_the_same_expected_timeonly_should_succeed() - { - // Arrange - TimeOnly subject = new(15, 06, 04, 123); - TimeOnly expectation = new(15, 06, 04, 123); - - // Act/Assert - subject.Should().NotBeAfter(expectation); - } - - [Fact] - public void When_asserting_subject_timeonly_is_on_or_after_earlier_expected_timeonly_should_succeed() - { - // Arrange - TimeOnly subject = new(15, 07); - TimeOnly expectation = new(15, 06); - - // Act/Assert - subject.Should().BeOnOrAfter(expectation); - } - - [Fact] - public void When_asserting_subject_timeonly_is_not_on_or_after_earlier_expected_timeonly_should_throw() - { - // Arrange - TimeOnly subject = new(15, 06, 04); - TimeOnly expectation = new(15, 06, 03); - - // Act - Action act = () => subject.Should().NotBeOnOrAfter(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be before <15:06:03.000>, but found <15:06:04.000>."); - } - - [Fact] - public void When_asserting_subject_timeonly_is_on_or_after_the_same_expected_timeonly_should_succeed() - { - // Arrange - TimeOnly subject = new(15, 06, 04); - TimeOnly expectation = new(15, 06, 04); - - // Act/Assert - subject.Should().BeOnOrAfter(expectation); - } - - [Fact] - public void When_asserting_subject_timeonly_is_not_on_or_after_the_same_expected_timeonly_should_throw() - { - // Arrange - TimeOnly subject = new(15, 06); - TimeOnly expectation = new(15, 06); - - // Act - Action act = () => subject.Should().NotBeOnOrAfter(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be before <15:06:00.000>, but found <15:06:00.000>."); - } - - [Fact] - public void When_asserting_subject_timeonly_is_on_or_after_later_expected_timeonly_should_throw() - { - // Arrange - TimeOnly subject = new(15, 06, 04); - TimeOnly expectation = new(15, 06, 05); - - // Act - Action act = () => subject.Should().BeOnOrAfter(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected subject to be on or after <15:06:05.000>, but found <15:06:04.000>."); - } - - [Fact] - public void When_asserting_subject_timeonly_is_not_on_or_after_later_expected_timeonly_should_succeed() - { - // Arrange - TimeOnly subject = new(15, 06, 04); - TimeOnly expectation = new(15, 06, 05); - - // Act/Assert - subject.Should().NotBeOnOrAfter(expectation); - } - } - - public class HaveHours - { - [Fact] - public void When_asserting_subject_timeonly_should_have_hours_with_the_same_value_should_succeed() - { - // Arrange - TimeOnly subject = new(15, 12, 31); - const int expectation = 15; - - // Act/Assert - subject.Should().HaveHours(expectation); - } - - [Fact] - public void When_asserting_subject_timeonly_should_not_have_hours_with_the_same_value_should_throw() - { - // Arrange - TimeOnly subject = new(15, 12, 31); - const int expectation = 15; - - // Act - Action act = () => subject.Should().NotHaveHours(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the hours part of subject to be 15, but it was."); - } - - [Fact] - public void When_asserting_subject_timeonly_should_have_hours_with_a_different_value_should_throw() - { - // Arrange - TimeOnly subject = new(15, 12, 31); - const int expectation = 14; - - // Act - Action act = () => subject.Should().HaveHours(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the hours part of subject to be 14, but found 15."); - } - - [Fact] - public void When_asserting_subject_timeonly_should_not_have_hours_with_a_different_value_should_succeed() - { - // Arrange - TimeOnly subject = new(21, 12, 31); - const int expectation = 23; - - // Act/Assert - subject.Should().NotHaveHours(expectation); - } - - [Fact] - public void When_asserting_subject_null_timeonly_should_have_hours_should_throw() - { - // Arrange - TimeOnly? subject = null; - const int expectation = 21; - - // Act - Action act = () => subject.Should().HaveHours(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the hours part of subject to be 21, but found ."); - } - - [Fact] - public void When_asserting_subject_null_timeonly_should_not_have_hours_should_throw() - { - // Arrange - TimeOnly? subject = null; - const int expectation = 19; - - // Act - Action act = () => subject.Should().NotHaveHours(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the hours part of subject to be 19, but found a TimeOnly."); - } - } - - public class HaveMinutes - { - [Fact] - public void When_asserting_subject_timeonly_should_have_minutes_with_the_same_value_it_should_succeed() - { - // Arrange - TimeOnly subject = new(21, 12, 31); - const int expectation = 12; - - // Act/Assert - subject.Should().HaveMinutes(expectation); - } - - [Fact] - public void When_asserting_subject_timeonly_should_not_have_minutes_with_the_same_value_it_should_throw() - { - // Arrange - TimeOnly subject = new(21, 12, 31); - const int expectation = 12; - - // Act - Action act = () => subject.Should().NotHaveMinutes(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the minutes part of subject to be 12, but it was."); - } - - [Fact] - public void When_asserting_subject_timeonly_should_have_a_minute_with_a_different_value_it_should_throw() - { - // Arrange - TimeOnly subject = new(15, 12, 31); - const int expectation = 11; - - // Act - Action act = () => subject.Should().HaveMinutes(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the minutes part of subject to be 11, but found 12."); - } - - [Fact] - public void When_asserting_subject_timeonly_should_not_have_a_minute_with_a_different_value_it_should_succeed() - { - // Arrange - TimeOnly subject = new(15, 12, 31); - const int expectation = 11; - - // Act/Assert - subject.Should().NotHaveMinutes(expectation); - } - - [Fact] - public void When_asserting_subject_null_timeonly_should_have_minutes_should_throw() - { - // Arrange - TimeOnly? subject = null; - const int expectation = 12; - - // Act - Action act = () => subject.Should().HaveMinutes(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the minutes part of subject to be 12, but found a TimeOnly."); - } - - [Fact] - public void When_asserting_subject_null_timeonly_should_not_have_minutes_should_throw() - { - // Arrange - TimeOnly? subject = null; - const int expectation = 12; - - // Act - Action act = () => subject.Should().NotHaveMinutes(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the minutes part of subject to be 12, but found a TimeOnly."); - } - } - - public class HaveSeconds - { - [Fact] - public void When_asserting_subject_timeonly_should_have_seconds_with_the_same_value_it_should_succeed() - { - // Arrange - TimeOnly subject = new(14, 12, 31); - const int expectation = 31; - - // Act/Assert - subject.Should().HaveSeconds(expectation); - } - - [Fact] - public void When_asserting_subject_timeonly_should_not_have_seconds_with_the_same_value_it_should_throw() - { - // Arrange - TimeOnly subject = new(14, 12, 31); - const int expectation = 31; - - // Act - Action act = () => subject.Should().NotHaveSeconds(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the seconds part of subject to be 31, but it was."); - } - - [Fact] - public void When_asserting_subject_timeonly_should_have_seconds_with_a_different_value_it_should_throw() - { - // Arrange - TimeOnly subject = new(15, 12, 31); - const int expectation = 30; - - // Act - Action act = () => subject.Should().HaveSeconds(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the seconds part of subject to be 30, but found 31."); - } - - [Fact] - public void When_asserting_subject_timeonly_should_not_have_seconds_with_a_different_value_it_should_succeed() - { - // Arrange - TimeOnly subject = new(15, 12, 31); - const int expectation = 30; - - // Act/Assert - subject.Should().NotHaveSeconds(expectation); - } - - [Fact] - public void When_asserting_subject_null_timeonly_should_have_seconds_should_throw() - { - // Arrange - TimeOnly? subject = null; - const int expectation = 22; - - // Act - Action act = () => subject.Should().HaveSeconds(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the seconds part of subject to be 22, but found a TimeOnly."); - } - - [Fact] - public void When_asserting_subject_null_timeonly_should_not_have_seconds_should_throw() - { - // Arrange - TimeOnly? subject = null; - const int expectation = 22; - - // Act - Action act = () => subject.Should().NotHaveSeconds(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the seconds part of subject to be 22, but found a TimeOnly."); - } - } - - public class HaveMilliseconds - { - [Fact] - public void When_asserting_subject_timeonly_should_have_milliseconds_with_the_same_value_it_should_succeed() - { - // Arrange - TimeOnly subject = new(14, 12, 31, 123); - const int expectation = 123; - - // Act/Assert - subject.Should().HaveMilliseconds(expectation); - } - - [Fact] - public void When_asserting_subject_timeonly_should_not_have_milliseconds_with_the_same_value_it_should_throw() - { - // Arrange - TimeOnly subject = new(14, 12, 31, 445); - const int expectation = 445; - - // Act - Action act = () => subject.Should().NotHaveMilliseconds(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the milliseconds part of subject to be 445, but it was."); - } - - [Fact] - public void When_asserting_subject_timeonly_should_have_milliseconds_with_a_different_value_it_should_throw() - { - // Arrange - TimeOnly subject = new(15, 12, 31, 555); - const int expectation = 12; - - // Act - Action act = () => subject.Should().HaveMilliseconds(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the milliseconds part of subject to be 12, but found 555."); - } - - [Fact] - public void When_asserting_subject_timeonly_should_not_have_milliseconds_with_a_different_value_it_should_succeed() - { - // Arrange - TimeOnly subject = new(15, 12, 31, 445); - const int expectation = 31; - - // Act/Assert - subject.Should().NotHaveMilliseconds(expectation); - } - - [Fact] - public void When_asserting_subject_null_timeonly_should_have_milliseconds_should_throw() - { - // Arrange - TimeOnly? subject = null; - const int expectation = 22; - - // Act - Action act = () => subject.Should().HaveMilliseconds(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Expected the milliseconds part of subject to be 22, but found a TimeOnly."); - } - - [Fact] - public void When_asserting_subject_null_timeonly_should_not_have_milliseconds_should_throw() - { - // Arrange - TimeOnly? subject = null; - const int expectation = 22; - - // Act - Action act = () => subject.Should().NotHaveMilliseconds(expectation); - - // Assert - act.Should().Throw() - .WithMessage("Did not expect the milliseconds part of subject to be 22, but found a TimeOnly."); - } - } - - public class BeOneOf - { - [Fact] - public void When_a_value_is_not_one_of_the_specified_values_it_should_throw() - { - // Arrange - TimeOnly value = new(15, 12, 20); - - // Act - Action action = () => value.Should().BeOneOf(value.AddHours(1), value.AddMinutes(-1)); - - // Assert - action.Should().Throw() - .WithMessage("Expected value to be one of {<16:12:20.000>, <15:11:20.000>}, but found <15:12:20.000>."); - } - - [Fact] - public void When_a_value_is_one_of_the_specified_values_it_should_succeed() - { - // Arrange - TimeOnly value = new(15, 12, 30); - - // Act/Assert - value.Should().BeOneOf(new TimeOnly(4, 1, 30), new TimeOnly(15, 12, 30)); - } - - [Fact] - public void When_a_null_value_is_not_one_of_the_specified_values_it_should_throw() - { - // Arrange - TimeOnly? value = null; - - // Act - Action action = () => value.Should().BeOneOf(new TimeOnly(15, 1, 30), new TimeOnly(5, 4, 10, 123)); - - // Assert - action.Should().Throw() - .WithMessage("Expected value to be one of {<15:01:30.000>, <05:04:10.123>}, but found ."); - } - - [Fact] - public void When_a_value_is_one_of_the_specified_values_it_should_succeed_when_timeonly_is_null() - { - // Arrange - TimeOnly? value = null; - - // Act/Assert - value.Should().BeOneOf(new TimeOnly(15, 1, 30), null); - } - } - - public class NotBe - { - [Fact] - public void Different_timeonly_values_are_valid() - { - // Arrange - TimeOnly time = new(19, 06, 04); - TimeOnly otherTime = new(20, 06, 05); - - // Act & Assert - time.Should().NotBe(otherTime); - } - - [Fact] - public void Different_timeonly_values_with_different_nullability_are_valid() - { - // Arrange - TimeOnly time = new(19, 06, 04); - TimeOnly? otherTime = new(19, 07, 05); - - // Act & Assert - time.Should().NotBe(otherTime); - } - - [Fact] - public void Same_timeonly_values_are_invalid() - { - // Arrange - TimeOnly time = new(19, 06, 04); - TimeOnly sameTime = new(19, 06, 04); - - // Act - Action act = - () => time.Should().NotBe(sameTime, "because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw() - .WithMessage( - "Expected time not to be <19:06:04.000> because we want to test the failure message, but it is."); - } - - [Fact] - public void Same_timeonly_values_with_different_nullability_are_invalid() - { - // Arrange - TimeOnly time = new(19, 06, 04); - TimeOnly? sameTime = new(19, 06, 04); - - // Act - Action act = - () => time.Should().NotBe(sameTime, "because we want to test the failure {0}", "message"); - - // Assert - act.Should().Throw() - .WithMessage( - "Expected time not to be <19:06:04.000> because we want to test the failure message, but it is."); - } - } - - public class AndChaining + [Fact] + public void Should_support_chaining_constraints_with_and() { - [Fact] - public void Should_support_chaining_constraints_with_and() - { - // Arrange - TimeOnly earlierTimeOnly = new(15, 06, 03); - TimeOnly? nullableTimeOnly = new(15, 06, 04); + // Arrange + TimeOnly earlierTimeOnly = new(15, 06, 03); + TimeOnly? nullableTimeOnly = new(15, 06, 04); - // Act/Assert - nullableTimeOnly.Should() - .HaveValue() - .And - .BeAfter(earlierTimeOnly); - } + // Act/Assert + nullableTimeOnly.Should() + .HaveValue() + .And + .BeAfter(earlierTimeOnly); } - public class Miscellaneous + [Fact] + public void Should_throw_a_helpful_error_when_accidentally_using_equals() { - [Fact] - public void Should_throw_a_helpful_error_when_accidentally_using_equals() - { - // Arrange - TimeOnly someTimeOnly = new(21, 1); + // Arrange + TimeOnly someTimeOnly = new(21, 1); - // Act - var act = () => someTimeOnly.Should().Equals(null); + // Act + var act = () => someTimeOnly.Should().Equals(null); - // Assert - act.Should().Throw() - .WithMessage("Equals is not part of Fluent Assertions. Did you mean Be() instead?"); - } + // Assert + act.Should().Throw() + .WithMessage("Equals is not part of Fluent Assertions. Did you mean Be() instead?"); } } diff --git a/Tests/FluentAssertions.Specs/Specialized/ActionAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Specialized/ActionAssertionSpecs.cs new file mode 100644 index 0000000000..fd60ac85a8 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Specialized/ActionAssertionSpecs.cs @@ -0,0 +1,50 @@ +using System; +using Xunit; + +namespace FluentAssertions.Specs.Specialized; + +public class ActionAssertionSpecs +{ + [Fact] + public void Null_clock_throws_exception() + { + // Arrange + Action subject = () => { }; + + // Act + var act = void () => subject.Should(clock: null).NotThrow(); + + // Assert + act.Should().ThrowExactly() + .WithParameterName("clock"); + } + + public class Throw + { + [Fact] + public void Allow_additional_assertions_on_return_value() + { + // Arrange + var exception = new Exception("foo"); + Action subject = () => throw exception; + + // Act / Assert + subject.Should().Throw() + .Which.Message.Should().Be("foo"); + } + } + + public class NotThrow + { + [Fact] + public void Allow_additional_assertions_on_return_value() + { + // Arrange + Action subject = () => { }; + + // Act / Assert + subject.Should().NotThrow() + .And.NotBeNull(); + } + } +} diff --git a/Tests/FluentAssertions.Specs/Specialized/AssemblyAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Specialized/AssemblyAssertionSpecs.cs index 5b109ee474..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() @@ -302,6 +318,16 @@ public void Throws_for_null_subject() act.Should().Throw() .WithMessage("Can't check for assembly signing if nullAssembly reference is ."); } + + [Fact] + public void Chaining_after_one_assertion() + { + // Arrange + var unsignedAssembly = FindAssembly.Stub(""); + + // Act & Assert + unsignedAssembly.Should().BeUnsigned().And.NotBeNull(); + } } public class BeSignedWithPublicKey @@ -362,6 +388,17 @@ public void Throws_for_null_assembly() act.Should().Throw() .WithMessage("Can't check for assembly signing if nullAssembly reference is ."); } + + [Fact] + public void Chaining_after_one_assertion() + { + // Arrange + var key = "0123456789ABCEF007"; + var signedAssembly = FindAssembly.Stub(key); + + // Act & Assert + signedAssembly.Should().BeSignedWithPublicKey(key).And.NotBeNull(); + } } } diff --git a/Tests/FluentAssertions.Specs/Specialized/DelegateAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Specialized/DelegateAssertionSpecs.cs index 781d70b3a6..68e8253b52 100644 --- a/Tests/FluentAssertions.Specs/Specialized/DelegateAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Specialized/DelegateAssertionSpecs.cs @@ -1,6 +1,5 @@ using System; using FluentAssertions.Execution; -using FluentAssertions.Specialized; using Xunit; namespace FluentAssertions.Specs.Specialized; @@ -8,32 +7,32 @@ namespace FluentAssertions.Specs.Specialized; public class DelegateAssertionSpecs { [Fact] - public void When_injecting_a_null_extractor_it_should_throw() + public void Null_clock_throws_exception() { // Arrange - Action subject = () => { }; + Func subject = () => 1; // Act - Func act = () => new ActionAssertions(subject, extractor: null); + var act = void () => subject.Should(clock: null).NotThrow(); - // Act + // Assert act.Should().ThrowExactly() - .WithParameterName("extractor"); + .WithParameterName("clock"); } - [Fact] - public void When_injecting_a_null_clock_it_should_throw() + public class Throw { - // Arrange - Action subject = () => { }; - IExtractExceptions extractor = new AggregateExceptionExtractor(); - - // Act - Func act = () => new ActionAssertions(subject, extractor, clock: null); + [Fact] + public void Allow_additional_assertions_on_return_value() + { + // Arrange + var exception = new Exception("foo"); + Action subject = () => throw exception; - // Act - act.Should().ThrowExactly() - .WithParameterName("clock"); + // Act / Assert + subject.Should().Throw() + .Which.Message.Should().Be("foo"); + } } public class ThrowExactly diff --git a/Tests/FluentAssertions.Specs/Specialized/ExecutionTimeAssertionsSpecs.cs b/Tests/FluentAssertions.Specs/Specialized/ExecutionTimeAssertionsSpecs.cs index 74af166f13..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; @@ -95,12 +96,24 @@ public void Actions_with_brackets_fail_with_correctly_formatted_message() // Act Action act = () => - subject.ExecutionTimeOf(s => s.AddRange(new object[] { })).Should().BeLessOrEqualTo(1.Nanoseconds()); + subject.ExecutionTimeOf(s => s.AddRange(new object[] { })).Should().BeLessThanOrEqualTo(1.Nanoseconds()); // Assert act.Should().ThrowExactly() .Which.Message.Should().Contain("{}").And.NotContain("{0}"); } + + [Fact] + public void Chaining_after_one_assertion() + { + // Arrange + var subject = new SleepingClass(); + + // Act / Assert + subject.ExecutionTimeOf(s => s.Sleep(0)) + .Should().BeLessThanOrEqualTo(500.Milliseconds()) + .And.BeCloseTo(0.Seconds(), 500.Milliseconds()); + } } public class BeLessThan @@ -310,6 +323,18 @@ public void Actions_with_brackets_fail_with_correctly_formatted_message() act.Should().ThrowExactly() .Which.Message.Should().Contain("{}").And.NotContain("{0}"); } + + [Fact] + public void Chaining_after_one_assertion() + { + // Arrange + var subject = new SleepingClass(); + + // Act / Assert + subject.ExecutionTimeOf(s => s.Sleep(100)) + .Should().BeGreaterThanOrEqualTo(50.Milliseconds()) + .And.BeCloseTo(0.Seconds(), 500.Milliseconds()); + } } public class BeGreaterThan @@ -556,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/JsonNodeSpecs.cs b/Tests/FluentAssertions.Specs/Specialized/JsonNodeSpecs.cs new file mode 100644 index 0000000000..6fe61c4e08 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Specialized/JsonNodeSpecs.cs @@ -0,0 +1,677 @@ +#if NET6_0_OR_GREATER + +using System.Collections.Generic; +using System.Globalization; +using System.Text.Json.Nodes; +using FluentAssertions.Extensions; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Specialized; + +public class JsonNodeSpecs +{ + public class HaveProperty + { + [Fact] + public void Can_ensure_a_property_exists() + { + // Arrange + var jsonNode = JsonNode.Parse("{ \"name\": \"John\" }"); + + // Act + var act = () => jsonNode.Should().HaveProperty("code", "that is what we expect"); + + // Assert + act.Should().Throw("Expected jsonNode to have property \"code\" because that is what we expect"); + } + + [Fact] + public void Can_continue_with_the_value_of_a_property() + { + // Arrange + var jsonNode = JsonNode.Parse("{ \"name\": \"John\" }"); + + // Act + var subject = jsonNode.Should().HaveProperty("name").Which; + + // Assert + subject.ToString().Should().Be("John"); + } + + [Fact] + public void Can_ensure_a_property_does_not_exist() + { + // Arrange + var jsonNode = JsonNode.Parse("{ \"name\": \"John\" }"); + + // Act & Assert + jsonNode.Should().NotHaveProperty("code"); + } + + [Fact] + public void Fails_for_an_unexpected_property() + { + // Arrange + var jsonNode = JsonNode.Parse("{ \"name\": \"John\" }"); + + // Act + var act = () => jsonNode.Should().NotHaveProperty("name", "because we expect it not to exist"); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect jsonNode to have property \"name\" because we expect it not to exist."); + } + + [Fact] + public void Cannot_check_for_a_property_on_a_null_node() + { + // Arrange + JsonNode jsonNode = null; + + // Act + var act = () => jsonNode.Should().HaveProperty("name"); + + // Assert + act.Should().Throw() + .WithMessage("Cannot assert the existence of a property on a JSON node*"); + } + + [Fact] + public void Cannot_check_for_the_absence_of_property_on_a_null_node() + { + // Arrange + JsonNode jsonNode = null; + + // Act + var act = () => jsonNode.Should().NotHaveProperty("name"); + + // Assert + act.Should().Throw() + .WithMessage("Cannot assert the existence of a property on a JSON node*"); + } + } + + public class BeArray + { + [Fact] + public void Can_ensure_a_json_fragment_is_an_array() + { + // Arrange + var jsonNode = JsonNode.Parse("[1, 2, 3]"); + + // Act + IEnumerable items = jsonNode.Should().BeAnArray().Which; + + // Assert + items.Should().HaveCount(3); + } + + [Fact] + public void Fails_for_a_non_array_json_fragment() + { + // Arrange + var jsonNode = JsonNode.Parse("{ \"name\": \"John\" }"); + + // Act + var act = () => jsonNode.Should().BeAnArray("we expect it to be an array"); + + // Assert + act.Should().Throw() + .WithMessage("Expected jsonNode to be an array because we expect it to be an array, but* is not."); + } + + [Fact] + public void Can_ensure_json_is_not_an_array() + { + // Arrange + var jsonNode = JsonNode.Parse("{ \"name\": \"John\" }"); + + // Act & Assert + jsonNode.Should().NotBeAnArray(); + } + + [Fact] + public void Fails_for_an_unexpected_array_fragment() + { + // Arrange + var jsonNode = JsonNode.Parse("[1, 2, 3]"); + + // Act + var act = () => jsonNode.Should().NotBeAnArray("we expect it not to be an array"); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect jsonNode to be an array because we expect it not to be an array, but*1*is."); + } + + [Fact] + public void A_null_node_cannot_be_an_array() + { + // Arrange + JsonNode jsonNode = null; + + // Act + var act = () => jsonNode.Should().BeAnArray(); + + // Assert + act.Should().Throw().WithMessage("Expected jsonNode to be an array*"); + } + + [Fact] + public void A_null_node_cannot_not_be_an_array() + { + // Arrange + JsonNode jsonNode = null; + + // Act & Assert + jsonNode.Should().NotBeAnArray(); + } + } + + public class BeNumeric + { + [Fact] + public void The_minimal_double_value_is_numeric() + { + // Arrange + var jsonNode = JsonNode.Parse(double.MinValue.ToString(CultureInfo.InvariantCulture)); + + // Act & Assert + jsonNode.Should().BeNumeric(); + } + + [Fact] + public void The_maximum_double_value_is_numeric() + { + // Arrange + var jsonNode = JsonNode.Parse(double.MaxValue.ToString(CultureInfo.InvariantCulture)); + + // Act & Assert + jsonNode.Should().BeNumeric(); + } + + [Fact] + public void The_minimal_long_value_is_numeric() + { + // Arrange + var jsonNode = JsonNode.Parse(long.MinValue.ToString(CultureInfo.InvariantCulture)); + + // Act & Assert + jsonNode.Should().BeNumeric(); + } + + [Fact] + public void The_maximum_long_value_is_numeric() + { + // Arrange + var jsonNode = JsonNode.Parse(long.MaxValue.ToString(CultureInfo.InvariantCulture)); + + // Act & Assert + jsonNode.Should().BeNumeric(); + } + + [Fact] + public void The_maximum_unsigned_long_value_is_numeric() + { + // Arrange + var jsonNode = JsonNode.Parse(ulong.MaxValue.ToString(CultureInfo.InvariantCulture)); + + // Act & Assert + jsonNode.Should().BeNumeric(); + } + + [Fact] + public void Can_return_the_actual_value() + { + // Arrange + var jsonNode = JsonNode.Parse("42"); + + // Act & Assert + jsonNode.Should().BeNumeric().Which.Should().Be("42"); + } + + [Fact] + public void Can_return_the_actual_numeric_value_as_a_specific_type() + { + // Arrange + var jsonNode = JsonNode.Parse("42"); + + // Act & Assert + jsonNode.Should().BeNumeric().Which.Should().Be(42); + } + + [Fact] + public void Includes_the_reason_for_something_other_than_a_number() + { + // Arrange + var jsonNode = JsonNode.Parse("\"not a number\""); + + // Act + var act = () => jsonNode.Should().BeNumeric("we expect an int"); + + // Assert + act.Should().Throw() + .WithMessage("Expected jsonNode to be a numeric value because we expect an int, but \"not a number\" is not."); + } + + [Fact] + public void Can_ensure_a_string_is_not_a_number() + { + // Arrange + var jsonNode = JsonNode.Parse("\"not a number\""); + + // Act & Assert + jsonNode.Should().NotBeNumeric(); + } + + [Fact] + public void Can_ensure_a_ulong_is_a_number() + { + // Arrange + var jsonNode = JsonNode.Parse(ulong.MaxValue.ToString(CultureInfo.InvariantCulture)); + + // Act + var act = () => jsonNode.Should().NotBeNumeric(); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Can_ensure_a_double_is_a_number() + { + // Arrange + var jsonNode = JsonNode.Parse(double.MaxValue.ToString(CultureInfo.InvariantCulture)); + + // Act + var act = () => jsonNode.Should().NotBeNumeric(); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Includes_a_reason_for_an_unexpected_number() + { + // Arrange + var jsonNode = JsonNode.Parse("42"); + + // Act + var act = () => jsonNode.Should().NotBeNumeric("we expect something else"); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect jsonNode to be a numeric value because we expect something else, but 42 is."); + } + + [Fact] + public void Fails_while_asserting_a_null_json_is_a_number() + { + // Arrange + JsonNode jsonNode = null; + + // Act + var act = () => jsonNode.Should().BeNumeric(); + + // Assert + act.Should().Throw().WithMessage("Expected jsonNode to be a numeric value*"); + } + + [Fact] + public void A_null_node_is_not_a_number() + { + // Arrange + JsonNode jsonNode = null; + + // Act & Assert + jsonNode.Should().NotBeNumeric(); + } + } + + public class BeLocalDate + { + [Fact] + public void Can_ensure_json_is_a_local_date() + { + // Arrange + var jsonNode = JsonNode.Parse("\"2025-09-11T21:17:00\""); + + // Act & Assert + jsonNode.Should().BeLocalDate().Which.Should().Be(11.September(2025).At(21, 17).AsLocal()); + } + + [Fact] + public void Fails_for_something_that_is_not_a_date() + { + // Arrange + var jsonNode = JsonNode.Parse("\"not a date\""); + + // Act + var act = () => jsonNode.Should().BeLocalDate("we expect a local date"); + + // Assert + act.Should().Throw() + .WithMessage("Expected jsonNode to be a local date because we expect a local date, but \"not a date\" is not."); + } + + [Fact] + public void A_utc_date_is_not_a_local_date() + { + // Arrange + var jsonNode = JsonNode.Parse("\"2025-09-11T21:17:00Z\""); + + // Act + var act = () => jsonNode.Should().BeLocalDate("we don't expect UTC"); + + // Assert + act.Should().Throw() + .WithMessage("Expected jsonNode to be a local date because we don't expect UTC, but*17:00Z*is not."); + } + + [Fact] + public void A_null_node_is_not_a_local_date() + { + // Arrange + JsonNode jsonNode = null; + + // Act + var act = () => jsonNode.Should().BeLocalDate(); + + // Assert + act.Should().Throw().WithMessage("Expected jsonNode to be a local date*"); + } + + [Fact] + public void An_arbitrary_string_is_not_a_date() + { + // Arrange + var jsonNode = JsonNode.Parse("\"not a date\""); + + // Act & Assert + jsonNode.Should().NotBeLocalDate(); + } + + [Fact] + public void Fails_for_a_local_date() + { + // Arrange + var jsonNode = JsonNode.Parse("\"2025-09-11T21:17:00\""); + + // Act + var act = () => jsonNode.Should().NotBeLocalDate("we expect something else"); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect jsonNode to be a local date because we expect something else, but*21:17* is."); + } + + [Fact] + public void A_utc_date_is_not_a_local_date_as_expected() + { + // Arrange + var jsonNode = JsonNode.Parse("\"2025-09-11T21:17:00Z\""); + + // Act & Assert + jsonNode.Should().NotBeLocalDate("we expect something else"); + } + + [Fact] + public void A_null_node_is_correctly_identified_as_not_a_local_date() + { + // Arrange + JsonNode jsonNode = null; + + // Act & Assert + jsonNode.Should().NotBeLocalDate(); + } + } + + public class BeUtcDate + { + [Fact] + public void Can_ensure_json_is_a_utc_date() + { + // Arrange + var jsonNode = JsonNode.Parse("\"2025-09-11T21:17:00Z\""); + + // Act & Assert + jsonNode.Should().BeUtcDate().Which.Should().Be(11.September(2025).At(21, 17).AsUtc()); + } + + [Fact] + public void Fails_for_something_that_is_not_a_date() + { + // Arrange + var jsonNode = JsonNode.Parse("\"not a date\""); + + // Act + var act = () => jsonNode.Should().BeUtcDate("we expect that"); + + // Assert + act.Should().Throw() + .WithMessage("Expected jsonNode to be a UTC date because we expect that, but \"not a date\" is not."); + } + + [Fact] + public void A_local_date_is_not_an_utc_date() + { + // Arrange + var jsonNode = JsonNode.Parse("\"2025-09-11T21:17:00\""); + + // Act + var act = () => jsonNode.Should().BeUtcDate("we don't expect local"); + + // Assert + act.Should().Throw() + .WithMessage("Expected jsonNode to be a UTC date because we don't expect local, but*17:00*is not."); + } + + [Fact] + public void A_null_node_is_not_a_local_date() + { + // Arrange + JsonNode jsonNode = null; + + // Act + var act = () => jsonNode.Should().BeUtcDate(); + + // Assert + act.Should().Throw().WithMessage("Expected jsonNode to be a UTC date, but*null*is not."); + } + + [Fact] + public void An_arbitrary_string_is_not_a_date() + { + // Arrange + var jsonNode = JsonNode.Parse("\"not a date\""); + + // Act & Assert + jsonNode.Should().NotBeUtcDate(); + } + + [Fact] + public void Fails_for_a_utc_date() + { + // Arrange + var jsonNode = JsonNode.Parse("\"2025-09-11T21:17:00Z\""); + + // Act + var act = () => jsonNode.Should().NotBeUtcDate("we expect something else"); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect jsonNode to be a UTC date because we expect something else, but*21:17* is."); + } + + [Fact] + public void A_local_date_is_not_a_utc_date_as_expected() + { + // Arrange + var jsonNode = JsonNode.Parse("\"2025-09-11T21:17:00\""); + + // Act & Assert + jsonNode.Should().NotBeUtcDate("we expect something else"); + } + + [Fact] + public void A_null_node_is_correctly_identified_as_not_a_UTC_date() + { + // Arrange + JsonNode jsonNode = null; + + // Act & Assert + jsonNode.Should().NotBeUtcDate(); + } + } + + public class BeBool + { + [Fact] + public void Can_ensure_the_json_fragment_is_a_bool() + { + // Arrange + var jsonNode = JsonNode.Parse("true"); + + // Act & Assert + jsonNode.Should().BeBool().Which.Should().BeTrue(); + } + + [Fact] + public void Fails_for_a_json_fragment_that_is_not_a_bool() + { + // Arrange + var jsonNode = JsonNode.Parse("\"not a boolean\""); + + // Act + var act = () => jsonNode.Should().BeBool("we expect a boolean"); + + // Assert + act.Should().Throw() + .WithMessage("Expected jsonNode to be a boolean because we expect a boolean, but \"not a boolean\" is not."); + } + + [Fact] + public void Null_is_not_a_json_fragment() + { + // Arrange + JsonNode jsonNode = null; + + // Act + var act = () => jsonNode.Should().BeBool(); + + // Assert + act.Should().Throw().WithMessage("Expected jsonNode to be a boolean, but is not."); + } + + [Fact] + public void Can_ensure_json_is_not_a_bool() + { + // Arrange + var jsonNode = JsonNode.Parse("\"not a boolean\""); + + // Act & Assert + jsonNode.Should().NotBeBool(); + } + + [Fact] + public void Fails_for_a_json_fragment_is_a_bool_after_all() + { + // Arrange + var jsonNode = JsonNode.Parse("true"); + + // Act + var act = () => jsonNode.Should().NotBeBool("we are expected something else"); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect jsonNode to be a boolean because we are expected something else, but*true* is."); + } + + [Fact] + public void Null_is_not_a_bool_as_expected() + { + // Arrange + JsonNode jsonNode = null; + + // Act & Assert + jsonNode.Should().NotBeBool(); + } + } + + public class BeString + { + [Fact] + public void Can_ensure_json_is_string() + { + // Arrange + var jsonNode = JsonNode.Parse("\"Hello World\""); + + // Act & Assert + jsonNode.Should().BeString().Which.Should().Be("Hello World"); + } + + [Fact] + public void Fails_for_anything_but_a_string() + { + // Arrange + var jsonNode = JsonNode.Parse("42"); + + // Act + var act = () => jsonNode.Should().BeString("we expect a string"); + + // Assert + act.Should().Throw() + .WithMessage("Expected jsonNode to be a string because we expect a string, but 42 is not."); + } + + [Fact] + public void A_null_value_is_not_a_json_string() + { + // Arrange + JsonNode jsonNode = null; + + // Act + var act = () => jsonNode.Should().BeString(); + + // Assert + act.Should().Throw().WithMessage("Expected jsonNode to be a string*"); + } + + [Fact] + public void Can_ensure_json_is_not_string() + { + // Arrange + var jsonNode = JsonNode.Parse("42"); + + // Act & Assert + jsonNode.Should().NotBeString(); + } + + [Fact] + public void Fails_for_a_string_that_is_not_expected() + { + // Arrange + var jsonNode = JsonNode.Parse("\"Hello World\""); + + // Act + var act = () => jsonNode.Should().NotBeString("we expect something else"); + + // Assert + act.Should().Throw() + .WithMessage("Did not expect jsonNode to be a string because we expect something else, but*Hello World*is."); + } + + [Fact] + public void A_null_value_is_not_a_string_as_expected() + { + // Arrange + JsonNode jsonNode = null; + + // Act & Assert + jsonNode.Should().NotBeString(); + } + } +} + +#endif diff --git a/Tests/FluentAssertions.Specs/Specialized/TaskAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Specialized/TaskAssertionSpecs.cs index dcab1926ff..1e2aa6ec3a 100644 --- a/Tests/FluentAssertions.Specs/Specialized/TaskAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Specialized/TaskAssertionSpecs.cs @@ -5,7 +5,6 @@ using FluentAssertions.Extensions; using Xunit; using Xunit.Sdk; - using static FluentAssertions.FluentActions; namespace FluentAssertions.Specs.Specialized; @@ -28,6 +27,38 @@ public void When_getting_the_subject_it_should_remain_unchanged() } } + public class NotThrow + { + [Fact] + public void Chaining_after_one_assertion() + { + // Arrange + Func> subject = () => Task.FromResult(42); + + // Act + Action action = () => subject.Should().Subject.As().Should().BeSameAs(subject); + + // Assert + action.Should().NotThrow("the Subject should remain the same").And.NotBeNull(); + } + } + + public class NotThrowAfter + { + [Fact] + public void Chaining_after_one_assertion() + { + // Arrange + Func> subject = () => Task.FromResult(42); + + // Act + Action action = () => subject.Should().Subject.As().Should().BeSameAs(subject); + + // Assert + action.Should().NotThrowAfter(1.Seconds(), 1.Seconds()).And.NotBeNull(); + } + } + public class CompleteWithinAsync { [Fact] @@ -99,11 +130,52 @@ public async Task When_task_completes_late_it_should_fail() // Act Func action = () => taskFactory.Awaiting(t => (Task)t.Task).Should(timer).CompleteWithinAsync(100.Milliseconds()); + timer.Complete(); // Assert await action.Should().ThrowAsync(); } + + [Fact] + public async Task Canceled_tasks_are_also_completed() + { + // Arrange + var timer = new FakeClock(); + var taskFactory = new TaskCompletionSource(); + + // Act + Func action = () => taskFactory + .Awaiting(t => (Task)t.Task) + .Should(timer) + .CompleteWithinAsync(100.Milliseconds()); + + taskFactory.SetCanceled(); + timer.Complete(); + + // Assert + await action.Should().NotThrowAsync(); + } + + [Fact] + public async Task Excepted_tasks_unexpectedly_completed() + { + // Arrange + var timer = new FakeClock(); + var taskFactory = new TaskCompletionSource(); + + // Act + Func action = () => taskFactory + .Awaiting(t => (Task)t.Task) + .Should(timer) + .CompleteWithinAsync(100.Milliseconds()); + + taskFactory.SetException(new OperationCanceledException()); + timer.Complete(); + + // Assert + await action.Should().ThrowAsync(); + } } public class NotCompleteWithinAsync @@ -185,6 +257,44 @@ public async Task When_task_completes_late_it_should_succeed() // Assert await action.Should().NotThrowAsync(); } + + [Fact] + public async Task Canceled_tasks_are_also_completed() + { + // Arrange + var timer = new FakeClock(); + var taskFactory = new TaskCompletionSource(); + + // Act + Func action = () => taskFactory + .Awaiting(t => (Task)t.Task).Should(timer) + .NotCompleteWithinAsync(100.Milliseconds()); + + taskFactory.SetCanceled(); + timer.Complete(); + + // Assert + await action.Should().ThrowAsync(); + } + + [Fact] + public async Task Excepted_tasks_unexpectedly_completed() + { + // Arrange + var timer = new FakeClock(); + var taskFactory = new TaskCompletionSource(); + + // Act + Func action = () => taskFactory + .Awaiting(t => (Task)t.Task).Should(timer) + .NotCompleteWithinAsync(100.Milliseconds()); + + taskFactory.SetException(new OperationCanceledException()); + timer.Complete(); + + // Assert + await action.Should().ThrowAsync(); + } } public class ThrowAsync @@ -214,6 +324,7 @@ public async Task When_subject_is_null_in_assertion_scope_it_should_throw() Func testAction = async () => { using var _ = new AssertionScope(); + await action.Should().ThrowAsync( "because we want to test the failure {0}", "message"); }; @@ -231,7 +342,7 @@ public async Task When_task_throws_it_should_succeed() { return Awaiting(() => Task.FromException(new InvalidOperationException("foo"))) - .Should().ThrowAsync(); + .Should().ThrowAsync(); }; // Assert @@ -246,7 +357,7 @@ public async Task When_task_throws_unexpected_exception_it_should_fail() { return Awaiting(() => Task.FromException(new NotSupportedException("foo"))) - .Should().ThrowAsync(); + .Should().ThrowAsync(); }; // Assert @@ -263,7 +374,7 @@ public async Task When_task_completes_without_exception_it_should_fail() { return Awaiting(() => Task.CompletedTask) - .Should().ThrowAsync(); + .Should().ThrowAsync(); }; // Assert @@ -299,6 +410,7 @@ public async Task When_subject_is_null_in_assertion_scope_it_should_throw() Func testAction = async () => { using var _ = new AssertionScope(); + await action.Should().ThrowWithinAsync( 100.Milliseconds(), "because we want to test the failure {0}", "message"); }; @@ -368,6 +480,7 @@ public async Task When_task_throws_asynchronous_it_should_succeed() return Awaiting(() => (Task)taskFactory.Task) .Should(timer).ThrowWithinAsync(1.Seconds()); }; + _ = action.Invoke(); taskFactory.SetException(new InvalidOperationException("foo")); @@ -390,6 +503,7 @@ public async Task When_task_not_completes_it_should_fail() .Should(timer).ThrowWithinAsync( 100.Ticks(), "because we want to test the failure {0}", "message"); }; + timer.Delay(101.Ticks()); // Assert @@ -413,6 +527,7 @@ public async Task When_task_completes_without_exception_it_should_fail() .Awaiting(t => (Task)t.Task) .Should(timer).ThrowWithinAsync(100.Milliseconds()); }; + taskFactory.SetResult(true); timer.Complete(); @@ -435,6 +550,7 @@ public async Task When_task_throws_unexpected_exception_it_should_fail() .Awaiting(t => (Task)t.Task) .Should(timer).ThrowWithinAsync(100.Milliseconds()); }; + taskFactory.SetException(new NotSupportedException("foo")); // Assert @@ -456,6 +572,7 @@ public async Task When_task_throws_unexpected_exception_asynchronous_it_should_f return Awaiting(() => (Task)taskFactory.Task) .Should(timer).ThrowWithinAsync(1.Seconds()); }; + _ = action.Invoke(); taskFactory.SetException(new NotSupportedException("foo")); diff --git a/Tests/FluentAssertions.Specs/Specialized/TaskCompletionSourceAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Specialized/TaskCompletionSourceAssertionSpecs.cs index 087c9b2d51..abe4e9b7dc 100644 --- a/Tests/FluentAssertions.Specs/Specialized/TaskCompletionSourceAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Specialized/TaskCompletionSourceAssertionSpecs.cs @@ -73,6 +73,38 @@ public async Task When_it_completes_in_time_and_it_is_not_expected_it_should_fai await action.Should().ThrowAsync().WithMessage("*to not complete within*because test testArg*"); } + [Fact] + public async Task When_it_is_canceled_before_completion_and_it_is_not_expected_it_should_fail() + { + // Arrange + var subject = new TaskCompletionSource(); + var timer = new FakeClock(); + + // Act + Func action = () => subject.Should(timer).NotCompleteWithinAsync(1.Seconds()); + subject.SetCanceled(); + timer.Complete(); + + // Assert + await action.Should().ThrowAsync(); + } + + [Fact] + public async Task When_it_throws_before_completion_and_it_is_not_expected_it_should_fail() + { + // Arrange + var subject = new TaskCompletionSource(); + var timer = new FakeClock(); + + // Act + Func action = () => subject.Should(timer).NotCompleteWithinAsync(1.Seconds()); + subject.SetException(new OperationCanceledException()); + timer.Complete(); + + // Assert + await action.Should().ThrowAsync(); + } + [Fact] public async Task When_it_did_not_complete_in_time_and_it_is_not_expected_it_should_succeed() { @@ -115,6 +147,38 @@ public async Task When_accidentally_using_equals_it_should_throw_a_helpful_error await action.Should().ThrowAsync() .WithMessage("Equals is not part of Fluent Assertions. Did you mean CompleteWithinAsync() instead?"); } + + [Fact] + public async Task Canceled_tasks_are_also_completed() + { + // Arrange + var subject = new TaskCompletionSource(); + var timer = new FakeClock(); + + // Act + Func action = () => subject.Should(timer).CompleteWithinAsync(1.Seconds()); + subject.SetCanceled(); + timer.Complete(); + + // Assert + await action.Should().NotThrowAsync(); + } + + [Fact] + public async Task Excepted_tasks_unexpectedly_completed() + { + // Arrange + var subject = new TaskCompletionSource(); + var timer = new FakeClock(); + + // Act + Func action = () => subject.Should(timer).CompleteWithinAsync(1.Seconds()); + subject.SetException(new OperationCanceledException()); + timer.Complete(); + + // Assert + await action.Should().NotThrowAsync(); + } } #endif @@ -136,6 +200,38 @@ public async Task When_it_completes_in_time_it_should_succeed() await action.Should().NotThrowAsync(); } + [Fact] + public async Task Canceled_tasks_do_not_return_default_value() + { + // Arrange + var subject = new TaskCompletionSource(); + var timer = new FakeClock(); + + // Act + Func action = () => subject.Should(timer).CompleteWithinAsync(1.Seconds()).WithResult(false); + subject.SetCanceled(); + timer.Complete(); + + // Assert + await action.Should().ThrowAsync(); + } + + [Fact] + public async Task Exception_throwing_tasks_do_not_cause_a_default_value_to_be_returned() + { + // Arrange + var subject = new TaskCompletionSource(); + var timer = new FakeClock(); + + // Act + Func action = () => subject.Should(timer).CompleteWithinAsync(1.Seconds()).WithResult(false); + subject.SetException(new OperationCanceledException()); + timer.Complete(); + + // Assert + await action.Should().ThrowAsync(); + } + [Fact] public async Task When_it_completes_in_time_and_result_is_expected_it_should_succeed() { @@ -201,7 +297,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] @@ -251,6 +347,38 @@ await action.Should().ThrowAsync() .WithMessage("Did not expect*to complete within*because test testArg*"); } + [Fact] + public async Task Canceled_tasks_are_also_completed() + { + // Arrange + var subject = new TaskCompletionSource(); + var timer = new FakeClock(); + + // Act + Func action = () => subject.Should(timer).NotCompleteWithinAsync(1.Seconds()); + subject.SetCanceled(); + timer.Complete(); + + // Assert + await action.Should().ThrowAsync(); + } + + [Fact] + public async Task Excepted_tasks_unexpectedly_completed() + { + // Arrange + var subject = new TaskCompletionSource(); + var timer = new FakeClock(); + + // Act + Func action = () => subject.Should(timer).NotCompleteWithinAsync(1.Seconds()); + subject.SetException(new OperationCanceledException()); + timer.Complete(); + + // Assert + await action.Should().ThrowAsync(); + } + [Fact] public async Task When_it_did_not_complete_in_time_and_it_is_not_expected_it_should_succeed() { diff --git a/Tests/FluentAssertions.Specs/Specialized/TaskOfTAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Specialized/TaskOfTAssertionSpecs.cs index 31bd834ef8..181d17c3be 100644 --- a/Tests/FluentAssertions.Specs/Specialized/TaskOfTAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Specialized/TaskOfTAssertionSpecs.cs @@ -1,10 +1,10 @@ -#if NETFRAMEWORK -using FluentAssertions.Specs.Common; -#endif -using System; +using System; using System.Threading.Tasks; using FluentAssertions.Execution; using FluentAssertions.Extensions; +#if NET47 +using FluentAssertions.Specs.Common; +#endif using FluentAssertions.Specs.Exceptions; using Xunit; using Xunit.Sdk; @@ -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()); } @@ -215,6 +238,48 @@ public async Task When_task_consumes_time_in_sync_portion_it_should_fail() // Assert await action.Should().ThrowAsync(); } + + [Fact] + public async Task Canceled_tasks_do_not_cause_a_default_value_to_be_returned() + { + // Arrange + var timer = new FakeClock(); + var taskFactory = new TaskCompletionSource(); + + // Act + Func action = () => taskFactory + .Awaiting(t => t.Task) + .Should(timer) + .CompleteWithinAsync(100.Milliseconds()) + .WithResult(0); + + taskFactory.SetCanceled(); + timer.Complete(); + + // Assert + await action.Should().ThrowAsync(); + } + + [Fact] + public async Task Exception_throwing_tasks_do_not_cause_a_default_value_to_be_returned() + { + // Arrange + var timer = new FakeClock(); + var taskFactory = new TaskCompletionSource(); + + // Act + Func action = () => taskFactory + .Awaiting(t => t.Task) + .Should(timer) + .CompleteWithinAsync(100.Milliseconds()) + .WithResult(0); + + taskFactory.SetException(new OperationCanceledException()); + timer.Complete(); + + // Assert + await action.Should().ThrowAsync(); + } } public class NotThrowAsync @@ -261,6 +326,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/Streams/BufferedStreamAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Streams/BufferedStreamAssertionSpecs.cs index 3107dc02b3..61de25fa7e 100644 --- a/Tests/FluentAssertions.Specs/Streams/BufferedStreamAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Streams/BufferedStreamAssertionSpecs.cs @@ -1,16 +1,14 @@ -#if NET6_0_OR_GREATER || NETSTANDARD2_1 +#if NET6_0_OR_GREATER using System; using System.IO; using FluentAssertions.Execution; using Xunit; using Xunit.Sdk; -#endif namespace FluentAssertions.Specs.Streams; public class BufferedStreamAssertionSpecs { -#if NET6_0_OR_GREATER || NETSTANDARD2_1 public class HaveBufferSize { [Fact] @@ -110,5 +108,5 @@ public void When_null_not_have_buffer_size_should_fail() .WithMessage("Expected the buffer size of stream not to be 10 *failure message*, but found a reference."); } } -#endif } +#endif diff --git a/Tests/FluentAssertions.Specs/TypeEnumerableExtensionsSpecs.cs b/Tests/FluentAssertions.Specs/TypeEnumerableExtensionsSpecs.cs index 75ff942c62..5a3ea26cd5 100644 --- a/Tests/FluentAssertions.Specs/TypeEnumerableExtensionsSpecs.cs +++ b/Tests/FluentAssertions.Specs/TypeEnumerableExtensionsSpecs.cs @@ -13,10 +13,10 @@ public class TypeEnumerableExtensionsSpecs [Fact] public void When_selecting_types_that_decorated_with_attribute_it_should_return_the_correct_type() { - var types = new[] - { + Type[] types = + [ typeof(JustAClass), typeof(ClassWithSomeAttribute), typeof(ClassDerivedFromClassWithSomeAttribute) - }; + ]; types.ThatAreDecoratedWith() .Should() @@ -27,10 +27,10 @@ public void When_selecting_types_that_decorated_with_attribute_it_should_return_ [Fact] public void When_selecting_types_that_decorated_with_attribute_or_inherit_it_should_return_the_correct_type() { - var types = new[] - { + Type[] types = + [ typeof(JustAClass), typeof(ClassWithSomeAttribute), typeof(ClassDerivedFromClassWithSomeAttribute) - }; + ]; types.ThatAreDecoratedWithOrInherit() .Should() @@ -42,10 +42,10 @@ public void When_selecting_types_that_decorated_with_attribute_or_inherit_it_sho [Fact] public void When_selecting_types_that_not_decorated_with_attribute_it_should_return_the_correct_type() { - var types = new[] - { + Type[] types = + [ typeof(JustAClass), typeof(ClassWithSomeAttribute), typeof(ClassDerivedFromClassWithSomeAttribute) - }; + ]; types.ThatAreNotDecoratedWith() .Should() @@ -57,10 +57,10 @@ public void When_selecting_types_that_not_decorated_with_attribute_it_should_ret [Fact] public void When_selecting_types_that_not_decorated_with_attribute_or_inherit_it_should_return_the_correct_type() { - var types = new[] - { + Type[] types = + [ typeof(JustAClass), typeof(ClassWithSomeAttribute), typeof(ClassDerivedFromClassWithSomeAttribute) - }; + ]; types.ThatAreNotDecoratedWithOrInherit() .Should() @@ -71,7 +71,7 @@ public void When_selecting_types_that_not_decorated_with_attribute_or_inherit_it [Fact] public void When_selecting_types_in_namespace_it_should_return_the_correct_type() { - var types = new[] { typeof(JustAClass), typeof(BaseNamespaceClass), typeof(NestedNamespaceClass) }; + Type[] types = [typeof(JustAClass), typeof(BaseNamespaceClass), typeof(NestedNamespaceClass)]; types.ThatAreInNamespace(typeof(BaseNamespaceClass).Namespace) .Should() @@ -82,7 +82,7 @@ public void When_selecting_types_in_namespace_it_should_return_the_correct_type( [Fact] public void When_selecting_types_under_namespace_it_should_return_the_correct_type() { - var types = new[] { typeof(JustAClass), typeof(BaseNamespaceClass), typeof(NestedNamespaceClass) }; + Type[] types = [typeof(JustAClass), typeof(BaseNamespaceClass), typeof(NestedNamespaceClass)]; types.ThatAreUnderNamespace(typeof(BaseNamespaceClass).Namespace) .Should() @@ -94,7 +94,7 @@ public void When_selecting_types_under_namespace_it_should_return_the_correct_ty [Fact] public void When_selecting_derived_classes_it_should_return_the_correct_type() { - var types = new[] { typeof(JustAClass), typeof(SomeBaseClass), typeof(SomeClassDerivedFromSomeBaseClass) }; + Type[] types = [typeof(JustAClass), typeof(SomeBaseClass), typeof(SomeClassDerivedFromSomeBaseClass)]; types.ThatDeriveFrom() .Should() @@ -105,7 +105,7 @@ public void When_selecting_derived_classes_it_should_return_the_correct_type() [Fact] public void When_selecting_types_that_implement_interface_it_should_return_the_correct_type() { - var types = new[] { typeof(JustAClass), typeof(ClassImplementingJustAnInterface), typeof(IJustAnInterface) }; + Type[] types = [typeof(JustAClass), typeof(ClassImplementingJustAnInterface), typeof(IJustAnInterface)]; types.ThatImplement() .Should() @@ -116,7 +116,7 @@ public void When_selecting_types_that_implement_interface_it_should_return_the_c [Fact] public void When_selecting_only_the_classes_it_should_return_the_correct_type() { - var types = new[] { typeof(JustAClass), typeof(IJustAnInterface) }; + Type[] types = [typeof(JustAClass), typeof(IJustAnInterface)]; types.ThatAreClasses() .Should() @@ -127,7 +127,7 @@ public void When_selecting_only_the_classes_it_should_return_the_correct_type() [Fact] public void When_selecting_not_a_classes_it_should_return_the_correct_type() { - var types = new[] { typeof(JustAClass), typeof(IJustAnInterface) }; + Type[] types = [typeof(JustAClass), typeof(IJustAnInterface)]; types.ThatAreNotClasses() .Should() @@ -138,7 +138,7 @@ public void When_selecting_not_a_classes_it_should_return_the_correct_type() [Fact] public void When_selecting_static_classes_it_should_return_the_correct_type() { - var types = new[] { typeof(JustAClass), typeof(AStaticClass) }; + Type[] types = [typeof(JustAClass), typeof(AStaticClass)]; types.ThatAreStatic() .Should() @@ -149,7 +149,7 @@ public void When_selecting_static_classes_it_should_return_the_correct_type() [Fact] public void When_selecting_not_a_static_classes_it_should_return_the_correct_type() { - var types = new[] { typeof(JustAClass), typeof(AStaticClass) }; + Type[] types = [typeof(JustAClass), typeof(AStaticClass)]; types.ThatAreNotStatic() .Should() @@ -160,7 +160,7 @@ public void When_selecting_not_a_static_classes_it_should_return_the_correct_typ [Fact] public void When_selecting_types_with_predicate_it_should_return_the_correct_type() { - var types = new[] { typeof(JustAClass), typeof(AStaticClass) }; + Type[] types = [typeof(JustAClass), typeof(AStaticClass)]; types.ThatSatisfy(t => t.IsSealed && t.IsAbstract) .Should() @@ -171,7 +171,7 @@ public void When_selecting_types_with_predicate_it_should_return_the_correct_typ [Fact] public void When_unwrap_task_types_it_should_return_the_correct_type() { - var types = new[] { typeof(Task), typeof(List) }; + Type[] types = [typeof(Task), typeof(List)]; types.UnwrapTaskTypes() .Should() @@ -183,7 +183,7 @@ public void When_unwrap_task_types_it_should_return_the_correct_type() [Fact] public void When_unwrap_enumerable_types_it_should_return_the_correct_type() { - var types = new[] { typeof(Task), typeof(List) }; + Type[] types = [typeof(Task), typeof(List)]; types.UnwrapEnumerableTypes() .Should() 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/MethodInfoAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Types/MethodInfoAssertionSpecs.cs index 770e490ac5..9a7020d52f 100644 --- a/Tests/FluentAssertions.Specs/Types/MethodInfoAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Types/MethodInfoAssertionSpecs.cs @@ -223,20 +223,6 @@ public void When_asserting_a_class_is_decorated_with_MethodImpl_attribute_and_it "System.Runtime.CompilerServices.MethodImplAttribute, but the attribute was not found."); } - [Fact] - public void When_a_method_is_decorated_with_an_attribute_it_should_allow_chaining_assertions_on_it() - { - // Arrange - MethodInfo methodInfo = - typeof(ClassWithAllMethodsDecoratedWithDummyAttribute).GetParameterlessMethod("PublicDoNothing"); - - // Act - Action act = () => methodInfo.Should().BeDecoratedWith().Which.Filter.Should().BeFalse(); - - // Assert - act.Should().Throw(); - } - [Fact] public void When_asserting_a_method_is_decorated_with_an_attribute_but_it_is_not_it_throws_with_a_useful_message() { @@ -414,7 +400,7 @@ public void When_asserting_a_constructor_is_not_decorated_with_MethodImpl_attrib { // Arrange ConstructorInfo constructorMethodInfo = - typeof(ClassWithMethodWithImplementationAttribute).GetConstructor(new[] { typeof(string) }); + typeof(ClassWithMethodWithImplementationAttribute).GetConstructor([typeof(string)]); // Act Action act = () => @@ -744,6 +730,7 @@ internal class ClassWithMethodWithImplementationAttribute [MethodImpl(MethodImplOptions.NoOptimization)] public ClassWithMethodWithImplementationAttribute() { } + // ReSharper disable once UnusedParameter.Local public ClassWithMethodWithImplementationAttribute(string _) { } [MethodImpl(MethodImplOptions.NoInlining)] diff --git a/Tests/FluentAssertions.Specs/Types/PropertyInfoAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Types/PropertyInfoAssertionSpecs.cs index 167204cd69..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."); } @@ -141,7 +139,7 @@ public void When_a_property_is_decorated_with_an_attribute_it_allow_chaining_ass propertyInfo.Should().BeDecoratedWith().Which.Value.Should().Be("OtherValue"); // Assert - act.Should().Throw().WithMessage("Expected*OtherValue*Value*"); + act.Should().Throw().WithMessage("Expected*Value*OtherValue*"); } [Fact] @@ -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/PropertyInfoSelectorSpecs.cs b/Tests/FluentAssertions.Specs/Types/PropertyInfoSelectorSpecs.cs index 4517255b21..7eb8480a9d 100644 --- a/Tests/FluentAssertions.Specs/Types/PropertyInfoSelectorSpecs.cs +++ b/Tests/FluentAssertions.Specs/Types/PropertyInfoSelectorSpecs.cs @@ -344,11 +344,11 @@ public void When_selecting_properties_return_types_it_should_return_the_correct_ // Assert returnTypes.Should() - .BeEquivalentTo(new[] - { + .BeEquivalentTo( + [ typeof(string), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string), typeof(string), typeof(int), typeof(int), typeof(int), typeof(int) - }); + ]); } public class ThatArePublicOrInternal diff --git a/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveConstructor.cs b/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveConstructor.cs index f02269e598..f51027030d 100644 --- a/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveConstructor.cs +++ b/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveConstructor.cs @@ -21,7 +21,7 @@ public void When_asserting_a_type_has_a_constructor_which_it_does_it_succeeds() // Act Action act = () => type.Should() - .HaveConstructor(new[] { typeof(string) }) + .HaveConstructor([typeof(string)]) .Which.Should().HaveAccessModifier(CSharpAccessModifier.Private); // Assert @@ -36,7 +36,7 @@ public void When_asserting_a_type_has_a_constructor_which_it_does_not_it_fails() // Act Action act = () => - type.Should().HaveConstructor(new[] { typeof(int), typeof(Type) }, "we want to test the failure {0}", "message"); + type.Should().HaveConstructor([typeof(int), typeof(Type)], "we want to test the failure {0}", "message"); // Assert act.Should().Throw() @@ -53,7 +53,7 @@ public void When_subject_is_null_have_constructor_should_fail() // Act Action act = () => - type.Should().HaveConstructor(new[] { typeof(string) }, "we want to test the failure {0}", "message"); + type.Should().HaveConstructor([typeof(string)], "we want to test the failure {0}", "message"); // Assert act.Should().Throw() @@ -87,7 +87,7 @@ public void When_asserting_a_type_does_not_have_a_constructor_which_it_does_not_ // Act Action act = () => type.Should() - .NotHaveConstructor(new[] { typeof(string) }); + .NotHaveConstructor([typeof(string)]); // Assert act.Should().NotThrow(); @@ -101,7 +101,7 @@ public void When_asserting_a_type_does_not_have_a_constructor_which_it_does_it_f // Act Action act = () => - type.Should().NotHaveConstructor(new[] { typeof(string) }, "we want to test the failure {0}", "message"); + type.Should().NotHaveConstructor([typeof(string)], "we want to test the failure {0}", "message"); // Assert act.Should().Throw() @@ -117,7 +117,7 @@ public void When_subject_is_null_not_have_constructor_should_fail() // Act Action act = () => - type.Should().NotHaveConstructor(new[] { typeof(string) }, "we want to test the failure {0}", "message"); + type.Should().NotHaveConstructor([typeof(string)], "we want to test the failure {0}", "message"); // Assert act.Should().Throw() diff --git a/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveDefaultConstructor.cs b/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveDefaultConstructor.cs index 14a7a5a8d8..951bd5cb99 100644 --- a/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveDefaultConstructor.cs +++ b/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveDefaultConstructor.cs @@ -185,6 +185,7 @@ internal class ClassWithCctor; internal class ClassWithCctorAndNonDefaultConstructor { + // ReSharper disable once UnusedParameter.Local public ClassWithCctorAndNonDefaultConstructor(int _) { } } } 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.HaveIndexer.cs b/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveIndexer.cs index 8e603f7871..9f3bd5022f 100644 --- a/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveIndexer.cs +++ b/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveIndexer.cs @@ -21,7 +21,7 @@ public void When_asserting_a_type_has_an_indexer_which_it_does_then_it_succeeds( // Act Action act = () => type.Should() - .HaveIndexer(typeof(string), new[] { typeof(string) }) + .HaveIndexer(typeof(string), [typeof(string)]) .Which.Should() .BeWritable(CSharpAccessModifier.Internal) .And.BeReadable(CSharpAccessModifier.Private); @@ -39,7 +39,7 @@ public void When_asserting_a_type_has_an_indexer_which_it_does_not_it_fails() // Act Action act = () => type.Should().HaveIndexer( - typeof(string), new[] { typeof(int), typeof(Type) }, "we want to test the failure {0}", "message"); + typeof(string), [typeof(int), typeof(Type)], "we want to test the failure {0}", "message"); // Assert act.Should().Throw() @@ -57,7 +57,7 @@ public void When_asserting_a_type_has_an_indexer_with_different_parameters_it_fa // Act Action act = () => type.Should().HaveIndexer( - typeof(string), new[] { typeof(int), typeof(Type) }, "we want to test the failure {0}", "message"); + typeof(string), [typeof(int), typeof(Type)], "we want to test the failure {0}", "message"); // Assert act.Should().Throw() @@ -73,7 +73,7 @@ public void When_subject_is_null_have_indexer_should_fail() // Act Action act = () => - type.Should().HaveIndexer(typeof(string), new[] { typeof(string) }, "we want to test the failure {0}", "message"); + type.Should().HaveIndexer(typeof(string), [typeof(string)], "we want to test the failure {0}", "message"); // Assert act.Should().Throw() @@ -88,7 +88,7 @@ public void When_asserting_a_type_has_an_indexer_for_null_it_should_throw() // Act Action act = () => - type.Should().HaveIndexer(null, new[] { typeof(string) }); + type.Should().HaveIndexer(null, [typeof(string)]); // Assert act.Should().ThrowExactly() @@ -121,7 +121,7 @@ public void When_asserting_a_type_does_not_have_an_indexer_which_it_does_not_it_ // Act Action act = () => - type.Should().NotHaveIndexer(new[] { typeof(string) }); + type.Should().NotHaveIndexer([typeof(string)]); // Assert act.Should().NotThrow(); @@ -135,7 +135,7 @@ public void When_asserting_a_type_does_not_have_an_indexer_which_it_does_it_fail // Act Action act = () => - type.Should().NotHaveIndexer(new[] { typeof(string) }, "we want to test the failure {0}", "message"); + type.Should().NotHaveIndexer([typeof(string)], "we want to test the failure {0}", "message"); // Assert act.Should().Throw() @@ -150,7 +150,7 @@ public void When_subject_is_null_not_have_indexer_should_fail() // Act Action act = () => - type.Should().NotHaveIndexer(new[] { typeof(string) }, "we want to test the failure {0}", "message"); + type.Should().NotHaveIndexer([typeof(string)], "we want to test the failure {0}", "message"); // Assert act.Should().Throw() diff --git a/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveMethod.cs b/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveMethod.cs index ef6db085ce..663994168d 100644 --- a/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveMethod.cs +++ b/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.HaveMethod.cs @@ -21,7 +21,7 @@ public void When_asserting_a_type_has_a_method_which_it_does_it_succeeds() // Act Action act = () => type.Should() - .HaveMethod("VoidMethod", new Type[] { }) + .HaveMethod("VoidMethod", []) .Which.Should() .HaveAccessModifier(CSharpAccessModifier.Private) .And.ReturnVoid(); @@ -39,7 +39,7 @@ public void When_asserting_a_type_has_a_method_which_it_does_not_it_fails() // Act Action act = () => type.Should().HaveMethod( - "NonExistentMethod", new[] { typeof(int), typeof(Type) }, "we want to test the failure {0}", "message"); + "NonExistentMethod", [typeof(int), typeof(Type)], "we want to test the failure {0}", "message"); // Assert act.Should().Throw() @@ -57,7 +57,7 @@ public void When_asserting_a_type_has_a_method_with_different_parameter_types_it // Act Action act = () => type.Should().HaveMethod( - "VoidMethod", new[] { typeof(int), typeof(Type) }, "we want to test the failure {0}", "message"); + "VoidMethod", [typeof(int), typeof(Type)], "we want to test the failure {0}", "message"); // Assert act.Should().Throw() @@ -74,7 +74,7 @@ public void When_subject_is_null_have_method_should_fail() // Act Action act = () => - type.Should().HaveMethod("Name", new[] { typeof(string) }, "we want to test the failure {0}", "message"); + type.Should().HaveMethod("Name", [typeof(string)], "we want to test the failure {0}", "message"); // Assert act.Should().Throw() @@ -89,7 +89,7 @@ public void When_asserting_a_type_has_a_method_with_a_null_name_it_should_throw( // Act Action act = () => - type.Should().HaveMethod(null, new[] { typeof(string) }); + type.Should().HaveMethod(null, [typeof(string)]); // Assert act.Should().ThrowExactly() @@ -104,7 +104,7 @@ public void When_asserting_a_type_has_a_method_with_an_empty_name_it_should_thro // Act Action act = () => - type.Should().HaveMethod(string.Empty, new[] { typeof(string) }); + type.Should().HaveMethod(string.Empty, [typeof(string)]); // Assert act.Should().ThrowExactly() @@ -137,7 +137,7 @@ public void When_asserting_a_type_does_not_have_a_method_which_it_does_not_it_su // Act Action act = () => - type.Should().NotHaveMethod("NonExistentMethod", new Type[] { }); + type.Should().NotHaveMethod("NonExistentMethod", []); // Assert act.Should().NotThrow(); @@ -151,7 +151,7 @@ public void When_asserting_a_type_does_not_have_a_method_which_it_has_with_diffe // Act Action act = () => - type.Should().NotHaveMethod("VoidMethod", new[] { typeof(int) }); + type.Should().NotHaveMethod("VoidMethod", [typeof(int)]); // Assert act.Should().NotThrow(); @@ -165,7 +165,7 @@ public void When_asserting_a_type_does_not_have_that_method_which_it_does_it_fai // Act Action act = () => - type.Should().NotHaveMethod("VoidMethod", new Type[] { }, "we want to test the failure {0}", "message"); + type.Should().NotHaveMethod("VoidMethod", [], "we want to test the failure {0}", "message"); // Assert act.Should().Throw() @@ -180,7 +180,7 @@ public void When_subject_is_null_not_have_method_should_fail() // Act Action act = () => - type.Should().NotHaveMethod("Name", new[] { typeof(string) }, "we want to test the failure {0}", "message"); + type.Should().NotHaveMethod("Name", [typeof(string)], "we want to test the failure {0}", "message"); // Assert act.Should().Throw() @@ -195,7 +195,7 @@ public void When_asserting_a_type_does_not_have_a_method_with_a_null_name_it_sho // Act Action act = () => - type.Should().NotHaveMethod(null, new[] { typeof(string) }); + type.Should().NotHaveMethod(null, [typeof(string)]); // Assert act.Should().ThrowExactly() @@ -210,7 +210,7 @@ public void When_asserting_a_type_does_not_have_a_method_with_an_empty_name_it_s // Act Action act = () => - type.Should().NotHaveMethod(string.Empty, new[] { typeof(string) }); + type.Should().NotHaveMethod(string.Empty, [typeof(string)]); // Assert act.Should().ThrowExactly() 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/Types/TypeAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.cs index ac0d89b03f..a6c4791b13 100644 --- a/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Types/TypeAssertionSpecs.cs @@ -49,6 +49,7 @@ public class ClassWithMembers { protected internal ClassWithMembers() { } + // ReSharper disable once UnusedParameter.Local private ClassWithMembers(string _) { } protected string PrivateWriteProtectedReadProperty { get => null; private set { } } diff --git a/Tests/FluentAssertions.Specs/Types/TypeSelectorAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Types/TypeSelectorAssertionSpecs.cs index cbe7bc2c7f..38cc5e931b 100644 --- a/Tests/FluentAssertions.Specs/Types/TypeSelectorAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Types/TypeSelectorAssertionSpecs.cs @@ -16,10 +16,10 @@ public class BeSealed public void When_all_types_are_sealed_it_succeeds() { // Arrange - var types = new TypeSelector(new[] - { + var types = new TypeSelector( + [ typeof(Sealed) - }); + ]); // Act / Assert types.Should().BeSealed(); @@ -29,11 +29,11 @@ public void When_all_types_are_sealed_it_succeeds() public void When_any_type_is_not_sealed_it_fails_with_a_meaningful_message() { // Arrange - var types = new TypeSelector(new[] - { + var types = new TypeSelector( + [ typeof(Sealed), typeof(Abstract) - }); + ]); // Act Action act = () => types.Should().BeSealed("we want to test the failure {0}", "message"); @@ -51,10 +51,10 @@ public class NotBeSealed public void When_all_types_are_not_sealed_it_succeeds() { // Arrange - var types = new TypeSelector(new[] - { + var types = new TypeSelector( + [ typeof(Abstract) - }); + ]); // Act / Assert types.Should().NotBeSealed(); @@ -64,11 +64,11 @@ public void When_all_types_are_not_sealed_it_succeeds() public void When_any_type_is_sealed_it_fails_with_a_meaningful_message() { // Arrange - var types = new TypeSelector(new[] - { + var types = new TypeSelector( + [ typeof(Abstract), typeof(Sealed) - }); + ]); // Act Action act = () => types.Should().NotBeSealed("we want to test the failure {0}", "message"); @@ -85,10 +85,10 @@ public class BeDecoratedWith public void When_asserting_a_selection_of_decorated_types_is_decorated_with_an_attribute_it_succeeds() { // Arrange - var types = new TypeSelector(new[] - { + var types = new TypeSelector( + [ typeof(ClassWithAttribute) - }); + ]); // Act Action act = () => @@ -102,12 +102,12 @@ public void When_asserting_a_selection_of_decorated_types_is_decorated_with_an_a public void When_asserting_a_selection_of_non_decorated_types_is_decorated_with_an_attribute_it_fails() { // Arrange - var types = new TypeSelector(new[] - { + var types = new TypeSelector( + [ typeof(ClassWithAttribute), typeof(ClassWithoutAttribute), typeof(OtherClassWithoutAttribute) - }); + ]); // Act Action act = () => @@ -140,12 +140,12 @@ public void When_injecting_a_null_predicate_into_TypeSelector_BeDecoratedWith_it public void When_asserting_a_selection_of_types_with_unexpected_attribute_property_it_fails() { // Arrange - var types = new TypeSelector(new[] - { + var types = new TypeSelector( + [ typeof(ClassWithAttribute), typeof(ClassWithoutAttribute), typeof(OtherClassWithoutAttribute) - }); + ]); // Act Action act = () => @@ -168,10 +168,10 @@ public class BeDecoratedWithOrInherit public void When_asserting_a_selection_of_decorated_types_inheriting_an_attribute_it_succeeds() { // Arrange - var types = new TypeSelector(new[] - { + var types = new TypeSelector( + [ typeof(ClassWithInheritedAttribute) - }); + ]); // Act Action act = () => @@ -185,12 +185,12 @@ public void When_asserting_a_selection_of_decorated_types_inheriting_an_attribut public void When_asserting_a_selection_of_non_decorated_types_inheriting_an_attribute_it_fails() { // Arrange - var types = new TypeSelector(new[] - { + var types = new TypeSelector( + [ typeof(ClassWithAttribute), typeof(ClassWithoutAttribute), typeof(OtherClassWithoutAttribute) - }); + ]); // Act Action act = () => @@ -224,13 +224,13 @@ public void When_asserting_a_selection_of_types_with_some_inheriting_attributes_with_unexpected_attribute_property_it_fails() { // Arrange - var types = new TypeSelector(new[] - { + var types = new TypeSelector( + [ typeof(ClassWithAttribute), typeof(ClassWithInheritedAttribute), typeof(ClassWithoutAttribute), typeof(OtherClassWithoutAttribute) - }); + ]); // Act Action act = () => @@ -253,11 +253,11 @@ public class NotBeDecoratedWith public void When_asserting_a_selection_of_non_decorated_types_is_not_decorated_with_an_attribute_it_succeeds() { // Arrange - var types = new TypeSelector(new[] - { + var types = new TypeSelector( + [ typeof(ClassWithoutAttribute), typeof(OtherClassWithoutAttribute) - }); + ]); // Act Action act = () => @@ -271,11 +271,11 @@ public void When_asserting_a_selection_of_non_decorated_types_is_not_decorated_w public void When_asserting_a_selection_of_decorated_types_is_not_decorated_with_an_attribute_it_fails() { // Arrange - var types = new TypeSelector(new[] - { + var types = new TypeSelector( + [ typeof(ClassWithoutAttribute), typeof(ClassWithAttribute) - }); + ]); // Act Action act = () => @@ -307,11 +307,11 @@ public void When_injecting_a_null_predicate_into_TypeSelector_NotBeDecoratedWith public void When_asserting_a_selection_of_types_with_unexpected_attribute_and_unexpected_attribute_property_it_fails() { // Arrange - var types = new TypeSelector(new[] - { + var types = new TypeSelector( + [ typeof(ClassWithoutAttribute), typeof(ClassWithAttribute) - }); + ]); // Act Action act = () => @@ -334,11 +334,11 @@ public class NotBeDecoratedWithOrInherit public void When_asserting_a_selection_of_non_decorated_types_does_not_inherit_an_attribute_it_succeeds() { // Arrange - var types = new TypeSelector(new[] - { + var types = new TypeSelector( + [ typeof(ClassWithoutAttribute), typeof(OtherClassWithoutAttribute) - }); + ]); // Act Action act = () => @@ -352,11 +352,11 @@ public void When_asserting_a_selection_of_non_decorated_types_does_not_inherit_a public void When_asserting_a_selection_of_decorated_types_does_not_inherit_an_attribute_it_fails() { // Arrange - var types = new TypeSelector(new[] - { + var types = new TypeSelector( + [ typeof(ClassWithoutAttribute), typeof(ClassWithInheritedAttribute) - }); + ]); // Act Action act = () => @@ -437,12 +437,12 @@ public void When_a_type_is_in_the_expected_namespace_it_should_not_throw() public void When_a_type_is_not_in_the_expected_namespace_it_should_throw() { // Arrange - var types = new TypeSelector(new[] - { + var types = new TypeSelector( + [ typeof(ClassInDummyNamespace), typeof(ClassNotInDummyNamespace), typeof(OtherClassNotInDummyNamespace) - }); + ]); // Act Action act = () => @@ -488,12 +488,12 @@ public void When_a_type_in_the_global_namespace_is_not_in_the_expected_namespace public void When_a_type_is_not_in_the_expected_global_namespace_it_should_throw() { // Arrange - var types = new TypeSelector(new[] - { + var types = new TypeSelector( + [ typeof(ClassInDummyNamespace), typeof(ClassNotInDummyNamespace), typeof(OtherClassNotInDummyNamespace) - }); + ]); // Act Action act = () => types.Should().BeInNamespace(null); @@ -539,12 +539,12 @@ public void When_a_type_is_not_in_the_unexpected_parent_namespace_it_should_not_ public void When_a_type_is_in_the_unexpected_namespace_it_should_throw() { // Arrange - var types = new TypeSelector(new[] - { + var types = new TypeSelector( + [ typeof(ClassInDummyNamespace), typeof(ClassNotInDummyNamespace), typeof(OtherClassNotInDummyNamespace) - }); + ]); // Act Action act = () => @@ -617,12 +617,12 @@ public void When_a_type_is_exactly_under_the_expected_global_namespace_it_should public void When_a_type_is_under_the_expected_global_namespace_it_should_not_throw() { // Arrange - var types = new TypeSelector(new[] - { + var types = new TypeSelector( + [ typeof(ClassInDummyNamespace), typeof(ClassNotInDummyNamespace), typeof(OtherClassNotInDummyNamespace) - }); + ]); // Act Action act = () => types.Should().BeUnderNamespace(null); @@ -651,11 +651,11 @@ public void When_a_type_only_shares_a_prefix_with_the_expected_namespace_it_shou public void When_asserting_a_selection_of_types_not_under_a_namespace_is_under_that_namespace_it_fails() { // Arrange - var types = new TypeSelector(new[] - { + var types = new TypeSelector( + [ typeof(ClassInDummyNamespace), typeof(ClassInInnerDummyNamespace) - }); + ]); // Act Action act = () => @@ -675,12 +675,12 @@ public class NotBeUnderNamespace public void When_a_types_is_not_under_the_unexpected_namespace_it_should_not_throw() { // Arrange - var types = new TypeSelector(new[] - { + var types = new TypeSelector( + [ typeof(ClassInDummyNamespace), typeof(ClassNotInDummyNamespace), typeof(OtherClassNotInDummyNamespace) - }); + ]); // Act Action act = () => @@ -760,12 +760,12 @@ public void When_a_type_is_under_the_unexpected_global_namespace_it_should_throw public void When_a_type_is_under_the_unexpected_parent_global_namespace_it_should_throw() { // Arrange - var types = new TypeSelector(new[] - { + var types = new TypeSelector( + [ typeof(ClassInDummyNamespace), typeof(ClassNotInDummyNamespace), typeof(OtherClassNotInDummyNamespace) - }); + ]); // Act Action act = () => types.Should().NotBeUnderNamespace(null); @@ -797,10 +797,10 @@ public class Miscellaneous public void When_accidentally_using_equals_it_should_throw_a_helpful_error() { // Arrange - var types = new TypeSelector(new[] - { + var types = new TypeSelector( + [ typeof(ClassWithAttribute) - }); + ]); // Act var action = () => types.Should().Equals(null); diff --git a/Tests/FluentAssertions.Specs/Types/TypeSelectorSpecs.cs b/Tests/FluentAssertions.Specs/Types/TypeSelectorSpecs.cs index 8accee5d36..024da6d991 100644 --- a/Tests/FluentAssertions.Specs/Types/TypeSelectorSpecs.cs +++ b/Tests/FluentAssertions.Specs/Types/TypeSelectorSpecs.cs @@ -666,7 +666,7 @@ public void When_unwrap_task_types_it_should_return_the_correct_types() .UnwrapTaskTypes(); types.Should() - .BeEquivalentTo(new[] { typeof(int), typeof(void), typeof(void), typeof(string), typeof(bool) }); + .BeEquivalentTo([typeof(int), typeof(void), typeof(void), typeof(string), typeof(bool)]); } [Fact] @@ -852,8 +852,8 @@ internal class ClassToExploreUnwrappedEnumerableTypes internal class ClassImplementingMultipleEnumerable : IEnumerable, IEnumerable { - private readonly IEnumerable integers = new int[0]; - private readonly IEnumerable strings = new string[0]; + private readonly IEnumerable integers = []; + private readonly IEnumerable strings = []; public IEnumerator GetEnumerator() => integers.GetEnumerator(); @@ -878,15 +878,15 @@ internal record struct InternalRecordStructValueType; internal class InternalClassNotValueType; - internal record class InternalRecordClass; + internal record InternalRecordClass; internal enum InternalEnumValueType; internal interface InternalInterfaceNotValueType; } -#pragma warning disable RCS1110, S3903 // Declare type inside namespace. +#pragma warning disable MA0047 internal class ClassInGlobalNamespace; -#pragma warning restore RCS1110, S3903 +#pragma warning restore MA0047 #endregion diff --git a/Tests/FluentAssertions.Specs/Xml/XDocumentAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Xml/XDocumentAssertionSpecs.cs index f09023363c..0c4736320e 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() { @@ -1156,7 +1193,7 @@ public void XElement matchedElement = document.Should().HaveElement("child").Subject; // Assert - matchedElement.Should().BeOfType().And.HaveAttribute("attr", "1"); + matchedElement.Should().BeOfType().And.HaveAttributeWithValue("attr", "1"); matchedElement.Name.Should().Be(XName.Get("child")); } @@ -1231,6 +1268,225 @@ public void When_asserting_a_document_has_an_element_with_a_null_xname_it_should } } + public class HaveElementWithValue + { + [Fact] + public void The_document_cannot_be_null() + { + // Arrange + XDocument document = null; + + // Act + Action act = () => document.Should().HaveElementWithValue("child", "b"); + + // Assert + act.Should().Throw().WithMessage("*child*b*element itself is *"); + } + + [Fact] + public void The_expected_element_with_the_expected_value_is_valid() + { + // Arrange + var document = XDocument.Parse( + """ + + a + b + + """); + + // Act / Assert + document.Should().HaveElementWithValue("child", "b"); + } + + [Fact] + public void Throws_when_element_is_not_found() + { + // Arrange + var document = XDocument.Parse( + """ + + a + b + + """); + + // Act + Action act = () => document.Should().HaveElementWithValue("grandchild", "f"); + + // Assert + act.Should().Throw().WithMessage("*grandchild*f*element*isn't found*"); + } + + [Fact] + public void Throws_when_element_found_but_value_does_not_match() + { + // Arrange + var document = XDocument.Parse( + """ + + a + b + + """); + + // Act + Action act = () => document.Should().HaveElementWithValue("child", "c"); + + // Assert + act.Should().Throw().WithMessage("*child*c*element*does not have such a value*"); + } + + [Fact] + public void Throws_when_expected_element_is_null() + { + // Arrange + var document = XDocument.Parse( + """ + + a + b + + """); + + // Act + Action act = () => document.Should().HaveElementWithValue(null, "a"); + + // Assert + act.Should().Throw().WithMessage("*expectedElement*"); + } + + [Fact] + public void Throws_when_expected_element_with_namespace_is_null() + { + // Arrange + var document = XDocument.Parse( + """ + + a + b + + """); + + // Act + Action act = () => document.Should().HaveElementWithValue((XName)null, "a"); + + // Assert + act.Should().Throw().WithMessage("*expectedElement*"); + } + + [Fact] + public void Throws_when_expected_value_is_null() + { + // Arrange + var document = XDocument.Parse( + """ + + a + b + + """); + + // Act + Action act = () => document.Should().HaveElementWithValue("child", null); + + // Assert + act.Should().Throw().WithMessage("*expectedValue*"); + } + + [Fact] + public void Throws_when_expected_value_is_null_and_searching_with_namespace() + { + // Arrange + var document = XDocument.Parse( + """ + + a + b + + """); + + // Act + Action act = () => document.Should().HaveElementWithValue(XNamespace.None + "child", null); + + // Assert + act.Should().Throw().WithMessage("*expectedValue*"); + } + + [Fact] + public void The_document_cannot_be_null_and_using_a_namespace() + { + // Arrange + XDocument document = null; + + // Act + Action act = () => + document.Should() + .HaveElementWithValue(XNamespace.None + "child", "b", "we want to test the {0} message", "failure"); + + // Assert + act.Should().Throw().WithMessage("*child*b*failure message*element itself is *"); + } + + [Fact] + public void Has_element_with_namespace_and_specified_value() + { + // Arrange + var document = XDocument.Parse( + """ + + a + b + + """); + + // Act / Assert + document.Should().HaveElementWithValue(XNamespace.None + "child", "b"); + } + + [Fact] + public void Throws_when_element_with_namespace_is_not_found() + { + // Arrange + var document = XDocument.Parse( + """ + + a + b + + """); + + // Act + Action act = () => + { + using var _ = new AssertionScope(); + document.Should().HaveElementWithValue(XNamespace.None + "grandchild", "f"); + }; + + // Assert + act.Should().Throw().WithMessage("*grandchild*f*element*isn't found*"); + } + + [Fact] + public void Throws_when_element_with_namespace_found_but_value_does_not_match() + { + // Arrange + var document = XDocument.Parse( + """ + + a + b + + """); + + // Act + Action act = () => document.Should().HaveElementWithValue(XNamespace.None + "child", "c"); + + // Assert + act.Should().Throw().WithMessage("*child*c*element*does not have such a value*"); + } + } + public class HaveElementWithOccurrence { [Fact] @@ -1409,4 +1665,333 @@ public void When_asserting_a_null_document_to_have_an_element_count_it_should_fa "Cannot assert the count if the document itself is ."); } } + + public class NotHaveElement + { + [Fact] + public void The_document_cannot_be_null() + { + // Arrange + XDocument document = null; + + // Act + Action act = () => document.Should().NotHaveElement("child"); + + // Assert + act.Should().Throw().WithMessage("*child*b*element itself is *"); + } + + [Fact] + public void The_document_does_not_have_this_element() + { + // Arrange + var document = XDocument.Parse( + """ + + a + b + + """); + + // Act / Assert + document.Should().NotHaveElement("c"); + } + + [Fact] + public void Throws_when_element_found_but_expected_to_be_absent() + { + // Arrange + var document = XDocument.Parse( + """ + + a + b + + """); + + // Act + Action act = () => document.Should().NotHaveElement("child"); + + // Assert + act.Should().Throw().WithMessage("*Did not*child*element*was found*"); + } + + [Fact] + public void Throws_when_unexpected_element_is_null() + { + // Arrange + var document = XDocument.Parse( + """ + + a + b + + """); + + // Act + Action act = () => document.Should().NotHaveElement(null); + + // Assert + act.Should().Throw().WithMessage("*unexpectedElement*"); + } + + [Fact] + public void Throws_when_unexpected_element_is_null_with_namespace() + { + // Arrange + var document = XDocument.Parse( + """ + + a + b + + """); + + // Act + Action act = () => document.Should().NotHaveElement((XName)null); + + // Assert + act.Should().Throw().WithMessage("*unexpectedElement*"); + } + + [Fact] + public void Throws_when_null_with_namespace() + { + // Arrange + XDocument document = null; + + // Act + Action act = () => + document.Should() + .NotHaveElement(XNamespace.None + "child", "we want to test the {0} message", "failure"); + + // Assert + act.Should().Throw().WithMessage("*child*failure message*element itself is *"); + } + + [Fact] + public void Not_have_element_with_with_namespace() + { + // Arrange + var document = XDocument.Parse( + """ + + a + b + + """); + + // Act / Assert + document.Should().NotHaveElement(XNamespace.None + "c"); + } + } + + public class NotHaveElementWithValue + { + [Fact] + public void The_document_cannot_be_null() + { + // Arrange + XDocument element = null; + + // Act + Action act = () => element.Should().NotHaveElementWithValue("child", "b"); + + // Assert + act.Should().Throw().WithMessage("*child*b*element itself is *"); + } + + [Fact] + public void Throws_when_element_with_specified_value_is_found() + { + // Arrange + var element = XDocument.Parse( + """ + + a + b + + """); + + // Act + Action act = () => element.Should().NotHaveElementWithValue("child", "b"); + + // Assert + act.Should().Throw().WithMessage("Did not*element*child*value*b*does have this value*"); + } + + [Fact] + public void Passes_when_element_not_found() + { + // Arrange + var element = XDocument.Parse( + """ + + a + b + + """); + + // Act / Assert + element.Should().NotHaveElementWithValue("c", "f"); + } + + [Fact] + public void Passes_when_element_found_but_value_does_not_match() + { + // Arrange + var element = XDocument.Parse( + """ + + a + b + + """); + + // Act / Assert + element.Should().NotHaveElementWithValue("child", "c"); + } + + [Fact] + public void Throws_when_expected_element_is_null() + { + // Arrange + var element = XDocument.Parse( + """ + + a + b + + """); + + // Act + Action act = () => element.Should().NotHaveElementWithValue(null, "a"); + + // Assert + act.Should().Throw().WithMessage("*expectedElement*"); + } + + [Fact] + public void Throws_when_expected_element_is_null_with_namespace() + { + // Arrange + var element = XDocument.Parse( + """ + + a + b + + """); + + // Act + Action act = () => element.Should().NotHaveElementWithValue((XName)null, "a"); + + // Assert + act.Should().Throw().WithMessage("*expectedElement*"); + } + + [Fact] + public void Throws_when_expected_value_is_null() + { + // Arrange + var element = XDocument.Parse( + """ + + a + b + + """); + + // Act + Action act = () => element.Should().NotHaveElementWithValue("child", null); + + // Assert + act.Should().Throw().WithMessage("*expectedValue*"); + } + + [Fact] + public void Throws_when_expected_value_is_null_with_namespace() + { + // Arrange + var element = XDocument.Parse( + """ + + a + b + + """); + + // Act + Action act = () => element.Should().NotHaveElementWithValue(XNamespace.None + "child", null); + + // Assert + act.Should().Throw().WithMessage("*expectedValue*"); + } + + [Fact] + public void The_document_cannot_be_null_and_searching_with_namespace() + { + // Arrange + XDocument element = null; + + // Act + Action act = () => + element.Should().NotHaveElementWithValue(XNamespace.None + "child", "b", "we want to test the {0} message", + "failure"); + + // Assert + act.Should().Throw().WithMessage("*child*b*failure message*element itself is *"); + } + + [Fact] + public void Throws_when_element_with_specified_value_is_found_with_namespace() + { + // Arrange + var element = XDocument.Parse( + """ + + a + b + + """); + + // Act + Action act = () => element.Should().NotHaveElementWithValue(XNamespace.None + "child", "b"); + + // Assert + act.Should().Throw().WithMessage("Did not expect*element*child*value*b*does have this value*"); + } + + [Fact] + public void Passes_when_element_with_namespace_not_found() + { + // Arrange + var element = XDocument.Parse( + """ + + a + b + + """); + + // Act / Assert + element.Should().NotHaveElementWithValue(XNamespace.None + "c", "f"); + } + + [Fact] + public void Passes_when_element_with_namespace_found_but_value_does_not_match() + { + // Arrange + var element = XDocument.Parse( + """ + + a + b + + """); + + // Act / Assert + element.Should().NotHaveElementWithValue(XNamespace.None + "child", "c"); + } + } } diff --git a/Tests/FluentAssertions.Specs/Xml/XElementAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Xml/XElementAssertionSpecs.cs index 1937e36e64..ff85c03724 100644 --- a/Tests/FluentAssertions.Specs/Xml/XElementAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Xml/XElementAssertionSpecs.cs @@ -667,8 +667,8 @@ public void When_asserting_a_xml_element_is_not_equivalent_to_a_different_xml_element_with_same_contents_but_different_ns_prefixes_it_should_fail() { // Arrange - var theElement = XElement.Parse(@""); - var otherXElement = XElement.Parse(@""); + var theElement = XElement.Parse(""""""); + var otherXElement = XElement.Parse(""""""); // Act Action act = () => @@ -849,458 +849,920 @@ public void When_xml_element_is_null_then_have_value_should_fail() public class HaveAttribute { [Fact] - public void When_asserting_element_has_attribute_with_specific_value_and_it_does_it_should_succeed() + public void Passes_when_attribute_found() { // Arrange var element = XElement.Parse(@""); - // Act - Action act = () => - element.Should().HaveAttribute("name", "martin"); + // Act / Assert + element.Should().HaveAttribute("name"); + } - // Assert - act.Should().NotThrow(); + [Fact] + public void Passes_when_attribute_found_with_namespace() + { + // Arrange + var element = XElement.Parse(""""""); + + // Act / Assert + element.Should().HaveAttribute(XName.Get("name", "http://www.example.com/2012/test")); } [Fact] - public void When_asserting_element_has_attribute_with_ns_and_specific_value_and_it_does_it_should_succeed() + public void Throws_when_attribute_is_not_found() { // Arrange - var element = XElement.Parse(@""); + var theElement = XElement.Parse(""""""); // Act Action act = () => - element.Should().HaveAttribute(XName.Get("name", "http://www.example.com/2012/test"), "martin"); + theElement.Should().HaveAttribute("age", "because we want to test the failure {0}", "message"); // Assert - act.Should().NotThrow(); + act.Should().Throw().WithMessage( + "Expected theElement to have attribute \"age\"*failure message*, but found no such attribute in *"); } [Fact] - public void When_asserting_element_has_attribute_with_specific_value_but_attribute_does_not_exist_it_should_fail() + public void Throws_when_attribute_is_not_found_with_namespace() { // Arrange - var theElement = XElement.Parse(@""); + var theElement = XElement.Parse(""""""); // Act Action act = () => - theElement.Should().HaveAttribute("age", "36"); + theElement.Should().HaveAttribute(XName.Get("age", "http://www.example.com/2012/test"), + "because we want to test the failure {0}", "message"); // Assert act.Should().Throw().WithMessage( - "Expected theElement to have attribute \"age\" with value \"36\", but found no such attribute in "); + "Expected theElement to have attribute \"{http://www.example.com/2012/test}age\"" + + "*failure message*but found no such attribute in *"); } [Fact] - public void When_asserting_element_has_attribute_with_ns_and_specific_value_but_attribute_does_not_exist_it_should_fail() + public void Throws_when_xml_element_is_null() { - // Arrange - var theElement = XElement.Parse(@""); + XElement theElement = null; // Act Action act = () => - theElement.Should().HaveAttribute(XName.Get("age", "http://www.example.com/2012/test"), "36"); + theElement.Should().HaveAttribute("name", "we want to test the failure {0}", "message"); // Assert - act.Should().Throw().WithMessage( - "Expected theElement to have attribute \"{http://www.example.com/2012/test}age\" with value \"36\"," - + " but found no such attribute in "); + act.Should().Throw() + .WithMessage( + "Expected attribute \"name\" in element *failure message*" + + ", but theElement is ."); } [Fact] - public void - When_asserting_element_has_attribute_with_specific_value_but_attribute_does_not_exist_it_should_fail_with_descriptive_message() + public void Throws_when_xml_element_is_null_with_namespace() { - // Arrange - var theElement = XElement.Parse(@""); + XElement theElement = null; // Act Action act = () => - { - using var _ = new AssertionScope(); - theElement.Should().HaveAttribute("age", "36", "because we want to test the failure {0}", "message"); - }; + theElement.Should().HaveAttribute((XName)"name", "we want to test the failure {0}", "message"); // Assert - act.Should().Throw().WithMessage( - "Expected theElement to have attribute \"age\" with value \"36\" because we want to test the failure message," - + " but found no such attribute in "); + act.Should().Throw() + .WithMessage( + "Expected attribute \"name\" in element*failure message*" + + ", but theElement is ."); } [Fact] - public void - When_asserting_element_has_attribute_with_ns_and_specific_value_but_attribute_does_not_exist_it_should_fail_with_descriptive_message() + public void Throws_when_expectation_is_null() { - // Arrange - var theElement = XElement.Parse(@""); + XElement theElement = new("element"); // Act Action act = () => - theElement.Should().HaveAttribute(XName.Get("age", "http://www.example.com/2012/test"), "36", - "because we want to test the failure {0}", "message"); + theElement.Should().HaveAttribute(null); // Assert - act.Should().Throw().WithMessage( - "Expected theElement to have attribute \"{http://www.example.com/2012/test}age\" with value \"36\"" - + " because we want to test the failure message," - + " but found no such attribute in "); + act.Should().ThrowExactly() + .WithParameterName("expectedName"); } [Fact] - public void When_asserting_element_has_attribute_with_specific_value_but_attribute_has_different_value_it_should_fail() + public void Throws_when_expectation_is_null_with_namespace() { - // Arrange - var theElement = XElement.Parse(@""); + XElement theElement = new("element"); // Act Action act = () => - theElement.Should().HaveAttribute("name", "dennis"); + theElement.Should().HaveAttribute((XName)null); // Assert - act.Should().Throw().WithMessage( - "Expected attribute \"name\" in theElement to have value \"dennis\", but found \"martin\"."); + act.Should().ThrowExactly() + .WithParameterName("expectedName"); } [Fact] - public void - When_asserting_element_has_attribute_with_ns_and_specific_value_but_attribute_has_different_value_it_should_fail() + public void Throws_when_expectation_is_empty() { - // Arrange - var theElement = XElement.Parse(@""); + XElement theElement = new("element"); // Act Action act = () => - theElement.Should().HaveAttribute(XName.Get("name", "http://www.example.com/2012/test"), "dennis"); + theElement.Should().HaveAttribute(string.Empty); // Assert - act.Should().Throw().WithMessage( - "Expected attribute \"{http://www.example.com/2012/test}name\" in theElement to have value \"dennis\", but found \"martin\"."); + act.Should().ThrowExactly() + .WithParameterName("expectedName"); } + } + public class NotHaveAttribute + { [Fact] - public void - When_asserting_element_has_attribute_with_specific_value_but_attribute_has_different_value_it_should_fail_with_descriptive_message() + public void Passes_when_attribute_not_found() + { + // Arrange + var element = XElement.Parse(@""); + + // Act / Assert + element.Should().NotHaveAttribute("surname"); + } + + [Fact] + public void Passes_when_attribute_not_found_with_namespace() + { + // Arrange + var element = XElement.Parse(""""""); + + // Act / Assert + element.Should().NotHaveAttribute(XName.Get("surname", "http://www.example.com/2012/test")); + } + + [Fact] + public void Throws_when_attribute_is_found() { // Arrange - var theElement = XElement.Parse(@""); + var theElement = XElement.Parse(""""""); // Act Action act = () => - theElement.Should().HaveAttribute("name", "dennis", "because we want to test the failure {0}", "message"); + theElement.Should().NotHaveAttribute("name", "because we want to test the failure {0}", "message"); // Assert act.Should().Throw().WithMessage( - "Expected attribute \"name\" in theElement to have value \"dennis\"" - + " because we want to test the failure message, but found \"martin\"."); + "Did not expect theElement to have attribute \"name\"*failure message*, but found such attribute in *"); } [Fact] - public void - When_asserting_element_has_attribute_with_ns_and_specific_value_but_attribute_has_different_value_it_should_fail_with_descriptive_message() + public void Throws_when_attribute_is_found_with_namespace() { // Arrange - var theElement = XElement.Parse(@""); + var theElement = XElement.Parse(""""""); // Act Action act = () => - theElement.Should().HaveAttribute(XName.Get("name", "http://www.example.com/2012/test"), "dennis", + theElement.Should().NotHaveAttribute(XName.Get("name", "http://www.example.com/2012/test"), "because we want to test the failure {0}", "message"); // Assert act.Should().Throw().WithMessage( - "Expected attribute \"{http://www.example.com/2012/test}name\" in theElement to have value \"dennis\"" - + " because we want to test the failure message, but found \"martin\"."); + "Did not expect theElement to have attribute \"{http://www.example.com/2012/test}name\"" + + "*failure message*but found such attribute in *"); } [Fact] - public void When_xml_element_is_null_then_have_attribute_should_fail() + public void Throws_when_xml_element_is_null() { XElement theElement = null; // Act Action act = () => - theElement.Should().HaveAttribute("name", "value", "we want to test the failure {0}", "message"); + theElement.Should().NotHaveAttribute("name", "we want to test the failure {0}", "message"); // Assert act.Should().Throw() .WithMessage( - "Expected attribute \"name\" in element to have value \"value\" *failure message*" + + "Did not expect attribute \"name\" in element *failure message*" + ", but theElement is ."); } [Fact] - public void When_xml_element_is_null_then_have_attribute_with_XName_should_fail() + public void Throws_when_xml_element_is_null_with_namespace() { XElement theElement = null; // Act Action act = () => - theElement.Should().HaveAttribute((XName)"name", "value", "we want to test the failure {0}", "message"); + theElement.Should().NotHaveAttribute((XName)"name", "we want to test the failure {0}", "message"); // Assert act.Should().Throw() .WithMessage( - "Expected attribute \"name\" in element to have value \"value\" *failure message*" + + "Did not expect attribute \"name\" in element*failure message*" + ", but theElement is ."); } [Fact] - public void When_asserting_element_has_an_attribute_with_a_null_name_it_should_throw() + public void Throws_when_expectation_is_null() { XElement theElement = new("element"); // Act Action act = () => - theElement.Should().HaveAttribute(null, "value"); + theElement.Should().NotHaveAttribute(null); // Assert act.Should().ThrowExactly() - .WithParameterName("expectedName"); + .WithParameterName("unexpectedName"); } [Fact] - public void When_asserting_element_has_an_attribute_with_a_null_XName_it_should_throw() + public void Throws_when_expectation_is_null_with_namespace() { XElement theElement = new("element"); // Act Action act = () => - theElement.Should().HaveAttribute((XName)null, "value"); + theElement.Should().NotHaveAttribute((XName)null); // Assert act.Should().ThrowExactly() - .WithParameterName("expectedName"); + .WithParameterName("unexpectedName"); } [Fact] - public void When_asserting_element_has_an_attribute_with_an_empty_name_it_should_throw() + public void Throws_when_expectation_is_empty() { XElement theElement = new("element"); // Act Action act = () => - theElement.Should().HaveAttribute(string.Empty, "value"); + theElement.Should().NotHaveAttribute(string.Empty); // Assert act.Should().ThrowExactly() - .WithParameterName("expectedName"); + .WithParameterName("unexpectedName"); } } - public class HaveElement + public class HaveAttributeWithValue { [Fact] - public void When_asserting_element_has_child_element_and_it_does_it_should_succeed() + public void When_asserting_element_has_attribute_with_specific_value_and_it_does_it_should_succeed() { // Arrange - var element = XElement.Parse( - """ - - - - """); + var element = XElement.Parse(@""); // Act Action act = () => - element.Should().HaveElement("child"); + element.Should().HaveAttributeWithValue("name", "martin"); // Assert act.Should().NotThrow(); } [Fact] - public void When_asserting_element_has_child_element_with_ns_and_it_does_it_should_succeed() + public void When_asserting_element_has_attribute_with_ns_and_specific_value_and_it_does_it_should_succeed() { // Arrange - var element = XElement.Parse( - """ - - - - """); + var element = XElement.Parse(""""""); // Act Action act = () => - element.Should().HaveElement(XName.Get("child", "http://www.example.com/2012/test")); + element.Should().HaveAttributeWithValue(XName.Get("name", "http://www.example.com/2012/test"), "martin"); // Assert act.Should().NotThrow(); } [Fact] - public void When_asserting_element_has_child_element_but_it_does_not_it_should_fail() + public void When_asserting_element_has_attribute_with_specific_value_but_attribute_does_not_exist_it_should_fail() { // Arrange - var theElement = XElement.Parse( - """ - - - - """); + var theElement = XElement.Parse(""""""); // Act Action act = () => - theElement.Should().HaveElement("unknown"); + theElement.Should().HaveAttributeWithValue("age", "36"); // Assert act.Should().Throw().WithMessage( - "Expected theElement to have child element \"unknown\", but no such child element was found."); + "Expected theElement to have attribute \"age\" with value \"36\", but found no such attribute in "); } [Fact] - public void When_asserting_element_has_child_element_with_ns_but_it_does_not_it_should_fail() + public void When_asserting_element_has_attribute_with_ns_and_specific_value_but_attribute_does_not_exist_it_should_fail() { // Arrange - var theElement = XElement.Parse( - """ - - - - """); + var theElement = XElement.Parse(""""""); // Act Action act = () => - theElement.Should().HaveElement(XName.Get("unknown", "http://www.example.com/2012/test")); + theElement.Should().HaveAttributeWithValue(XName.Get("age", "http://www.example.com/2012/test"), "36"); // Assert act.Should().Throw().WithMessage( - "Expected theElement to have child element \"{{http://www.example.com/2012/test}}unknown\", but no such child element was found."); + "Expected theElement to have attribute \"{http://www.example.com/2012/test}age\" with value \"36\"," + + " but found no such attribute in "); } [Fact] - public void When_asserting_element_has_child_element_but_it_does_not_it_should_fail_with_descriptive_message() + public void + When_asserting_element_has_attribute_with_specific_value_but_attribute_does_not_exist_it_should_fail_with_descriptive_message() { // Arrange - var theElement = XElement.Parse( - """ - - - - """); + var theElement = XElement.Parse(""""""); // Act Action act = () => - theElement.Should().HaveElement("unknown", "because we want to test the failure message"); + { + using var _ = new AssertionScope(); + theElement.Should().HaveAttributeWithValue("age", "36", "because we want to test the failure {0}", "message"); + }; // Assert act.Should().Throw().WithMessage( - "Expected theElement to have child element \"unknown\" because we want to test the failure message," - + " but no such child element was found."); + "Expected theElement to have attribute \"age\" with value \"36\" because we want to test the failure message," + + " but found no such attribute in "); } [Fact] - public void When_asserting_element_has_child_element_with_ns_but_it_does_not_it_should_fail_with_descriptive_message() + public void + When_asserting_element_has_attribute_with_ns_and_specific_value_but_attribute_does_not_exist_it_should_fail_with_descriptive_message() { // Arrange - var theElement = XElement.Parse( - """ - - - - """); + var theElement = XElement.Parse(""""""); // Act Action act = () => - theElement.Should().HaveElement(XName.Get("unknown", "http://www.example.com/2012/test"), - "because we want to test the failure message"); + theElement.Should().HaveAttributeWithValue(XName.Get("age", "http://www.example.com/2012/test"), "36", + "because we want to test the failure {0}", "message"); // Assert act.Should().Throw().WithMessage( - "Expected theElement to have child element \"{{http://www.example.com/2012/test}}unknown\"" - + " because we want to test the failure message, but no such child element was found."); + "Expected theElement to have attribute \"{http://www.example.com/2012/test}age\" with value \"36\"" + + " because we want to test the failure message," + + " but found no such attribute in "); } [Fact] - public void When_asserting_element_has_child_element_it_should_return_the_matched_element_in_the_which_property() + public void When_asserting_element_has_attribute_with_specific_value_but_attribute_has_different_value_it_should_fail() { // Arrange - var element = XElement.Parse( - """ - - - - """); + var theElement = XElement.Parse(""""""); // Act - var matchedElement = element.Should().HaveElement("child").Subject; + Action act = () => + theElement.Should().HaveAttributeWithValue("name", "dennis"); // Assert - matchedElement.Should().BeOfType() - .And.HaveAttribute("attr", "1"); - - matchedElement.Name.Should().Be(XName.Get("child")); + act.Should().Throw().WithMessage( + "Expected attribute \"name\" in theElement to have value \"dennis\", but found \"martin\"."); } [Fact] - public void When_asserting_element_has_a_child_element_with_a_null_name_it_should_throw() + public void + When_asserting_element_has_attribute_with_ns_and_specific_value_but_attribute_has_different_value_it_should_fail() { - XElement theElement = new("element"); + // Arrange + var theElement = XElement.Parse(""""""); // Act Action act = () => - theElement.Should().HaveElement(null); + theElement.Should().HaveAttributeWithValue(XName.Get("name", "http://www.example.com/2012/test"), "dennis"); // Assert - act.Should().ThrowExactly() - .WithParameterName("expected"); + act.Should().Throw().WithMessage( + "Expected attribute \"{http://www.example.com/2012/test}name\" in theElement to have value \"dennis\", but found \"martin\"."); } [Fact] - public void When_asserting_element_has_a_child_element_with_a_null_XName_it_should_throw() + public void + When_asserting_element_has_attribute_with_specific_value_but_attribute_has_different_value_it_should_fail_with_descriptive_message() { - XElement theElement = new("element"); + // Arrange + var theElement = XElement.Parse(""""""); // Act Action act = () => - theElement.Should().HaveElement((XName)null); + theElement.Should() + .HaveAttributeWithValue("name", "dennis", "because we want to test the failure {0}", "message"); // Assert - act.Should().ThrowExactly() - .WithParameterName("expected"); + act.Should().Throw().WithMessage( + "Expected attribute \"name\" in theElement to have value \"dennis\"" + + " because we want to test the failure message, but found \"martin\"."); } [Fact] - public void When_asserting_element_has_a_child_element_with_an_empty_name_it_should_throw() + public void + When_asserting_element_has_attribute_with_ns_and_specific_value_but_attribute_has_different_value_it_should_fail_with_descriptive_message() { - XElement theElement = new("element"); + // Arrange + var theElement = XElement.Parse(""""""); // Act Action act = () => - theElement.Should().HaveElement(string.Empty); + theElement.Should().HaveAttributeWithValue(XName.Get("name", "http://www.example.com/2012/test"), "dennis", + "because we want to test the failure {0}", "message"); // Assert - act.Should().ThrowExactly() - .WithParameterName("expected"); + act.Should().Throw().WithMessage( + "Expected attribute \"{http://www.example.com/2012/test}name\" in theElement to have value \"dennis\"" + + " because we want to test the failure message, but found \"martin\"."); } - } - public class HaveElementWithOccurrence - { [Fact] - public void Element_has_two_child_elements_and_it_expected_does_it_succeeds() + public void When_xml_element_is_null_then_have_attribute_should_fail() { - // Arrange - var element = XElement.Parse( - """ - - - - - """); + XElement theElement = null; - // Act / Assert - element.Should().HaveElement("child", Exactly.Twice()); + // Act + Action act = () => + theElement.Should().HaveAttributeWithValue("name", "value", "we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw() + .WithMessage( + "Expected attribute \"name\" in element to have value \"value\" *failure message*" + + ", but theElement is ."); } [Fact] - public void Asserting_element_inside_an_assertion_scope_it_checks_the_whole_assertion_scope_before_failing() + public void When_xml_element_is_null_then_have_attribute_with_XName_should_fail() { - // Arrange - XElement element = null; + XElement theElement = null; // Act Action act = () => - { + theElement.Should().HaveAttributeWithValue((XName)"name", "value", "we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw() + .WithMessage( + "Expected attribute \"name\" in element to have value \"value\" *failure message*" + + ", but theElement is ."); + } + + [Fact] + public void When_asserting_element_has_an_attribute_with_a_null_name_it_should_throw() + { + XElement theElement = new("element"); + + // Act + Action act = () => + theElement.Should().HaveAttributeWithValue(null, "value"); + + // Assert + act.Should().ThrowExactly() + .WithParameterName("expectedName"); + } + + [Fact] + public void When_asserting_element_has_an_attribute_with_a_null_XName_it_should_throw() + { + XElement theElement = new("element"); + + // Act + Action act = () => + theElement.Should().HaveAttributeWithValue((XName)null, "value"); + + // Assert + act.Should().ThrowExactly() + .WithParameterName("expectedName"); + } + + [Fact] + public void When_asserting_element_has_an_attribute_with_an_empty_name_it_should_throw() + { + XElement theElement = new("element"); + + // Act + Action act = () => + theElement.Should().HaveAttributeWithValue(string.Empty, "value"); + + // Assert + act.Should().ThrowExactly() + .WithParameterName("expectedName"); + } + } + + public class NotHaveAttributeWithValue + { + [Fact] + public void Passes_when_attribute_does_not_fit() + { + // Arrange + var element = XElement.Parse(@""); + + // Act / Assert + element.Should().NotHaveAttributeWithValue("surname", "martin"); + } + + [Fact] + public void Passes_when_attribute_does_not_fit_with_namespace() + { + // Arrange + var element = XElement.Parse(""""""); + + // Act / Assert + element.Should().NotHaveAttributeWithValue(XName.Get("surname", "http://www.example.com/2012/test"), "martin"); + } + + [Fact] + public void Passes_when_attribute_and_value_does_not_fit() + { + // Arrange + var element = XElement.Parse(@""); + + // Act / Assert + element.Should().NotHaveAttributeWithValue("surname", "mike"); + } + + [Fact] + public void Passes_when_attribute_and_value_does_not_fit_with_namespace() + { + // Arrange + var element = XElement.Parse(""""""); + + // Act / Assert + element.Should().NotHaveAttributeWithValue(XName.Get("surname", "http://www.example.com/2012/test"), "mike"); + } + + [Fact] + public void Passes_when_attribute_fits_and_value_does_not() + { + // Arrange + var element = XElement.Parse(@""); + + // Act / Assert + element.Should().NotHaveAttributeWithValue("name", "mike"); + } + + [Fact] + public void Passes_when_attribute_fits_and_value_does_not_with_namespace() + { + // Arrange + var element = XElement.Parse(""""""); + + // Act / Assert + element.Should().NotHaveAttributeWithValue(XName.Get("name", "http://www.example.com/2012/test"), "mike"); + } + + [Fact] + public void Throws_when_attribute_and_name_fits() + { + // Arrange + var theElement = XElement.Parse(""""""); + + // Act + Action act = () => + { + using var _ = new AssertionScope(); + + theElement.Should() + .NotHaveAttributeWithValue("name", "martin", "because we want to test the failure {0}", "message"); + }; + + // Assert + act.Should().Throw().WithMessage( + "Did not expect theElement to have attribute \"name\" with value \"martin\" because we want to test the failure message," + + " but found such attribute in *"); + } + + [Fact] + public void Throws_when_attribute_and_name_fits_with_namespace() + { + // Arrange + var theElement = XElement.Parse(""""""); + + // Act + Action act = () => + theElement.Should().NotHaveAttributeWithValue(XName.Get("name", "http://www.example.com/2012/test"), "martin", + "because we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw().WithMessage( + "Did not expect theElement to have attribute \"{http://www.example.com/2012/test}name\" with value \"martin\"" + + " because we want to test the failure message," + + " but found such attribute in *"); + } + + [Fact] + public void Throws_when_element_is_null() + { + XElement theElement = null; + + // Act + Action act = () => + theElement.Should().NotHaveAttributeWithValue("name", "value", "we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw() + .WithMessage( + "Did not expect attribute \"name\" in element to have value \"value\"*failure message*, but theElement is ."); + } + + [Fact] + public void Throws_when_element_is_null_with_namespace() + { + XElement theElement = null; + + // Act + Action act = () => + theElement.Should() + .NotHaveAttributeWithValue((XName)"name", "value", "we want to test the failure {0}", "message"); + + // Assert + act.Should().Throw() + .WithMessage( + "Did not expect attribute \"name\" in element to have value \"value\"*failure message*, but theElement is ."); + } + + [Fact] + public void Throws_when_expected_is_null() + { + XElement theElement = new("element"); + + // Act + Action act = () => + theElement.Should().NotHaveAttributeWithValue(null, "value"); + + // Assert + act.Should().ThrowExactly() + .WithParameterName("unexpectedName"); + } + + [Fact] + public void Throws_when_expected_is_null_with_namespace() + { + XElement theElement = new("element"); + + // Act + Action act = () => + theElement.Should().NotHaveAttributeWithValue((XName)null, "value"); + + // Assert + act.Should().ThrowExactly() + .WithParameterName("unexpectedName"); + } + + [Fact] + public void Throws_when_expected_attribute_is_something_but_value_is_null() + { + XElement theElement = new("element"); + + // Act + Action act = () => + theElement.Should().NotHaveAttributeWithValue("some", null); + + // Assert + act.Should().ThrowExactly() + .WithParameterName("unexpectedValue"); + } + + [Fact] + public void Throws_when_expected_attribute_is_something_but_value_is_null_with_namespace() + { + XElement theElement = new("element"); + + // Act + Action act = () => + theElement.Should().NotHaveAttributeWithValue((XName)"some", null); + + // Assert + act.Should().ThrowExactly() + .WithParameterName("unexpectedValue"); + } + + [Fact] + public void Throws_when_expected_is_empty() + { + XElement theElement = new("element"); + + // Act + Action act = () => + theElement.Should().NotHaveAttributeWithValue(string.Empty, "value"); + + // Assert + act.Should().ThrowExactly() + .WithParameterName("unexpectedName"); + } + } + + public class HaveElement + { + [Fact] + public void When_asserting_element_has_child_element_and_it_does_it_should_succeed() + { + // Arrange + var element = XElement.Parse( + """ + + + + """); + + // Act + Action act = () => + element.Should().HaveElement("child"); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_element_has_child_element_with_ns_and_it_does_it_should_succeed() + { + // Arrange + var element = XElement.Parse( + """ + + + + """); + + // Act + Action act = () => + element.Should().HaveElement(XName.Get("child", "http://www.example.com/2012/test")); + + // Assert + act.Should().NotThrow(); + } + + [Fact] + public void When_asserting_element_has_child_element_but_it_does_not_it_should_fail() + { + // Arrange + var theElement = XElement.Parse( + """ + + + + """); + + // Act + Action act = () => + theElement.Should().HaveElement("unknown"); + + // Assert + act.Should().Throw().WithMessage( + "Expected theElement to have child element \"unknown\", but no such child element was found."); + } + + [Fact] + public void When_asserting_element_has_child_element_with_ns_but_it_does_not_it_should_fail() + { + // Arrange + var theElement = XElement.Parse( + """ + + + + """); + + // Act + Action act = () => + theElement.Should().HaveElement(XName.Get("unknown", "http://www.example.com/2012/test")); + + // Assert + act.Should().Throw().WithMessage( + "Expected theElement to have child element \"{{http://www.example.com/2012/test}}unknown\", but no such child element was found."); + } + + [Fact] + public void When_asserting_element_has_child_element_but_it_does_not_it_should_fail_with_descriptive_message() + { + // Arrange + var theElement = XElement.Parse( + """ + + + + """); + + // Act + Action act = () => + theElement.Should().HaveElement("unknown", "because we want to test the failure message"); + + // Assert + act.Should().Throw().WithMessage( + "Expected theElement to have child element \"unknown\" because we want to test the failure message," + + " but no such child element was found."); + } + + [Fact] + public void When_asserting_element_has_child_element_with_ns_but_it_does_not_it_should_fail_with_descriptive_message() + { + // Arrange + var theElement = XElement.Parse( + """ + + + + """); + + // Act + Action act = () => + theElement.Should().HaveElement(XName.Get("unknown", "http://www.example.com/2012/test"), + "because we want to test the failure message"); + + // Assert + act.Should().Throw().WithMessage( + "Expected theElement to have child element \"{{http://www.example.com/2012/test}}unknown\"" + + " because we want to test the failure message, but no such child element was found."); + } + + [Fact] + public void When_asserting_element_has_child_element_it_should_return_the_matched_element_in_the_which_property() + { + // Arrange + var element = XElement.Parse( + """ + + + + """); + + // Act + var matchedElement = element.Should().HaveElement("child").Subject; + + // Assert + matchedElement.Should().BeOfType() + .And.HaveAttributeWithValue("attr", "1"); + + matchedElement.Name.Should().Be(XName.Get("child")); + } + + [Fact] + public void When_asserting_element_has_a_child_element_with_a_null_name_it_should_throw() + { + XElement theElement = new("element"); + + // Act + Action act = () => + theElement.Should().HaveElement(null); + + // Assert + act.Should().ThrowExactly() + .WithParameterName("expected"); + } + + [Fact] + public void When_asserting_element_has_a_child_element_with_a_null_XName_it_should_throw() + { + XElement theElement = new("element"); + + // Act + Action act = () => + theElement.Should().HaveElement((XName)null); + + // Assert + act.Should().ThrowExactly() + .WithParameterName("expected"); + } + + [Fact] + public void When_asserting_element_has_a_child_element_with_an_empty_name_it_should_throw() + { + XElement theElement = new("element"); + + // Act + Action act = () => + theElement.Should().HaveElement(string.Empty); + + // Assert + act.Should().ThrowExactly() + .WithParameterName("expected"); + } + } + + public class HaveElementWithOccurrence + { + [Fact] + public void Element_has_two_child_elements_and_it_expected_does_it_succeeds() + { + // Arrange + var element = XElement.Parse( + """ + + + + + """); + + // Act / Assert + element.Should().HaveElement("child", Exactly.Twice()); + } + + [Fact] + public void Asserting_element_inside_an_assertion_scope_it_checks_the_whole_assertion_scope_before_failing() + { + // Arrange + XElement element = null; + + // Act + Action act = () => + { using (new AssertionScope()) { element.Should().HaveElement("child", Exactly.Twice()); @@ -1309,124 +1771,672 @@ public void Asserting_element_inside_an_assertion_scope_it_checks_the_whole_asse }; // Assert - act.Should().NotThrow(); + act.Should().NotThrow(); + } + + [Fact] + public void Element_has_two_child_elements_and_three_expected_it_fails() + { + // Arrange + var element = XElement.Parse( + """ + + + + + + """); + + // Act + Action act = () => element.Should().HaveElement("child", Exactly.Twice()); + + // Assert + act.Should().Throw() + .WithMessage("Expected element to have an element \"child\"*exactly*2 times, but found it 3 times."); + } + + [Fact] + public void Element_is_valid_and_expected_null_with_string_overload_it_fails() + { + // Arrange + var element = XElement.Parse( + """ + + + + + + """); + + // Act + Action act = () => element.Should().HaveElement(null, Exactly.Twice()); + + // Assert + act.Should().Throw().WithMessage( + "Cannot assert the element has an element if the expected name is .*"); + } + + [Fact] + public void Element_is_valid_and_expected_null_with_x_name_overload_it_fails() + { + // Arrange + var element = XElement.Parse( + """ + + + + + + """); + + // Act + Action act = () => element.Should().HaveElement((XName)null, Exactly.Twice()); + + // Assert + act.Should().Throw().WithMessage( + "Cannot assert the element has an element count if the element name is .*"); + } + + [Fact] + public void Chaining_after_a_successful_occurrence_check_does_continue_the_assertion() + { + // Arrange + var element = XElement.Parse( + """ + + + + + + """); + + // Act / Assert + element.Should().HaveElement("child", AtLeast.Twice()) + .Which.Should().NotBeNull(); + } + + [Fact] + public void Chaining_after_a_non_successful_occurrence_check_does_not_continue_the_assertion() + { + // Arrange + var element = XElement.Parse( + """ + + + + + + """); + + // Act + Action act = () => element.Should().HaveElement("child", Exactly.Once()) + .Which.Should().NotBeNull(); + + // Assert + act.Should().Throw() + .WithMessage("Expected element to have an element \"child\"*exactly*1 time, but found it 3 times."); + } + + [Fact] + public void Null_element_is_expected_to_have_an_element_count_it_should_fail() + { + // Arrange + XElement xElement = null; + + // Act + Action act = () => xElement.Should().HaveElement("child", AtLeast.Once()); + + // Assert + act.Should().Throw().WithMessage( + "Expected* to have an element with count of *, but the element itself is ."); + } + } + + public class HaveElementWithValue + { + [Fact] + public void The_element_cannot_be_null() + { + // Arrange + XElement element = null; + + // Act + Action act = () => element.Should().HaveElementWithValue("child", "b"); + + // Assert + act.Should().Throw().WithMessage("*child*b*element itself is *"); + } + + [Fact] + public void Has_element_with_specified_value() + { + // Arrange + var element = XElement.Parse( + """ + + a + b + + """); + + // Act / Assert + element.Should().HaveElementWithValue("child", "b"); + } + + [Fact] + public void Throws_when_element_is_not_found() + { + // Arrange + var element = XElement.Parse( + """ + + a + b + + """); + + // Act + Action act = () => element.Should().HaveElementWithValue("c", "f"); + + // Assert + act.Should().Throw().WithMessage("*c*f*element*isn't found*"); + } + + [Fact] + public void Throws_when_element_found_but_value_does_not_match() + { + // Arrange + var element = XElement.Parse( + """ + + a + b + + """); + + // Act + Action act = () => element.Should().HaveElementWithValue("child", "c"); + + // Assert + act.Should().Throw().WithMessage("*child*c*element*does not have such a value*"); + } + + [Fact] + public void Throws_when_expected_element_is_null() + { + // Arrange + var element = XElement.Parse( + """ + + a + b + + """); + + // Act + Action act = () => element.Should().HaveElementWithValue(null, "a"); + + // Assert + act.Should().Throw().WithMessage("*expectedElement*"); + } + + [Fact] + public void Throws_when_expected_element_is_null_with_namespace() + { + // Arrange + var element = XElement.Parse( + """ + + a + b + + """); + + // Act + Action act = () => element.Should().HaveElementWithValue((XName)null, "a"); + + // Assert + act.Should().Throw().WithMessage("*expectedElement*"); + } + + [Fact] + public void Throws_when_expected_value_is_null() + { + // Arrange + var element = XElement.Parse( + """ + + a + b + + """); + + // Act + Action act = () => element.Should().HaveElementWithValue("child", null); + + // Assert + act.Should().Throw().WithMessage("*expectedValue*"); + } + + [Fact] + public void Throws_when_expected_value_is_null_with_namespace() + { + // Arrange + var element = XElement.Parse( + """ + + a + b + + """); + + // Act + Action act = () => element.Should().HaveElementWithValue(XNamespace.None + "child", null); + + // Assert + act.Should().Throw().WithMessage("*expectedValue*"); + } + + [Fact] + public void The_element_cannot_be_null_and_searching_with_namespace() + { + // Arrange + XElement element = null; + + // Act + Action act = () => + element.Should() + .HaveElementWithValue(XNamespace.None + "child", "b", "we want to test the {0} message", "failure"); + + // Assert + act.Should().Throw().WithMessage("*child*b*failure message*element itself is *"); + } + + [Fact] + public void Has_element_with_specified_value_with_namespace() + { + // Arrange + var element = XElement.Parse( + """ + + a + b + + """); + + // Act / Assert + element.Should().HaveElementWithValue(XNamespace.None + "child", "b"); + } + + [Fact] + public void Throws_when_element_with_namespace_not_found() + { + // Arrange + var element = XElement.Parse( + """ + + a + b + + """); + + // Act + Action act = () => + { + using var _ = new AssertionScope(); + element.Should().HaveElementWithValue(XNamespace.None + "c", "f"); + }; + + // Assert + act.Should().Throw().WithMessage("*c*f*element*isn't found*"); } [Fact] - public void Element_has_two_child_elements_and_three_expected_it_fails() + public void Throws_when_element_with_namespace_found_but_value_does_not_match() { // Arrange var element = XElement.Parse( """ - - - + a + b """); // Act - Action act = () => element.Should().HaveElement("child", Exactly.Twice()); + Action act = () => element.Should().HaveElementWithValue(XNamespace.None + "child", "c"); // Assert - act.Should().Throw() - .WithMessage("Expected element to have an element \"child\"*exactly*2 times, but found it 3 times."); + act.Should().Throw().WithMessage("*child*c*element*does not have such a value*"); } + } + public class NotHaveElement + { [Fact] - public void Element_is_valid_and_expected_null_with_string_overload_it_fails() + public void The_element_cannot_be_null() + { + // Arrange + XElement element = null; + + // Act + Action act = () => element.Should().NotHaveElement("child"); + + // Assert + act.Should().Throw().WithMessage("*child*b*element itself is *"); + } + + [Fact] + public void Element_does_not_have_this_child_element() { // Arrange var element = XElement.Parse( """ - - - + a + b + + """); + + // Act / Assert + element.Should().NotHaveElement("c"); + } + + [Fact] + public void Throws_when_element_found_but_expected_to_be_absent() + { + // Arrange + var element = XElement.Parse( + """ + + a + b """); // Act - Action act = () => element.Should().HaveElement(null, Exactly.Twice()); + Action act = () => element.Should().NotHaveElement("child"); // Assert - act.Should().Throw().WithMessage( - "Cannot assert the element has an element if the expected name is .*"); + act.Should().Throw().WithMessage("*Did not*child*element*was found*"); } [Fact] - public void Element_is_valid_and_expected_null_with_x_name_overload_it_fails() + public void Throws_when_unexpected_element_is_null() { // Arrange var element = XElement.Parse( """ - - - + a + b """); // Act - Action act = () => element.Should().HaveElement((XName)null, Exactly.Twice()); + Action act = () => element.Should().NotHaveElement(null); // Assert - act.Should().Throw().WithMessage( - "Cannot assert the element has an element count if the element name is .*"); + act.Should().Throw().WithMessage("*unexpectedElement*"); } [Fact] - public void Chaining_after_a_successful_occurrence_check_does_continue_the_assertion() + public void Throws_when_unexpected_element_is_null_with_namespace() { // Arrange var element = XElement.Parse( """ - - - + a + b + + """); + + // Act + Action act = () => element.Should().NotHaveElement((XName)null); + + // Assert + act.Should().Throw().WithMessage("*unexpectedElement*"); + } + + [Fact] + public void The_element_cannot_be_null_and_searching_with_namespace() + { + // Arrange + XElement element = null; + + // Act + Action act = () => + element.Should() + .NotHaveElement(XNamespace.None + "child", "we want to test the {0} message", "failure"); + + // Assert + act.Should().Throw().WithMessage("*child*failure message*element itself is *"); + } + + [Fact] + public void Not_have_element_with_with_namespace() + { + // Arrange + var element = XElement.Parse( + """ + + a + b """); // Act / Assert - element.Should().HaveElement("child", AtLeast.Twice()) - .Which.Should().NotBeNull(); + element.Should().NotHaveElement(XNamespace.None + "c"); + } + } + + public class NotHaveElementWithValue + { + [Fact] + public void The_element_cannot_be_null() + { + // Arrange + XElement element = null; + + // Act + Action act = () => element.Should().NotHaveElementWithValue("child", "b"); + + // Assert + act.Should().Throw().WithMessage("*child*b*element itself is *"); } [Fact] - public void Chaining_after_a_non_successful_occurrence_check_does_not_continue_the_assertion() + public void Throws_when_element_with_specified_value_is_found() { // Arrange var element = XElement.Parse( """ - - - + a + b """); // Act - Action act = () => element.Should().HaveElement("child", Exactly.Once()) - .Which.Should().NotBeNull(); + Action act = () => element.Should().NotHaveElementWithValue("child", "b"); // Assert - act.Should().Throw() - .WithMessage("Expected element to have an element \"child\"*exactly*1 time, but found it 3 times."); + act.Should().Throw().WithMessage("Did not*element*child*value*b*does have this value*"); } [Fact] - public void Null_element_is_expected_to_have_an_element_count_it_should_fail() + public void Passes_when_element_not_found() { // Arrange - XElement xElement = null; + var element = XElement.Parse( + """ + + a + b + + """); + + // Act / Assert + element.Should().NotHaveElementWithValue("c", "f"); + } + + [Fact] + public void Passes_when_element_found_but_value_does_not_match() + { + // Arrange + var element = XElement.Parse( + """ + + a + b + + """); + + // Act / Assert + element.Should().NotHaveElementWithValue("child", "c"); + } + + [Fact] + public void Throws_when_expected_element_is_null() + { + // Arrange + var element = XElement.Parse( + """ + + a + b + + """); // Act - Action act = () => xElement.Should().HaveElement("child", AtLeast.Once()); + Action act = () => element.Should().NotHaveElementWithValue(null, "a"); // Assert - act.Should().Throw().WithMessage( - "Expected* to have an element with count of *, but the element itself is ."); + act.Should().Throw().WithMessage("*expectedElement*"); + } + + [Fact] + public void Throws_when_expected_element_is_null_with_namespace() + { + // Arrange + var element = XElement.Parse( + """ + + a + b + + """); + + // Act + Action act = () => element.Should().NotHaveElementWithValue((XName)null, "a"); + + // Assert + act.Should().Throw().WithMessage("*expectedElement*"); + } + + [Fact] + public void Throws_when_expected_value_is_null() + { + // Arrange + var element = XElement.Parse( + """ + + a + b + + """); + + // Act + Action act = () => element.Should().NotHaveElementWithValue("child", null); + + // Assert + act.Should().Throw().WithMessage("*expectedValue*"); + } + + [Fact] + public void Throws_when_expected_value_is_null_with_namespace() + { + // Arrange + var element = XElement.Parse( + """ + + a + b + + """); + + // Act + Action act = () => element.Should().NotHaveElementWithValue(XNamespace.None + "child", null); + + // Assert + act.Should().Throw().WithMessage("*expectedValue*"); + } + + [Fact] + public void The_element_cannot_be_null_and_searching_with_namespace() + { + // Arrange + XElement element = null; + + // Act + Action act = () => + element.Should().NotHaveElementWithValue(XNamespace.None + "child", "b", "we want to test the {0} message", + "failure"); + + // Assert + act.Should().Throw().WithMessage("*child*b*failure message*element itself is *"); + } + + [Fact] + public void Throws_when_element_with_specified_value_is_found_with_namespace() + { + // Arrange + var element = XElement.Parse( + """ + + a + b + + """); + + // Act + Action act = () => element.Should().NotHaveElementWithValue(XNamespace.None + "child", "b"); + + // Assert + act.Should().Throw().WithMessage("Did not expect*element*child*value*b*does have this value*"); + } + + [Fact] + public void Passes_when_element_with_namespace_not_found() + { + // Arrange + var element = XElement.Parse( + """ + + a + b + + """); + + // Act / Assert + element.Should().NotHaveElementWithValue(XNamespace.None + "c", "f"); + } + + [Fact] + public void Passes_when_element_with_namespace_found_but_value_does_not_match() + { + // Arrange + var element = XElement.Parse( + """ + + a + b + + """); + + // Act / Assert + element.Should().NotHaveElementWithValue(XNamespace.None + "child", "c"); } } } diff --git a/Tests/FluentAssertions.Specs/Xml/XElementFormatterSpecs.cs b/Tests/FluentAssertions.Specs/Xml/XElementFormatterSpecs.cs index ce5103d05d..6d90cbce5d 100644 --- a/Tests/FluentAssertions.Specs/Xml/XElementFormatterSpecs.cs +++ b/Tests/FluentAssertions.Specs/Xml/XElementFormatterSpecs.cs @@ -21,11 +21,10 @@ public void When_element_has_attributes_it_should_include_them_in_the_output() public void When_element_has_child_element_it_should_not_include_them_in_the_output() { // Act - var element = XElement.Parse( - """ + var element = XElement.Parse(""" - - + + """); string result = Formatter.ToString(element); diff --git a/Tests/FluentAssertions.Specs/Xml/XmlNodeFormatterSpecs.cs b/Tests/FluentAssertions.Specs/Xml/XmlNodeFormatterSpecs.cs index ef83197743..2cbc17d949 100644 --- a/Tests/FluentAssertions.Specs/Xml/XmlNodeFormatterSpecs.cs +++ b/Tests/FluentAssertions.Specs/Xml/XmlNodeFormatterSpecs.cs @@ -1,5 +1,4 @@ -using System; -using System.Xml; +using System.Xml; using FluentAssertions.Formatting; using Xunit; @@ -18,7 +17,7 @@ public void When_a_node_is_20_chars_long_it_should_not_be_trimmed() string result = Formatter.ToString(xmlDoc); // Assert - result.Should().Be(@"" + Environment.NewLine); + result.Should().Be(@""); } [Fact] @@ -32,6 +31,6 @@ public void When_a_node_is_longer_then_20_chars_it_should_be_trimmed() string result = Formatter.ToString(xmlDoc); // Assert - result.Should().Be(@"().Which; - exception.GetType() - .FullName.Should() - .ContainEquivalentOf("Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException"); + + // Don't reference the exception type explicitly like this: act.Should().Throw() + // It could cause this specs project to load the assembly containing the exception (this actually happens for xUnit) + exception.GetType().FullName.Should().Be("Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException"); } } diff --git a/Tests/TestFrameworks/MSTestV2.Specs/MSTestV2.Specs.csproj b/Tests/TestFrameworks/MSTestV2.Specs/MSTestV2.Specs.csproj index 17c7e7c127..c0c4b31b65 100644 --- a/Tests/TestFrameworks/MSTestV2.Specs/MSTestV2.Specs.csproj +++ b/Tests/TestFrameworks/MSTestV2.Specs/MSTestV2.Specs.csproj @@ -8,11 +8,11 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + - \ No newline at end of file + diff --git a/Tests/TestFrameworks/MSpec.Specs/FrameworkSpecs.cs b/Tests/TestFrameworks/MSpec.Specs/FrameworkSpecs.cs index 4572dd850c..4c26788adb 100644 --- a/Tests/TestFrameworks/MSpec.Specs/FrameworkSpecs.cs +++ b/Tests/TestFrameworks/MSpec.Specs/FrameworkSpecs.cs @@ -9,8 +9,11 @@ public class When_mspec_is_used { Because of = () => Exception = Catch.Exception(() => 0.Should().Be(1)); - It should_fail = () => Exception.Should().NotBeNull().And.BeAssignableTo(); - It should_have_a_specific_reason = () => Exception.GetType().FullName.Should().ContainEquivalentOf("Machine.Specifications.SpecificationException"); + It should_fail = () => Exception.Should().BeAssignableTo(); + + // Don't reference the exception type explicitly like this: Exception.Should().BeAssignableTo() + // It could cause this specs project to load the assembly containing the exception (this actually happens for xUnit) + It should_fail_with_a_specification_exception = () => Exception.GetType().FullName.Should().Be("Machine.Specifications.SpecificationException"); private static Exception Exception; } diff --git a/Tests/TestFrameworks/MSpec.Specs/MSpec.Specs.csproj b/Tests/TestFrameworks/MSpec.Specs/MSpec.Specs.csproj index 55dc9b5f52..2c55a4349a 100644 --- a/Tests/TestFrameworks/MSpec.Specs/MSpec.Specs.csproj +++ b/Tests/TestFrameworks/MSpec.Specs/MSpec.Specs.csproj @@ -3,16 +3,17 @@ net6.0 MSpec.Specs MSpec.Specs + $(NoWarn);NU1903 - + runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - \ No newline at end of file + diff --git a/Tests/TestFrameworks/NUnit3.Specs/FrameworkSpecs.cs b/Tests/TestFrameworks/NUnit3.Specs/FrameworkSpecs.cs index 95576d269f..ad15835584 100644 --- a/Tests/TestFrameworks/NUnit3.Specs/FrameworkSpecs.cs +++ b/Tests/TestFrameworks/NUnit3.Specs/FrameworkSpecs.cs @@ -15,8 +15,9 @@ public void When_nunit3_is_used_it_should_throw_nunit_exceptions_for_assertion_f // Assert Exception exception = act.Should().Throw().Which; - exception.GetType() - .FullName.Should() - .ContainEquivalentOf("NUnit.Framework.AssertionException"); + + // Don't reference the exception type explicitly like this: act.Should().Throw() + // It could cause this specs project to load the assembly containing the exception (this actually happens for xUnit) + exception.GetType().FullName.Should().Be("NUnit.Framework.AssertionException"); } } diff --git a/Tests/TestFrameworks/NUnit3.Specs/NUnit3.Specs.csproj b/Tests/TestFrameworks/NUnit3.Specs/NUnit3.Specs.csproj index fbd5917962..37882581c8 100644 --- a/Tests/TestFrameworks/NUnit3.Specs/NUnit3.Specs.csproj +++ b/Tests/TestFrameworks/NUnit3.Specs/NUnit3.Specs.csproj @@ -8,12 +8,12 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Tests/TestFrameworks/NUnit4.Specs/FrameworkSpecs.cs b/Tests/TestFrameworks/NUnit4.Specs/FrameworkSpecs.cs new file mode 100644 index 0000000000..6085148a77 --- /dev/null +++ b/Tests/TestFrameworks/NUnit4.Specs/FrameworkSpecs.cs @@ -0,0 +1,23 @@ +using System; +using FluentAssertions; +using NUnit.Framework; + +namespace NUnit4.Specs; + +[TestFixture] +public class FrameworkSpecs +{ + [Test] + public void Throw_nunit_framework_exception_for_nunit4_tests() + { + // Act + Action act = () => 0.Should().Be(1); + + // Assert + Exception exception = act.Should().Throw().Which; + + // Don't reference the exception type explicitly like this: act.Should().Throw() + // It could cause this specs project to load the assembly containing the exception (this actually happens for xUnit) + exception.GetType().FullName.Should().Be("NUnit.Framework.AssertionException"); + } +} diff --git a/Tests/TestFrameworks/NUnit4.Specs/NUnit4.Specs.csproj b/Tests/TestFrameworks/NUnit4.Specs/NUnit4.Specs.csproj new file mode 100644 index 0000000000..80dae3cc6e --- /dev/null +++ b/Tests/TestFrameworks/NUnit4.Specs/NUnit4.Specs.csproj @@ -0,0 +1,21 @@ + + + net6.0 + NUnit4.Specs + NUnit4.Specs + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/Tests/TestFrameworks/TUnit.Specs/FrameworkSpecs.cs b/Tests/TestFrameworks/TUnit.Specs/FrameworkSpecs.cs new file mode 100644 index 0000000000..ee06efd844 --- /dev/null +++ b/Tests/TestFrameworks/TUnit.Specs/FrameworkSpecs.cs @@ -0,0 +1,21 @@ +using System; +using FluentAssertions; + +namespace TUnit.Specs; + +public class FrameworkSpecs +{ + [Test] + public void When_tunit_is_used_it_should_throw_tunit_exceptions_for_assertion_failures() + { + // Act + Action act = () => 0.Should().Be(1); + + // Assert + Exception exception = act.Should().Throw().Which; + + // Don't reference the exception type explicitly like this: act.Should().Throw() + // It could cause this specs project to load the assembly containing the exception (this actually happens for xUnit) + exception.GetType().FullName.Should().Be("TUnit.Assertions.Exceptions.AssertionException"); + } +} diff --git a/Tests/TestFrameworks/TUnit.Specs/TUnit.Specs.csproj b/Tests/TestFrameworks/TUnit.Specs/TUnit.Specs.csproj new file mode 100644 index 0000000000..8ca10b1e23 --- /dev/null +++ b/Tests/TestFrameworks/TUnit.Specs/TUnit.Specs.csproj @@ -0,0 +1,19 @@ + + + + Exe + net8.0 + TUnit.Specs + TUnit.Specs + + + + + + + + + + + + diff --git a/Tests/TestFrameworks/XUnit2.Specs/FrameworkSpecs.cs b/Tests/TestFrameworks/XUnit2.Specs/FrameworkSpecs.cs index af307cbc30..7920d90718 100644 --- a/Tests/TestFrameworks/XUnit2.Specs/FrameworkSpecs.cs +++ b/Tests/TestFrameworks/XUnit2.Specs/FrameworkSpecs.cs @@ -14,6 +14,9 @@ public void When_xunit2_is_used_it_should_throw_xunit_exceptions_for_assertion_f // Assert Exception exception = act.Should().Throw().Which; - exception.GetType().FullName.Should().ContainEquivalentOf("xunit"); + + // Don't reference the exception type explicitly like this: act.Should().Throw() + // It could cause this specs project to load the assembly containing the exception (this actually happens for xUnit) + exception.GetType().FullName.Should().Be("Xunit.Sdk.XunitException"); } } diff --git a/Tests/TestFrameworks/XUnit2.Specs/XUnit2.Specs.csproj b/Tests/TestFrameworks/XUnit2.Specs/XUnit2.Specs.csproj index b49c02527f..d53fd12997 100644 --- a/Tests/TestFrameworks/XUnit2.Specs/XUnit2.Specs.csproj +++ b/Tests/TestFrameworks/XUnit2.Specs/XUnit2.Specs.csproj @@ -1,4 +1,4 @@ - + net47;net6.0 XUnit2.Specs @@ -8,17 +8,14 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + all runtime; build; native; contentfiles; analyzers - - - - \ No newline at end of file + diff --git a/Tests/TestFrameworks/XUnit3.Specs/FrameworkSpecs.cs b/Tests/TestFrameworks/XUnit3.Specs/FrameworkSpecs.cs new file mode 100644 index 0000000000..87db4492eb --- /dev/null +++ b/Tests/TestFrameworks/XUnit3.Specs/FrameworkSpecs.cs @@ -0,0 +1,24 @@ +using System; +using System.Linq; +using FluentAssertions; +using Xunit; + +namespace XUnit3.Specs; + +public class FrameworkSpecs +{ + [Fact] + public void When_xunit3_is_used_it_should_throw_xunit_exceptions_for_assertion_failures() + { + // Act + Action act = () => 0.Should().Be(1); + + // Assert + Exception exception = act.Should().Throw().Which; + + // Don't reference the exception type explicitly like this: act.Should().Throw() + // It could cause this specs project to load the assembly containing the exception (this actually happens for xUnit) + exception.GetType().GetInterfaces().Select(e => e.Name).Should().Contain("IAssertionException"); + exception.GetType().FullName.Should().Be("Xunit.Sdk.XunitException"); + } +} diff --git a/Tests/TestFrameworks/XUnit3.Specs/XUnit3.Specs.csproj b/Tests/TestFrameworks/XUnit3.Specs/XUnit3.Specs.csproj new file mode 100644 index 0000000000..4459530fcf --- /dev/null +++ b/Tests/TestFrameworks/XUnit3.Specs/XUnit3.Specs.csproj @@ -0,0 +1,15 @@ + + + net472;net8.0 + Exe + + + + + + + + + + + diff --git a/Tests/TestFrameworks/XUnit3Core.Specs/FrameworkSpecs.cs b/Tests/TestFrameworks/XUnit3Core.Specs/FrameworkSpecs.cs new file mode 100644 index 0000000000..d72a1825fc --- /dev/null +++ b/Tests/TestFrameworks/XUnit3Core.Specs/FrameworkSpecs.cs @@ -0,0 +1,20 @@ +using System; +using System.Linq; +using FluentAssertions; +using Xunit; + +namespace XUnit3Core.Specs; + +public class FrameworkSpecs +{ + [Fact] + public void When_xunit3_without_xunit_assert_is_used_it_should_throw_IAssertionException_for_assertion_failures() + { + // Act + Action act = () => 0.Should().Be(1); + + // Assert + Exception exception = act.Should().Throw().Which; + exception.GetType().GetInterfaces().Select(e => e.Name).Should().Contain("IAssertionException"); + } +} diff --git a/Tests/TestFrameworks/XUnit3Core.Specs/XUnit3Core.Specs.csproj b/Tests/TestFrameworks/XUnit3Core.Specs/XUnit3Core.Specs.csproj new file mode 100644 index 0000000000..1458da7a3a --- /dev/null +++ b/Tests/TestFrameworks/XUnit3Core.Specs/XUnit3Core.Specs.csproj @@ -0,0 +1,15 @@ + + + net472;net8.0 + Exe + + + + + + + + + + + diff --git a/Tests/UWP.Specs/App.xaml.cs b/Tests/UWP.Specs/App.xaml.cs index 0b335f4e45..768f758892 100644 --- a/Tests/UWP.Specs/App.xaml.cs +++ b/Tests/UWP.Specs/App.xaml.cs @@ -1,10 +1,7 @@ -using System; -using Microsoft.VisualStudio.TestPlatform.TestExecutor; +using Microsoft.VisualStudio.TestPlatform.TestExecutor; using Windows.ApplicationModel; using Windows.ApplicationModel.Activation; using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Navigation; namespace UWP.Specs; @@ -18,19 +15,9 @@ public App() protected override void OnLaunched(LaunchActivatedEventArgs args) { - if (Window.Current.Content is not Frame rootFrame) - { - rootFrame = new Frame(); - rootFrame.NavigationFailed += OnNavigationFailed; - Window.Current.Content = rootFrame; - } - UnitTestClient.Run(args.Arguments); } - private void OnNavigationFailed(object sender, NavigationFailedEventArgs e) => - throw new Exception("Failed to load Page " + e.SourcePageType.FullName); - private void OnSuspending(object sender, SuspendingEventArgs e) => e.SuspendingOperation.GetDeferral().Complete(); } diff --git a/Tests/UWP.Specs/Directory.Build.props b/Tests/UWP.Specs/Directory.Build.props new file mode 100644 index 0000000000..0d251bdbd1 --- /dev/null +++ b/Tests/UWP.Specs/Directory.Build.props @@ -0,0 +1,7 @@ + + + 13.0 + false + true + + diff --git a/Tests/UWP.Specs/Package.appxmanifest b/Tests/UWP.Specs/Package.appxmanifest index 2a32c3ad91..bbe9c9803a 100644 --- a/Tests/UWP.Specs/Package.appxmanifest +++ b/Tests/UWP.Specs/Package.appxmanifest @@ -10,7 +10,7 @@ - + diff --git a/Tests/UWP.Specs/UWP.Specs.csproj b/Tests/UWP.Specs/UWP.Specs.csproj index 9f73358eae..3dd53810ee 100644 --- a/Tests/UWP.Specs/UWP.Specs.csproj +++ b/Tests/UWP.Specs/UWP.Specs.csproj @@ -19,6 +19,8 @@ false $(NoWarn);2008 true + true + 17.3.3 true @@ -86,14 +88,14 @@ 6.2.14 - 2.2.10 + 3.10.4 - 2.2.10 + 3.10.4 - 4.3.0 + 4.7.0 - + \ No newline at end of file diff --git a/Tests/VB.Specs/VB.Specs.vbproj b/Tests/VB.Specs/VB.Specs.vbproj index bdc93f368c..cfc7e3edc4 100644 --- a/Tests/VB.Specs/VB.Specs.vbproj +++ b/Tests/VB.Specs/VB.Specs.vbproj @@ -3,16 +3,17 @@ net6.0 VB.Specs + 16.9 - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/Tests/VB.Specs/VBSpecs.vb b/Tests/VB.Specs/VBSpecs.vb index 6cd7fcd8f0..9d109a0864 100644 --- a/Tests/VB.Specs/VBSpecs.vb +++ b/Tests/VB.Specs/VBSpecs.vb @@ -2,31 +2,28 @@ Imports FluentAssertions Imports Xunit Imports Xunit.Sdk -Namespace VB.Specs - Public Class VBSpecs - - Public Sub Caller_identification_works_with_parentheses() - ' Arrange - Const subject = False +Public Class VBSpecs + + Public Sub Caller_identification_works_with_parentheses() + ' Arrange + Const subject = False - ' Act - Dim act As Action = Sub() subject.Should().BeTrue() + ' Act + Dim act As Action = Sub() subject.Should().BeTrue() - ' Assert - act.Should().Throw(Of XunitException).WithMessage("Expected subject to be true, but found false.") - End Sub + ' Assert + act.Should().Throw(Of XunitException).WithMessage("Expected subject to be true, but found false.") + End Sub - - Public Sub Caller_identification_works_without_parentheses() - ' Arrange - Const subject = False + + Public Sub Caller_identification_works_without_parentheses() + ' Arrange + Const subject = False - ' Act - Dim act As Action = Sub() subject.Should.BeTrue() - - ' Assert - act.Should().Throw(Of XunitException).WithMessage("Expected subject to be true, but found false.") - End Sub - End Class -End Namespace + ' Act + Dim act As Action = Sub() subject.Should.BeTrue() + ' Assert + act.Should().Throw(Of XunitException).WithMessage("Expected subject to be true, but found false.") + End Sub +End Class diff --git a/cSpell.json b/cSpell.json index 4f9714acdc..9f7010011f 100644 --- a/cSpell.json +++ b/cSpell.json @@ -4,22 +4,31 @@ "words": [ "browsable", "comparands", - "Doomen", "Faqt", "formattable", + "unformattable", "Guids", "LINQ", "MVVM", "parameterless", "refactorings", - "struct" + "struct", + "reportables", + "Gallio", + "plict", + "enumerables" ], "patterns": [ { "name": "Markdown links", - "pattern": "\\((.*)\\)", + "pattern": "/\\[.*\\]\\((.*)\\)/", "description": "" }, + { + "name": "Contributors", + "pattern": "/\\@.*/g", + "description": "Exclude contributors e.g. from releases.md" + }, { "name": "Inline code blocks", "pattern": "\\`([^\\`\\r\\n]+?)\\`", @@ -60,6 +69,7 @@ ], "ignoreRegExpList": [ "Markdown links", + "Contributors", "Markdown code blocks", "Inline code blocks", "Link contents", @@ -70,7 +80,5 @@ "Frontmatter", "Liquid Includes" ], - "ignorePaths": [ - "docs/_pages/releases.md" - ] + "ignorePaths": [] } diff --git a/docs/Gemfile b/docs/Gemfile index 136ccdaf66..9b2ec3c67e 100644 --- a/docs/Gemfile +++ b/docs/Gemfile @@ -1,3 +1,2 @@ source 'https://rubygems.org' -gem "github-pages", group: :jekyll_plugins -gem "webrick", "~> 1.8.1" \ No newline at end of file +gem "github-pages", group: :jekyll_plugins \ No newline at end of file diff --git a/docs/LICENSE.txt b/docs/LICENSE.txt deleted file mode 100644 index afcb825ce2..0000000000 --- a/docs/LICENSE.txt +++ /dev/null @@ -1,63 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2013-2018 Michael Rose and contributors - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -Minimal Mistakes incorporates icons from [The Noun Project](https://thenounproject.com/) -creators Garrett Knoll, Arthur Shlain, and Tracy Tam. -Icons are distributed under Creative Commons Attribution 3.0 United States (CC BY 3.0 US). - -Minimal Mistakes incorporates [Font Awesome](http://fontawesome.io/), -Copyright (c) 2017 Dave Gandy. -Font Awesome is distributed under the terms of the [SIL OFL 1.1](http://scripts.sil.org/OFL) -and [MIT License](http://opensource.org/licenses/MIT). - -Minimal Mistakes incorporates photographs from [Unsplash](https://unsplash.com). - -Minimal Mistakes incorporates [Susy](http://susy.oddbird.net/), -Copyright (c) 2017, Miriam Eric Suzanne. -Susy is distributed under the terms of the [BSD 3-clause "New" or "Revised" License](https://opensource.org/licenses/BSD-3-Clause). - -Minimal Mistakes incorporates [Breakpoint](http://breakpoint-sass.com/). -Breakpoint is distributed under the terms of the [MIT/GPL Licenses](http://opensource.org/licenses/MIT). - -Minimal Mistakes incorporates [FitVids.js](https://github.com/davatron5000/FitVids.js/), -Copyright (c) 2013 Dave Rubert and Chris Coyier. -FitVids is distributed under the terms of the [WTFPL License](http://sam.zoy.org/wtfpl/). - -Minimal Mistakes incorporates [Magnific Popup](http://dimsemenov.com/plugins/magnific-popup/), -Copyright (c) 2014-2016 Dmitry Semenov, http://dimsemenov.com. -Magnific Popup is distributed under the terms of the MIT License. - -Minimal Mistakes incorporates [jQuery Smooth Scroll](https://github.com/kswedberg/jquery-smooth-scroll), -Copyright (c) 2017 Karl Swedberg. -jQuery Smooth Scroll is distributed under the terms of the [MIT License](http://opensource.org/licenses/MIT). - -Minimal Mistakes incorporates [GreedyNav.js](https://github.com/lukejacksonn/GreedyNav), -Copyright (c) 2015 Luke Jackson. -GreedyNav.js is distributed under the terms of the [MIT License](http://opensource.org/licenses/MIT). - -Minimal Mistakes incorporates [Jekyll Group-By-Array](https://github.com/mushishi78/jekyll-group-by-array), -Copyright (c) 2015 Max White . -Jekyll Group-By-Array is distributed under the terms of the [MIT License](http://opensource.org/licenses/MIT). - -Minimal Mistakes incorporates [Lunr](http://lunrjs.com), -Copyright (c) 2017 Oliver Nightingale. -Lunr is distributed under the terms of the [MIT License](http://opensource.org/licenses/MIT). diff --git a/docs/_config.yml b/docs/_config.yml index 0a5592958a..52ceb3badd 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -19,7 +19,7 @@ locale : "en-US" title : "Fluent Assertions" title_separator : "-" name : "Dennis Doomen" -description : "A very extensive set of extension methods that allow you to more naturally specify the expected outcome of a TDD or BDD-style unit tests. Targets .NET Framework 4.7, .NET 6.0, as well as .NET Standard 2.0 and 2.1." +description : "A very extensive set of extension methods that allow you to more naturally specify the expected outcome of a TDD or BDD-style unit tests. Works with .NET Standard 2.0 and higher, .NET Framework 4.7 and higher and .NET 6 and higher." url : "http://www.fluentassertions.com" # the base hostname & protocol for your site e.g. "https://mmistakes.github.io" baseurl : # the subpath of your site, e.g. "/blog" repository : "fluentassertions/fluentassertions" # GitHub username/repo-name e.g. "mmistakes/minimal-mistakes" @@ -39,7 +39,7 @@ comments: colorscheme : # "light" (default), "dark" staticman: allowedFields : # ['name', 'email', 'url', 'message'] - branch : # "main" + branch : # "master" commitMessage : # "New comment." filename : # comment-{@timestamp} format : # "yml" diff --git a/docs/_data/mstest-migration/assert.yml b/docs/_data/mstest-migration/assert.yml index 8e43dc0cf4..35f1b2bbe2 100644 --- a/docs/_data/mstest-migration/assert.yml +++ b/docs/_data/mstest-migration/assert.yml @@ -246,7 +246,7 @@ Assert.IsFalse(actual > expected); new: | - actual.Should().BeLessOrEqualTo(expected); + actual.Should().BeLessThanOrEqualTo(expected); old-message: | Assert.IsFalse failed. @@ -258,7 +258,7 @@ Assert.IsTrue(actual >= expected); new: | - actual.Should().BeGreaterOrEqualTo(expected); + actual.Should().BeGreaterThanOrEqualTo(expected); old-message: | Assert.IsTrue failed. @@ -294,7 +294,7 @@ Assert.IsFalse(actual < expected); new: | - actual.Should().BeGreaterOrEqualTo(expected); + actual.Should().BeGreaterThanOrEqualTo(expected); old-message: | Assert.IsFalse failed. @@ -306,7 +306,7 @@ Assert.IsTrue(actual <= expected); new: | - actual.Should().BeLessOrEqualTo(expected); + actual.Should().BeLessThanOrEqualTo(expected); old-message: | Assert.IsTrue failed. diff --git a/docs/_data/navigation.yml b/docs/_data/navigation.yml index 74e4b85ee7..bad82fae37 100644 --- a/docs/_data/navigation.yml +++ b/docs/_data/navigation.yml @@ -33,6 +33,8 @@ sidebar: url: /introduction#detecting-test-frameworks - title: Subject Identification url: /introduction#subject-identification + - title: Global Configuration + url: /introduction#global-configuration - title: Assertion Scopes url: /introduction#assertion-scopes - title: Basic Assertions @@ -55,11 +57,11 @@ sidebar: url: /guids - title: Enums url: /enums - - title: System.Data - url: /data + - title: JSON (System.Text.Json) + url: /json - title: Exceptions url: /exceptions - - title: Object graph comparison + - title: Object Graphs url: /objectgraphs - title: Event Monitoring url: /eventmonitoring @@ -99,7 +101,13 @@ sidebar: url: /tips/#improved-assertions - title: Migrating from MSTest url: /tips/#mstest-migration + + - title: Upgrading + url: /upgradingtov6 + children: - title: Upgrading to 5.0 url: https://continuousimprover.com/2018/02/fluent-assertions-50-best-unit-test.html#upgrading-tips - title: Upgrading to 6.0 url: /upgradingtov6 + - title: Upgrading to 8.0 + url: /upgradingtov8 diff --git a/docs/_includes/footer.html b/docs/_includes/footer.html index 8f50a773f9..51f3a19c16 100644 --- a/docs/_includes/footer.html +++ b/docs/_includes/footer.html @@ -22,4 +22,4 @@ - + diff --git a/docs/_includes/head.html b/docs/_includes/head.html index 3b99a733bd..104c204e41 100644 --- a/docs/_includes/head.html +++ b/docs/_includes/head.html @@ -15,7 +15,8 @@ - + + diff --git a/docs/_includes/masthead.html b/docs/_includes/masthead.html index 59020ef4f4..8f71e1d0dc 100644 --- a/docs/_includes/masthead.html +++ b/docs/_includes/masthead.html @@ -2,7 +2,7 @@